Merge pull request #545 from dart-lang/merge-jnigen

Merge `package:jnigen` and `package:jni` into dart-lang/native.
diff --git a/.github/workflows/jnigen.yaml b/.github/workflows/jnigen.yaml
new file mode 100644
index 0000000..2deae7f
--- /dev/null
+++ b/.github/workflows/jnigen.yaml
@@ -0,0 +1,428 @@
+# Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Note: There's a script under `jnigen/tool` which runs a subset of these
+# tests on local machine. It's a useful little script for checking the
+# code before making a PR. If you add a task here, you might want to add
+# the equivalent in that script as well.
+
+name: Dart CI
+
+on:
+  # Run on PRs and pushes to the default branch.
+  push:
+    branches: [main]
+    paths:
+      - '.github/workflows/jnigen.yaml'
+      - 'pkgs/jnigen/**'
+      - 'pkgs/jni/**'
+  pull_request:
+    branches: [main]
+    paths:
+      - '.github/workflows/jnigen.yaml'
+      - 'pkgs/jnigen/**'
+      - 'pkgs/jni/**'
+  schedule:
+    - cron: '0 0 * * 0'
+
+env:
+  PUB_ENVIRONMENT: bot.github
+
+jobs:
+  analyze_jnigen:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jnigen
+    strategy:
+      fail-fast: false
+      matrix:
+        sdk: [stable]
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: ${{ matrix.sdk }}
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: axel-op/googlejavaformat-action@fe78db8a90171b6a836449f8d0e982d5d71e5c5a
+        name: 'Check Java formatting with google-java-format'
+        with:
+          args: '--set-exit-if-changed'
+      - id: install
+        name: Install dependencies
+        run: dart pub get
+      - name: install dependencies for android test runner
+        run: flutter pub get
+        working-directory: ./pkgs/jnigen/android_test_runner
+      - name: Check formatting
+        run: dart format --output=none --set-exit-if-changed .
+        if: always() && steps.install.outcome == 'success'
+      - name: Analyze code
+        run: flutter analyze --fatal-infos
+        if: always() && steps.install.outcome == 'success'
+
+  test_jnigen:
+    needs: [analyze_jnigen]
+    runs-on: ${{ matrix.os }}
+    defaults:
+      run:
+        working-directory: ./pkgs/jnigen
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest]
+        sdk: [stable, beta]
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: ${{ matrix.sdk }}
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+          cache: maven
+      ## Committed bindings are formatted with clang-format.
+      ## So this is required to format generated bindings identically
+      - name: install clang tools
+        run: |
+          sudo apt-get update -y
+          sudo apt-get install -y clang-format
+      - name: Install dependencies
+        run: dart pub get
+      - name: build in_app_java APK
+        run: flutter build apk --target-platform=android-arm64
+        working-directory: ./pkgs/jnigen/example/in_app_java
+      - name: build notification_plugin example APK
+        run: flutter build apk --target-platform=android-arm64
+        working-directory: ./pkgs/jnigen/example/notification_plugin/example
+      - name: Run summarizer tests
+        run: mvn surefire:test
+        working-directory: ./pkgs/jnigen/java
+      - name: Build summarizer
+        run: dart run jnigen:setup
+      - name: Generate runtime tests
+        run: dart run tool/generate_runtime_tests.dart
+      - name: Run VM tests
+        run: dart test --test-randomize-ordering-seed random
+      - name: Install coverage
+        run: dart pub global activate coverage
+      - name: Collect coverage
+        run: dart pub global run coverage:test_with_coverage
+      - name: Upload coverage
+        uses: coverallsapp/github-action@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          flag-name: jnigen_tests
+          parallel: true
+          path-to-lcov: ./pkgs/jnigen/coverage/lcov.info
+        if: ${{ matrix.sdk == 'stable' }}
+
+  analyze_jni:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jni
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+      - uses: axel-op/googlejavaformat-action@fe78db8a90171b6a836449f8d0e982d5d71e5c5a
+        name: 'Check Java formatting with google-java-format'
+        with:
+          args: '--set-exit-if-changed'
+      - name: install clang tools & CMake
+        run: |
+          sudo apt-get update -y
+          sudo apt-get install -y clang-format build-essential cmake
+      - run: flutter pub get
+      - name: Check formatting
+        run: dart format --output=none --set-exit-if-changed .
+      - name: Run lints
+        run: flutter analyze --fatal-infos
+      - name: Check C code formatting using clang-format
+        run: clang-format --dry-run -Werror dartjni.c dartjni.h third_party/*.c third_party/*.h
+        working-directory: ./pkgs/jni/src
+      - name: verify that tool/generate_ide_files.dart generates a file
+        run: |
+          dart run tool/generate_ide_files.dart
+          ls src/compile_commands.json
+
+  test_jni:
+    runs-on: ubuntu-latest
+    needs: [analyze_jni]
+    defaults:
+      run:
+        working-directory: ./pkgs/jni
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+      - run: |
+          sudo apt-get update -y
+          sudo apt-get install -y ninja-build libgtk-3-dev libclang-dev
+      - run: dart pub get
+      - run: dart run jni:setup
+      - name: Get dependencies
+        run: dart pub get
+      - name: Run tests
+        run: dart test --test-randomize-ordering-seed random
+      - name: Install coverage
+        run: dart pub global activate coverage
+      - name: Collect coverage
+        run: dart pub global run coverage:test_with_coverage
+      - name: Upload coverage
+        uses: coverallsapp/github-action@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          flag-name: jni_tests
+          parallel: true
+          path-to-lcov: ./pkgs/jni/coverage/lcov.info
+      # TODO(https://github.com/dart-lang/ffigen/issues/555): Ffigen generated
+      # on my machine has macOS specific stuff and CI does not.
+      # We should just generate the struct as opaque, but we currently can't.
+      #
+      # - name: regenerate & compare ffigen bindings
+      #   ## Use git to verify no source files have changed
+      #   run: |
+      #     dart run tool/generate_ffi_bindings.dart
+      #     git diff --exit-code -- lib/src/third_party src/third_party
+
+  ## Run tests for package:jni on windows, just to confirm it works.
+  ## Do not, for example, collect coverage or check formatting.
+  test_jni_windows_minimal:
+    needs: [analyze_jni]
+    runs-on: windows-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jni
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+      - run: Add-Content $env:GITHUB_PATH "$env:JAVA_HOME\bin\server"
+      - run: dart pub get
+      - run: dart run jni:setup
+      - run: dart test --test-randomize-ordering-seed random
+
+  test_jnigen_windows_minimal:
+    needs: [analyze_jnigen]
+    runs-on: windows-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jnigen
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - name: Setup clang
+        uses: egor-tensin/setup-clang@ef434b41eb33a70396fb336b1bae39c76d740c3d
+        with:
+          version: latest
+          platform: x64
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+      - run: git config --global core.autocrlf true
+      - run: Add-Content $env:GITHUB_PATH "$env:JAVA_HOME\bin\server"
+      - run: dart pub get
+      - run: dart run jnigen:setup
+      - name: Build summarizer
+        run: dart run jnigen:setup
+      - name: Generate runtime tests
+        run: dart run tool/generate_runtime_tests.dart
+      - name: Run tests
+        run: dart test --test-randomize-ordering-seed random
+
+  test_jni_macos_minimal:
+    needs: [analyze_jni]
+    runs-on: macos-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jni
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'temurin'
+          java-version: '11'
+      - run: dart pub get
+      - run: dart run jni:setup
+      - run: dart test --test-randomize-ordering-seed random
+
+  test_jnigen_macos_minimal:
+    needs: [analyze_jnigen]
+    runs-on: macos-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jnigen
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - name: Setup clang format
+        uses: ConorMacBride/install-package@3e7ad059e07782ee54fa35f827df52aae0626f30
+        with:
+          brew: clang-format
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'temurin'
+          java-version: '11'
+      - run: git config --global core.autocrlf true
+      - run: dart pub get
+      - name: Build summarizer
+        run: dart run jnigen:setup
+      - name: Generate runtime tests
+        run: dart run tool/generate_runtime_tests.dart
+      - name: Run tests
+        run: dart test --test-randomize-ordering-seed random
+
+  build_jni_example_linux:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jni/example
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+      - run: |
+          sudo apt-get update -y
+          sudo apt-get install -y ninja-build libgtk-3-dev
+      - run: flutter config --enable-linux-desktop
+      - run: flutter pub get
+      - run: dart run jni:setup
+      - run: flutter build linux
+
+  build_jni_example_windows:
+    runs-on: windows-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jni/example
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+      - run: flutter config --enable-windows-desktop
+      - run: flutter pub get
+      - run: flutter build windows
+
+  build_jni_example_android:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jni/example
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - run: flutter pub get
+      - run: flutter build apk
+
+  run_pdfbox_example_linux:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./pkgs/jnigen/example/pdfbox_plugin
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
+        with:
+          channel: 'stable'
+          cache: true
+          cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
+      - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0
+        with:
+          distribution: 'zulu'
+          java-version: '11'
+      - run: |
+          sudo apt-get update -y
+          sudo apt-get install -y ninja-build libgtk-3-dev clang-format
+      - run: flutter config --enable-linux-desktop
+      - run: dart pub get
+      - name: Generate full bindings
+        run: dart run jnigen --config jnigen.yaml --override classes="org.apache.pdfbox"
+      - name: Analyze generated bindings
+        run: |
+          flutter pub get # dart-analyze errors on flutter example
+          flutter analyze
+      - name: Run standalone example
+        run: |
+          dart pub get
+          dart run jni:setup
+          wget 'https://dart.dev/guides/language/specifications/DartLangSpec-v2.2.pdf'
+          dart run bin/pdf_info.dart DartLangSpec-v2.2.pdf
+        working-directory: ./pkgs/jnigen/example/pdfbox_plugin/dart_example
+      - name: Build flutter example for pdfbox_plugin
+        run: |
+          flutter pub get
+          flutter build linux
+        working-directory: ./pkgs/jnigen/example/pdfbox_plugin/example
+
+  coveralls_finish:
+    needs: [test_jnigen, test_jni]
+    runs-on: ubuntu-latest
+    steps:
+      - name: Coveralls finished
+        uses: coverallsapp/github-action@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949
+        with:
+          github-token: ${{ secrets.github_token }}
+          parallel-finished: true
diff --git a/pkgs/jni/.gitignore b/pkgs/jni/.gitignore
new file mode 100644
index 0000000..ae8c6c2
--- /dev/null
+++ b/pkgs/jni/.gitignore
@@ -0,0 +1,32 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.packages
+build/
+
+.vscode/
\ No newline at end of file
diff --git a/pkgs/jni/.metadata b/pkgs/jni/.metadata
new file mode 100644
index 0000000..26fc23f
--- /dev/null
+++ b/pkgs/jni/.metadata
@@ -0,0 +1,39 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+  revision: 676cefaaff197f27424942307668886253e1ec35
+  channel: stable
+
+project_type: plugin_ffi
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: 676cefaaff197f27424942307668886253e1ec35
+      base_revision: 676cefaaff197f27424942307668886253e1ec35
+    - platform: android
+      create_revision: 676cefaaff197f27424942307668886253e1ec35
+      base_revision: 676cefaaff197f27424942307668886253e1ec35
+    - platform: linux
+      create_revision: 676cefaaff197f27424942307668886253e1ec35
+      base_revision: 676cefaaff197f27424942307668886253e1ec35
+    - platform: macos
+      create_revision: 676cefaaff197f27424942307668886253e1ec35
+      base_revision: 676cefaaff197f27424942307668886253e1ec35
+    - platform: windows
+      create_revision: 676cefaaff197f27424942307668886253e1ec35
+      base_revision: 676cefaaff197f27424942307668886253e1ec35
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/pkgs/jni/CHANGELOG.md b/pkgs/jni/CHANGELOG.md
new file mode 100644
index 0000000..2065bb1
--- /dev/null
+++ b/pkgs/jni/CHANGELOG.md
@@ -0,0 +1,84 @@
+## 0.8.0-wip
+
+- **Breaking Change** ([#394](https://github.com/dart-lang/jnigen/issues/394)):
+  Converted various `Exception`s into `Error`s:
+  - `UseAfterReleaseException` -> `UseAfterReleaseError`
+  - `DoubleReleaseException` -> `DoubleReleaseError`
+  - `SpawnException` -> `JniError` (It's now a `sealed class`)
+  - `JNullException` -> `JNullError`
+  - `InvalidCallTypeException` -> `InvalidCallTypeError`
+  - `HelperNotFoundException` -> `HelperNotFoundError`
+  - `JvmExistsException` -> `JniVmExistsError`
+  - `NoJvmInstanceException` -> `NoJvmInstanceError`
+- **Breaking Change**: Removed `InvalidJStringException`.
+- **Breaking Change**: The default return `callType` of type parameter `int` for
+  methods such as `JObject.callMethodByName<int>` is now Java's `long` instead
+  of `int` to be consistent with the way arguments work.
+- **Breaking Change**: `JType` is now `sealed`.
+- **Breaking Change**: Primitive types and their type classes are now `final`.
+- **Breaking Change**: `JArray.filled` now uses the generated type class of the
+  `fill` object and not its Java runtime type.
+
+## 0.7.2
+- Fixed a bug where reading non-null terminated strings would overflow.
+
+## 0.7.1
+- Removed macOS Flutter plugin until package:jni supports it ([#41](https://github.com/dart-lang/jnigen/issues/41)).
+
+## 0.7.0
+
+- **Breaking Change** ([#387](https://github.com/dart-lang/jnigen/issues/387)):
+  Added `JBuffer` and `JByteBuffer` classes as default classes for
+  `java.nio.Buffer` and `java.nio.ByteBuffer` respectively.
+- **Breaking Change**: Made the type classes `final`.
+- Fixed a bug where `addAll`, `removeAll` and `retainAll` in `JSet` would run
+  their respective operation twice.
+- Fixed a bug where `JList.insertAll` would not throw the potentially thrown
+  Java exception.
+
+## 0.6.1
+
+- Depend on the stable version of Dart 3.1.
+
+## 0.6.0
+
+- **Breaking Change** ([#131](https://github.com/dart-lang/jnigen/issues/131)):
+  Renamed `delete*` to `release*`.
+- Added `PortProxy` and related methods used for interface implementation.
+- Added the missing binding for `java.lang.Character`.
+
+## 0.5.0
+
+- **Breaking Change** ([#137](https://github.com/dart-lang/jnigen/issues/137)):
+  Java primitive types are now all lowercase like `jint`, `jshort`, ...
+- The bindings for `java.util.Set`, `java.util.Map`, `java.util.List` and the
+  numeric types like `java.lang.Integer`, `java.lang.Boolean`, ... are now
+  included in `package:jni`.
+
+## 0.4.0
+
+- Type classes now have `superCount` and `superType` getters used for type
+  inference.
+
+## 0.3.0
+
+- Added `PortContinuation` used for `suspend fun` in Kotlin.
+- `dartjni` now depends on `dart_api_dl.h`.
+
+## 0.2.1
+
+- Added `.clang-format` to pub.
+
+## 0.2.0
+
+- Added array support
+- Added generic support
+- `JniX` turned into `JX` for a more terse code.
+
+## 0.1.1
+
+- Windows support for running tests and examples on development machines.
+
+## 0.1.0
+
+- Initial version: Android and Linux support, JObject API
diff --git a/pkgs/jni/LICENSE b/pkgs/jni/LICENSE
new file mode 100644
index 0000000..33474aa
--- /dev/null
+++ b/pkgs/jni/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2022, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google LLC nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/jni/README.md b/pkgs/jni/README.md
new file mode 100644
index 0000000..37f5b36
--- /dev/null
+++ b/pkgs/jni/README.md
@@ -0,0 +1,18 @@
+# jni
+
+This is a support library to access JNI from Dart / Flutter code. This provides the common infrastructure to bindings generated by [jnigen](https://pub.dev/packages/jnigen), as well as some utility methods.
+
+This library contains:
+
+* Functions to access the JNIEnv and JavaVM variables from JNI, and wrapper functions to those provided by JNI. JNIEnv is exposed via `GlobalJniEnv` type which provides a thin abstraction over JNIEnv, so that it can be used from multiple threads.
+* Functions to spawn a JVM on desktop platforms (`Jni.spawn`).
+* Some Android-specific helpers (get application context and current activity references).
+* `JObject` class, which provides base class for classes generated by jnigen.
+* Commonly used Java classes like `JList`, `JMap`, `JInteger`, ...
+
+Apart from being the base library for code generated by `jnigen` this can also be used for one-off uses of the JNI and debugging. __To generate type-safe bindings from Java libraries, use `jnigen`.__
+
+## Documentation
+The test/ directory contains files with comments explaining the basics of this module, and the example/ directory contains a flutter example which also touches some Android-specifics.
+
+Using this library assumes some familiarity with JNI - it's threading model and object references, among other things.
diff --git a/pkgs/jni/analysis_options.yaml b/pkgs/jni/analysis_options.yaml
new file mode 100644
index 0000000..e591be8
--- /dev/null
+++ b/pkgs/jni/analysis_options.yaml
@@ -0,0 +1,13 @@
+include: package:flutter_lints/flutter.yaml
+
+analyzer:
+  exclude: [build/**, third_party/**]
+  language:
+    strict-raw-types: true
+
+linter:
+  rules:
+    - prefer_final_locals
+    - prefer_const_declarations
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jni/android/.gitignore b/pkgs/jni/android/.gitignore
new file mode 100644
index 0000000..161bdcd
--- /dev/null
+++ b/pkgs/jni/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/pkgs/jni/android/README.md b/pkgs/jni/android/README.md
new file mode 100644
index 0000000..7d56c69
--- /dev/null
+++ b/pkgs/jni/android/README.md
@@ -0,0 +1 @@
+This folder contains the [Android plugin class](src/main/java/com/github/dart_lang/jni/JniPlugin.java) which takes care of initializing JNI on Android.
diff --git a/pkgs/jni/android/build.gradle b/pkgs/jni/android/build.gradle
new file mode 100644
index 0000000..3f650e8
--- /dev/null
+++ b/pkgs/jni/android/build.gradle
@@ -0,0 +1,89 @@
+// The Android Gradle Plugin builds the native code with the Android NDK.
+
+group 'com.github.dart_lang.jni'
+version '1.0'
+
+buildscript {
+    ext.kotlin_version = '1.6.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        // The Android Gradle Plugin knows how to build native code with the NDK.
+        classpath 'com.android.tools.build:gradle:7.1.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+rootProject.allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    // Keeping the classes from being removed by proguard.
+    defaultConfig {
+        consumerProguardFiles 'consumer-rules.pro'
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+        }
+    }
+
+    // Condition for namespace compatibility in AGP 8
+    if (project.android.hasProperty("namespace")) {
+        namespace 'com.github.dart_lang.jni'
+    }
+
+    // Adding [PortContinuation] and [PortProxy] classes shared between Flutter and
+    // Dart-standalone versions of package:jni.
+    sourceSets {
+        main {
+            java {
+                srcDirs '../java/src/main/java'
+            }
+        }
+    }
+
+    // Bumping the plugin compileSdkVersion requires all clients of this plugin
+    // to bump the version in their app.
+    compileSdkVersion 31
+
+    // Bumping the plugin ndkVersion requires all clients of this plugin to bump
+    // the version in their app and to download a newer version of the NDK.
+    // Note(MaheshH) - Flutter seems to download minimum NDK of flutter when
+    // below line is commented out.
+    // How about leaving it?
+    // ndkVersion "21.1.6352462"
+
+    // Invoke the shared CMake build with the Android Gradle Plugin.
+    externalNativeBuild {
+        cmake {
+            path "../src/CMakeLists.txt"
+
+            // The default CMake version for the Android Gradle Plugin is 3.10.2.
+            // https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
+            //
+            // The Flutter tooling requires that developers have CMake 3.10 or later
+            // installed. You should not increase this version, as doing so will cause
+            // the plugin to fail to compile for some customers of the plugin.
+            // version "3.10.2"
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    defaultConfig {
+        minSdkVersion 16
+    }
+}
diff --git a/pkgs/jni/android/consumer-rules.pro b/pkgs/jni/android/consumer-rules.pro
new file mode 100644
index 0000000..269e421
--- /dev/null
+++ b/pkgs/jni/android/consumer-rules.pro
@@ -0,0 +1 @@
+-keep class com.github.dart_lang.jni.** { *; }
diff --git a/pkgs/jni/android/settings.gradle b/pkgs/jni/android/settings.gradle
new file mode 100644
index 0000000..ac55455
--- /dev/null
+++ b/pkgs/jni/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'jni'
diff --git a/pkgs/jni/android/src/main/AndroidManifest.xml b/pkgs/jni/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9a6646f
--- /dev/null
+++ b/pkgs/jni/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.github.dart_lang.jni">
+</manifest>
diff --git a/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/JniPlugin.java b/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/JniPlugin.java
new file mode 100644
index 0000000..d3a1ebe
--- /dev/null
+++ b/pkgs/jni/android/src/main/java/com/github/dart_lang/jni/JniPlugin.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jni;
+
+import android.app.Activity;
+import android.content.Context;
+import androidx.annotation.NonNull;
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.embedding.engine.plugins.activity.ActivityAware;
+import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
+import io.flutter.plugin.common.PluginRegistry.Registrar;
+
+public class JniPlugin implements FlutterPlugin, ActivityAware {
+
+  @Override
+  public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
+    setup(binding.getApplicationContext());
+  }
+
+  public static void registerWith(Registrar registrar) {
+    JniPlugin plugin = new JniPlugin();
+    plugin.setup(registrar.activeContext());
+  }
+
+  private void setup(Context context) {
+    initializeJni(context, getClass().getClassLoader());
+  }
+
+  @Override
+  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
+
+  // Activity handling methods
+  @Override
+  public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
+    Activity activity = binding.getActivity();
+    setJniActivity(activity, activity.getApplicationContext());
+  }
+
+  @Override
+  public void onDetachedFromActivityForConfigChanges() {}
+
+  @Override
+  public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
+    Activity activity = binding.getActivity();
+    setJniActivity(activity, activity.getApplicationContext());
+  }
+
+  @Override
+  public void onDetachedFromActivity() {}
+
+  native void initializeJni(Context context, ClassLoader classLoader);
+
+  native void setJniActivity(Activity activity, Context context);
+
+  static {
+    System.loadLibrary("dartjni");
+  }
+}
diff --git a/pkgs/jni/bin/setup.dart b/pkgs/jni/bin/setup.dart
new file mode 100644
index 0000000..7727cc1
--- /dev/null
+++ b/pkgs/jni/bin/setup.dart
@@ -0,0 +1,262 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:package_config/package_config.dart';
+
+import 'package:jni/src/build_util/build_util.dart';
+
+const jniNativeBuildDirective =
+    '# jni_native_build (Build with jni:setup. Do not delete this line.)';
+
+// When changing this constant here, also change corresponding path in
+// test/test_util.
+const _defaultRelativeBuildPath = "build/jni_libs";
+
+const _buildPath = "build-path";
+const _srcPath = "add-source";
+const _packageName = 'add-package';
+const _verbose = "verbose";
+const _cmakeArgs = "cmake-args";
+
+Future<void> runCommand(
+    String exec, List<String> args, String workingDir) async {
+  // For printing relative path always.
+  var current = Directory.current.path;
+  if (!current.endsWith(Platform.pathSeparator)) {
+    current += Platform.pathSeparator;
+  }
+  if (workingDir.startsWith(current)) {
+    workingDir.replaceFirst(current, "");
+  }
+
+  final cmd = "$exec ${args.join(" ")}";
+  stderr.writeln("+ [$workingDir] $cmd");
+  int status;
+  if (options.verbose) {
+    final process = await Process.start(
+      exec, args,
+      workingDirectory: workingDir,
+      mode: ProcessStartMode.inheritStdio,
+      // without `runInShell`, sometimes cmake doesn't run on windows.
+      runInShell: true,
+    );
+    status = await process.exitCode;
+    if (status != 0) {
+      exitCode = status;
+    }
+  } else {
+    // ProcessStartMode.normal sometimes hangs on windows. No idea why.
+    final process = await Process.run(exec, args,
+        runInShell: true, workingDirectory: workingDir);
+    status = process.exitCode;
+    if (status != 0) {
+      exitCode = status;
+      var out = process.stdout;
+      var err = process.stderr;
+      if (stdout.supportsAnsiEscapes) {
+        out = "$ansiRed$out$ansiDefault";
+        err = "$ansiRed$err$ansiDefault";
+      }
+      stdout.writeln(out);
+      stderr.writeln(err);
+    }
+  }
+  if (status != 0) {
+    stderr.writeln('Command exited with status code $status');
+  }
+}
+
+class Options {
+  Options(ArgResults arg)
+      : buildPath = arg[_buildPath],
+        sources = arg[_srcPath],
+        packages = arg[_packageName],
+        cmakeArgs = arg[_cmakeArgs],
+        verbose = arg[_verbose] ?? false;
+
+  String? buildPath;
+  List<String> sources;
+  List<String> packages;
+  List<String> cmakeArgs;
+  bool verbose;
+}
+
+late Options options;
+
+void verboseLog(String msg) {
+  if (options.verbose) {
+    stderr.writeln(msg);
+  }
+}
+
+/// Find path to C or Java sources in pub cache for package specified by
+/// [packageName].
+///
+/// If package cannot be found, null is returned.
+Future<String> findSources(String packageName, String subDirectory) async {
+  final packageConfig = await findPackageConfig(Directory.current);
+  if (packageConfig == null) {
+    throw UnsupportedError("Please run from project root.");
+  }
+  final package = packageConfig[packageName];
+  if (package == null) {
+    throw UnsupportedError("Cannot find package: $packageName");
+  }
+  return package.root.resolve(subDirectory).toFilePath();
+}
+
+/// Return '/src' directories of all dependencies which has a CMakeLists.txt
+/// file.
+Future<Map<String, String>> findDependencySources() async {
+  final packageConfig = await findPackageConfig(Directory.current);
+  if (packageConfig == null) {
+    throw UnsupportedError("Please run the command from project root.");
+  }
+  final sources = <String, String>{};
+  for (var package in packageConfig.packages) {
+    final src = package.root.resolve("src/");
+    final cmakeLists = src.resolve("CMakeLists.txt");
+    final cmakeListsFile = File.fromUri(cmakeLists);
+    if (cmakeListsFile.existsSync()) {
+      final firstLine = cmakeListsFile.readAsLinesSync().first;
+      if (firstLine == jniNativeBuildDirective) {
+        sources[package.name] = src.toFilePath();
+      }
+    }
+  }
+  return sources;
+}
+
+/// Returns the name of file built using sources in [cDir]
+String getTargetName(Directory cDir) {
+  for (final file in cDir.listSync(recursive: true)) {
+    if (file.path.endsWith(".c")) {
+      final cFileName = file.uri.pathSegments.last;
+      final librarySuffix = Platform.isWindows ? "dll" : "so";
+      return cFileName.substring(0, cFileName.length - 1) + librarySuffix;
+    }
+  }
+  throw Exception("Could not find a C file in ${cDir.path}");
+}
+
+void main(List<String> arguments) async {
+  final parser = ArgParser()
+    ..addOption(_buildPath,
+        abbr: 'b', help: 'Directory to place built artifacts')
+    ..addMultiOption(_srcPath,
+        abbr: 's', help: 'alternative path to package:jni sources')
+    ..addMultiOption(_packageName,
+        abbr: 'p',
+        help: 'package for which native'
+            'library should be built')
+    ..addFlag(_verbose, abbr: 'v', help: 'Enable verbose output')
+    ..addMultiOption(_cmakeArgs,
+        abbr: 'm', help: 'Pass additional argument to CMake');
+  final argResults = parser.parse(arguments);
+  options = Options(argResults);
+  final rest = argResults.rest;
+
+  if (rest.isNotEmpty) {
+    stderr.writeln("one or more unrecognized arguments: $rest");
+    stderr.writeln("usage: dart run jni:setup <options>");
+    stderr.writeln(parser.usage);
+    exitCode = 1;
+    return;
+  }
+
+  final sources = options.sources;
+  for (var packageName in options.packages) {
+    // It's assumed C FFI sources are in "src/" relative to package root.
+    sources.add(await findSources(packageName, 'src'));
+  }
+  if (sources.isEmpty) {
+    final dependencySources = await findDependencySources();
+    stderr.writeln("selecting source directories for dependencies: "
+        "${dependencySources.keys}");
+    sources.addAll(dependencySources.values);
+  } else {
+    stderr.writeln("selecting source directories: $sources");
+  }
+  if (sources.isEmpty) {
+    stderr.writeln('No source paths to build!');
+    exitCode = 1;
+    return;
+  }
+
+  final currentDirUri = Uri.directory(".");
+  final buildPath = options.buildPath ??
+      currentDirUri.resolve(_defaultRelativeBuildPath).toFilePath();
+  final buildDir = Directory(buildPath);
+  await buildDir.create(recursive: true);
+
+  final javaSrc = await findSources('jni', 'java');
+  final targetJar = File.fromUri(buildDir.uri.resolve('jni.jar'));
+  if (!needsBuild(targetJar, Directory.fromUri(Uri.directory(javaSrc)))) {
+    verboseLog('Last modified of ${targetJar.path}: '
+        '${targetJar.lastModifiedSync()}.');
+    stderr.writeln('Target newer than source, skipping build.');
+  } else {
+    verboseLog('Running mvn package for jni java sources to $buildPath.');
+    await runCommand(
+      'mvn',
+      ['package', '-Dtarget=${buildDir.absolute.path}'],
+      await findSources('jni', 'java'),
+    );
+  }
+
+  for (var srcPath in sources) {
+    final srcDir = Directory(srcPath);
+    if (!srcDir.existsSync()) {
+      stderr.writeln('Directory $srcPath does not exist');
+      exitCode = 1;
+      return;
+    }
+
+    verboseLog("srcPath: $srcPath");
+    verboseLog("buildPath: $buildPath");
+
+    final targetFileUri = buildDir.uri.resolve(getTargetName(srcDir));
+    final targetFile = File.fromUri(targetFileUri);
+    if (!needsBuild(targetFile, srcDir)) {
+      verboseLog("Last modified of ${targetFile.path}: "
+          "${targetFile.lastModifiedSync()}.");
+      stderr.writeln('Target newer than source, skipping build.');
+      continue;
+    }
+
+    // Note: creating temp dir in .dart_tool/jni instead of SystemTemp
+    // because latter can fail tests on Windows CI, when system temp is on
+    // separate drive or something.
+    final jniDirUri = Uri.directory(".dart_tool").resolve("jni");
+    final jniDir = Directory.fromUri(jniDirUri);
+    await jniDir.create(recursive: true);
+    final tempDir = await jniDir.createTemp("jni_native_build_");
+    final cmakeArgs = <String>[];
+    cmakeArgs.addAll(options.cmakeArgs);
+    // Pass absolute path of srcDir because cmake command is run in temp dir
+    cmakeArgs.add(srcDir.absolute.path);
+    await runCommand("cmake", cmakeArgs, tempDir.path);
+    await runCommand("cmake", ["--build", "."], tempDir.path);
+    final dllDirUri =
+        Platform.isWindows ? tempDir.uri.resolve("Debug") : tempDir.uri;
+    final dllDir = Directory.fromUri(dllDirUri);
+    for (var entry in dllDir.listSync()) {
+      verboseLog(entry.toString());
+      final dllSuffix = Platform.isWindows
+          ? "dll"
+          : Platform.isMacOS
+              ? "dylib"
+              : "so";
+      if (entry.path.endsWith(dllSuffix)) {
+        final dllName = entry.uri.pathSegments.last;
+        final target = buildDir.uri.resolve(dllName);
+        entry.renameSync(target.toFilePath());
+      }
+    }
+    await tempDir.delete(recursive: true);
+  }
+}
diff --git a/pkgs/jni/dart_test.yaml b/pkgs/jni/dart_test.yaml
new file mode 100644
index 0000000..361ada4
--- /dev/null
+++ b/pkgs/jni/dart_test.yaml
@@ -0,0 +1,7 @@
+## Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+## for details. All rights reserved. Use of this source code is governed by a
+## BSD-style license that can be found in the LICENSE file.
+
+tags:
+  load_test:
+    timeout: 10x
diff --git a/pkgs/jni/example/.gitignore b/pkgs/jni/example/.gitignore
new file mode 100644
index 0000000..a8e938c
--- /dev/null
+++ b/pkgs/jni/example/.gitignore
@@ -0,0 +1,47 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/pkgs/jni/example/README.md b/pkgs/jni/example/README.md
new file mode 100644
index 0000000..583f960
--- /dev/null
+++ b/pkgs/jni/example/README.md
@@ -0,0 +1,3 @@
+# jni_example
+Demonstrates how to use the jni plugin.
+
diff --git a/pkgs/jni/example/analysis_options.yaml b/pkgs/jni/example/analysis_options.yaml
new file mode 100644
index 0000000..7f0b15f
--- /dev/null
+++ b/pkgs/jni/example/analysis_options.yaml
@@ -0,0 +1,36 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+analyzer:
+  exclude: [build/**]
+  language:
+    strict-raw-types: true
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    - prefer_final_locals
+    - prefer_const_declarations
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jni/example/android/.gitignore b/pkgs/jni/example/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/pkgs/jni/example/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/pkgs/jni/example/android/app/build.gradle b/pkgs/jni/example/android/app/build.gradle
new file mode 100644
index 0000000..fbcc356
--- /dev/null
+++ b/pkgs/jni/example/android/app/build.gradle
@@ -0,0 +1,70 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion flutter.compileSdkVersion
+    ndkVersion flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        applicationId "com.github.dart_lang.jni_example"
+        // You can update the following values to match your application needs.
+        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/pkgs/jni/example/android/app/src/debug/AndroidManifest.xml b/pkgs/jni/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..1a2af73
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.github.dart_lang.jni_example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jni/example/android/app/src/main/AndroidManifest.xml b/pkgs/jni/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5a849fc
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.github.dart_lang.jni_example">
+   <application
+        android:label="jni_example"
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/pkgs/jni/example/android/app/src/main/java/com/github/dart_lang/jni_example/Toaster.java b/pkgs/jni/example/android/app/src/main/java/com/github/dart_lang/jni_example/Toaster.java
new file mode 100644
index 0000000..ec61c8a
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/java/com/github/dart_lang/jni_example/Toaster.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jni_example;
+
+import android.app.Activity;
+import android.content.Context;
+import android.widget.Toast;
+import androidx.annotation.Keep;
+
+@Keep
+class Toaster {
+  static Toaster makeText(Activity mainActivity, Context context, CharSequence text, int duration) {
+    Toaster toast = new Toaster();
+    toast.mainActivity = mainActivity;
+    toast.context = context;
+    toast.text = text;
+    toast.duration = duration;
+    return toast;
+  }
+
+  void show() {
+    mainActivity.runOnUiThread(() -> Toast.makeText(context, text, duration).show());
+  }
+
+  Activity mainActivity;
+  Context context;
+  CharSequence text;
+  int duration;
+}
diff --git a/pkgs/jni/example/android/app/src/main/kotlin/dev/dart/jni_example/MainActivity.kt b/pkgs/jni/example/android/app/src/main/kotlin/dev/dart/jni_example/MainActivity.kt
new file mode 100644
index 0000000..2aa0914
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/kotlin/dev/dart/jni_example/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.github.dart_lang.jni_example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/pkgs/jni/example/android/app/src/main/res/drawable-v21/launch_background.xml b/pkgs/jni/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jni/example/android/app/src/main/res/drawable/launch_background.xml b/pkgs/jni/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jni/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pkgs/jni/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pkgs/jni/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pkgs/jni/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pkgs/jni/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pkgs/jni/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jni/example/android/app/src/main/res/values-night/styles.xml b/pkgs/jni/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jni/example/android/app/src/main/res/values/styles.xml b/pkgs/jni/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jni/example/android/app/src/profile/AndroidManifest.xml b/pkgs/jni/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..1a2af73
--- /dev/null
+++ b/pkgs/jni/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.github.dart_lang.jni_example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jni/example/android/build.gradle b/pkgs/jni/example/android/build.gradle
new file mode 100644
index 0000000..3cdaac9
--- /dev/null
+++ b/pkgs/jni/example/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+    ext.kotlin_version = '1.6.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.1.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+tasks.register("clean", Delete) {
+    delete rootProject.buildDir
+}
diff --git a/pkgs/jni/example/android/gradle.properties b/pkgs/jni/example/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/pkgs/jni/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/pkgs/jni/example/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/jni/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..cc5527d
--- /dev/null
+++ b/pkgs/jni/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
diff --git a/pkgs/jni/example/android/settings.gradle b/pkgs/jni/example/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/pkgs/jni/example/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/pkgs/jni/example/integration_test/on_device_jni_test.dart b/pkgs/jni/example/integration_test/on_device_jni_test.dart
new file mode 100644
index 0000000..cdc7296
--- /dev/null
+++ b/pkgs/jni/example/integration_test/on_device_jni_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+
+import '../../test/global_env_test.dart' as global_env_test;
+import '../../test/exception_test.dart' as exception_test;
+import '../../test/jlist_test.dart' as jlist_test;
+import '../../test/jmap_test.dart' as jmap_test;
+import '../../test/jobject_test.dart' as jobject_test;
+import '../../test/jstring_test.dart' as jstring_test;
+import '../../test/jset_test.dart' as jset_test;
+import '../../test/jarray_test.dart' as jarray_test;
+import '../../test/boxed_test.dart' as boxed_test;
+import '../../test/type_test.dart' as type_test;
+import '../../test/load_test.dart' as load_test;
+
+void integrationTestRunner(String description, void Function() testCallback) {
+  testWidgets(description, (widgetTester) async => testCallback());
+}
+
+void main() {
+  final testSuites = [
+    global_env_test.run,
+    exception_test.run,
+    jlist_test.run,
+    jmap_test.run,
+    jobject_test.run,
+    jstring_test.run,
+    jset_test.run,
+    jarray_test.run,
+    boxed_test.run,
+    type_test.run,
+    load_test.run,
+  ];
+  for (var testSuite in testSuites) {
+    testSuite(testRunner: integrationTestRunner);
+  }
+}
diff --git a/pkgs/jni/example/lib/main.dart b/pkgs/jni/example/lib/main.dart
new file mode 100644
index 0000000..4c73693
--- /dev/null
+++ b/pkgs/jni/example/lib/main.dart
@@ -0,0 +1,224 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: library_private_types_in_public_api
+
+import 'package:flutter/material.dart';
+
+import 'dart:io';
+import 'dart:ffi';
+
+import 'package:jni/jni.dart';
+
+// An example of calling JNI methods using low level primitives.
+// GlobalJniEnv is a thin abstraction over JNIEnv in JNI C API.
+//
+// For a more ergonomic API for common use cases of calling methods and
+// accessing fields, see next examples using JObject and JClass.
+String toJavaStringUsingEnv(int n) => using((arena) {
+      final env = Jni.env;
+      final cls = env.FindClass("java/lang/String".toNativeChars(arena));
+      final mId = env.GetStaticMethodID(cls, "valueOf".toNativeChars(),
+          "(I)Ljava/lang/String;".toNativeChars(arena));
+      final i = arena<JValue>();
+      i.ref.i = n;
+      final res = env.CallStaticObjectMethodA(cls, mId, i);
+      final str = env.toDartString(res);
+      env.deleteAllRefs([res, cls]);
+      return str;
+    });
+
+int randomUsingEnv(int n) => using((arena) {
+      final env = Jni.env;
+      final randomCls = env.FindClass("java/util/Random".toNativeChars(arena));
+      final ctor = env.GetMethodID(
+          randomCls, "<init>".toNativeChars(arena), "()V".toNativeChars(arena));
+      final random = env.NewObject(randomCls, ctor);
+      final nextInt = env.GetMethodID(randomCls, "nextInt".toNativeChars(arena),
+          "(I)I".toNativeChars(arena));
+      final res = env.CallIntMethodA(random, nextInt, Jni.jvalues([n]));
+      env.deleteAllRefs([randomCls, random]);
+      return res;
+    });
+double randomDouble() {
+  final math = Jni.findJClass("java/lang/Math");
+  final random = math.callStaticMethodByName<double>("random", "()D", []);
+  math.release();
+  return random;
+}
+
+int uptime() {
+  return Jni.findJClass("android/os/SystemClock").use(
+    (systemClock) => systemClock.callStaticMethodByName<int>(
+        "uptimeMillis", "()J", [], JniCallType.longType),
+  );
+}
+
+String backAndForth() {
+  final jstring = '🪓'.toJString();
+  final dartString = jstring.toDartString(releaseOriginal: true);
+  return dartString;
+}
+
+void quit() {
+  JObject.fromRef(Jni.getCurrentActivity())
+      .use((ac) => ac.callMethodByName<void>("finish", "()V", []));
+}
+
+void showToast(String text) {
+  // This is example for calling your app's custom java code.
+  // Place the Toaster class in the app's android/ source Folder, with a Keep
+  // annotation or appropriate proguard rules to retain classes in release mode.
+  //
+  // In this example, Toaster class wraps android.widget.Toast so that it
+  // can be called from any thread. See
+  // android/app/src/main/java/com/github/dart_lang/jni_example/Toaster.java
+  Jni.invokeStaticMethod<JObject>(
+      "com/github/dart_lang/jni_example/Toaster",
+      "makeText",
+      "(Landroid/app/Activity;Landroid/content/Context;"
+          "Ljava/lang/CharSequence;I)"
+          "Lcom/github/dart_lang/jni_example/Toaster;",
+      [
+        Jni.getCurrentActivity(),
+        Jni.getCachedApplicationContext(),
+        "😀",
+        0,
+      ]).callMethodByName<void>("show", "()V", []);
+}
+
+void main() {
+  if (!Platform.isAndroid) {
+    Jni.spawn();
+  }
+  final examples = [
+    Example("String.valueOf(1332)", () => toJavaStringUsingEnv(1332)),
+    Example("Generate random number", () => randomUsingEnv(180),
+        runInitially: false),
+    Example("Math.random()", () => randomDouble(), runInitially: false),
+    if (Platform.isAndroid) ...[
+      Example("Minutes of usage since reboot",
+          () => (uptime() / (60 * 1000)).floor()),
+      Example("Back and forth string conversion", () => backAndForth()),
+      Example(
+          "Device name",
+          () => Jni.retrieveStaticField<String>(
+              "android/os/Build", "DEVICE", "Ljava/lang/String;")),
+      Example(
+        "Package name",
+        () => JObject.fromRef(Jni.getCurrentActivity()).use((activity) =>
+            activity.callMethodByName<String>(
+                "getPackageName", "()Ljava/lang/String;", [])),
+      ),
+      Example("Show toast", () => showToast("Hello from JNI!"),
+          runInitially: false),
+      Example(
+        "Quit",
+        quit,
+        runInitially: false,
+      ),
+    ]
+  ];
+  runApp(MyApp(examples));
+}
+
+class Example {
+  String title;
+  dynamic Function() callback;
+  bool runInitially;
+  Example(this.title, this.callback, {this.runInitially = true});
+}
+
+class MyApp extends StatefulWidget {
+  const MyApp(this.examples, {Key? key}) : super(key: key);
+  final List<Example> examples;
+
+  @override
+  _MyAppState createState() => _MyAppState();
+}
+
+class _MyAppState extends State<MyApp> {
+  @override
+  void initState() {
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      home: Scaffold(
+        appBar: AppBar(
+          title: const Text('JNI Examples'),
+        ),
+        body: ListView.builder(
+            itemCount: widget.examples.length,
+            itemBuilder: (context, i) {
+              final eg = widget.examples[i];
+              return ExampleCard(eg);
+            }),
+      ),
+    );
+  }
+}
+
+class ExampleCard extends StatefulWidget {
+  const ExampleCard(this.example, {Key? key}) : super(key: key);
+  final Example example;
+
+  @override
+  _ExampleCardState createState() => _ExampleCardState();
+}
+
+class _ExampleCardState extends State<ExampleCard> {
+  Widget _pad(Widget w, double h, double v) {
+    return Padding(
+        padding: EdgeInsets.symmetric(horizontal: h, vertical: v), child: w);
+  }
+
+  bool _run = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _run = widget.example.runInitially;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final eg = widget.example;
+    var result = "";
+    var hasError = false;
+    if (_run) {
+      try {
+        result = eg.callback().toString();
+      } on Exception catch (e) {
+        hasError = true;
+        result = e.toString();
+      } on Error catch (e) {
+        hasError = true;
+        result = e.toString();
+      }
+    }
+    var resultStyle = const TextStyle(fontFamily: "Monospace");
+    if (hasError) {
+      resultStyle = const TextStyle(fontFamily: "Monospace", color: Colors.red);
+    }
+    return Card(
+      child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
+        Text(eg.title,
+            softWrap: true,
+            style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
+        _pad(
+            Text(result.toString(), softWrap: true, style: resultStyle), 8, 16),
+        _pad(
+            ElevatedButton(
+              child: Text(_run ? "Run again" : "Run"),
+              onPressed: () => setState(() => _run = true),
+            ),
+            8,
+            8),
+      ]),
+    );
+  }
+}
diff --git a/pkgs/jni/example/linux/.gitignore b/pkgs/jni/example/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/pkgs/jni/example/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/pkgs/jni/example/linux/CMakeLists.txt b/pkgs/jni/example/linux/CMakeLists.txt
new file mode 100644
index 0000000..8df6c44
--- /dev/null
+++ b/pkgs/jni/example/linux/CMakeLists.txt
@@ -0,0 +1,138 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "jni_example")
+# The unique GTK application identifier for this application. See:
+# https://wiki.gnome.org/HowDoI/ChooseApplicationID
+set(APPLICATION_ID "com.github.dart_lang.jni")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(SET CMP0063 NEW)
+
+# Load bundled libraries from the lib/ directory relative to the binary.
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+  set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# Define build configuration options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  set(CMAKE_BUILD_TYPE "Debug" CACHE
+    STRING "Flutter build mode" FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+    "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_14)
+  target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+  target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Define the application target. To change its name, change BINARY_NAME above,
+# not the value here, or `flutter run` will no longer work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME}
+  "main.cc"
+  "my_application.cc"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add dependency libraries. Add any application-specific dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
+
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+  PROPERTIES
+  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+  file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+  " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
+  install(FILES "${bundled_library}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endforeach(bundled_library)
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+  install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
diff --git a/pkgs/jni/example/linux/flutter/CMakeLists.txt b/pkgs/jni/example/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..d5bd016
--- /dev/null
+++ b/pkgs/jni/example/linux/flutter/CMakeLists.txt
@@ -0,0 +1,88 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+    set(NEW_LIST "")
+    foreach(element ${${LIST_NAME}})
+        list(APPEND NEW_LIST "${PREFIX}${element}")
+    endforeach(element)
+    set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "fl_basic_message_channel.h"
+  "fl_binary_codec.h"
+  "fl_binary_messenger.h"
+  "fl_dart_project.h"
+  "fl_engine.h"
+  "fl_json_message_codec.h"
+  "fl_json_method_codec.h"
+  "fl_message_codec.h"
+  "fl_method_call.h"
+  "fl_method_channel.h"
+  "fl_method_codec.h"
+  "fl_method_response.h"
+  "fl_plugin_registrar.h"
+  "fl_plugin_registry.h"
+  "fl_standard_message_codec.h"
+  "fl_standard_method_codec.h"
+  "fl_string_codec.h"
+  "fl_value.h"
+  "fl_view.h"
+  "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+  PkgConfig::GTK
+  PkgConfig::GLIB
+  PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+      ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/pkgs/jni/example/linux/flutter/generated_plugin_registrant.cc b/pkgs/jni/example/linux/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..e71a16d
--- /dev/null
+++ b/pkgs/jni/example/linux/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,11 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+}
diff --git a/pkgs/jni/example/linux/flutter/generated_plugin_registrant.h b/pkgs/jni/example/linux/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..e0f0a47
--- /dev/null
+++ b/pkgs/jni/example/linux/flutter/generated_plugin_registrant.h
@@ -0,0 +1,15 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter_linux/flutter_linux.h>
+
+// Registers Flutter plugins.
+void fl_register_plugins(FlPluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/pkgs/jni/example/linux/flutter/generated_plugins.cmake b/pkgs/jni/example/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..be1ee3e
--- /dev/null
+++ b/pkgs/jni/example/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,24 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+  jni
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/pkgs/jni/example/linux/main.cc b/pkgs/jni/example/linux/main.cc
new file mode 100644
index 0000000..e7c5c54
--- /dev/null
+++ b/pkgs/jni/example/linux/main.cc
@@ -0,0 +1,6 @@
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+  g_autoptr(MyApplication) app = my_application_new();
+  return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/pkgs/jni/example/linux/my_application.cc b/pkgs/jni/example/linux/my_application.cc
new file mode 100644
index 0000000..3420542
--- /dev/null
+++ b/pkgs/jni/example/linux/my_application.cc
@@ -0,0 +1,104 @@
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+  GtkApplication parent_instance;
+  char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+  MyApplication* self = MY_APPLICATION(application);
+  GtkWindow* window =
+      GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+  // Use a header bar when running in GNOME as this is the common style used
+  // by applications and is the setup most users will be using (e.g. Ubuntu
+  // desktop).
+  // If running on X and not using GNOME then just use a traditional title bar
+  // in case the window manager does more exotic layout, e.g. tiling.
+  // If running on Wayland assume the header bar will work (may need changing
+  // if future cases occur).
+  gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+  GdkScreen* screen = gtk_window_get_screen(window);
+  if (GDK_IS_X11_SCREEN(screen)) {
+    const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+    if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+      use_header_bar = FALSE;
+    }
+  }
+#endif
+  if (use_header_bar) {
+    GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+    gtk_widget_show(GTK_WIDGET(header_bar));
+    gtk_header_bar_set_title(header_bar, "jni_example");
+    gtk_header_bar_set_show_close_button(header_bar, TRUE);
+    gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+  } else {
+    gtk_window_set_title(window, "jni_example");
+  }
+
+  gtk_window_set_default_size(window, 1280, 720);
+  gtk_widget_show(GTK_WIDGET(window));
+
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+  fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
+
+  FlView* view = fl_view_new(project);
+  gtk_widget_show(GTK_WIDGET(view));
+  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+  fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+  gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
+  MyApplication* self = MY_APPLICATION(application);
+  // Strip out the first argument as it is the binary name.
+  self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+  g_autoptr(GError) error = nullptr;
+  if (!g_application_register(application, nullptr, &error)) {
+     g_warning("Failed to register: %s", error->message);
+     *exit_status = 1;
+     return TRUE;
+  }
+
+  g_application_activate(application);
+  *exit_status = 0;
+
+  return TRUE;
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+  MyApplication* self = MY_APPLICATION(object);
+  g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+  G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+  G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
+  G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+  return MY_APPLICATION(g_object_new(my_application_get_type(),
+                                     "application-id", APPLICATION_ID,
+                                     "flags", G_APPLICATION_NON_UNIQUE,
+                                     nullptr));
+}
diff --git a/pkgs/jni/example/linux/my_application.h b/pkgs/jni/example/linux/my_application.h
new file mode 100644
index 0000000..72271d5
--- /dev/null
+++ b/pkgs/jni/example/linux/my_application.h
@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+                     GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif  // FLUTTER_MY_APPLICATION_H_
diff --git a/pkgs/jni/example/macos/.gitignore b/pkgs/jni/example/macos/.gitignore
new file mode 100644
index 0000000..746adbb
--- /dev/null
+++ b/pkgs/jni/example/macos/.gitignore
@@ -0,0 +1,7 @@
+# Flutter-related
+**/Flutter/ephemeral/
+**/Pods/
+
+# Xcode-related
+**/dgph
+**/xcuserdata/
diff --git a/pkgs/jni/example/macos/Flutter/Flutter-Debug.xcconfig b/pkgs/jni/example/macos/Flutter/Flutter-Debug.xcconfig
new file mode 100644
index 0000000..4b81f9b
--- /dev/null
+++ b/pkgs/jni/example/macos/Flutter/Flutter-Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/pkgs/jni/example/macos/Flutter/Flutter-Release.xcconfig b/pkgs/jni/example/macos/Flutter/Flutter-Release.xcconfig
new file mode 100644
index 0000000..5caa9d1
--- /dev/null
+++ b/pkgs/jni/example/macos/Flutter/Flutter-Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/pkgs/jni/example/macos/Flutter/GeneratedPluginRegistrant.swift b/pkgs/jni/example/macos/Flutter/GeneratedPluginRegistrant.swift
new file mode 100644
index 0000000..cccf817
--- /dev/null
+++ b/pkgs/jni/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -0,0 +1,10 @@
+//
+//  Generated file. Do not edit.
+//
+
+import FlutterMacOS
+import Foundation
+
+
+func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+}
diff --git a/pkgs/jni/example/macos/Podfile b/pkgs/jni/example/macos/Podfile
new file mode 100644
index 0000000..dade8df
--- /dev/null
+++ b/pkgs/jni/example/macos/Podfile
@@ -0,0 +1,40 @@
+platform :osx, '10.11'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def flutter_root
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
+  unless File.exist?(generated_xcode_build_settings_path)
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
+  end
+
+  File.foreach(generated_xcode_build_settings_path) do |line|
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
+    return matches[1].strip if matches
+  end
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_macos_podfile_setup
+
+target 'Runner' do
+  use_frameworks!
+  use_modular_headers!
+
+  flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    flutter_additional_macos_build_settings(target)
+  end
+end
diff --git a/pkgs/jni/example/macos/Runner.xcodeproj/project.pbxproj b/pkgs/jni/example/macos/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..79f7a20
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,572 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 51;
+	objects = {
+
+/* Begin PBXAggregateTarget section */
+		33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
+			isa = PBXAggregateTarget;
+			buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
+			buildPhases = (
+				33CC111E2044C6BF0003C045 /* ShellScript */,
+			);
+			dependencies = (
+			);
+			name = "Flutter Assemble";
+			productName = FLX;
+		};
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+		335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
+		33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
+		33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
+		33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
+		33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 33CC111A2044C6BA0003C045;
+			remoteInfo = FLX;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		33CC110E2044A8840003C045 /* Bundle Framework */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Bundle Framework";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
+		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
+		33CC10ED2044A3C60003C045 /* jni_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jni_example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
+		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
+		33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
+		33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
+		33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
+		33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
+		33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
+		33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
+		33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
+		33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		33CC10EA2044A3C60003C045 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		33BA886A226E78AF003329D5 /* Configs */ = {
+			isa = PBXGroup;
+			children = (
+				33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+			);
+			path = Configs;
+			sourceTree = "<group>";
+		};
+		33CC10E42044A3C60003C045 = {
+			isa = PBXGroup;
+			children = (
+				33FAB671232836740065AC1E /* Runner */,
+				33CEB47122A05771004F2AC0 /* Flutter */,
+				33CC10EE2044A3C60003C045 /* Products */,
+				D73912EC22F37F3D000D13A0 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		33CC10EE2044A3C60003C045 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10ED2044A3C60003C045 /* jni_example.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		33CC11242044D66E0003C045 /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F22044A3C60003C045 /* Assets.xcassets */,
+				33CC10F42044A3C60003C045 /* MainMenu.xib */,
+				33CC10F72044A3C60003C045 /* Info.plist */,
+			);
+			name = Resources;
+			path = ..;
+			sourceTree = "<group>";
+		};
+		33CEB47122A05771004F2AC0 /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+				33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+				33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+				33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+			);
+			path = Flutter;
+			sourceTree = "<group>";
+		};
+		33FAB671232836740065AC1E /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F02044A3C60003C045 /* AppDelegate.swift */,
+				33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
+				33E51913231747F40026EE4D /* DebugProfile.entitlements */,
+				33E51914231749380026EE4D /* Release.entitlements */,
+				33CC11242044D66E0003C045 /* Resources */,
+				33BA886A226E78AF003329D5 /* Configs */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+		D73912EC22F37F3D000D13A0 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		33CC10EC2044A3C60003C045 /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				33CC10E92044A3C60003C045 /* Sources */,
+				33CC10EA2044A3C60003C045 /* Frameworks */,
+				33CC10EB2044A3C60003C045 /* Resources */,
+				33CC110E2044A8840003C045 /* Bundle Framework */,
+				3399D490228B24CF009A79C7 /* ShellScript */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				33CC11202044C79F0003C045 /* PBXTargetDependency */,
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 33CC10ED2044A3C60003C045 /* jni_example.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		33CC10E52044A3C60003C045 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastSwiftUpdateCheck = 0920;
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					33CC10EC2044A3C60003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						LastSwiftMigration = 1100;
+						ProvisioningStyle = Automatic;
+						SystemCapabilities = {
+							com.apple.Sandbox = {
+								enabled = 1;
+							};
+						};
+					};
+					33CC111A2044C6BA0003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						ProvisioningStyle = Manual;
+					};
+				};
+			};
+			buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 33CC10E42044A3C60003C045;
+			productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				33CC10EC2044A3C60003C045 /* Runner */,
+				33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		33CC10EB2044A3C60003C045 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
+				33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3399D490228B24CF009A79C7 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
+		};
+		33CC111E2044C6BF0003C045 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				Flutter/ephemeral/FlutterInputs.xcfilelist,
+			);
+			inputPaths = (
+				Flutter/ephemeral/tripwire,
+			);
+			outputFileListPaths = (
+				Flutter/ephemeral/FlutterOutputs.xcfilelist,
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		33CC10E92044A3C60003C045 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
+				33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
+				335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
+			targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				33CC10F52044A3C60003C045 /* Base */,
+			);
+			name = MainMenu.xib;
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		338D0CE9231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Profile;
+		};
+		338D0CEA231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Profile;
+		};
+		338D0CEB231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Profile;
+		};
+		33CC10F92044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+			};
+			name = Debug;
+		};
+		33CC10FA2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Release;
+		};
+		33CC10FC2044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Debug;
+		};
+		33CC10FD2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Release;
+		};
+		33CC111C2044C6BA0003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		33CC111D2044C6BA0003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10F92044A3C60003C045 /* Debug */,
+				33CC10FA2044A3C60003C045 /* Release */,
+				338D0CE9231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10FC2044A3C60003C045 /* Debug */,
+				33CC10FD2044A3C60003C045 /* Release */,
+				338D0CEA231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC111C2044C6BA0003C045 /* Debug */,
+				33CC111D2044C6BA0003C045 /* Release */,
+				338D0CEB231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 33CC10E52044A3C60003C045 /* Project object */;
+}
diff --git a/pkgs/jni/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/pkgs/jni/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/pkgs/jni/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/pkgs/jni/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..2b12d61
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+               BuildableName = "jni_example.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "jni_example.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "jni_example.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "jni_example.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/pkgs/jni/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/pkgs/jni/example/macos/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/pkgs/jni/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/pkgs/jni/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/pkgs/jni/example/macos/Runner/AppDelegate.swift b/pkgs/jni/example/macos/Runner/AppDelegate.swift
new file mode 100644
index 0000000..d53ef64
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/AppDelegate.swift
@@ -0,0 +1,9 @@
+import Cocoa
+import FlutterMacOS
+
+@NSApplicationMain
+class AppDelegate: FlutterAppDelegate {
+  override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
+    return true
+  }
+}
diff --git a/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..a2ec33f
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+  "images" : [
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_16.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_64.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_128.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_1024.png",
+      "scale" : "2x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
new file mode 100644
index 0000000..3c4935a
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
Binary files differ
diff --git a/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
new file mode 100644
index 0000000..ed4cc16
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
Binary files differ
diff --git a/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
new file mode 100644
index 0000000..483be61
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
Binary files differ
diff --git a/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
new file mode 100644
index 0000000..bcbf36d
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
Binary files differ
diff --git a/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
new file mode 100644
index 0000000..9c0a652
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
Binary files differ
diff --git a/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
new file mode 100644
index 0000000..e71a726
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
Binary files differ
diff --git a/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
new file mode 100644
index 0000000..8a31fe2
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
Binary files differ
diff --git a/pkgs/jni/example/macos/Runner/Base.lproj/MainMenu.xib b/pkgs/jni/example/macos/Runner/Base.lproj/MainMenu.xib
new file mode 100644
index 0000000..80e867a
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Base.lproj/MainMenu.xib
@@ -0,0 +1,343 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+            <connections>
+                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
+            <connections>
+                <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
+                <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
+            </connections>
+        </customObject>
+        <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
+        <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+            <items>
+                <menuItem title="APP_NAME" id="1Xt-HY-uBw">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
+                        <items>
+                            <menuItem title="About APP_NAME" id="5kV-Vb-QxS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+                            <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
+                            <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+                            <menuItem title="Services" id="NMo-om-nkz">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+                            <menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
+                                <connections>
+                                    <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Show All" id="Kd2-mp-pUS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+                            <menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
+                                <connections>
+                                    <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Edit" id="5QF-Oa-p0T">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+                        <items>
+                            <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+                                <connections>
+                                    <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+                                <connections>
+                                    <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+                            <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+                                <connections>
+                                    <action selector="cut:" target="-1" id="YJe-68-I9s"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+                                <connections>
+                                    <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+                                <connections>
+                                    <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Delete" id="pa3-QI-u2k">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+                                <connections>
+                                    <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+                            <menuItem title="Find" id="4EN-yA-p0u">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Find" id="1b7-l0-nxx">
+                                    <items>
+                                        <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+                                            <connections>
+                                                <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+                                    <items>
+                                        <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+                                            <connections>
+                                                <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+                                            <connections>
+                                                <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+                                        <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Substitutions" id="9ic-FL-obx">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+                                    <items>
+                                        <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+                                        <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Links" id="cwL-P1-jid">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Data Detectors" id="tRr-pd-1PS">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Transformations" id="2oI-Rn-ZJC">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+                                    <items>
+                                        <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Speech" id="xrE-MZ-jX0">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+                                    <items>
+                                        <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="View" id="H8h-7b-M4v">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="View" id="HyV-fh-RgO">
+                        <items>
+                            <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
+                                <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+                                <connections>
+                                    <action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Window" id="aUF-d1-5bR">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+                        <items>
+                            <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+                                <connections>
+                                    <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Zoom" id="R4o-n2-Eq4">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+                            <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Help" id="EPT-qC-fAb">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
+                </menuItem>
+            </items>
+            <point key="canvasLocation" x="142" y="-258"/>
+        </menu>
+        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+            <rect key="contentRect" x="335" y="390" width="800" height="600"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
+            <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
+                <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
+                <autoresizingMask key="autoresizingMask"/>
+            </view>
+        </window>
+    </objects>
+</document>
diff --git a/pkgs/jni/example/macos/Runner/Configs/AppInfo.xcconfig b/pkgs/jni/example/macos/Runner/Configs/AppInfo.xcconfig
new file mode 100644
index 0000000..acda531
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Configs/AppInfo.xcconfig
@@ -0,0 +1,14 @@
+// Application-level settings for the Runner target.
+//
+// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
+// future. If not, the values below would default to using the project name when this becomes a
+// 'flutter create' template.
+
+// The application's name. By default this is also the title of the Flutter window.
+PRODUCT_NAME = jni_example
+
+// The application's bundle identifier
+PRODUCT_BUNDLE_IDENTIFIER = com.github.dart_lang.jniExample
+
+// The copyright displayed in application information
+PRODUCT_COPYRIGHT = Copyright © 2022 com.github.dart_lang. All rights reserved.
diff --git a/pkgs/jni/example/macos/Runner/Configs/Debug.xcconfig b/pkgs/jni/example/macos/Runner/Configs/Debug.xcconfig
new file mode 100644
index 0000000..36b0fd9
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Configs/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Debug.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/pkgs/jni/example/macos/Runner/Configs/Release.xcconfig b/pkgs/jni/example/macos/Runner/Configs/Release.xcconfig
new file mode 100644
index 0000000..dff4f49
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Configs/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Release.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/pkgs/jni/example/macos/Runner/Configs/Warnings.xcconfig b/pkgs/jni/example/macos/Runner/Configs/Warnings.xcconfig
new file mode 100644
index 0000000..42bcbf4
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Configs/Warnings.xcconfig
@@ -0,0 +1,13 @@
+WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
+GCC_WARN_UNDECLARED_SELECTOR = YES
+CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
+CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
+CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
+CLANG_WARN_PRAGMA_PACK = YES
+CLANG_WARN_STRICT_PROTOTYPES = YES
+CLANG_WARN_COMMA = YES
+GCC_WARN_STRICT_SELECTOR_MATCH = YES
+CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
+CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
+GCC_WARN_SHADOW = YES
+CLANG_WARN_UNREACHABLE_CODE = YES
diff --git a/pkgs/jni/example/macos/Runner/DebugProfile.entitlements b/pkgs/jni/example/macos/Runner/DebugProfile.entitlements
new file mode 100644
index 0000000..dddb8a3
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/DebugProfile.entitlements
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+	<key>com.apple.security.cs.allow-jit</key>
+	<true/>
+	<key>com.apple.security.network.server</key>
+	<true/>
+</dict>
+</plist>
diff --git a/pkgs/jni/example/macos/Runner/Info.plist b/pkgs/jni/example/macos/Runner/Info.plist
new file mode 100644
index 0000000..4789daa
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIconFile</key>
+	<string></string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>$(PRODUCT_COPYRIGHT)</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
diff --git a/pkgs/jni/example/macos/Runner/MainFlutterWindow.swift b/pkgs/jni/example/macos/Runner/MainFlutterWindow.swift
new file mode 100644
index 0000000..2722837
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/MainFlutterWindow.swift
@@ -0,0 +1,15 @@
+import Cocoa
+import FlutterMacOS
+
+class MainFlutterWindow: NSWindow {
+  override func awakeFromNib() {
+    let flutterViewController = FlutterViewController.init()
+    let windowFrame = self.frame
+    self.contentViewController = flutterViewController
+    self.setFrame(windowFrame, display: true)
+
+    RegisterGeneratedPlugins(registry: flutterViewController)
+
+    super.awakeFromNib()
+  }
+}
diff --git a/pkgs/jni/example/macos/Runner/Release.entitlements b/pkgs/jni/example/macos/Runner/Release.entitlements
new file mode 100644
index 0000000..852fa1a
--- /dev/null
+++ b/pkgs/jni/example/macos/Runner/Release.entitlements
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+</dict>
+</plist>
diff --git a/pkgs/jni/example/pubspec.lock b/pkgs/jni/example/pubspec.lock
new file mode 100644
index 0000000..0da0519
--- /dev/null
+++ b/pkgs/jni/example/pubspec.lock
@@ -0,0 +1,523 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
+      url: "https://pub.dev"
+    source: hosted
+    version: "61.0.0"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
+      url: "https://pub.dev"
+    source: hosted
+    version: "5.13.0"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.2"
+  async:
+    dependency: transitive
+    description:
+      name: async
+      sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.11.0"
+  boolean_selector:
+    dependency: transitive
+    description:
+      name: boolean_selector
+      sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.1"
+  characters:
+    dependency: transitive
+    description:
+      name: characters
+      sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.0"
+  clock:
+    dependency: transitive
+    description:
+      name: clock
+      sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.1"
+  collection:
+    dependency: transitive
+    description:
+      name: collection
+      sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.17.2"
+  convert:
+    dependency: transitive
+    description:
+      name: convert
+      sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.1"
+  coverage:
+    dependency: transitive
+    description:
+      name: coverage
+      sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.6.3"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.3"
+  cupertino_icons:
+    dependency: "direct main"
+    description:
+      name: cupertino_icons
+      sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.5"
+  fake_async:
+    dependency: transitive
+    description:
+      name: fake_async
+      sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.1"
+  ffi:
+    dependency: "direct main"
+    description:
+      name: ffi
+      sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.0"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.1.4"
+  flutter:
+    dependency: "direct main"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_driver:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_lints:
+    dependency: "direct dev"
+    description:
+      name: flutter_lints
+      sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.2"
+  flutter_test:
+    dependency: "direct dev"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  frontend_server_client:
+    dependency: transitive
+    description:
+      name: frontend_server_client
+      sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.0"
+  fuchsia_remote_debug_protocol:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  glob:
+    dependency: transitive
+    description:
+      name: glob
+      sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.2"
+  http_multi_server:
+    dependency: transitive
+    description:
+      name: http_multi_server
+      sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.1"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.2"
+  integration_test:
+    dependency: "direct dev"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  io:
+    dependency: transitive
+    description:
+      name: io
+      sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
+  jni:
+    dependency: "direct main"
+    description:
+      path: ".."
+      relative: true
+    source: path
+    version: "0.6.1"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.6.7"
+  lints:
+    dependency: transitive
+    description:
+      name: lints
+      sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.1"
+  logging:
+    dependency: transitive
+    description:
+      name: logging
+      sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.0"
+  matcher:
+    dependency: transitive
+    description:
+      name: matcher
+      sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.12.16"
+  material_color_utilities:
+    dependency: transitive
+    description:
+      name: material_color_utilities
+      sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.5.0"
+  meta:
+    dependency: transitive
+    description:
+      name: meta
+      sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.9.1"
+  mime:
+    dependency: transitive
+    description:
+      name: mime
+      sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
+  node_preamble:
+    dependency: transitive
+    description:
+      name: node_preamble
+      sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.2"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.0"
+  path:
+    dependency: transitive
+    description:
+      name: path
+      sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.8.3"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.5"
+  pool:
+    dependency: transitive
+    description:
+      name: pool
+      sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.5.1"
+  process:
+    dependency: transitive
+    description:
+      name: process
+      sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.2.4"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.4"
+  shelf:
+    dependency: transitive
+    description:
+      name: shelf
+      sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.4.1"
+  shelf_packages_handler:
+    dependency: transitive
+    description:
+      name: shelf_packages_handler
+      sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.2"
+  shelf_static:
+    dependency: transitive
+    description:
+      name: shelf_static
+      sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.2"
+  shelf_web_socket:
+    dependency: transitive
+    description:
+      name: shelf_web_socket
+      sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
+  sky_engine:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.99"
+  source_map_stack_trace:
+    dependency: transitive
+    description:
+      name: source_map_stack_trace
+      sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.1"
+  source_maps:
+    dependency: transitive
+    description:
+      name: source_maps
+      sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.10.12"
+  source_span:
+    dependency: transitive
+    description:
+      name: source_span
+      sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.10.0"
+  stack_trace:
+    dependency: transitive
+    description:
+      name: stack_trace
+      sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.11.0"
+  stream_channel:
+    dependency: transitive
+    description:
+      name: stream_channel
+      sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.1"
+  string_scanner:
+    dependency: transitive
+    description:
+      name: string_scanner
+      sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.0"
+  sync_http:
+    dependency: transitive
+    description:
+      name: sync_http
+      sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.3.1"
+  term_glyph:
+    dependency: transitive
+    description:
+      name: term_glyph
+      sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.1"
+  test:
+    dependency: "direct dev"
+    description:
+      name: test
+      sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.24.3"
+  test_api:
+    dependency: transitive
+    description:
+      name: test_api
+      sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.6.0"
+  test_core:
+    dependency: transitive
+    description:
+      name: test_core
+      sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.5.3"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.2"
+  vector_math:
+    dependency: transitive
+    description:
+      name: vector_math
+      sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.4"
+  vm_service:
+    dependency: transitive
+    description:
+      name: vm_service
+      sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f
+      url: "https://pub.dev"
+    source: hosted
+    version: "11.7.1"
+  watcher:
+    dependency: transitive
+    description:
+      name: watcher
+      sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.0"
+  web:
+    dependency: transitive
+    description:
+      name: web
+      sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.1.4-beta"
+  web_socket_channel:
+    dependency: transitive
+    description:
+      name: web_socket_channel
+      sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.0"
+  webdriver:
+    dependency: transitive
+    description:
+      name: webdriver
+      sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.2"
+  webkit_inspection_protocol:
+    dependency: transitive
+    description:
+      name: webkit_inspection_protocol
+      sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.0"
+  yaml:
+    dependency: transitive
+    description:
+      name: yaml
+      sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.2"
+sdks:
+  dart: ">=3.1.0 <4.0.0"
+  flutter: ">=2.11.0"
diff --git a/pkgs/jni/example/pubspec.yaml b/pkgs/jni/example/pubspec.yaml
new file mode 100644
index 0000000..ce06464
--- /dev/null
+++ b/pkgs/jni/example/pubspec.yaml
@@ -0,0 +1,100 @@
+name: jni_example
+description: Demonstrates how to use the jni plugin.
+
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+version: 1.0.0+1
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+  flutter:
+    sdk: flutter
+
+  ffi: ^2.0.0
+
+  jni:
+    # When depending on this package from a real application you should use:
+    #   jni: ^x.y.z
+    # See https://dart.dev/tools/pub/dependencies#version-constraints
+    # The example app is bundled with the plugin so we use a path dependency on
+    # the parent directory to use the current plugin's version.
+    path: ../
+
+  # The following adds the Cupertino Icons font to your application.
+  # Use with the CupertinoIcons class for iOS style icons.
+  cupertino_icons: ^1.0.2
+
+dev_dependencies:
+  test: any
+  flutter_test:
+    sdk: flutter
+  integration_test:
+    sdk: flutter
+
+  # The "flutter_lints" package below contains a set of recommended lints to
+  # encourage good coding practices. The lint set provided by the package is
+  # activated in the `analysis_options.yaml` file located at the root of your
+  # package. See that file for information about deactivating specific lint
+  # rules and activating additional ones.
+  flutter_lints: ^2.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+  # The following line ensures that the Material Icons font is
+  # included with your application, so that you can use the icons in
+  # the material Icons class.
+  uses-material-design: true
+
+  # To add assets to your application, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware
+
+  # For details regarding adding assets from package dependencies, see
+  # https://flutter.dev/assets-and-images/#from-packages
+
+  # To add custom fonts to your application, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts from package dependencies,
+  # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jni/example/windows/.gitignore b/pkgs/jni/example/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/pkgs/jni/example/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/pkgs/jni/example/windows/CMakeLists.txt b/pkgs/jni/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..1948892
--- /dev/null
+++ b/pkgs/jni/example/windows/CMakeLists.txt
@@ -0,0 +1,101 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.14)
+project(jni_example LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "jni_example")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(SET CMP0063 NEW)
+
+# Define build configuration option.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+# Define settings for the Profile build mode.
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build; see runner/CMakeLists.txt.
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/pkgs/jni/example/windows/flutter/CMakeLists.txt b/pkgs/jni/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..930d207
--- /dev/null
+++ b/pkgs/jni/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,104 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.14)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+  "flutter_texture_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/pkgs/jni/example/windows/flutter/generated_plugin_registrant.cc b/pkgs/jni/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..8b6d468
--- /dev/null
+++ b/pkgs/jni/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,11 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {
+}
diff --git a/pkgs/jni/example/windows/flutter/generated_plugin_registrant.h b/pkgs/jni/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..dc139d8
--- /dev/null
+++ b/pkgs/jni/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,15 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter/plugin_registry.h>
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/pkgs/jni/example/windows/flutter/generated_plugins.cmake b/pkgs/jni/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..3ad69c6
--- /dev/null
+++ b/pkgs/jni/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,24 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+  jni
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/pkgs/jni/example/windows/runner/CMakeLists.txt b/pkgs/jni/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..b9e550f
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.14)
+project(runner LANGUAGES CXX)
+
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Disable Windows macros that collide with C++ standard library functions.
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+
+# Add dependency libraries and include directories. Add any application-specific
+# dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/pkgs/jni/example/windows/runner/Runner.rc b/pkgs/jni/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..79c3952
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "com.github.dart_lang" "\0"
+            VALUE "FileDescription", "jni_example" "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "jni_example" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2022 Dart Project Authors. All rights reserved." "\0"
+            VALUE "OriginalFilename", "jni_example.exe" "\0"
+            VALUE "ProductName", "jni_example" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/pkgs/jni/example/windows/runner/flutter_window.cpp b/pkgs/jni/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..b43b909
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,61 @@
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(const flutter::DartProject& project)
+    : project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opportunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/pkgs/jni/example/windows/runner/flutter_window.h b/pkgs/jni/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..6da0652
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/flutter_window.h
@@ -0,0 +1,33 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow hosting a Flutter view running |project|.
+  explicit FlutterWindow(const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/pkgs/jni/example/windows/runner/main.cpp b/pkgs/jni/example/windows/runner/main.cpp
new file mode 100644
index 0000000..fa17f9e
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/main.cpp
@@ -0,0 +1,43 @@
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  flutter::DartProject project(L"data");
+
+  std::vector<std::string> command_line_arguments =
+      GetCommandLineArguments();
+
+  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+  FlutterWindow window(project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"jni_example", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  ::MSG msg;
+  while (::GetMessage(&msg, nullptr, 0, 0)) {
+    ::TranslateMessage(&msg);
+    ::DispatchMessage(&msg);
+  }
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/pkgs/jni/example/windows/runner/resource.h b/pkgs/jni/example/windows/runner/resource.h
new file mode 100644
index 0000000..66a65d1
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON                    101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        102
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1001
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif
diff --git a/pkgs/jni/example/windows/runner/resources/app_icon.ico b/pkgs/jni/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/pkgs/jni/example/windows/runner/runner.exe.manifest b/pkgs/jni/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/pkgs/jni/example/windows/runner/utils.cpp b/pkgs/jni/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..f5bf9fa
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/utils.cpp
@@ -0,0 +1,64 @@
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE *unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
+
+std::vector<std::string> GetCommandLineArguments() {
+  // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+  int argc;
+  wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+  if (argv == nullptr) {
+    return std::vector<std::string>();
+  }
+
+  std::vector<std::string> command_line_arguments;
+
+  // Skip the first argument as it's the binary name.
+  for (int i = 1; i < argc; i++) {
+    command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+  }
+
+  ::LocalFree(argv);
+
+  return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+  if (utf16_string == nullptr) {
+    return std::string();
+  }
+  int target_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+      -1, nullptr, 0, nullptr, nullptr);
+  std::string utf8_string;
+  if (target_length == 0 || target_length > utf8_string.max_size()) {
+    return utf8_string;
+  }
+  utf8_string.resize(target_length);
+  int converted_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+      -1, utf8_string.data(),
+      target_length, nullptr, nullptr);
+  if (converted_length == 0) {
+    return std::string();
+  }
+  return utf8_string;
+}
diff --git a/pkgs/jni/example/windows/runner/utils.h b/pkgs/jni/example/windows/runner/utils.h
new file mode 100644
index 0000000..3879d54
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/utils.h
@@ -0,0 +1,19 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include <string>
+#include <vector>
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector<std::string>,
+// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
+std::vector<std::string> GetCommandLineArguments();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/pkgs/jni/example/windows/runner/win32_window.cpp b/pkgs/jni/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c10f08d
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/win32_window.cpp
@@ -0,0 +1,245 @@
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() {
+  ++g_active_window_count;
+}
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title,
+                                const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window,
+                                      UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd,
+                            UINT const message,
+                            WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE: {
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+    }
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() {
+  return window_handle_;
+}
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/pkgs/jni/example/windows/runner/win32_window.h b/pkgs/jni/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..17ba431
--- /dev/null
+++ b/pkgs/jni/example/windows/runner/win32_window.h
@@ -0,0 +1,98 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title,
+                     const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window,
+                                 UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window,
+                                  UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/pkgs/jni/ffigen.yaml b/pkgs/jni/ffigen.yaml
new file mode 100644
index 0000000..782668e
--- /dev/null
+++ b/pkgs/jni/ffigen.yaml
@@ -0,0 +1,157 @@
+# Run with `dart run ffigen --config ffigen.yaml`.
+name: JniBindings
+description: |
+  Bindings for libdartjni.so which is part of jni plugin.
+
+  It also transitively includes type definitions such as JNIEnv from third_party/jni.h;
+
+  However, functions prefixed JNI_ are not usable because they are in a different shared library.
+
+  Regenerate bindings with `flutter pub run ffigen.dart --config ffigen.yaml`.
+output: 'lib/src/third_party/jni_bindings_generated.dart'
+headers:
+  entry-points:
+    - 'src/dartjni.h' # Exports majority of JNI functions
+    - 'src/third_party/global_jni_env.h' # Exports GlobalJniEnv type
+    - 'src/jni_constants.h'
+  include-directives:
+    - 'src/dartjni.h'
+    - 'src/third_party/global_jni_env.h'
+    - 'third_party/jni.h' # jni.h from Android NDK
+    - 'src/jni_constants.h'
+compiler-opts:
+  - '-Ithird_party/'
+enums:
+  rename:
+    'JniType': 'JniCallType'
+    'jobjectRefType': 'JObjectRefType'
+functions:
+  exclude:
+    # Exclude init functions supposed to be defined in loaded DLL, not JNI
+    - 'JNI_.*'
+    - 'GetJniContextPtr'
+    - 'setJniGetters'
+    - 'jni_log'
+    # Inline functions
+    # keep-sorted start
+    - 'acquire_lock'
+    - 'attach_thread'
+    - 'check_exception'
+    - 'destroy_cond'
+    - 'destroy_lock'
+    - 'init_cond'
+    - 'init_lock'
+    - 'load_class'
+    - 'load_class_global_ref'
+    - 'load_class_local_ref'
+    - 'load_class_platform'
+    - 'load_env'
+    - 'load_field'
+    - 'load_method'
+    - 'load_static_field'
+    - 'load_static_method'
+    - 'release_lock'
+    - 'signal_cond'
+    - 'thread_id'
+    - 'to_global_ref'
+    - 'to_global_ref_result'
+    - 'wait_for'
+    # keep-sorted end
+structs:
+  exclude:
+    - 'JniContext'
+    - 'JniLocks'
+    - 'JNIEnv'
+    - '_JNIEnv'
+    - 'JNIInvokeInterface'
+    - '__va_list_tag'
+    - 'CallbackResult'
+  rename:
+    ## opaque struct definitions, base types of jfieldID and jmethodID
+    '_jfieldID': 'jfieldID_'
+    '_jmethodID':
+      'jmethodID_'
+      #'JNI(.*)': 'Jni$1'
+unions:
+  rename:
+    'jvalue': 'JValue'
+globals:
+  exclude:
+    - 'jni'
+    - 'jniEnv'
+    - 'context_getter'
+    - 'env_getter'
+typedefs:
+  exclude:
+    - 'va_list'
+    - '__builtin_va_list'
+  rename:
+    'JNI(.*)': 'Jni$1'
+    # Primitives
+    'jbyte': 'JByteMarker'
+    'jboolean': 'JBooleanMarker'
+    'jchar': 'JCharMarker'
+    'jshort': 'JShortMarker'
+    'jint': 'JIntMarker'
+    'jlong': 'JLongMarker'
+    'jfloat': 'JFloatMarker'
+    'jdouble': 'JDoubleMarker'
+    'jsize': 'JSizeMarker'
+
+    'jclass': 'JClassPtr'
+    'jobject': 'JObjectPtr'
+    'jmethodID': 'JMethodIDPtr'
+    'jfieldID': 'JFieldIDPtr'
+    'jthrowable': 'JThrowablePtr'
+    'jstring': 'JStringPtr'
+    'jarray': 'JArrayPtr'
+    'jobjectArray': 'JObjectArrayPtr'
+    'jbooleanArray': 'JBooleanArrayPtr'
+    'jbyteArray': 'JByteArrayPtr'
+    'jcharArray': 'JCharArrayPtr'
+    'jshortArray': 'JShortArrayPtr'
+    'jintArray': 'JIntArrayPtr'
+    'jlongArray': 'JLongArrayPtr'
+    'jfloatArray': 'JFloatArrayPtr'
+    'jdoubleArray': 'JDoubleArrayPtr'
+    'jweak': 'JWeakPtr'
+    'jvalue': 'JValue'
+preamble: |
+  // Autogenerated file. Do not edit.
+  // Generated from an annotated version of jni.h provided in Android NDK.
+  // (NDK Version 23.1.7779620)
+  // The license for original file is provided below:
+
+  /*
+   * Copyright (C) 2006 The Android Open Source Project
+   *
+   * 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.
+   */
+
+  /*
+   * JNI specification, as defined by Sun:
+   * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
+   *
+   * Everything here is expected to be VM-neutral.
+   */
+
+  // ignore_for_file: always_specify_types
+  // ignore_for_file: camel_case_types
+  // ignore_for_file: non_constant_identifier_names
+  // ignore_for_file: constant_identifier_names
+  // ignore_for_file: unused_field
+  // ignore_for_file: unused_element
+  // coverage:ignore-file
+comments:
+  style: any
+  length: full
diff --git a/pkgs/jni/ffigen_exts.yaml b/pkgs/jni/ffigen_exts.yaml
new file mode 100644
index 0000000..d292ad6
--- /dev/null
+++ b/pkgs/jni/ffigen_exts.yaml
@@ -0,0 +1,16 @@
+# Run with `dart run ffigen --config ffigen.yaml`.
+name: JniExtensions
+description: |
+  This config is for use by the script that generates extension methods in C.
+  This config only scans jni.h and does not do renaming etc..
+output: 'unused.dart'
+headers:
+  entry-points:
+    - 'third_party/jni.h' # Exports majority of JNI functions
+    #- 'src/third_party/global_jni_env.h' # Exports GlobalJniEnv type
+  include-directives:
+    #- 'src/dartjni.h'
+    #- 'src/third_party/global_jni_env.h'
+    - 'third_party/jni.h' # jni.h from Android NDK
+compiler-opts:
+  - '-Ithird_party/'
diff --git a/pkgs/jni/java/.gitignore b/pkgs/jni/java/.gitignore
new file mode 100644
index 0000000..9f97022
--- /dev/null
+++ b/pkgs/jni/java/.gitignore
@@ -0,0 +1 @@
+target/
\ No newline at end of file
diff --git a/pkgs/jni/java/README.md b/pkgs/jni/java/README.md
new file mode 100644
index 0000000..18a6cd3
--- /dev/null
+++ b/pkgs/jni/java/README.md
@@ -0,0 +1,4 @@
+# Jni Java
+
+This is a standalone java support which includes `PortContinuation` to support
+converting from Kotlin's `suspend fun` to Dart's `async` functions.
diff --git a/pkgs/jni/java/pom.xml b/pkgs/jni/java/pom.xml
new file mode 100644
index 0000000..2bd6f88
--- /dev/null
+++ b/pkgs/jni/java/pom.xml
@@ -0,0 +1,35 @@
+<?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.example</groupId>
+    <artifactId>java</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <build>
+        <finalName>jni</finalName>
+        <directory>${target}</directory>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-stdlib</artifactId>
+            <version>1.6.20</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-coroutines-core</artifactId>
+            <version>1.6.4</version>
+            <type>pom</type>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortCleaner.java b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortCleaner.java
new file mode 100644
index 0000000..161ef5e
--- /dev/null
+++ b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortCleaner.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jni;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+
+/// A registry of Java objects with associated Dart resources that cleans up the
+/// resources after they get unreachable and collected by the garbage collector.
+///
+/// A simple alternative to [java.lang.ref.Cleaner] which is only available in
+/// Android API level 33+.
+class PortCleaner {
+  static {
+    System.loadLibrary("dartjni");
+  }
+
+  private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
+  private final PortPhantom list = new PortPhantom();
+
+  private class PortPhantom extends PhantomReference<Object> {
+    final long port;
+
+    /// Form a linked list.
+    PortPhantom prev = this, next = this;
+
+    PortPhantom(Object referent, long port) {
+      super(referent, queue);
+      this.port = port;
+      insert();
+    }
+
+    /// Only used for the head of the list.
+    PortPhantom() {
+      super(null, null);
+      this.port = 0;
+    }
+
+    void insert() {
+      synchronized (list) {
+        prev = list;
+        next = list.next;
+        next.prev = this;
+        list.next = this;
+      }
+    }
+
+    private void remove() {
+      synchronized (list) {
+        next.prev = prev;
+        prev.next = next;
+        prev = this;
+        next = this;
+      }
+    }
+  }
+
+  PortCleaner() {
+    // Only a single PortCleaner and therefore thread will be created.
+    Thread thread =
+        new Thread(
+            () -> {
+              while (true) {
+                try {
+                  PortPhantom portPhantom = (PortPhantom) queue.remove();
+                  portPhantom.remove();
+                  if (portPhantom.port != 0) {
+                    clean(portPhantom.port);
+                  }
+                } catch (Throwable e) {
+                  // Ignore.
+                }
+              }
+            },
+            "PortCleaner");
+    thread.setDaemon(true);
+    thread.start();
+  }
+
+  /// Registers [obj] to be cleaned up later by sending a signal through [port].
+  void register(Object obj, long port) {
+    new PortPhantom(obj, port);
+  }
+
+  private static native void clean(long port);
+}
diff --git a/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortContinuation.java b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortContinuation.java
new file mode 100644
index 0000000..2900ab1
--- /dev/null
+++ b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortContinuation.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jni;
+
+import kotlin.coroutines.Continuation;
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.Dispatchers;
+
+/// An implementation of kotlin.coroutines.Continuation which sends the address
+/// of the object to Dart through a native port.
+///
+/// This allows converting Kotlin coroutines to Dart async methods.
+/// The implementation of native void _resumeWith is located in `dartjni.c`.
+public class PortContinuation<T> implements Continuation<T> {
+  static {
+    System.loadLibrary("dartjni");
+  }
+
+  private final long port;
+
+  public PortContinuation(long port) {
+    this.port = port;
+  }
+
+  @Override
+  public CoroutineContext getContext() {
+    return (CoroutineContext) Dispatchers.getIO();
+  }
+
+  @Override
+  public void resumeWith(Object o) {
+    _resumeWith(port, o);
+  }
+
+  private native void _resumeWith(long port, Object result);
+}
diff --git a/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxy.java b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxy.java
new file mode 100644
index 0000000..3caf02a
--- /dev/null
+++ b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/PortProxy.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jni;
+
+import java.lang.reflect.*;
+
+public class PortProxy implements InvocationHandler {
+  static {
+    System.loadLibrary("dartjni");
+  }
+
+  private static final PortCleaner cleaner = new PortCleaner();
+  private final long port;
+  private final long isolateId;
+  private final long functionPtr;
+
+  private PortProxy(long port, long isolateId, long functionPtr) {
+    this.port = port;
+    this.isolateId = isolateId;
+    this.functionPtr = functionPtr;
+  }
+
+  private static String getDescriptor(Method method) {
+    StringBuilder descriptor = new StringBuilder();
+    descriptor.append(method.getName()).append('(');
+    Class<?>[] parameterTypes = method.getParameterTypes();
+    for (Class<?> paramType : parameterTypes) {
+      appendType(descriptor, paramType);
+    }
+    descriptor.append(')');
+    appendType(descriptor, method.getReturnType());
+    return descriptor.toString();
+  }
+
+  private static void appendType(StringBuilder descriptor, Class<?> type) {
+    if (type == void.class) {
+      descriptor.append('V');
+    } else if (type == boolean.class) {
+      descriptor.append('Z');
+    } else if (type == byte.class) {
+      descriptor.append('B');
+    } else if (type == char.class) {
+      descriptor.append('C');
+    } else if (type == short.class) {
+      descriptor.append('S');
+    } else if (type == int.class) {
+      descriptor.append('I');
+    } else if (type == long.class) {
+      descriptor.append('J');
+    } else if (type == float.class) {
+      descriptor.append('F');
+    } else if (type == double.class) {
+      descriptor.append('D');
+    } else if (type.isArray()) {
+      descriptor.append('[');
+      appendType(descriptor, type.getComponentType());
+    } else {
+      descriptor.append('L').append(type.getName().replace('.', '/')).append(';');
+    }
+  }
+
+  public static Object newInstance(String binaryName, long port, long isolateId, long functionPtr)
+      throws ClassNotFoundException {
+    Class<?> clazz = Class.forName(binaryName);
+    Object obj =
+        Proxy.newProxyInstance(
+            clazz.getClassLoader(),
+            new Class[] {clazz},
+            new PortProxy(port, isolateId, functionPtr));
+    cleaner.register(obj, port);
+    return obj;
+  }
+
+  private static final class DartException extends Exception {
+    private DartException(String message) {
+      super(message);
+    }
+  }
+
+  @Override
+  public Object invoke(Object proxy, Method method, Object[] args) throws DartException {
+    Object[] result = _invoke(port, isolateId, functionPtr, proxy, getDescriptor(method), args);
+    _cleanUp((Long) result[0]);
+    if (result[1] instanceof DartException) {
+      throw (DartException) result[1];
+    }
+    return result[1];
+  }
+
+  /// Returns an array with two objects:
+  /// [0]: The address of the result pointer used for the clean-up.
+  /// [1]: The result of the invocation.
+  private static native Object[] _invoke(
+      long port,
+      long isolateId,
+      long functionPtr,
+      Object proxy,
+      String methodDescriptor,
+      Object[] args);
+
+  private static native void _cleanUp(long resultPtr);
+}
diff --git a/pkgs/jni/lib/internal_helpers_for_jnigen.dart b/pkgs/jni/lib/internal_helpers_for_jnigen.dart
new file mode 100644
index 0000000..257b0c2
--- /dev/null
+++ b/pkgs/jni/lib/internal_helpers_for_jnigen.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This library exports the methods meant for use by generated code only, and
+/// not to be used directly.
+library internal_helpers_for_jnigen;
+
+export 'src/accessors.dart';
+export 'src/jni.dart' show ProtectedJniExtensions;
+export 'src/jreference.dart';
+export 'src/method_invocation.dart';
diff --git a/pkgs/jni/lib/jni.dart b/pkgs/jni/lib/jni.dart
new file mode 100644
index 0000000..5f0f34c
--- /dev/null
+++ b/pkgs/jni/lib/jni.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Package jni provides dart bindings for the Java Native Interface (JNI) on
+/// Android and desktop platforms.
+///
+/// It's intended as a supplement to the jnigen tool, a Java wrapper generator
+/// using JNI. The goal of this package is to provide sufficiently complete
+/// and ergonomic access to underlying JNI APIs.
+///
+/// Therefore, some understanding of JNI is required to use this module.
+///
+/// ## Java VM
+/// On Android, the existing JVM is used, a new JVM needs to be spawned on
+/// flutter desktop & standalone targets.
+///
+/// ```dart
+/// if (!Platform.isAndroid) {
+///   // Spin up a JVM instance with custom classpath etc..
+///   Jni.spawn(/* options */);
+/// }
+/// ```
+///
+/// ## Dart standalone support
+/// On dart standalone target, we unfortunately have no mechanism to bundle
+/// the wrapper libraries with the executable. Thus it needs to be explicitly
+/// placed in a accessible directory and provided as an argument to Jni.spawn.
+///
+/// This module depends on a shared library written in C. Therefore on dart
+/// standalone:
+///
+/// * Run `dart run jni:setup` to build the shared library. This command builds
+/// all dependency libraries with native code (package:jni and jnigen-generated)
+/// libraries if any.
+///
+/// The default output directory is build/jni_libs, which can be changed
+/// using `-B` switch.
+///
+/// * Provide the location of library to `Jni.spawn` call.
+///
+/// ## JNIEnv
+/// `GlobalJniEnv` type provides a thin wrapper over `JNIEnv*` which can be used
+/// from across threads, and always returns JNI global references. This is
+/// needed because Dart doesn't make guarantees about even the straight-line
+/// code being scheduled on the same thread.
+///
+/// ## Debugging
+/// Debugging JNI errors hard in general.
+///
+/// * On desktop platforms you can use JniEnv.ExceptionDescribe to print any
+/// pending exception to stdout.
+/// * On Android, things are slightly easier since CheckJNI is usually enabled
+/// in debug builds. If you are not getting clear stack traces on JNI errors,
+/// check the Android NDK page on how to enable CheckJNI using ADB.
+/// * As a rule of thumb, when there's a NoClassDefFound / NoMethodFound error,
+/// first check your class and method signatures for typos. Another common
+/// reason for NoClassDefFound error is missing classes in classpath.
+
+/// This library provides classes and functions for JNI interop from Dart.
+library jni;
+
+export 'src/errors.dart';
+export 'src/jni.dart' hide ProtectedJniExtensions;
+export 'src/jvalues.dart' hide JValueArgs, toJValues;
+export 'src/types.dart';
+export 'src/jarray.dart';
+export 'src/jobject.dart';
+export 'src/jreference.dart' show JReferenceUseExtension;
+
+export 'src/lang/lang.dart';
+export 'src/nio/nio.dart';
+export 'src/util/util.dart';
+
+export 'src/third_party/generated_bindings.dart'
+    hide JniBindings, JniEnv, JniEnv1, JniExceptionDetails;
+
+export 'package:ffi/ffi.dart' show using, Arena;
+export 'dart:ffi' show nullptr;
diff --git a/pkgs/jni/lib/jni_symbols.yaml b/pkgs/jni/lib/jni_symbols.yaml
new file mode 100644
index 0000000..ac4a74c
--- /dev/null
+++ b/pkgs/jni/lib/jni_symbols.yaml
@@ -0,0 +1,85 @@
+version: 1.0.0
+files:
+  'jni.dart':
+    'java.lang.Object':
+      name: JObject
+      type_class: JObjectType
+      super_count: 0
+    'java.lang.String':
+      name: JString
+      type_class: JStringType
+      super_count: 1
+    'java.lang.Number':
+      name: JNumber
+      type_class: JNumberType
+      super_count: 1
+    'java.lang.Byte':
+      name: JByte
+      type_class: JByteType
+      super_count: 2
+    'java.lang.Short':
+      name: JShort
+      type_class: JShortType
+      super_count: 2
+    'java.lang.Integer':
+      name: JInteger
+      type_class: JIntegerType
+      super_count: 2
+    'java.lang.Long':
+      name: JLong
+      type_class: JLongType
+      super_count: 2
+    'java.lang.Float':
+      name: JFloat
+      type_class: JFloatType
+      super_count: 2
+    'java.lang.Double':
+      name: JDouble
+      type_class: JDoubleType
+      super_count: 2
+    'java.lang.Boolean':
+      name: JBoolean
+      type_class: JBooleanType
+      super_count: 1
+    'java.lang.Character':
+      name: JCharacter
+      type_class: JCharacterType
+      super_count: 1
+    'java.util.Set':
+      name: JSet
+      type_class: JSetType
+      super_count: 1
+      type_params:
+        E:
+          'java.lang.Object': DECLARED
+    'java.util.List':
+      name: JList
+      type_class: JListType
+      super_count: 1
+      type_params:
+        E:
+          'java.lang.Object': DECLARED
+    'java.util.Iterator':
+      name: JIterator
+      type_class: JIteratorType
+      super_count: 1
+      type_params:
+        E:
+          'java.lang.Object': DECLARED
+    'java.util.Map':
+      name: JMap
+      type_class: JMapType
+      super_count: 1
+      type_params:
+        K:
+          'java.lang.Object': DECLARED
+        V:
+          'java.lang.Object': DECLARED
+    'java.nio.Buffer':
+      name: JBuffer
+      type_class: JBufferType
+      super_count: 1
+    'java.nio.ByteBuffer':
+      name: JByteBuffer
+      type_class: JByteBufferType
+      super_count: 2
diff --git a/pkgs/jni/lib/src/accessors.dart b/pkgs/jni/lib/src/accessors.dart
new file mode 100644
index 0000000..e505b94
--- /dev/null
+++ b/pkgs/jni/lib/src/accessors.dart
@@ -0,0 +1,162 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+import 'package:ffi/ffi.dart' show using;
+
+import 'package:jni/src/jvalues.dart';
+
+import 'errors.dart';
+import 'third_party/generated_bindings.dart';
+import 'jni.dart';
+
+void _check(JThrowablePtr exception) {
+  if (exception != nullptr) {
+    Jni.accessors.throwException(exception);
+  }
+}
+
+extension JniResultMethods on JniResult {
+  void check() => _check(exception);
+
+  int get byte {
+    check();
+    return value.b;
+  }
+
+  int get short {
+    check();
+    return value.s;
+  }
+
+  int get char {
+    check();
+    return value.c;
+  }
+
+  int get integer {
+    check();
+    return value.i;
+  }
+
+  int get long {
+    check();
+    return value.j;
+  }
+
+  double get float {
+    check();
+    return value.f;
+  }
+
+  double get doubleFloat {
+    check();
+    return value.d;
+  }
+
+  JObjectPtr get object {
+    check();
+    return value.l;
+  }
+
+  bool get boolean {
+    check();
+    return value.z != 0;
+  }
+}
+
+extension JniIdLookupResultMethods on JniPointerResult {
+  JMethodIDPtr get methodID {
+    _check(exception);
+    return value.cast<jmethodID_>();
+  }
+
+  JFieldIDPtr get fieldID {
+    _check(exception);
+    return value.cast<jfieldID_>();
+  }
+
+  Pointer<Void> get checkedRef {
+    _check(exception);
+    return value;
+  }
+
+  Pointer<T> getPointer<T extends NativeType>() {
+    return value.cast<T>();
+  }
+}
+
+extension JniClassLookupResultMethods on JniClassLookupResult {
+  JClassPtr get checkedClassRef {
+    _check(exception);
+    return value;
+  }
+}
+
+extension JThrowableCheckMethod on JThrowablePtr {
+  void check() {
+    _check(this);
+  }
+}
+
+extension JniAccessorWrappers on JniAccessors {
+  /// Rethrows Java exception in Dart as [JniException].
+  ///
+  /// The original exception object is deleted by this method. The message
+  /// and Java stack trace are included in the exception.
+  void throwException(JThrowablePtr exception) {
+    final details = getExceptionDetails(exception);
+    final env = Jni.env;
+    final message = env.toDartString(details.message);
+    final stacktrace = env.toDartString(details.stacktrace);
+    env.DeleteGlobalRef(exception);
+    env.DeleteGlobalRef(details.message);
+    env.DeleteGlobalRef(details.stacktrace);
+    throw JniException(message, stacktrace);
+  }
+
+  // TODO(PR): How to name these methods? These only wrap toNativeChars()
+  // so that generated bindings are less verbose.
+  JClassPtr getClassOf(String internalName) =>
+      using((arena) => getClass(internalName.toNativeChars(arena)))
+          .checkedClassRef;
+
+  JMethodIDPtr getMethodIDOf(JClassPtr cls, String name, String signature) =>
+      using((arena) => getMethodID(
+              cls, name.toNativeChars(arena), signature.toNativeChars(arena)))
+          .methodID;
+
+  JMethodIDPtr getStaticMethodIDOf(
+          JClassPtr cls, String name, String signature) =>
+      using((arena) => getStaticMethodID(
+              cls, name.toNativeChars(arena), signature.toNativeChars(arena)))
+          .methodID;
+
+  JFieldIDPtr getFieldIDOf(JClassPtr cls, String name, String signature) =>
+      using((arena) => getFieldID(
+              cls, name.toNativeChars(arena), signature.toNativeChars(arena)))
+          .fieldID;
+
+  JFieldIDPtr getStaticFieldIDOf(
+          JClassPtr cls, String name, String signature) =>
+      using((arena) => getStaticFieldID(
+              cls, name.toNativeChars(arena), signature.toNativeChars(arena)))
+          .fieldID;
+
+  JniResult newObjectWithArgs(
+          JClassPtr cls, JMethodIDPtr ctor, List<dynamic> args) =>
+      using((arena) {
+        return newObject(cls, ctor, toJValues(args, allocator: arena));
+      });
+
+  JniResult callMethodWithArgs(
+          JObjectPtr obj, JMethodIDPtr id, int callType, List<dynamic> args) =>
+      using((arena) =>
+          callMethod(obj, id, callType, toJValues(args, allocator: arena)));
+
+  JniResult callStaticMethodWithArgs(
+          JClassPtr cls, JMethodIDPtr id, int callType, List<dynamic> args) =>
+      using((arena) => callStaticMethod(
+          cls, id, callType, toJValues(args, allocator: arena)));
+}
diff --git a/pkgs/jni/lib/src/build_util/build_util.dart b/pkgs/jni/lib/src/build_util/build_util.dart
new file mode 100644
index 0000000..8d267df
--- /dev/null
+++ b/pkgs/jni/lib/src/build_util/build_util.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Any shared build logic should be here. This way it can be reused across bin/,
+// tool/ and test/.
+
+import 'dart:io';
+
+const ansiRed = '\x1b[31m';
+const ansiDefault = '\x1b[39;49m';
+
+/// Returns true if [artifact] does not exist, or any file in [sourceDir] is
+/// newer than [artifact].
+bool needsBuild(File artifact, Directory sourceDir) {
+  if (!artifact.existsSync()) return true;
+  final fileLastModified = artifact.lastModifiedSync();
+  for (final entry in sourceDir.listSync(recursive: true)) {
+    if (entry.statSync().modified.isAfter(fileLastModified)) {
+      return true;
+    }
+  }
+  return false;
+}
diff --git a/pkgs/jni/lib/src/errors.dart b/pkgs/jni/lib/src/errors.dart
new file mode 100644
index 0000000..848d93e
--- /dev/null
+++ b/pkgs/jni/lib/src/errors.dart
@@ -0,0 +1,144 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:jni/src/third_party/generated_bindings.dart';
+
+// TODO(#393): Add the fact that [JException] is now a [JObject] to the
+// CHANGELOG.
+
+final class UseAfterReleaseError extends Error {
+  @override
+  String toString() {
+    return 'Use after release error';
+  }
+}
+
+// TODO(#393): Use NullPointerError once it's available.
+final class JNullError extends Error {
+  @override
+  String toString() => 'The reference was null';
+}
+
+final class DoubleReleaseError extends Error {
+  @override
+  String toString() {
+    return 'Double release error';
+  }
+}
+
+/// Represents JNI errors that might be returned by methods like
+/// `JNI_CreateJavaVM`.
+sealed class JniError extends Error {
+  static const _errors = {
+    JniErrorCode.JNI_ERR: JniGenericError.new,
+    JniErrorCode.JNI_EDETACHED: JniThreadDetachedError.new,
+    JniErrorCode.JNI_EVERSION: JniVersionError.new,
+    JniErrorCode.JNI_ENOMEM: JniOutOfMemoryError.new,
+    JniErrorCode.JNI_EEXIST: JniVmExistsError.new,
+    JniErrorCode.JNI_EINVAL: JniArgumentError.new,
+  };
+
+  final String message;
+
+  JniError(this.message);
+
+  factory JniError.of(int status) {
+    if (!_errors.containsKey(status)) {
+      status = JniErrorCode.JNI_ERR;
+    }
+    return _errors[status]!();
+  }
+
+  @override
+  String toString() {
+    return 'JniError: $message';
+  }
+}
+
+final class JniGenericError extends JniError {
+  JniGenericError() : super('Generic JNI error');
+}
+
+final class JniThreadDetachedError extends JniError {
+  JniThreadDetachedError() : super('Thread detached from VM');
+}
+
+final class JniVersionError extends JniError {
+  JniVersionError() : super('JNI version error');
+}
+
+final class JniOutOfMemoryError extends JniError {
+  JniOutOfMemoryError() : super('Out of memory');
+}
+
+final class JniVmExistsError extends JniError {
+  JniVmExistsError() : super('VM Already created');
+}
+
+final class JniArgumentError extends JniError {
+  JniArgumentError() : super('Invalid arguments');
+}
+
+final class NoJvmInstanceError extends Error {
+  @override
+  String toString() => 'No JNI instance is available';
+}
+
+// TODO(#395): Remove this when calltypes are removed.
+extension on int {
+  static const _names = {
+    JniCallType.booleanType: 'bool',
+    JniCallType.byteType: 'byte',
+    JniCallType.shortType: 'short',
+    JniCallType.charType: 'char',
+    JniCallType.intType: 'int',
+    JniCallType.longType: 'long',
+    JniCallType.floatType: 'float',
+    JniCallType.doubleType: 'double',
+    JniCallType.objectType: 'object',
+    JniCallType.voidType: 'void',
+  };
+  String str() => _names[this]!;
+}
+
+// TODO(#395): Remove this when `JniCallType`s are removed.
+final class InvalidCallTypeError extends Error {
+  final int type;
+  final Set<int> allowed;
+
+  InvalidCallTypeError(this.type, this.allowed);
+
+  @override
+  String toString() => 'Invalid type for call ${type.str()}. '
+      'Allowed types are ${allowed.map((t) => t.str()).toSet()}';
+}
+
+// TODO(#393): Remove this class in favor of `JThrowable`.
+class JniException implements Exception {
+  /// Error message from Java exception.
+  final String message;
+
+  /// Stack trace from Java.
+  final String stackTrace;
+
+  JniException(this.message, this.stackTrace);
+
+  @override
+  String toString() => 'Exception in Java code called through JNI: '
+      '$message\n\n$stackTrace\n';
+}
+
+final class HelperNotFoundError extends Error {
+  final String path;
+
+  HelperNotFoundError(this.path);
+
+  @override
+  String toString() => '''
+Lookup for helper library $path failed.
+Please ensure that `dartjni` shared library is built.
+Provided jni:setup script can be used to build the shared library.
+If the library is already built, ensure that the JVM libraries can be 
+loaded from Dart.''';
+}
diff --git a/pkgs/jni/lib/src/jarray.dart b/pkgs/jni/lib/src/jarray.dart
new file mode 100644
index 0000000..2364312
--- /dev/null
+++ b/pkgs/jni/lib/src/jarray.dart
@@ -0,0 +1,369 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: unnecessary_cast, overridden_fields
+
+import 'dart:ffi';
+
+import 'package:collection/collection.dart';
+import 'package:ffi/ffi.dart';
+import 'package:jni/src/accessors.dart';
+import 'package:jni/src/third_party/generated_bindings.dart';
+
+import 'jni.dart';
+import 'jobject.dart';
+import 'types.dart';
+
+final class JArrayType<T> extends JObjType<JArray<T>> {
+  final JType<T> elementType;
+
+  const JArrayType(this.elementType);
+
+  @override
+  String get signature => '[${elementType.signature}';
+
+  @override
+  JArray<T> fromRef(Pointer<Void> ref) => JArray.fromRef(elementType, ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final int superCount = 1;
+
+  @override
+  int get hashCode => Object.hash(JArrayType, elementType);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == (JArrayType<T>) &&
+        other is JArrayType<T> &&
+        elementType == other.elementType;
+  }
+}
+
+class JArray<E> extends JObject {
+  final JType<E> elementType;
+
+  @override
+  late final JArrayType<E> $type = type(elementType) as JArrayType<E>;
+
+  /// The type which includes information such as the signature of this class.
+  static JObjType<JArray<T>> type<T>(JType<T> innerType) =>
+      JArrayType(innerType);
+
+  /// Construct a new [JArray] with [reference] as its underlying reference.
+  JArray.fromRef(this.elementType, JArrayPtr reference)
+      : super.fromRef(reference);
+
+  /// Creates a [JArray] of the given length from the given [type].
+  ///
+  /// The [length] must be a non-negative integer.
+  factory JArray(JType<E> type, int length) {
+    const primitiveCallTypes = {
+      'B': JniCallType.byteType,
+      'Z': JniCallType.booleanType,
+      'C': JniCallType.charType,
+      'S': JniCallType.shortType,
+      'I': JniCallType.intType,
+      'J': JniCallType.longType,
+      'F': JniCallType.floatType,
+      'D': JniCallType.doubleType,
+    };
+    if (!primitiveCallTypes.containsKey(type.signature) && type is JObjType) {
+      final clazz = (type as JObjType).getClass();
+      final array = JArray<E>.fromRef(
+        type,
+        Jni.accessors.newObjectArray(length, clazz.reference, nullptr).object,
+      );
+      clazz.release();
+      return array;
+    }
+    return JArray.fromRef(
+      type,
+      Jni.accessors
+          .newPrimitiveArray(length, primitiveCallTypes[type.signature]!)
+          .object,
+    );
+  }
+
+  /// Creates a [JArray] of the given length with [fill] at each position.
+  ///
+  /// The [length] must be a non-negative integer.
+  static JArray<E> filled<E extends JObject>(int length, E fill) {
+    RangeError.checkNotNegative(length);
+    final clazz = fill.$type.getClass();
+    final array = JArray<E>.fromRef(
+      fill.$type as JObjType<E>,
+      Jni.accessors
+          .newObjectArray(length, clazz.reference, fill.reference)
+          .object,
+    );
+    clazz.release();
+    return array;
+  }
+
+  int? _length;
+
+  JniResult elementAt(int index, int type) {
+    RangeError.checkValidIndex(index, this);
+    return Jni.accessors.getArrayElement(reference, index, type);
+  }
+
+  /// The number of elements in this array.
+  int get length {
+    return _length ??= Jni.env.GetArrayLength(reference);
+  }
+}
+
+extension NativeArray<E extends JPrimitive> on JArray<E> {
+  void _allocate<T extends NativeType>(
+    int size,
+    void Function(Pointer<T> ptr) use,
+  ) {
+    using((arena) {
+      final ptr = arena.allocate<T>(size);
+      use(ptr);
+    }, malloc);
+  }
+}
+
+extension BoolArray on JArray<jboolean> {
+  bool operator [](int index) {
+    return elementAt(index, JniCallType.booleanType).boolean;
+  }
+
+  void operator []=(int index, bool value) {
+    RangeError.checkValidIndex(index, this);
+    _allocate<JBooleanMarker>(sizeOf<JBooleanMarker>(), (ptr) {
+      ptr.value = value ? 1 : 0;
+      Jni.env.SetBooleanArrayRegion(reference, index, 1, ptr);
+    });
+  }
+
+  void setRange(int start, int end, Iterable<bool> iterable,
+      [int skipCount = 0]) {
+    RangeError.checkValidRange(start, end, length);
+    final size = end - start;
+    final it = iterable.skip(skipCount).take(size);
+    _allocate<JBooleanMarker>(sizeOf<JBooleanMarker>() * size, (ptr) {
+      it.forEachIndexed((index, element) {
+        ptr[index] = element ? 1 : 0;
+      });
+      Jni.env.SetBooleanArrayRegion(reference, start, size, ptr);
+    });
+  }
+}
+
+extension ByteArray on JArray<jbyte> {
+  int operator [](int index) {
+    return elementAt(index, JniCallType.byteType).byte;
+  }
+
+  void operator []=(int index, int value) {
+    RangeError.checkValidIndex(index, this);
+    _allocate<JByteMarker>(sizeOf<JByteMarker>(), (ptr) {
+      ptr.value = value;
+      Jni.env.SetByteArrayRegion(reference, index, 1, ptr);
+    });
+  }
+
+  void setRange(int start, int end, Iterable<int> iterable,
+      [int skipCount = 0]) {
+    RangeError.checkValidRange(start, end, length);
+    final size = end - start;
+    final it = iterable.skip(skipCount).take(size);
+    _allocate<JByteMarker>(sizeOf<JByteMarker>() * size, (ptr) {
+      it.forEachIndexed((index, element) {
+        ptr[index] = element;
+      });
+      Jni.env.SetByteArrayRegion(reference, start, size, ptr);
+    });
+  }
+}
+
+extension CharArray on JArray<jchar> {
+  String operator [](int index) {
+    return String.fromCharCode(
+      elementAt(index, JniCallType.charType).char,
+    );
+  }
+
+  void operator []=(int index, String value) {
+    RangeError.checkValidIndex(index, this);
+    _allocate<JCharMarker>(sizeOf<JCharMarker>(), (ptr) {
+      ptr.value = value.codeUnits.first;
+      Jni.env.SetCharArrayRegion(reference, index, 1, ptr);
+    });
+  }
+
+  void setRange(int start, int end, Iterable<String> iterable,
+      [int skipCount = 0]) {
+    RangeError.checkValidRange(start, end, length);
+    final size = end - start;
+    final it = iterable.skip(skipCount).take(size);
+    _allocate<JCharMarker>(sizeOf<JCharMarker>() * size, (ptr) {
+      it.forEachIndexed((index, element) {
+        ptr[index] = element.codeUnits.first;
+      });
+      Jni.env.SetCharArrayRegion(reference, start, size, ptr);
+    });
+  }
+}
+
+extension ShortArray on JArray<jshort> {
+  int operator [](int index) {
+    return elementAt(index, JniCallType.shortType).short;
+  }
+
+  void operator []=(int index, int value) {
+    RangeError.checkValidIndex(index, this);
+    _allocate<JShortMarker>(sizeOf<JShortMarker>(), (ptr) {
+      ptr.value = value;
+      Jni.env.SetShortArrayRegion(reference, index, 1, ptr);
+    });
+  }
+
+  void setRange(int start, int end, Iterable<int> iterable,
+      [int skipCount = 0]) {
+    RangeError.checkValidRange(start, end, length);
+    final size = end - start;
+    final it = iterable.skip(skipCount).take(size);
+    _allocate<JShortMarker>(sizeOf<JShortMarker>() * size, (ptr) {
+      it.forEachIndexed((index, element) {
+        ptr[index] = element;
+      });
+      Jni.env.SetShortArrayRegion(reference, start, size, ptr);
+    });
+  }
+}
+
+extension IntArray on JArray<jint> {
+  int operator [](int index) {
+    return elementAt(index, JniCallType.intType).integer;
+  }
+
+  void operator []=(int index, int value) {
+    RangeError.checkValidIndex(index, this);
+    _allocate<JIntMarker>(sizeOf<JIntMarker>(), (ptr) {
+      ptr.value = value;
+      Jni.env.SetIntArrayRegion(reference, index, 1, ptr);
+    });
+  }
+
+  void setRange(int start, int end, Iterable<int> iterable,
+      [int skipCount = 0]) {
+    RangeError.checkValidRange(start, end, length);
+    final size = end - start;
+    final it = iterable.skip(skipCount).take(size);
+    _allocate<JIntMarker>(sizeOf<JIntMarker>() * size, (ptr) {
+      it.forEachIndexed((index, element) {
+        ptr[index] = element;
+      });
+      Jni.env.SetIntArrayRegion(reference, start, size, ptr);
+    });
+  }
+}
+
+extension LongArray on JArray<jlong> {
+  int operator [](int index) {
+    return elementAt(index, JniCallType.longType).long;
+  }
+
+  void operator []=(int index, int value) {
+    RangeError.checkValidIndex(index, this);
+    _allocate<JLongMarker>(sizeOf<JLongMarker>(), (ptr) {
+      ptr.value = value;
+      Jni.env.SetLongArrayRegion(reference, index, 1, ptr);
+    });
+  }
+
+  void setRange(int start, int end, Iterable<int> iterable,
+      [int skipCount = 0]) {
+    RangeError.checkValidRange(start, end, length);
+    final size = end - start;
+    final it = iterable.skip(skipCount).take(size);
+    _allocate<JLongMarker>(sizeOf<JLongMarker>() * size, (ptr) {
+      it.forEachIndexed((index, element) {
+        ptr[index] = element;
+      });
+      Jni.env.SetLongArrayRegion(reference, start, size, ptr);
+    });
+  }
+}
+
+extension FloatArray on JArray<jfloat> {
+  double operator [](int index) {
+    return elementAt(index, JniCallType.floatType).float;
+  }
+
+  void operator []=(int index, double value) {
+    RangeError.checkValidIndex(index, this);
+    _allocate<JFloatMarker>(sizeOf<JFloatMarker>(), (ptr) {
+      ptr.value = value;
+      Jni.env.SetFloatArrayRegion(reference, index, 1, ptr);
+    });
+  }
+
+  void setRange(int start, int end, Iterable<double> iterable,
+      [int skipCount = 0]) {
+    RangeError.checkValidRange(start, end, length);
+    final size = end - start;
+    final it = iterable.skip(skipCount).take(size);
+    _allocate<JFloatMarker>(sizeOf<JFloatMarker>() * size, (ptr) {
+      it.forEachIndexed((index, element) {
+        ptr[index] = element;
+      });
+      Jni.env.SetFloatArrayRegion(reference, start, size, ptr);
+    });
+  }
+}
+
+extension DoubleArray on JArray<jdouble> {
+  double operator [](int index) {
+    return elementAt(index, JniCallType.doubleType).doubleFloat;
+  }
+
+  void operator []=(int index, double value) {
+    RangeError.checkValidIndex(index, this);
+    _allocate<JDoubleMarker>(sizeOf<JDoubleMarker>(), (ptr) {
+      ptr.value = value;
+      Jni.env.SetDoubleArrayRegion(reference, index, 1, ptr);
+    });
+  }
+
+  void setRange(int start, int end, Iterable<double> iterable,
+      [int skipCount = 0]) {
+    RangeError.checkValidRange(start, end, length);
+    final size = end - start;
+    final it = iterable.skip(skipCount).take(size);
+    _allocate<JDoubleMarker>(sizeOf<JDoubleMarker>() * size, (ptr) {
+      it.forEachIndexed((index, element) {
+        ptr[index] = element;
+      });
+      Jni.env.SetDoubleArrayRegion(reference, start, size, ptr);
+    });
+  }
+}
+
+extension ObjectArray<T extends JObject> on JArray<T> {
+  T operator [](int index) {
+    return (elementType as JObjType<T>)
+        .fromRef(elementAt(index, JniCallType.objectType).object);
+  }
+
+  void operator []=(int index, T value) {
+    RangeError.checkValidIndex(index, this);
+    Jni.env.SetObjectArrayElement(reference, index, value.reference);
+  }
+
+  void setRange(int start, int end, Iterable<T> iterable, [int skipCount = 0]) {
+    RangeError.checkValidRange(start, end, length);
+    final size = end - start;
+    final it = iterable.skip(skipCount).take(size);
+    it.forEachIndexed((index, element) {
+      this[index] = element;
+    });
+  }
+}
diff --git a/pkgs/jni/lib/src/jfinal_string.dart b/pkgs/jni/lib/src/jfinal_string.dart
new file mode 100644
index 0000000..ce32799
--- /dev/null
+++ b/pkgs/jni/lib/src/jfinal_string.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+
+import 'jreference.dart';
+import 'lang/jstring.dart';
+import 'third_party/generated_bindings.dart';
+
+/// Used for `static final` Java strings, where the constant string is
+/// available.
+///
+/// If only its value is used using [toDartString], the [reference] is never
+/// populated, saving a method call.
+class JFinalString extends JString with JLazyReference {
+  @override
+  final JObjectPtr Function() lazyReference;
+
+  final String string;
+
+  JFinalString(this.lazyReference, this.string) : super.fromRef(nullptr);
+
+  @override
+  String toDartString({bool releaseOriginal = false}) {
+    if (releaseOriginal) {
+      release();
+    }
+    return string;
+  }
+}
diff --git a/pkgs/jni/lib/src/jni.dart b/pkgs/jni/lib/src/jni.dart
new file mode 100644
index 0000000..4993aee
--- /dev/null
+++ b/pkgs/jni/lib/src/jni.dart
@@ -0,0 +1,383 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:ffi/ffi.dart';
+import 'package:path/path.dart';
+
+import 'errors.dart';
+import 'jobject.dart';
+import 'third_party/generated_bindings.dart';
+import 'jvalues.dart';
+import 'accessors.dart';
+
+String _getLibraryFileName(String base) {
+  if (Platform.isLinux || Platform.isAndroid) {
+    return "lib$base.so";
+  } else if (Platform.isWindows) {
+    return "$base.dll";
+  } else if (Platform.isMacOS) {
+    return "lib$base.dylib";
+  } else {
+    throw UnsupportedError("cannot derive library name: unsupported platform");
+  }
+}
+
+/// Load Dart-JNI Helper library.
+///
+/// If path is provided, it's used to load the library.
+/// Else just the platform-specific filename is passed to DynamicLibrary.open
+DynamicLibrary _loadDartJniLibrary({String? dir, String baseName = "dartjni"}) {
+  final fileName = _getLibraryFileName(baseName);
+  final libPath = (dir != null) ? join(dir, fileName) : fileName;
+  try {
+    final dylib = DynamicLibrary.open(libPath);
+    return dylib;
+  } on Error {
+    throw HelperNotFoundError(libPath);
+  }
+}
+
+/// Utilities to spawn and manage JNI.
+abstract final class Jni {
+  static final DynamicLibrary _dylib = _loadDartJniLibrary(dir: _dylibDir);
+  static final JniBindings _bindings = JniBindings(_dylib);
+  static final _getJniEnvFn = _dylib.lookup<Void>('GetJniEnv');
+  static final _getJniContextFn = _dylib.lookup<Void>('GetJniContextPtr');
+
+  /// Store dylibDir if any was used.
+  static String? _dylibDir;
+
+  /// Sets the directory where dynamic libraries are looked for.
+  /// On dart standalone, call this in new isolate before doing
+  /// any JNI operation.
+  ///
+  /// (The reason is that dylibs need to be loaded in every isolate.
+  /// On flutter it's done by library. On dart standalone we don't
+  /// know the library path.)
+  static void setDylibDir({required String dylibDir}) {
+    _dylibDir = dylibDir;
+  }
+
+  /// Initializes DartApiDL used for Continuations and interface implementation.
+  static void initDLApi() {
+    assert(NativeApi.majorVersion == 2);
+    assert(NativeApi.minorVersion >= 3);
+    final result = _bindings.InitDartApiDL(NativeApi.initializeApiDLData);
+    assert(result == 0);
+  }
+
+  /// Spawn an instance of JVM using JNI. This method should be called at the
+  /// beginning of the program with appropriate options, before other isolates
+  /// are spawned.
+  ///
+  /// [dylibDir] is path of the directory where the wrapper library is found.
+  /// This parameter needs to be passed manually on __Dart standalone target__,
+  /// since we have no reliable way to bundle it with the package.
+  ///
+  /// [jvmOptions], [ignoreUnrecognized], & [jniVersion] are passed to the JVM.
+  /// Strings in [classPath], if any, are used to construct an additional
+  /// JVM option of the form "-Djava.class.path={paths}".
+  static void spawn({
+    String? dylibDir,
+    List<String> jvmOptions = const [],
+    List<String> classPath = const [],
+    bool ignoreUnrecognized = false,
+    int jniVersion = JniVersions.JNI_VERSION_1_6,
+  }) {
+    final status = spawnIfNotExists(
+      dylibDir: dylibDir,
+      jvmOptions: jvmOptions,
+      classPath: classPath,
+      ignoreUnrecognized: ignoreUnrecognized,
+      jniVersion: jniVersion,
+    );
+    if (status == false) {
+      throw JniVmExistsError();
+    }
+  }
+
+  /// Same as [spawn] but if a JVM exists, returns silently instead of
+  /// throwing [JvmExistsError].
+  ///
+  /// If the options are different than that of existing VM, the existing VM's
+  /// options will remain in effect.
+  static bool spawnIfNotExists({
+    String? dylibDir,
+    List<String> jvmOptions = const [],
+    List<String> classPath = const [],
+    bool ignoreUnrecognized = false,
+    int jniVersion = JniVersions.JNI_VERSION_1_6,
+  }) =>
+      using((arena) {
+        _dylibDir = dylibDir;
+        final jvmArgs = _createVMArgs(
+          options: jvmOptions,
+          classPath: classPath,
+          version: jniVersion,
+          dylibPath: dylibDir,
+          ignoreUnrecognized: ignoreUnrecognized,
+          allocator: arena,
+        );
+        final status = _bindings.SpawnJvm(jvmArgs);
+        if (status == JniErrorCode.JNI_OK) {
+          return true;
+        } else if (status == DART_JNI_SINGLETON_EXISTS) {
+          return false;
+        } else {
+          throw JniError.of(status);
+        }
+      });
+
+  static Pointer<JavaVMInitArgs> _createVMArgs({
+    List<String> options = const [],
+    List<String> classPath = const [],
+    String? dylibPath,
+    bool ignoreUnrecognized = false,
+    int version = JniVersions.JNI_VERSION_1_6,
+    required Allocator allocator,
+  }) {
+    final args = allocator<JavaVMInitArgs>();
+    if (options.isNotEmpty || classPath.isNotEmpty) {
+      final count = options.length +
+          (dylibPath != null ? 1 : 0) +
+          (classPath.isNotEmpty ? 1 : 0);
+      final optsPtr = (count != 0) ? allocator<JavaVMOption>(count) : nullptr;
+      args.ref.options = optsPtr;
+      for (int i = 0; i < options.length; i++) {
+        optsPtr.elementAt(i).ref.optionString =
+            options[i].toNativeChars(allocator);
+      }
+      if (dylibPath != null) {
+        optsPtr
+                .elementAt(count - 1 - (classPath.isNotEmpty ? 1 : 0))
+                .ref
+                .optionString =
+            "-Djava.library.path=$dylibPath".toNativeChars(allocator);
+      }
+      if (classPath.isNotEmpty) {
+        final classPathString = classPath.join(Platform.isWindows ? ';' : ":");
+        optsPtr.elementAt(count - 1).ref.optionString =
+            "-Djava.class.path=$classPathString".toNativeChars(allocator);
+      }
+      args.ref.nOptions = count;
+    }
+    args.ref.ignoreUnrecognized = ignoreUnrecognized ? 1 : 0;
+    args.ref.version = version;
+    return args;
+  }
+
+  /// Returns pointer to current JNI JavaVM instance
+  Pointer<JavaVM> getJavaVM() {
+    return _bindings.GetJavaVM();
+  }
+
+  /// Returns the instance of [GlobalJniEnvStruct], which is an abstraction over
+  /// JNIEnv without the same-thread restriction.
+  static Pointer<GlobalJniEnvStruct> _fetchGlobalEnv() {
+    final env = _bindings.GetGlobalEnv();
+    if (env == nullptr) {
+      throw NoJvmInstanceError();
+    }
+    return env;
+  }
+
+  /// Points to a process-wide shared instance of [GlobalJniEnv].
+  ///
+  /// It provides an indirection over [JniEnv] so that it can be used from
+  /// any thread, and always returns global object references.
+  static final env = GlobalJniEnv(_fetchGlobalEnv());
+
+  static final accessors = JniAccessors(_bindings.GetAccessors());
+
+  /// Returns current application context on Android.
+  static JObjectPtr getCachedApplicationContext() {
+    return _bindings.GetApplicationContext();
+  }
+
+  /// Returns current activity
+  static JObjectPtr getCurrentActivity() => _bindings.GetCurrentActivity();
+
+  /// Get the initial classLoader of the application.
+  ///
+  /// This is especially useful on Android, where
+  /// JNI threads cannot access application classes using
+  /// the usual `JniEnv.FindClass` method.
+  static JObjectPtr getApplicationClassLoader() => _bindings.GetClassLoader();
+
+  /// Returns class reference found through system-specific mechanism
+  static JClassPtr findClass(String qualifiedName) => using((arena) {
+        final cls = accessors.getClass(qualifiedName.toNativeChars(arena));
+        return cls.checkedClassRef;
+      });
+
+  /// Returns class for [qualifiedName] found by platform-specific mechanism,
+  /// wrapped in a [JClass].
+  static JClass findJClass(String qualifiedName) =>
+      JClass.fromRef(findClass(qualifiedName));
+
+  /// Constructs an instance of class with given arguments.
+  ///
+  /// Use it when one instance is needed, but the constructor or class aren't
+  /// required themselves.
+  static JObject newInstance(
+      String qualifiedName, String ctorSignature, List<dynamic> args) {
+    final cls = findJClass(qualifiedName);
+    final ctor = cls.getCtorID(ctorSignature);
+    final obj = cls.newInstance(ctor, args);
+    cls.release();
+    return obj;
+  }
+
+  /// Converts passed arguments to JValue array.
+  ///
+  /// long, bool, double and JObject types are converted out of the box.
+  /// Wrap values in types such as [JValueInt] to convert to other primitive
+  /// types such as `int`, `short` and `char`.
+  static Pointer<JValue> jvalues(List<dynamic> args,
+      {Allocator allocator = calloc}) {
+    return toJValues(args, allocator: allocator);
+  }
+
+  /// Returns the value of static field identified by [fieldName] & [signature].
+  ///
+  /// See [JObject.getField] for more explanations about [callType] and [T].
+  static T retrieveStaticField<T>(
+      String className, String fieldName, String signature,
+      [int? callType]) {
+    final cls = findJClass(className);
+    final result = cls.getStaticFieldByName<T>(fieldName, signature, callType);
+    cls.release();
+    return result;
+  }
+
+  /// Calls static method identified by [methodName] and [signature]
+  /// on [className] with [args] as and [callType].
+  ///
+  /// For more explanation on [args] and [callType], see [JObject.getField]
+  /// and [JObject.callMethod] respectively.
+  static T invokeStaticMethod<T>(
+      String className, String methodName, String signature, List<dynamic> args,
+      [int? callType]) {
+    final cls = findJClass(className);
+    final result =
+        cls.callStaticMethodByName<T>(methodName, signature, args, callType);
+    cls.release();
+    return result;
+  }
+}
+
+typedef _SetJniGettersNativeType = Void Function(Pointer<Void>, Pointer<Void>);
+typedef _SetJniGettersDartType = void Function(Pointer<Void>, Pointer<Void>);
+
+/// Extensions for use by `jnigen` generated code.
+extension ProtectedJniExtensions on Jni {
+  static Pointer<T> Function<T extends NativeType>(String) initGeneratedLibrary(
+      String name) {
+    var path = _getLibraryFileName(name);
+    if (Jni._dylibDir != null) {
+      path = join(Jni._dylibDir!, path);
+    }
+    final dl = DynamicLibrary.open(path);
+    final setJniGetters =
+        dl.lookupFunction<_SetJniGettersNativeType, _SetJniGettersDartType>(
+            'setJniGetters');
+    setJniGetters(Jni._getJniContextFn, Jni._getJniEnvFn);
+    final lookup = dl.lookup;
+    return lookup;
+  }
+
+  /// Returns a new DartException.
+  static JObjectPtr newDartException(String message) {
+    return Jni._bindings
+        .DartException__ctor(Jni.env.toJStringPtr(message))
+        .object;
+  }
+
+  /// Returns a new PortContinuation.
+  static JObjectPtr newPortContinuation(ReceivePort port) {
+    return Jni._bindings
+        .PortContinuation__ctor(port.sendPort.nativePort)
+        .object;
+  }
+
+  /// Returns a new PortProxy for a class with the given [binaryName].
+  static JObjectPtr newPortProxy(
+      String binaryName,
+      ReceivePort port,
+      Pointer<
+              NativeFunction<
+                  Pointer<Void> Function(Uint64, Pointer<Void>, Pointer<Void>)>>
+          functionPtr) {
+    return Jni._bindings
+        .PortProxy__newInstance(
+          Jni.env.toJStringPtr(binaryName),
+          port.sendPort.nativePort,
+          functionPtr.address,
+        )
+        .object;
+  }
+
+  /// Returns the result of a callback..
+  static void returnResult(
+      Pointer<CallbackResult> result, JObjectPtr object) async {
+    Jni._bindings.resultFor(result, object);
+  }
+}
+
+extension AdditionalEnvMethods on GlobalJniEnv {
+  /// Convenience method for converting a [JStringPtr] to dart string.
+  /// if [releaseOriginal] is specified, jstring passed will be deleted using
+  /// DeleteGlobalRef.
+  String toDartString(JStringPtr jstringPtr, {bool releaseOriginal = false}) {
+    if (jstringPtr == nullptr) {
+      throw JNullError();
+    }
+    final chars = GetStringChars(jstringPtr, nullptr);
+    if (chars == nullptr) {
+      throw ArgumentError('Not a valid jstring pointer.');
+    }
+    final length = GetStringLength(jstringPtr);
+    final result = chars.cast<Utf16>().toDartString(length: length);
+    ReleaseStringChars(jstringPtr, chars);
+    if (releaseOriginal) {
+      DeleteGlobalRef(jstringPtr);
+    }
+    return result;
+  }
+
+  /// Returns a new [JStringPtr] from contents of [s].
+  JStringPtr toJStringPtr(String s) => using((arena) {
+        final utf = s.toNativeUtf16(allocator: arena).cast<Uint16>();
+        final result = NewString(utf, s.length);
+        if (utf == nullptr) {
+          throw 'Fatal: cannot convert string to Java string: $s';
+        }
+        return result;
+      });
+
+  /// Deletes all references in [refs].
+  void deleteAllRefs(List<JObjectPtr> refs) {
+    for (final ref in refs) {
+      DeleteGlobalRef(ref);
+    }
+  }
+}
+
+extension StringMethodsForJni on String {
+  /// Returns a Utf-8 encoded Pointer<Char> with contents same as this string.
+  Pointer<Char> toNativeChars([Allocator allocator = malloc]) {
+    return toNativeUtf8(allocator: allocator).cast<Char>();
+  }
+}
+
+extension CharPtrMethodsForJni on Pointer<Char> {
+  /// Same as calling `cast<Utf8>` followed by `toDartString`.
+  String toDartString({int? length}) {
+    return cast<Utf8>().toDartString(length: length);
+  }
+}
diff --git a/pkgs/jni/lib/src/jobject.dart b/pkgs/jni/lib/src/jobject.dart
new file mode 100644
index 0000000..15c6972
--- /dev/null
+++ b/pkgs/jni/lib/src/jobject.dart
@@ -0,0 +1,431 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+
+import 'package:ffi/ffi.dart';
+import 'package:jni/src/accessors.dart';
+
+import 'errors.dart';
+import 'jni.dart';
+import 'jreference.dart';
+import 'lang/jstring.dart';
+import 'jvalues.dart';
+import 'third_party/generated_bindings.dart';
+import 'types.dart';
+
+// This typedef is needed because void is a keyword and cannot be used in
+// type switch like a regular type.
+typedef _VoidType = void;
+
+final class JObjectType extends JObjType<JObject> {
+  const JObjectType();
+
+  @override
+  String get signature => "Ljava/lang/Object;";
+
+  @override
+  JObject fromRef(Pointer<Void> ref) => JObject.fromRef(ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  // TODO(#70): Once interface implementation lands, other than [superType],
+  // we should have a list of implemented interfaces.
+
+  @override
+  final int superCount = 0;
+
+  @override
+  int get hashCode => (JObjectType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JObjectType && other is JObjectType;
+  }
+}
+
+Pointer<T> _getID<T extends NativeType>(
+  JniPointerResult Function(
+          Pointer<Void> ptr, Pointer<Char> name, Pointer<Char> sig)
+      f,
+  Pointer<Void> ptr,
+  String name,
+  String sig,
+) {
+  final result = using(
+      (arena) => f(ptr, name.toNativeChars(arena), sig.toNativeChars(arena)));
+  if (result.exception != nullptr) {
+    Jni.accessors.throwException(result.exception);
+  }
+  return result.value.cast<T>();
+}
+
+int _getCallType(int? callType, int defaultType, Set<int> allowed) {
+  if (callType == null) return defaultType;
+  if (allowed.contains(callType)) return callType;
+  throw InvalidCallTypeError(callType, allowed);
+}
+
+T _callOrGet<T>(int? callType, JniResult Function(int) function) {
+  final int finalCallType;
+  late T result;
+  switch (T) {
+    case bool:
+      finalCallType = _getCallType(
+          callType, JniCallType.booleanType, {JniCallType.booleanType});
+      result = function(finalCallType).boolean as T;
+      break;
+    case int:
+      finalCallType = _getCallType(callType, JniCallType.longType, {
+        JniCallType.byteType,
+        JniCallType.charType,
+        JniCallType.shortType,
+        JniCallType.intType,
+        JniCallType.longType,
+      });
+      final jniResult = function(finalCallType);
+      switch (finalCallType) {
+        case JniCallType.byteType:
+          result = jniResult.byte as T;
+          break;
+        case JniCallType.shortType:
+          result = jniResult.short as T;
+          break;
+        case JniCallType.charType:
+          result = jniResult.char as T;
+          break;
+        case JniCallType.intType:
+          result = jniResult.integer as T;
+          break;
+        case JniCallType.longType:
+          result = jniResult.long as T;
+          break;
+      }
+      break;
+    case double:
+      finalCallType = _getCallType(callType, JniCallType.doubleType,
+          {JniCallType.floatType, JniCallType.doubleType});
+      final jniResult = function(finalCallType);
+      switch (finalCallType) {
+        case JniCallType.floatType:
+          result = jniResult.float as T;
+          break;
+        case JniCallType.doubleType:
+          result = jniResult.doubleFloat as T;
+          break;
+      }
+      break;
+    case String:
+    case JObject:
+    case JString:
+      finalCallType = _getCallType(
+          callType, JniCallType.objectType, {JniCallType.objectType});
+      final ref = function(finalCallType).object;
+      final ctor = T == String
+          ? (ref) => Jni.env.toDartString(ref, releaseOriginal: true)
+          : (T == JObject ? JObject.fromRef : JString.fromRef);
+      result = ctor(ref) as T;
+      break;
+    case const (Pointer<Void>): // JObjectPtr
+      finalCallType = _getCallType(
+          callType, JniCallType.objectType, {JniCallType.objectType});
+      result = function(finalCallType).object as T;
+      break;
+    case _VoidType:
+      finalCallType =
+          _getCallType(callType, JniCallType.voidType, {JniCallType.voidType});
+      function(finalCallType).check();
+      result = null as T;
+      break;
+    case dynamic:
+      throw UnsupportedError("Return type not specified for JNI call");
+    default:
+      throw UnsupportedError('Unknown type $T');
+  }
+  return result;
+}
+
+T _callMethod<T>(int? callType, List<dynamic> args,
+        JniResult Function(int, Pointer<JValue>) f) =>
+    using((arena) {
+      final jArgs = JValueArgs(args, arena);
+      return _callOrGet<T>(callType, (ct) => f(ct, jArgs.values));
+    });
+
+T _getField<T>(int? callType, JniResult Function(int) f) {
+  final result = _callOrGet<T>(callType, f);
+  return result;
+}
+
+/// A high-level wrapper for JNI global object reference.
+///
+/// This is the base class for classes generated by `jnigen`.
+class JObject extends JReference {
+  late final JObjType<JObject> $type = type;
+
+  /// The type which includes information such as the signature of this class.
+  static const JObjType<JObject> type = JObjectType();
+
+  /// Construct a new [JObject] with [reference] as its underlying reference.
+  JObject.fromRef(JObjectPtr reference) : super.fromRef(reference);
+
+  JClass? _jClass;
+
+  JClass get _class {
+    return _jClass ??= getClass();
+  }
+
+  @override
+  void release() {
+    _jClass?.release();
+    super.release();
+  }
+
+  /// Returns [JClass] corresponding to concrete class of this object.
+  ///
+  /// This may be a subclass of compile-time class.
+  JClass getClass() {
+    final classRef = Jni.env.GetObjectClass(reference);
+    if (classRef == nullptr) {
+      Jni.accessors.throwException(Jni.env.ExceptionOccurred());
+    }
+    return JClass.fromRef(classRef);
+  }
+
+  /// Get [JFieldIDPtr] of instance field identified by [fieldName] & [signature].
+  JFieldIDPtr getFieldID(String fieldName, String signature) {
+    return _getID(
+        Jni.accessors.getFieldID, _class.reference, fieldName, signature);
+  }
+
+  /// Get [JFieldIDPtr] of static field identified by [fieldName] & [signature].
+  JFieldIDPtr getStaticFieldID(String fieldName, String signature) {
+    return _getID<jfieldID_>(
+        Jni.accessors.getStaticFieldID, _class.reference, fieldName, signature);
+  }
+
+  /// Get [JMethodIDPtr] of instance method [methodName] with [signature].
+  JMethodIDPtr getMethodID(String methodName, String signature) {
+    return _getID<jmethodID_>(
+        Jni.accessors.getMethodID, _class.reference, methodName, signature);
+  }
+
+  /// Get [JMethodIDPtr] of static method [methodName] with [signature].
+  JMethodIDPtr getStaticMethodID(String methodName, String signature) {
+    return _getID<jmethodID_>(Jni.accessors.getStaticMethodID, _class.reference,
+        methodName, signature);
+  }
+
+  /// Retrieve the value of the field using [fieldID].
+  ///
+  /// [callType] determines the return type of the underlying JNI call made.
+  /// If the Java field is of `long` type, this must be [JniCallType.longType] and
+  /// so on. Default is chosen based on return type [T], which maps int -> int,
+  /// double -> double, void -> void, and JObject types to `Object`.
+  ///
+  /// If [T] is String or [JObject], required conversions are performed and
+  /// final value is returned.
+  T getField<T>(JFieldIDPtr fieldID, [int? callType]) {
+    if (callType == JniCallType.voidType) {
+      throw ArgumentError("void is not a valid field type.");
+    }
+    return _getField<T>(
+        callType, (ct) => Jni.accessors.getField(reference, fieldID, ct));
+  }
+
+  /// Get value of the field identified by [name] and [signature].
+  ///
+  /// See [getField] for an explanation about [callType] parameter.
+  T getFieldByName<T>(String name, String signature, [int? callType]) {
+    final id = getFieldID(name, signature);
+    return getField<T>(id, callType);
+  }
+
+  /// Get value of the static field using [fieldID].
+  ///
+  /// See [getField] for an explanation about [callType] parameter.
+  T getStaticField<T>(JFieldIDPtr fieldID, [int? callType]) {
+    if (callType == JniCallType.voidType) {
+      throw ArgumentError("void is not a valid field type.");
+    }
+    return _getField<T>(callType,
+        (ct) => Jni.accessors.getStaticField(_class.reference, fieldID, ct));
+  }
+
+  /// Get value of the static field identified by [name] and [signature].
+  ///
+  /// See [getField] for an explanation about [callType] parameter.
+  T getStaticFieldByName<T>(String name, String signature, [int? callType]) {
+    final id = getStaticFieldID(name, signature);
+    return getStaticField<T>(id, callType);
+  }
+
+  /// Call the method using [methodID],
+  ///
+  /// [args] can consist of primitive types, JNI primitive wrappers such as
+  /// [JValueLong], strings, and subclasses of [JObject].
+  ///
+  /// See [getField] for an explanation about [callType] and return type [T].
+  T callMethod<T>(JMethodIDPtr methodID, List<dynamic> args, [int? callType]) {
+    return _callMethod<T>(callType, args,
+        (ct, jvs) => Jni.accessors.callMethod(reference, methodID, ct, jvs));
+  }
+
+  /// Call instance method identified by [name] and [signature].
+  ///
+  /// This implementation looks up the method and calls it using [callMethod].
+  T callMethodByName<T>(String name, String signature, List<dynamic> args,
+      [int? callType]) {
+    final id = getMethodID(name, signature);
+    return callMethod<T>(id, args, callType);
+  }
+
+  /// Call static method using [methodID]. See [callMethod] and [getField] for
+  /// more details about [args] and [callType].
+  T callStaticMethod<T>(JMethodIDPtr methodID, List<dynamic> args,
+      [int? callType]) {
+    return _callMethod<T>(
+        callType,
+        args,
+        (ct, jvs) =>
+            Jni.accessors.callStaticMethod(reference, methodID, ct, jvs));
+  }
+
+  /// Call static method identified by [name] and [signature].
+  ///
+  /// This implementation looks up the method and calls [callStaticMethod].
+  T callStaticMethodByName<T>(String name, String signature, List<dynamic> args,
+      [int? callType]) {
+    final id = getStaticMethodID(name, signature);
+    return callStaticMethod<T>(id, args, callType);
+  }
+
+  /// Casts this object to another [type].
+  ///
+  /// If [releaseOriginal] is `true`, the casted object will be released.
+  T castTo<T extends JObject>(
+    JObjType<T> type, {
+    bool releaseOriginal = false,
+  }) {
+    if (releaseOriginal) {
+      _jClass?.release();
+      final ret = type.fromRef(reference);
+      setAsReleased();
+      return ret;
+    }
+    final newRef = Jni.env.NewGlobalRef(reference);
+    return type.fromRef(newRef);
+  }
+
+  static final _objectClass = Jni.findJClass('java/lang/Object');
+
+  static final _hashCodeId =
+      Jni.accessors.getMethodIDOf(_objectClass.reference, r"hashCode", r"()I");
+  @override
+  int get hashCode => Jni.accessors.callMethodWithArgs(
+      reference, _hashCodeId, JniCallType.intType, []).integer;
+
+  static final _equalsId = Jni.accessors.getMethodIDOf(
+      _objectClass.reference, r"equals", r"(Ljava/lang/Object;)Z");
+  @override
+  bool operator ==(Object other) {
+    if (other is! JObject) {
+      return false;
+    }
+    return Jni.accessors.callMethodWithArgs(reference, _equalsId,
+        JniCallType.booleanType, [other.reference]).boolean;
+  }
+
+  static final _toStringId = Jni.accessors.getMethodIDOf(
+      _objectClass.reference, r"toString", r"()Ljava/lang/String;");
+  @override
+  String toString() {
+    return JString.fromRef(Jni.accessors.callMethodWithArgs(
+            reference, _toStringId, JniCallType.objectType, []).object)
+        .toDartString(releaseOriginal: true);
+  }
+}
+
+/// A high level wrapper over a JNI class reference.
+class JClass extends JReference {
+  /// Construct a new [JClass] with [reference] as its underlying reference.
+  JClass.fromRef(JObjectPtr reference) : super.fromRef(reference);
+
+  /// Get [JFieldIDPtr] of static field [fieldName] with [signature].
+  JFieldIDPtr getStaticFieldID(String fieldName, String signature) {
+    return _getID<jfieldID_>(
+        Jni.accessors.getStaticFieldID, reference, fieldName, signature);
+  }
+
+  /// Get [JMethodIDPtr] of static method [methodName] with [signature].
+  JMethodIDPtr getStaticMethodID(String methodName, String signature) {
+    return _getID<jmethodID_>(
+        Jni.accessors.getStaticMethodID, reference, methodName, signature);
+  }
+
+  /// Get [JFieldIDPtr] of field [fieldName] with [signature].
+  JFieldIDPtr getFieldID(String fieldName, String signature) {
+    return _getID<jfieldID_>(
+        Jni.accessors.getFieldID, reference, fieldName, signature);
+  }
+
+  /// Get [JMethodIDPtr] of method [methodName] with [signature].
+  JMethodIDPtr getMethodID(String methodName, String signature) {
+    return _getID<jmethodID_>(
+        Jni.accessors.getMethodID, reference, methodName, signature);
+  }
+
+  /// Get [JMethodIDPtr] of constructor with [signature].
+  JMethodIDPtr getCtorID(String signature) => getMethodID("<init>", signature);
+
+  /// Get the value of static field using [fieldID].
+  ///
+  /// See [JObject.getField] for more explanation about [callType].
+  T getStaticField<T>(JFieldIDPtr fieldID, [int? callType]) {
+    if (callType == JniCallType.voidType) {
+      throw ArgumentError("void is not a valid field type.");
+    }
+    return _getField<T>(
+        callType, (ct) => Jni.accessors.getStaticField(reference, fieldID, ct));
+  }
+
+  /// Get the value of static field identified by [name] and [signature].
+  ///
+  /// This implementation looks up the field ID and calls [getStaticField].
+  T getStaticFieldByName<T>(String name, String signature, [int? callType]) {
+    final id = getStaticFieldID(name, signature);
+    return getStaticField<T>(id, callType);
+  }
+
+  /// Call the static method using [methodID].
+  ///
+  /// See [JObject.callMethod] and [JObject.getField] for more explanation
+  /// about [args] and [callType].
+  T callStaticMethod<T>(JMethodIDPtr methodID, List<dynamic> args,
+      [int? callType]) {
+    return _callMethod<T>(
+        callType,
+        args,
+        (ct, jvs) =>
+            Jni.accessors.callStaticMethod(reference, methodID, ct, jvs));
+  }
+
+  /// Call the static method identified by [name] and [signature].
+  ///
+  /// This implementation looks up the method ID and calls [callStaticMethod].
+  T callStaticMethodByName<T>(String name, String signature, List<dynamic> args,
+      [int? callType]) {
+    final id = getStaticMethodID(name, signature);
+    return callStaticMethod<T>(id, args, callType);
+  }
+
+  /// Create a new instance of this class with [ctor] and [args].
+  JObject newInstance(JMethodIDPtr ctor, List<dynamic> args) => using((arena) {
+        final jArgs = JValueArgs(args, arena);
+        final res =
+            Jni.accessors.newObject(reference, ctor, jArgs.values).object;
+        return JObject.fromRef(res);
+      });
+}
diff --git a/pkgs/jni/lib/src/jprimitives.dart b/pkgs/jni/lib/src/jprimitives.dart
new file mode 100644
index 0000000..04448da
--- /dev/null
+++ b/pkgs/jni/lib/src/jprimitives.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// The types here are mapped to primitive types in Java, so they're all in
+// lowercase.
+// ignore_for_file: camel_case_types
+
+part of 'types.dart';
+
+abstract final class JPrimitive {}
+
+abstract final class jbyte extends JPrimitive {
+  static const type = jbyteType();
+}
+
+final class jbyteType extends JType<jbyte> {
+  const jbyteType();
+
+  @override
+  final signature = 'B';
+}
+
+abstract final class jboolean extends JPrimitive {
+  static const type = jbooleanType();
+}
+
+final class jbooleanType extends JType<jboolean> {
+  const jbooleanType();
+
+  @override
+  final signature = 'Z';
+}
+
+abstract final class jchar extends JPrimitive {
+  static const type = jcharType();
+}
+
+final class jcharType extends JType<jchar> {
+  const jcharType();
+
+  @override
+  final signature = 'C';
+}
+
+abstract final class jshort extends JPrimitive {
+  static const type = jshortType();
+}
+
+final class jshortType extends JType<jshort> {
+  const jshortType();
+
+  @override
+  final signature = 'S';
+}
+
+abstract final class jint extends JPrimitive {
+  static const type = jintType();
+}
+
+final class jintType extends JType<jint> {
+  const jintType();
+
+  @override
+  final signature = 'I';
+}
+
+abstract final class jlong extends JPrimitive {
+  static const type = jlongType();
+}
+
+final class jlongType extends JType<jlong> {
+  const jlongType();
+
+  @override
+  final signature = 'J';
+}
+
+abstract final class jfloat extends JPrimitive {
+  static const type = jfloatType();
+}
+
+final class jfloatType extends JType<jfloat> {
+  const jfloatType();
+
+  @override
+  final signature = 'F';
+}
+
+abstract final class jdouble extends JPrimitive {
+  static const type = jdoubleType();
+}
+
+final class jdoubleType extends JType<jdouble> {
+  const jdoubleType();
+
+  @override
+  final signature = 'D';
+}
diff --git a/pkgs/jni/lib/src/jreference.dart b/pkgs/jni/lib/src/jreference.dart
new file mode 100644
index 0000000..093e297
--- /dev/null
+++ b/pkgs/jni/lib/src/jreference.dart
@@ -0,0 +1,136 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+
+import 'package:ffi/ffi.dart';
+import 'package:jni/src/third_party/generated_bindings.dart';
+
+import 'errors.dart';
+import 'jni.dart';
+
+extension ProtectedJReference on JReference {
+  void setAsReleased() {
+    if (_released) {
+      throw DoubleReleaseError();
+    }
+    _released = true;
+    JReference._finalizer.detach(this);
+  }
+
+  void ensureNotNull() {
+    if (isNull) {
+      throw JNullError();
+    }
+  }
+
+  /// Similar to [reference].
+  ///
+  /// Detaches the finalizer so the underlying pointer will not be deleted.
+  JObjectPtr toPointer() {
+    final ref = reference;
+    setAsReleased();
+    return ref;
+  }
+}
+
+/// A managed JNI global reference.
+///
+/// Uses a [NativeFinalizer] to delete the JNI global reference when finalized.
+abstract class JReference implements Finalizable {
+  static final _finalizer =
+      NativeFinalizer(Jni.env.ptr.ref.DeleteGlobalRef.cast());
+
+  JReference.fromRef(this._reference) {
+    if (_reference != nullptr) {
+      _finalizer.attach(this, _reference, detach: this);
+    }
+  }
+
+  bool _released = false;
+
+  /// Whether the underlying JNI reference is `null` or not.
+  bool get isNull => reference == nullptr;
+
+  /// Whether the underlying JNI reference is deleted or not.
+  bool get isReleased => _released;
+
+  /// Deletes the underlying JNI reference and marks this as released.
+  ///
+  /// Throws [DoubleReleaseError] if this is already released.
+  ///
+  /// Further uses of this object will throw [UseAfterReleaseError].
+  void release() {
+    setAsReleased();
+    Jni.env.DeleteGlobalRef(_reference);
+  }
+
+  /// The underlying JNI global object reference.
+  ///
+  /// Throws [UseAfterReleaseError] if the object is previously released.
+  ///
+  /// Be careful when storing this in a variable since it might have gotten
+  /// released upon use.
+  JObjectPtr get reference {
+    if (_released) throw UseAfterReleaseError();
+    return _reference;
+  }
+
+  final JObjectPtr _reference;
+
+  /// Registers this object to be released at the end of [arena]'s lifetime.
+  void releasedBy(Arena arena) => arena.onReleaseAll(release);
+}
+
+/// Creates a "lazy" [JReference].
+///
+/// The first use of [reference] will call [lazyReference].
+///
+/// This is useful when the Java object is not necessarily used directly, and
+/// there are alternative ways to get a Dart representation of the Object.
+///
+/// Object mixed in with this must call their super.[fromRef] constructor
+/// with [nullptr].
+///
+/// Also see [JFinalString].
+mixin JLazyReference on JReference {
+  JObjectPtr? _lazyReference;
+
+  JObjectPtr Function() get lazyReference;
+
+  @override
+  JObjectPtr get reference {
+    if (_lazyReference == null) {
+      _lazyReference = lazyReference();
+      JReference._finalizer.attach(this, _lazyReference!, detach: this);
+      return _lazyReference!;
+    }
+    if (_released) {
+      throw UseAfterReleaseError();
+    }
+    return _lazyReference!;
+  }
+
+  @override
+  void release() {
+    setAsReleased();
+    if (_lazyReference == null) {
+      return;
+    }
+    Jni.env.DeleteGlobalRef(_lazyReference!);
+  }
+}
+
+extension JReferenceUseExtension<T extends JReference> on T {
+  /// Applies [callback] on [this] object and then delete the underlying JNI
+  /// reference, returning the result of [callback].
+  R use<R>(R Function(T) callback) {
+    try {
+      final result = callback(this);
+      return result;
+    } finally {
+      release();
+    }
+  }
+}
diff --git a/pkgs/jni/lib/src/jvalues.dart b/pkgs/jni/lib/src/jvalues.dart
new file mode 100644
index 0000000..dc17736
--- /dev/null
+++ b/pkgs/jni/lib/src/jvalues.dart
@@ -0,0 +1,144 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+import 'package:ffi/ffi.dart';
+
+import 'jobject.dart';
+import 'third_party/generated_bindings.dart';
+import 'jni.dart';
+
+void _fillJValue(Pointer<JValue> pos, dynamic arg) {
+  if (arg is JObject) {
+    pos.ref.l = arg.reference;
+    return;
+  }
+
+  switch (arg.runtimeType) {
+    case int:
+      pos.ref.j = arg;
+      break;
+    case bool:
+      pos.ref.z = arg ? 1 : 0;
+      break;
+    case const (Pointer<Void>):
+    case const (Pointer<Never>): // for nullptr
+      pos.ref.l = arg;
+      break;
+    case double:
+      pos.ref.d = arg;
+      break;
+    case JValueFloat:
+      pos.ref.f = (arg as JValueFloat).value;
+      break;
+    case JValueInt:
+      pos.ref.i = (arg as JValueInt).value;
+      break;
+    case JValueShort:
+      pos.ref.s = (arg as JValueShort).value;
+      break;
+    case JValueChar:
+      pos.ref.c = (arg as JValueChar).value;
+      break;
+    case JValueByte:
+      pos.ref.b = (arg as JValueByte).value;
+      break;
+    default:
+      throw UnsupportedError("cannot convert ${arg.runtimeType} to jvalue");
+  }
+}
+
+/// Converts passed arguments to JValue array
+/// for use in methods that take arguments.
+///
+/// int, bool, double and JObject types are converted out of the box.
+/// wrap values in types such as [JValueLong]
+/// to convert to other primitive types instead.
+Pointer<JValue> toJValues(List<dynamic> args, {Allocator allocator = calloc}) {
+  final result = allocator<JValue>(args.length);
+  for (int i = 0; i < args.length; i++) {
+    final arg = args[i];
+    final pos = result.elementAt(i);
+    _fillJValue(pos, arg);
+  }
+  return result;
+}
+
+/// Use this class as wrapper to convert an integer
+/// to Java `int` in jvalues method.
+final class JValueInt {
+  int value;
+  JValueInt(this.value);
+}
+
+/// Use this class as wrapper to convert an integer
+/// to Java `short` in jvalues method.
+final class JValueShort {
+  int value;
+  JValueShort(this.value);
+}
+
+/// Use this class as wrapper to convert an integer
+/// to Java `byte` in jvalues method.
+final class JValueByte {
+  int value;
+  JValueByte(this.value);
+}
+
+/// Use this class as wrapper to convert an double
+/// to Java `float` in jvalues method.
+final class JValueFloat {
+  double value;
+  JValueFloat(this.value);
+}
+
+/// Use this class as wrapper to convert an integer
+/// to Java `char` in jvalues method.
+final class JValueChar {
+  int value;
+  JValueChar(this.value);
+  JValueChar.fromString(String s) : value = 0 {
+    if (s.length != 1) {
+      throw "Expected string of length 1";
+    }
+    value = s.codeUnitAt(0).toInt();
+  }
+}
+
+/// class used to convert dart types passed to convenience methods
+/// into their corresponding Java values.
+///
+/// Similar to Jni.jvalues, but instead of a pointer, an instance
+/// with a dispose method is returned.
+/// This allows us to take dart strings.
+///
+/// Returned value is allocated using provided allocator.
+/// But default allocator may be used for string conversions.
+final class JValueArgs {
+  late Pointer<JValue> values;
+  final List<JObjectPtr> createdRefs = [];
+
+  JValueArgs(List<dynamic> args, Arena arena) {
+    values = arena<JValue>(args.length);
+    for (int i = 0; i < args.length; i++) {
+      final arg = args[i];
+      final ptr = values.elementAt(i);
+      if (arg is String) {
+        final jstr = Jni.env.toJStringPtr(arg);
+        ptr.ref.l = jstr;
+        createdRefs.add(jstr);
+      } else {
+        _fillJValue(ptr, arg);
+      }
+      arena.onReleaseAll(_dispose);
+    }
+  }
+
+  /// Deletes temporary references such as [JStringPtr]s.
+  void _dispose() {
+    for (var ref in createdRefs) {
+      Jni.env.DeleteGlobalRef(ref);
+    }
+  }
+}
diff --git a/pkgs/jni/lib/src/lang/jboolean.dart b/pkgs/jni/lib/src/lang/jboolean.dart
new file mode 100644
index 0000000..9ea92b4
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jboolean.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jobject.dart';
+import '../jreference.dart';
+import '../jni.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+
+final class JBooleanType extends JObjType<JBoolean> {
+  const JBooleanType();
+
+  @override
+  String get signature => r"Ljava/lang/Boolean;";
+
+  @override
+  JBoolean fromRef(JObjectPtr ref) => JBoolean.fromRef(ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => (JBooleanType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JBooleanType && other is JBooleanType;
+  }
+}
+
+class JBoolean extends JObject {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JBoolean> $type = type;
+
+  JBoolean.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JBooleanType();
+
+  static final _class = Jni.findJClass(r"java/lang/Boolean");
+
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(Z)V");
+  JBoolean(bool boolean)
+      : super.fromRef(Jni.accessors.newObjectWithArgs(
+            _class.reference, _ctorId, [boolean ? 1 : 0]).object);
+
+  static final _booleanValueId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"booleanValue", r"()Z");
+
+  bool booleanValue({bool releaseOriginal = false}) {
+    ensureNotNull();
+    final ret = Jni.accessors.callMethodWithArgs(
+        reference, _booleanValueId, JniCallType.booleanType, []).boolean;
+    if (releaseOriginal) {
+      release();
+    }
+    return ret;
+  }
+}
diff --git a/pkgs/jni/lib/src/lang/jbyte.dart b/pkgs/jni/lib/src/lang/jbyte.dart
new file mode 100644
index 0000000..6b5aa98
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jbyte.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jnumber.dart';
+
+final class JByteType extends JObjType<JByte> {
+  const JByteType();
+
+  @override
+  String get signature => r"Ljava/lang/Byte;";
+
+  @override
+  JByte fromRef(JObjectPtr ref) => JByte.fromRef(ref);
+
+  @override
+  JObjType get superType => const JNumberType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => (JByteType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JByteType && other is JByteType;
+  }
+}
+
+class JByte extends JNumber {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JByte> $type = type;
+
+  JByte.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JByteType();
+
+  static final _class = Jni.findJClass(r"java/lang/Byte");
+
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(B)V");
+  JByte(int num)
+      : super.fromRef(Jni.accessors.newObjectWithArgs(
+            _class.reference, _ctorId, [JValueByte(num)]).object);
+}
diff --git a/pkgs/jni/lib/src/lang/jcharacter.dart b/pkgs/jni/lib/src/lang/jcharacter.dart
new file mode 100644
index 0000000..b50f67f
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jcharacter.dart
@@ -0,0 +1,66 @@
+import '../accessors.dart';
+import '../jni.dart';
+import '../jobject.dart';
+import '../jreference.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+
+final class JCharacterType extends JObjType<JCharacter> {
+  const JCharacterType();
+
+  @override
+  String get signature => r"Ljava/lang/Character;";
+
+  @override
+  JCharacter fromRef(JObjectPtr ref) => JCharacter.fromRef(ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => (JCharacterType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == (JCharacterType) && other is JCharacterType;
+  }
+}
+
+class JCharacter extends JObject {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JCharacter> $type = type;
+
+  JCharacter.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JCharacterType();
+
+  static final _class = Jni.findJClass(r"java/lang/Character");
+
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(C)V");
+
+  JCharacter(int c)
+      : super.fromRef(Jni.accessors.newObjectWithArgs(
+            _class.reference, _ctorId, [JValueChar(c)]).object);
+
+  static final _charValueId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"charValue", r"()C");
+
+  int charValue({bool releaseOriginal = false}) {
+    ensureNotNull();
+    final ret = Jni.accessors.callMethodWithArgs(
+        reference, _charValueId, JniCallType.charType, []).char;
+    if (releaseOriginal) {
+      release();
+    }
+    return ret;
+  }
+}
diff --git a/pkgs/jni/lib/src/lang/jdouble.dart b/pkgs/jni/lib/src/lang/jdouble.dart
new file mode 100644
index 0000000..6db163e
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jdouble.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jnumber.dart';
+
+final class JDoubleType extends JObjType<JDouble> {
+  const JDoubleType();
+
+  @override
+  String get signature => r"Ljava/lang/Double;";
+
+  @override
+  JDouble fromRef(JObjectPtr ref) => JDouble.fromRef(ref);
+
+  @override
+  JObjType get superType => const JNumberType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => (JDoubleType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JDoubleType && other is JDoubleType;
+  }
+}
+
+class JDouble extends JNumber {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JDouble> $type = type;
+
+  JDouble.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JDoubleType();
+
+  static final _class = Jni.findJClass(r"java/lang/Double");
+
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(D)V");
+  JDouble(double num)
+      : super.fromRef(Jni.accessors
+            .newObjectWithArgs(_class.reference, _ctorId, [num]).object);
+}
diff --git a/pkgs/jni/lib/src/lang/jfloat.dart b/pkgs/jni/lib/src/lang/jfloat.dart
new file mode 100644
index 0000000..a352659
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jfloat.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jnumber.dart';
+
+final class JFloatType extends JObjType<JFloat> {
+  const JFloatType();
+
+  @override
+  String get signature => r"Ljava/lang/Float;";
+
+  @override
+  JFloat fromRef(JObjectPtr ref) => JFloat.fromRef(ref);
+
+  @override
+  JObjType get superType => const JNumberType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => (JFloatType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JFloatType && other is JFloatType;
+  }
+}
+
+class JFloat extends JNumber {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JFloat> $type = type;
+
+  JFloat.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JFloatType();
+
+  static final _class = Jni.findJClass(r"java/lang/Float");
+
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(F)V");
+
+  JFloat(double num)
+      : super.fromRef(Jni.accessors.newObjectWithArgs(
+            _class.reference, _ctorId, [JValueFloat(num)]).object);
+}
diff --git a/pkgs/jni/lib/src/lang/jinteger.dart b/pkgs/jni/lib/src/lang/jinteger.dart
new file mode 100644
index 0000000..8ece466
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jinteger.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jnumber.dart';
+
+final class JIntegerType extends JObjType<JInteger> {
+  const JIntegerType();
+
+  @override
+  String get signature => r"Ljava/lang/Integer;";
+
+  @override
+  JInteger fromRef(JObjectPtr ref) => JInteger.fromRef(ref);
+
+  @override
+  JObjType get superType => const JNumberType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => (JIntegerType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JIntegerType && other is JIntegerType;
+  }
+}
+
+class JInteger extends JNumber {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JInteger> $type = type;
+
+  JInteger.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JIntegerType();
+
+  static final _class = Jni.findJClass(r"java/lang/Integer");
+
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(I)V");
+
+  JInteger(int num)
+      : super.fromRef(Jni.accessors.newObjectWithArgs(
+            _class.reference, _ctorId, [JValueInt(num)]).object);
+}
diff --git a/pkgs/jni/lib/src/lang/jlong.dart b/pkgs/jni/lib/src/lang/jlong.dart
new file mode 100644
index 0000000..66d054b
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jlong.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jnumber.dart';
+
+final class JLongType extends JObjType<JLong> {
+  const JLongType();
+
+  @override
+  String get signature => r"Ljava/lang/Long;";
+
+  @override
+  JLong fromRef(JObjectPtr ref) => JLong.fromRef(ref);
+
+  @override
+  JObjType get superType => const JNumberType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => (JLongType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JLongType && other is JLongType;
+  }
+}
+
+class JLong extends JNumber {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JLong> $type = type;
+
+  JLong.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JLongType();
+
+  static final _class = Jni.findJClass(r"java/lang/Long");
+
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(J)V");
+
+  JLong(int num)
+      : super.fromRef(Jni.accessors
+            .newObjectWithArgs(_class.reference, _ctorId, [num]).object);
+}
diff --git a/pkgs/jni/lib/src/lang/jnumber.dart b/pkgs/jni/lib/src/lang/jnumber.dart
new file mode 100644
index 0000000..45d86dc
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jnumber.dart
@@ -0,0 +1,158 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jobject.dart';
+import '../jreference.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jboolean.dart';
+import 'jbyte.dart';
+import 'jcharacter.dart';
+import 'jdouble.dart';
+import 'jfloat.dart';
+import 'jinteger.dart';
+import 'jlong.dart';
+import 'jshort.dart';
+
+final class JNumberType extends JObjType<JNumber> {
+  const JNumberType();
+
+  @override
+  String get signature => r"Ljava/lang/Number;";
+
+  @override
+  JNumber fromRef(JObjectPtr ref) => JNumber.fromRef(ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => (JNumberType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JNumberType && other is JNumberType;
+  }
+}
+
+class JNumber extends JObject {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JNumber> $type = type;
+
+  JNumber.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = Jni.findJClass(r"java/lang/Number");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JNumberType();
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  JNumber()
+      : super.fromRef(Jni.accessors
+            .newObjectWithArgs(_class.reference, _ctorId, []).object);
+
+  static final _intValueId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"intValue", r"()I");
+
+  int intValue({bool releaseOriginal = false}) {
+    ensureNotNull();
+    final ret = Jni.accessors.callMethodWithArgs(
+        reference, _intValueId, JniCallType.intType, []).integer;
+    if (releaseOriginal) {
+      release();
+    }
+    return ret;
+  }
+
+  static final _longValueId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"longValue", r"()J");
+
+  int longValue({bool releaseOriginal = false}) {
+    ensureNotNull();
+    final ret = Jni.accessors.callMethodWithArgs(
+        reference, _longValueId, JniCallType.longType, []).long;
+    if (releaseOriginal) {
+      release();
+    }
+    return ret;
+  }
+
+  static final _floatValueId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"floatValue", r"()F");
+
+  double floatValue({bool releaseOriginal = false}) {
+    ensureNotNull();
+    final ret = Jni.accessors.callMethodWithArgs(
+        reference, _floatValueId, JniCallType.floatType, []).float;
+    if (releaseOriginal) {
+      release();
+    }
+    return ret;
+  }
+
+  static final _doubleValueId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"doubleValue", r"()D");
+
+  double doubleValue({bool releaseOriginal = false}) {
+    ensureNotNull();
+    final ret = Jni.accessors.callMethodWithArgs(
+        reference, _doubleValueId, JniCallType.doubleType, []).doubleFloat;
+    if (releaseOriginal) {
+      release();
+    }
+    return ret;
+  }
+
+  static final _byteValueId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"byteValue", r"()B");
+
+  int byteValue({bool releaseOriginal = false}) {
+    ensureNotNull();
+    final ret = Jni.accessors.callMethodWithArgs(
+        reference, _byteValueId, JniCallType.byteType, []).byte;
+    if (releaseOriginal) {
+      release();
+    }
+    return ret;
+  }
+
+  static final _shortValueId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"shortValue", r"()S");
+
+  int shortValue({bool releaseOriginal = false}) {
+    ensureNotNull();
+    final ret = Jni.accessors.callMethodWithArgs(
+        reference, _shortValueId, JniCallType.shortType, []).short;
+    if (releaseOriginal) {
+      release();
+    }
+    return ret;
+  }
+}
+
+extension IntToJava on int {
+  JByte toJByte() => JByte(this);
+  JShort toJShort() => JShort(this);
+  JInteger toJInteger() => JInteger(this);
+  JLong toJLong() => JLong(this);
+  JCharacter toJCharacter() => JCharacter(this);
+}
+
+extension DoubleToJava on double {
+  JFloat toJFloat() => JFloat(this);
+  JDouble toJDouble() => JDouble(this);
+}
+
+extension BoolToJava on bool {
+  JBoolean toJBoolean() => JBoolean(this);
+}
diff --git a/pkgs/jni/lib/src/lang/jshort.dart b/pkgs/jni/lib/src/lang/jshort.dart
new file mode 100644
index 0000000..39d3c1d
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jshort.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jnumber.dart';
+
+final class JShortType extends JObjType<JShort> {
+  const JShortType();
+
+  @override
+  String get signature => r"Ljava/lang/Short;";
+
+  @override
+  JShort fromRef(JObjectPtr ref) => JShort.fromRef(ref);
+
+  @override
+  JObjType get superType => const JNumberType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => (JShortType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JShortType && other is JShortType;
+  }
+}
+
+class JShort extends JNumber {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JShort> $type = type;
+
+  JShort.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JShortType();
+
+  static final _class = Jni.findJClass(r"java/lang/Short");
+
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(S)V");
+
+  JShort(int num)
+      : super.fromRef(Jni.accessors.newObjectWithArgs(
+            _class.reference, _ctorId, [JValueShort(num)]).object);
+}
diff --git a/pkgs/jni/lib/src/lang/jstring.dart b/pkgs/jni/lib/src/lang/jstring.dart
new file mode 100644
index 0000000..9b8fbbb
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/jstring.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+
+import 'package:jni/src/jreference.dart';
+
+import '../jni.dart';
+import '../jobject.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+
+final class JStringType extends JObjType<JString> {
+  const JStringType();
+
+  @override
+  String get signature => "Ljava/lang/String;";
+
+  @override
+  JString fromRef(Pointer<Void> ref) => JString.fromRef(ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final int superCount = 1;
+
+  @override
+  int get hashCode => (JStringType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JStringType && other is JStringType;
+  }
+}
+
+class JString extends JObject {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JString> $type = type;
+
+  /// The type which includes information such as the signature of this class.
+  static const JObjType<JString> type = JStringType();
+
+  /// Construct a new [JString] with [reference] as its underlying reference.
+  JString.fromRef(JStringPtr reference) : super.fromRef(reference);
+
+  /// The number of Unicode characters in this Java string.
+  int get length => Jni.env.GetStringLength(reference);
+
+  /// Construct a [JString] from the contents of Dart string [s].
+  JString.fromString(String s) : super.fromRef(Jni.env.toJStringPtr(s));
+
+  /// Returns the contents as a Dart String.
+  ///
+  /// If [releaseOriginal] is true, the underlying reference is deleted
+  /// after conversion and this object will be marked as released.
+  String toDartString({bool releaseOriginal = false}) {
+    ensureNotNull();
+    final result = Jni.env.toDartString(reference);
+    if (releaseOriginal) {
+      release();
+    }
+    return result;
+  }
+}
+
+extension ToJStringMethod on String {
+  /// Returns a [JString] with the contents of this String.
+  JString toJString() {
+    return JString.fromString(this);
+  }
+}
diff --git a/pkgs/jni/lib/src/lang/lang.dart b/pkgs/jni/lib/src/lang/lang.dart
new file mode 100644
index 0000000..786488c
--- /dev/null
+++ b/pkgs/jni/lib/src/lang/lang.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'jboolean.dart';
+export 'jbyte.dart';
+export 'jcharacter.dart';
+export 'jdouble.dart';
+export 'jfloat.dart';
+export 'jinteger.dart';
+export 'jlong.dart';
+export 'jnumber.dart';
+export 'jshort.dart';
+export 'jstring.dart';
diff --git a/pkgs/jni/lib/src/method_invocation.dart b/pkgs/jni/lib/src/method_invocation.dart
new file mode 100644
index 0000000..69cdc4a
--- /dev/null
+++ b/pkgs/jni/lib/src/method_invocation.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+
+import 'third_party/generated_bindings.dart';
+
+import 'lang/jstring.dart';
+import 'jarray.dart';
+import 'jobject.dart';
+
+class $MethodInvocation {
+  final Pointer<CallbackResult> result;
+  final JString methodDescriptor;
+  final JArray<JObject> args;
+
+  $MethodInvocation._(this.result, this.methodDescriptor, this.args);
+
+  factory $MethodInvocation.fromAddresses(
+    int resultAddress,
+    int descriptorAddress,
+    int argsAddress,
+  ) {
+    return $MethodInvocation._(
+      Pointer<CallbackResult>.fromAddress(resultAddress),
+      JString.fromRef(Pointer<Void>.fromAddress(descriptorAddress)),
+      JArray.fromRef(
+        const JObjectType(),
+        Pointer<Void>.fromAddress(argsAddress),
+      ),
+    );
+  }
+
+  factory $MethodInvocation.fromMessage(List<dynamic> message) {
+    return $MethodInvocation.fromAddresses(message[0], message[1], message[2]);
+  }
+}
diff --git a/pkgs/jni/lib/src/nio/jbuffer.dart b/pkgs/jni/lib/src/nio/jbuffer.dart
new file mode 100644
index 0000000..cae6bc9
--- /dev/null
+++ b/pkgs/jni/lib/src/nio/jbuffer.dart
@@ -0,0 +1,255 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jobject.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+
+final class JBufferType extends JObjType<JBuffer> {
+  const JBufferType();
+
+  @override
+  String get signature => r"Ljava/nio/Buffer;";
+
+  @override
+  JBuffer fromRef(JObjectPtr ref) => JBuffer.fromRef(ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => (JBufferType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == (JBufferType) && other is JBufferType;
+  }
+}
+
+/// A container for data of a specific primitive type.
+///
+/// The bindings for `java.nio.Buffer`.
+///
+/// A buffer is a linear, finite sequence of elements of a specific primitive
+/// type. Aside from its content, the essential properties of a buffer are its
+/// [capacity], [limit], and [position].
+///
+/// There is one subclass of this class for each non-boolean primitive type.
+/// We currently only have the bindings for `java.nio.ByteBuffer` in this
+/// package as [JByteBuffer].
+class JBuffer extends JObject {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JBuffer> $type = type;
+
+  JBuffer.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = Jni.findJClass(r"java/nio/Buffer");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JBufferType();
+
+  static final _capacityId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"capacity", r"()I");
+
+  /// The number of elements this buffer contains.
+  ///
+  /// It is never negative and never changes.
+  int get capacity {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _capacityId, JniCallType.intType, []).integer;
+  }
+
+  static final _positionId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"position", r"()I");
+
+  /// The index of the next element to be read or written.
+  ///
+  /// It is never negative and is never greater than its [limit].
+  int get position {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _positionId, JniCallType.intType, []).integer;
+  }
+
+  static final _setPositionId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"position", r"(I)Ljava/nio/Buffer;");
+
+  /// Throws:
+  /// * [IllegalArgumentException] - If the preconditions on [newPosition] do
+  ///   not hold.
+  set position(int position) {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(reference,
+        _setPositionId, JniCallType.objectType, [JValueInt(position)]).object);
+  }
+
+  static final _limitId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"limit", r"()I");
+
+  /// The index of the first element that should not be read or written.
+  ///
+  /// It is never negative and is never greater than its [capacity].
+  int get limit {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _limitId, JniCallType.intType, []).integer;
+  }
+
+  static final _setLimitId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"limit", r"(I)Ljava/nio/Buffer;");
+
+  /// Throws:
+  /// * [IllegalArgumentException] - If the preconditions on [newLimit] do not
+  ///   hold.
+  set limit(int newLimit) {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(reference,
+        _setLimitId, JniCallType.objectType, [JValueInt(newLimit)]).object);
+  }
+
+  static final _markId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"mark", r"()Ljava/nio/Buffer;");
+
+  /// Sets this buffer's mark at its [position].
+  ///
+  /// Mark is the index to which its [position] will be reset when the [reset]
+  /// method is invoked.
+  void mark() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _markId, JniCallType.objectType, []).object);
+  }
+
+  static final _resetId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"reset", r"()Ljava/nio/Buffer;");
+
+  /// Resets this buffer's [position] to the previously-marked position.
+  ///
+  /// Throws:
+  /// * [InvalidMarkException] - If the mark has not been set
+  void reset() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _resetId, JniCallType.objectType, []).object);
+  }
+
+  static final _clearId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"clear", r"()Ljava/nio/Buffer;");
+
+  /// Clears this buffer.
+  ///
+  /// The [position] is set to zero, the [limit] is set to
+  /// the [capacity], and the mark is discarded.
+  void clear() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _clearId, JniCallType.objectType, []).object);
+  }
+
+  static final _flipId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"flip", r"()Ljava/nio/Buffer;");
+
+  /// Flips this buffer.
+  ///
+  /// The limit is set to the current [position] and then the [position] is set
+  /// to zero. If the mark is defined then it is discarded.
+  void flip() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _flipId, JniCallType.objectType, []).object);
+  }
+
+  static final _rewindId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"rewind", r"()Ljava/nio/Buffer;");
+
+  /// Rewinds this buffer.
+  ///
+  /// The [position] is set to zero and the mark is discarded.
+  void rewind() {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _rewindId, JniCallType.objectType, []).object);
+  }
+
+  static final _remainingId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"remaining", r"()I");
+
+  /// The number of elements between the current [position] and the
+  /// [limit].
+  int get remaining {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _remainingId, JniCallType.intType, []).integer;
+  }
+
+  static final _hasRemainingId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"hasRemaining", r"()Z");
+
+  /// Whether there are any elements between the current [position] and
+  /// the [limit].
+  bool get hasRemaining {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _hasRemainingId, JniCallType.booleanType, []).boolean;
+  }
+
+  static final _isReadOnlyId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"isReadOnly", r"()Z");
+
+  /// Whether or not this buffer is read-only.
+  bool get isReadOnly {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _isReadOnlyId, JniCallType.booleanType, []).boolean;
+  }
+
+  static final _hasArrayId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"hasArray", r"()Z");
+
+  /// Whether or not this buffer is backed by an accessible array.
+  bool get hasArray {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _hasArrayId, JniCallType.booleanType, []).boolean;
+  }
+
+  static final _arrayId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"array", r"()Ljava/lang/Object;");
+
+  /// The array that backs this buffer.
+  ///
+  /// Concrete subclasses like [JByteBuffer] provide more strongly-typed return
+  /// values for this method.
+  ///
+  /// Throws:
+  /// * [ReadOnlyBufferException] - If this buffer is backed by an array but is
+  ///   read-only
+  /// * [UnsupportedOperationException] - If this buffer is not backed by an
+  ///   accessible array
+  JObject get array {
+    return const JObjectType().fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _arrayId, JniCallType.objectType, []).object);
+  }
+
+  static final _arrayOffsetId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"arrayOffset", r"()I");
+
+  /// The offset within this buffer's backing array of the first element
+  /// of the buffer.
+  ///
+  /// Throws:
+  /// * [ReadOnlyBufferException] - If this buffer is backed by an array but is
+  ///   read-only
+  /// * [UnsupportedOperationException] - If this buffer is not backed by an
+  ///   accessible array
+  int get arrayOffset {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _arrayOffsetId, JniCallType.intType, []).integer;
+  }
+
+  static final _isDirectId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"isDirect", r"()Z");
+
+  /// Whether or not this buffer is direct.
+  bool get isDirect {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _isDirectId, JniCallType.booleanType, []).boolean;
+  }
+}
diff --git a/pkgs/jni/lib/src/nio/jbyte_buffer.dart b/pkgs/jni/lib/src/nio/jbyte_buffer.dart
new file mode 100644
index 0000000..d5f5488
--- /dev/null
+++ b/pkgs/jni/lib/src/nio/jbyte_buffer.dart
@@ -0,0 +1,315 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+import 'dart:typed_data';
+
+import '../accessors.dart';
+import '../jarray.dart';
+import '../jni.dart';
+import '../jreference.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jbuffer.dart';
+
+final class JByteBufferType extends JObjType<JByteBuffer> {
+  const JByteBufferType();
+
+  @override
+  String get signature => r"Ljava/nio/ByteBuffer;";
+
+  @override
+  JByteBuffer fromRef(JObjectPtr ref) => JByteBuffer.fromRef(ref);
+
+  @override
+  JObjType get superType => const JBufferType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => (JByteBufferType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == (JByteBufferType) && other is JByteBufferType;
+  }
+}
+
+/// A byte [JBuffer].
+///
+/// The bindings for `java.nio.ByteBuffer`.
+///
+/// This enables fast memory copying between Java and Dart when directly
+/// allocated (See [JByteBuffer.allocateDirect]).
+///
+/// To create a [JByteBuffer] from the content of a [Uint8List],
+/// use [JByteBuffer.fromList]. This uses direct allocation to enable fast
+/// copying.
+///
+/// [asUint8List] provides a direct access to the underlying [Uint8List] that
+/// this buffer uses. This means any changes to it will change the content of
+/// the buffer and vice versa. This can be used to access to [Uint8List] methods
+/// such as [Uint8List.setRange].
+///
+/// Example:
+/// ```dart
+/// final directBuffer = JByteBuffer.allocateDirect(3);
+/// directBuffer.asUint8List().setAll(0, [1, 2, 3]);
+/// // The buffer is now 1, 2, 3.
+/// ```
+///
+/// Both the original buffer and the [Uint8List] keep the underlying Java buffer
+/// alive. Once all the instances of the original buffer and the lists produced
+/// from [asUint8List] are inaccessible both in Java and Dart, Java will
+/// correctly garbage collects the buffer and frees its underlying memory.
+///
+/// Example:
+/// ```dart
+/// final directBuffer = JByteBuffer.allocateDirect(3);
+/// final data = directBuffer.asUint8List();
+/// directBuffer.release(); // Releasing the original buffer.
+/// data.setAll(0, [1, 2, 3]); // Works! [data] is still accessible.
+/// ```
+///
+/// The original buffer can be [release]d when calling [asUint8List]
+/// by setting the `releaseOriginal` parameter to `true`.
+///
+/// Example:
+/// ```dart
+/// final directBuffer = JByteBuffer.allocateDirect(3);
+/// // [releaseOriginal] is `false` by default.
+/// final data1 = directBuffer.asUint8List();
+/// directBuffer.nextByte = 42; // No problem!
+/// print(data1[0]); // prints 42!
+/// final data2 = directBuffer.asUint8List(releaseOriginal: true);
+/// // directBuffer.nextByte = 42; // throws [UseAfterReleaseException]!
+/// ```
+class JByteBuffer extends JBuffer {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JByteBuffer> $type = type;
+
+  JByteBuffer.fromRef(
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = Jni.findJClass(r"java/nio/ByteBuffer");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = JByteBufferType();
+
+  static final _allocateDirectId = Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"allocateDirect", r"(I)Ljava/nio/ByteBuffer;");
+
+  /// Allocates a new direct byte buffer.
+  ///
+  /// Throws:
+  /// * [IllegalArgumentException] - If the capacity is a negative integer
+  factory JByteBuffer.allocateDirect(int capacity) {
+    return JByteBuffer.fromRef(
+      Jni.accessors.callStaticMethodWithArgs(
+          _class.reference,
+          _allocateDirectId,
+          JniCallType.objectType,
+          [JValueInt(capacity)]).object,
+    );
+  }
+
+  static final _allocateId = Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"allocate", r"(I)Ljava/nio/ByteBuffer;");
+
+  /// Allocates a new byte buffer.
+  ///
+  /// Throws:
+  /// * [IllegalArgumentException] - If the capacity is a negative integer
+  factory JByteBuffer.allocate(int capacity) {
+    return const JByteBufferType().fromRef(Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _allocateId,
+            JniCallType.objectType, [JValueInt(capacity)]).object);
+  }
+
+  static final _wrapWholeId = Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"wrap", r"([B)Ljava/nio/ByteBuffer;");
+  static final _wrapId = Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"wrap", r"([BII)Ljava/nio/ByteBuffer;");
+
+  /// Wraps a byte array into a buffer.
+  ///
+  /// The new buffer will be backed by the given byte array; that is,
+  /// modifications to the buffer will cause the array to be modified
+  /// and vice versa.
+  static JByteBuffer wrap(
+    JArray<jbyte> array, [
+    int? offset,
+    int? length,
+  ]) {
+    if (offset == null && length == null) {
+      return const JByteBufferType().fromRef(
+        Jni.accessors.callStaticMethodWithArgs(
+          _class.reference,
+          _wrapWholeId,
+          JniCallType.objectType,
+          [array.reference],
+        ).object,
+      );
+    }
+    offset ??= 0;
+    length ??= array.length - offset;
+    return const JByteBufferType().fromRef(
+      Jni.accessors.callStaticMethodWithArgs(
+        _class.reference,
+        _wrapId,
+        JniCallType.objectType,
+        [array.reference, JValueInt(offset), JValueInt(length)],
+      ).object,
+    );
+  }
+
+  /// Creates a [JByteBuffer] from the content of [list].
+  ///
+  /// The [JByteBuffer] will be allocated using [JByteBuffer.allocateDirect].
+  factory JByteBuffer.fromList(Uint8List list) {
+    final buffer = JByteBuffer.allocateDirect(list.length);
+    buffer._asUint8ListUnsafe().setAll(0, list);
+    return buffer;
+  }
+
+  static final _sliceId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"slice", r"()Ljava/nio/ByteBuffer;");
+
+  /// Creates a new byte buffer whose content is a shared subsequence of this
+  /// buffer's content.
+  JByteBuffer slice() {
+    return const JByteBufferType().fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _sliceId, JniCallType.objectType, []).object);
+  }
+
+  static final _duplicateId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"duplicate", r"()Ljava/nio/ByteBuffer;");
+
+  /// Creates a new byte buffer that shares this buffer's content.
+  JByteBuffer duplicate() {
+    return const JByteBufferType().fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _duplicateId, JniCallType.objectType, []).object);
+  }
+
+  static final _asReadOnlyBufferId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"asReadOnlyBuffer", r"()Ljava/nio/ByteBuffer;");
+
+  /// Creates a new, read-only byte buffer that shares this buffer's content.
+  JByteBuffer asReadOnlyBuffer() {
+    return const JByteBufferType().fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _asReadOnlyBufferId, JniCallType.objectType, []).object);
+  }
+
+  static final _getId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"get", r"()B");
+
+  /// Reads the byte at this buffer's current [position], and then increments the
+  /// [position].
+  ///
+  /// Throws:
+  ///  * [BufferOverflowException] - If the buffer's current [position] is not
+  ///    smaller than its [limit]
+  int get nextByte {
+    return Jni.accessors
+        .callMethodWithArgs(reference, _getId, JniCallType.byteType, []).byte;
+  }
+
+  static final _putId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"put", r"(B)Ljava/nio/ByteBuffer;");
+
+  /// Writes the given byte into this buffer at the current [position], and then
+  /// increments the [position].
+  ///
+  /// Throws:
+  /// * [BufferOverflowException] - If this buffer's current [position] is not
+  ///   smaller than its [limit]
+  /// * [ReadOnlyBufferException] - If this buffer is read-only
+  set nextByte(int b) {
+    Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(
+        reference, _putId, JniCallType.objectType, [JValueByte(b)]).object);
+  }
+
+  static final _arrayId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"array", r"()[B");
+
+  @override
+  JArray<jbyte> get array {
+    return const JArrayType(jbyteType()).fromRef(Jni.accessors
+        .callMethodWithArgs(
+            reference, _arrayId, JniCallType.objectType, []).object);
+  }
+
+  void _ensureIsDirect() {
+    if (!isDirect) {
+      throw StateError(
+        'The buffer must be created with `JByteBuffer.allocateDirect`.',
+      );
+    }
+  }
+
+  Pointer<Void> _directBufferAddress() {
+    final address = Jni.env.GetDirectBufferAddress(reference);
+    if (address == nullptr) {
+      throw StateError(
+        'The memory region is undefined or '
+        'direct buffer access is not supported by this JVM.',
+      );
+    }
+    return address;
+  }
+
+  int _directBufferCapacity() {
+    final capacity = Jni.env.GetDirectBufferCapacity(reference);
+    if (capacity == -1) {
+      throw StateError(
+        'The object is an unaligned view buffer and the processor '
+        'architecture does not support unaligned access.',
+      );
+    }
+    return capacity;
+  }
+
+  Uint8List _asUint8ListUnsafe() {
+    _ensureIsDirect();
+    final address = _directBufferAddress();
+    final capacity = _directBufferCapacity();
+    return address.cast<Uint8>().asTypedList(capacity);
+  }
+
+  /// Returns this byte buffer as a [Uint8List].
+  ///
+  /// If [releaseOriginal] is `true`, this byte buffer will be released.
+  ///
+  /// Throws [StateError] if the buffer is not direct
+  /// (see [JByteBuffer.allocateDirect]) or the JVM does not support the direct
+  /// buffer operations or the object is an unaligned view buffer and
+  /// the processor does not support unaligned access.
+  Uint8List asUint8List({bool releaseOriginal = false}) {
+    _ensureIsDirect();
+    final address = _directBufferAddress();
+    final capacity = _directBufferCapacity();
+    final token = releaseOriginal ? reference : Jni.env.NewGlobalRef(reference);
+    if (releaseOriginal) {
+      setAsReleased();
+    }
+    return address.cast<Uint8>().asTypedList(
+          capacity,
+          token: token,
+          finalizer: Jni.env.ptr.ref.DeleteGlobalRef.cast(),
+        );
+  }
+}
+
+extension Uint8ListToJava on Uint8List {
+  /// Creates a [JByteBuffer] from the content of this list.
+  ///
+  /// The [JByteBuffer] will be allocated using [JByteBuffer.allocateDirect].
+  JByteBuffer toJByteBuffer() {
+    return JByteBuffer.fromList(this);
+  }
+}
diff --git a/pkgs/jni/lib/src/nio/nio.dart b/pkgs/jni/lib/src/nio/nio.dart
new file mode 100644
index 0000000..3092870
--- /dev/null
+++ b/pkgs/jni/lib/src/nio/nio.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'jbuffer.dart';
+export 'jbyte_buffer.dart';
diff --git a/pkgs/jni/lib/src/third_party/generated_bindings.dart b/pkgs/jni/lib/src/third_party/generated_bindings.dart
new file mode 100644
index 0000000..6047975
--- /dev/null
+++ b/pkgs/jni/lib/src/third_party/generated_bindings.dart
@@ -0,0 +1,2 @@
+export 'jni_bindings_generated.dart';
+export 'global_env_extensions.dart';
diff --git a/pkgs/jni/lib/src/third_party/global_env_extensions.dart b/pkgs/jni/lib/src/third_party/global_env_extensions.dart
new file mode 100644
index 0000000..2fe4001
--- /dev/null
+++ b/pkgs/jni/lib/src/third_party/global_env_extensions.dart
@@ -0,0 +1,1514 @@
+// Auto generated file. Do not edit.
+
+// This is generated from JNI header in Android NDK. License for the same is
+// provided below.
+
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/*
+ * JNI specification, as defined by Sun:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
+ *
+ * Everything here is expected to be VM-neutral.
+ */
+
+// ignore_for_file: non_constant_identifier_names
+// coverage:ignore-file
+
+import "dart:ffi" as ffi;
+
+import "jni_bindings_generated.dart";
+
+import "../accessors.dart";
+
+/// Wraps over Pointer<GlobalJniEnvStruct> and exposes function pointer fields
+/// as methods.
+class GlobalJniEnv {
+  final ffi.Pointer<GlobalJniEnvStruct> ptr;
+  GlobalJniEnv(this.ptr);
+  late final _GetVersion =
+      ptr.ref.GetVersion.asFunction<JniResult Function()>();
+
+  int GetVersion() => _GetVersion().integer;
+
+  late final _DefineClass = ptr.ref.DefineClass.asFunction<
+      JniClassLookupResult Function(ffi.Pointer<ffi.Char> name,
+          JObjectPtr loader, ffi.Pointer<JByteMarker> buf, int bufLen)>();
+
+  JClassPtr DefineClass(ffi.Pointer<ffi.Char> name, JObjectPtr loader,
+          ffi.Pointer<JByteMarker> buf, int bufLen) =>
+      _DefineClass(name, loader, buf, bufLen).value;
+
+  late final _FindClass = ptr.ref.FindClass
+      .asFunction<JniClassLookupResult Function(ffi.Pointer<ffi.Char> name)>();
+
+  JClassPtr FindClass(ffi.Pointer<ffi.Char> name) => _FindClass(name).value;
+
+  late final _FromReflectedMethod = ptr.ref.FromReflectedMethod
+      .asFunction<JniPointerResult Function(JObjectPtr method)>();
+
+  JMethodIDPtr FromReflectedMethod(JObjectPtr method) =>
+      _FromReflectedMethod(method).methodID;
+
+  late final _FromReflectedField = ptr.ref.FromReflectedField
+      .asFunction<JniPointerResult Function(JObjectPtr field)>();
+
+  JFieldIDPtr FromReflectedField(JObjectPtr field) =>
+      _FromReflectedField(field).fieldID;
+
+  late final _ToReflectedMethod = ptr.ref.ToReflectedMethod.asFunction<
+      JniResult Function(JClassPtr cls, JMethodIDPtr methodId, int isStatic)>();
+
+  JObjectPtr ToReflectedMethod(
+          JClassPtr cls, JMethodIDPtr methodId, int isStatic) =>
+      _ToReflectedMethod(cls, methodId, isStatic).object;
+
+  late final _GetSuperclass = ptr.ref.GetSuperclass
+      .asFunction<JniClassLookupResult Function(JClassPtr clazz)>();
+
+  JClassPtr GetSuperclass(JClassPtr clazz) => _GetSuperclass(clazz).value;
+
+  late final _IsAssignableFrom = ptr.ref.IsAssignableFrom
+      .asFunction<JniResult Function(JClassPtr clazz1, JClassPtr clazz2)>();
+
+  bool IsAssignableFrom(JClassPtr clazz1, JClassPtr clazz2) =>
+      _IsAssignableFrom(clazz1, clazz2).boolean;
+
+  late final _ToReflectedField = ptr.ref.ToReflectedField.asFunction<
+      JniResult Function(JClassPtr cls, JFieldIDPtr fieldID, int isStatic)>();
+
+  JObjectPtr ToReflectedField(
+          JClassPtr cls, JFieldIDPtr fieldID, int isStatic) =>
+      _ToReflectedField(cls, fieldID, isStatic).object;
+
+  late final _Throw =
+      ptr.ref.Throw.asFunction<JniResult Function(JThrowablePtr obj)>();
+
+  int Throw(JThrowablePtr obj) => _Throw(obj).integer;
+
+  late final _ThrowNew = ptr.ref.ThrowNew.asFunction<
+      JniResult Function(JClassPtr clazz, ffi.Pointer<ffi.Char> message)>();
+
+  int ThrowNew(JClassPtr clazz, ffi.Pointer<ffi.Char> message) =>
+      _ThrowNew(clazz, message).integer;
+
+  late final _ExceptionOccurred =
+      ptr.ref.ExceptionOccurred.asFunction<JniResult Function()>();
+
+  JThrowablePtr ExceptionOccurred() => _ExceptionOccurred().object;
+
+  late final _ExceptionDescribe =
+      ptr.ref.ExceptionDescribe.asFunction<JThrowablePtr Function()>();
+
+  void ExceptionDescribe() => _ExceptionDescribe().check();
+
+  late final _ExceptionClear =
+      ptr.ref.ExceptionClear.asFunction<JThrowablePtr Function()>();
+
+  void ExceptionClear() => _ExceptionClear().check();
+
+  late final _FatalError = ptr.ref.FatalError
+      .asFunction<JThrowablePtr Function(ffi.Pointer<ffi.Char> msg)>();
+
+  void FatalError(ffi.Pointer<ffi.Char> msg) => _FatalError(msg).check();
+
+  late final _PushLocalFrame =
+      ptr.ref.PushLocalFrame.asFunction<JniResult Function(int capacity)>();
+
+  int PushLocalFrame(int capacity) => _PushLocalFrame(capacity).integer;
+
+  late final _PopLocalFrame =
+      ptr.ref.PopLocalFrame.asFunction<JniResult Function(JObjectPtr result)>();
+
+  JObjectPtr PopLocalFrame(JObjectPtr result) => _PopLocalFrame(result).object;
+
+  late final _NewGlobalRef =
+      ptr.ref.NewGlobalRef.asFunction<JniResult Function(JObjectPtr obj)>();
+
+  JObjectPtr NewGlobalRef(JObjectPtr obj) => _NewGlobalRef(obj).object;
+
+  late final _DeleteGlobalRef = ptr.ref.DeleteGlobalRef
+      .asFunction<JThrowablePtr Function(JObjectPtr globalRef)>();
+
+  void DeleteGlobalRef(JObjectPtr globalRef) =>
+      _DeleteGlobalRef(globalRef).check();
+
+  late final _DeleteLocalRef = ptr.ref.DeleteLocalRef
+      .asFunction<JThrowablePtr Function(JObjectPtr localRef)>();
+
+  void DeleteLocalRef(JObjectPtr localRef) => _DeleteLocalRef(localRef).check();
+
+  late final _IsSameObject = ptr.ref.IsSameObject
+      .asFunction<JniResult Function(JObjectPtr ref1, JObjectPtr ref2)>();
+
+  bool IsSameObject(JObjectPtr ref1, JObjectPtr ref2) =>
+      _IsSameObject(ref1, ref2).boolean;
+
+  late final _NewLocalRef =
+      ptr.ref.NewLocalRef.asFunction<JniResult Function(JObjectPtr obj)>();
+
+  JObjectPtr NewLocalRef(JObjectPtr obj) => _NewLocalRef(obj).object;
+
+  late final _EnsureLocalCapacity = ptr.ref.EnsureLocalCapacity
+      .asFunction<JniResult Function(int capacity)>();
+
+  int EnsureLocalCapacity(int capacity) =>
+      _EnsureLocalCapacity(capacity).integer;
+
+  late final _AllocObject =
+      ptr.ref.AllocObject.asFunction<JniResult Function(JClassPtr clazz)>();
+
+  JObjectPtr AllocObject(JClassPtr clazz) => _AllocObject(clazz).object;
+
+  late final _NewObject = ptr.ref.NewObject
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  JObjectPtr NewObject(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _NewObject(clazz, methodID).object;
+
+  late final _NewObjectA = ptr.ref.NewObjectA.asFunction<
+      JniResult Function(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  JObjectPtr NewObjectA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _NewObjectA(clazz, methodID, args).object;
+
+  late final _GetObjectClass = ptr.ref.GetObjectClass
+      .asFunction<JniClassLookupResult Function(JObjectPtr obj)>();
+
+  JClassPtr GetObjectClass(JObjectPtr obj) => _GetObjectClass(obj).value;
+
+  late final _IsInstanceOf = ptr.ref.IsInstanceOf
+      .asFunction<JniResult Function(JObjectPtr obj, JClassPtr clazz)>();
+
+  bool IsInstanceOf(JObjectPtr obj, JClassPtr clazz) =>
+      _IsInstanceOf(obj, clazz).boolean;
+
+  late final _GetMethodID = ptr.ref.GetMethodID.asFunction<
+      JniPointerResult Function(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+          ffi.Pointer<ffi.Char> sig)>();
+
+  JMethodIDPtr GetMethodID(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+          ffi.Pointer<ffi.Char> sig) =>
+      _GetMethodID(clazz, name, sig).methodID;
+
+  late final _CallObjectMethod = ptr.ref.CallObjectMethod
+      .asFunction<JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  JObjectPtr CallObjectMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallObjectMethod(obj, methodID).object;
+
+  late final _CallObjectMethodA = ptr.ref.CallObjectMethodA.asFunction<
+      JniResult Function(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  JObjectPtr CallObjectMethodA(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallObjectMethodA(obj, methodID, args).object;
+
+  late final _CallBooleanMethod = ptr.ref.CallBooleanMethod
+      .asFunction<JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  bool CallBooleanMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallBooleanMethod(obj, methodID).boolean;
+
+  late final _CallBooleanMethodA = ptr.ref.CallBooleanMethodA.asFunction<
+      JniResult Function(
+          JObjectPtr obj, JMethodIDPtr methodId, ffi.Pointer<JValue> args)>();
+
+  bool CallBooleanMethodA(
+          JObjectPtr obj, JMethodIDPtr methodId, ffi.Pointer<JValue> args) =>
+      _CallBooleanMethodA(obj, methodId, args).boolean;
+
+  late final _CallByteMethod = ptr.ref.CallByteMethod
+      .asFunction<JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  int CallByteMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallByteMethod(obj, methodID).byte;
+
+  late final _CallByteMethodA = ptr.ref.CallByteMethodA.asFunction<
+      JniResult Function(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallByteMethodA(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallByteMethodA(obj, methodID, args).byte;
+
+  late final _CallCharMethod = ptr.ref.CallCharMethod
+      .asFunction<JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  int CallCharMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallCharMethod(obj, methodID).char;
+
+  late final _CallCharMethodA = ptr.ref.CallCharMethodA.asFunction<
+      JniResult Function(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallCharMethodA(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallCharMethodA(obj, methodID, args).char;
+
+  late final _CallShortMethod = ptr.ref.CallShortMethod
+      .asFunction<JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  int CallShortMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallShortMethod(obj, methodID).short;
+
+  late final _CallShortMethodA = ptr.ref.CallShortMethodA.asFunction<
+      JniResult Function(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallShortMethodA(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallShortMethodA(obj, methodID, args).short;
+
+  late final _CallIntMethod = ptr.ref.CallIntMethod
+      .asFunction<JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  int CallIntMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallIntMethod(obj, methodID).integer;
+
+  late final _CallIntMethodA = ptr.ref.CallIntMethodA.asFunction<
+      JniResult Function(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallIntMethodA(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallIntMethodA(obj, methodID, args).integer;
+
+  late final _CallLongMethod = ptr.ref.CallLongMethod
+      .asFunction<JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  int CallLongMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallLongMethod(obj, methodID).long;
+
+  late final _CallLongMethodA = ptr.ref.CallLongMethodA.asFunction<
+      JniResult Function(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallLongMethodA(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallLongMethodA(obj, methodID, args).long;
+
+  late final _CallFloatMethod = ptr.ref.CallFloatMethod
+      .asFunction<JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  double CallFloatMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallFloatMethod(obj, methodID).float;
+
+  late final _CallFloatMethodA = ptr.ref.CallFloatMethodA.asFunction<
+      JniResult Function(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  double CallFloatMethodA(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallFloatMethodA(obj, methodID, args).float;
+
+  late final _CallDoubleMethod = ptr.ref.CallDoubleMethod
+      .asFunction<JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  double CallDoubleMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallDoubleMethod(obj, methodID).doubleFloat;
+
+  late final _CallDoubleMethodA = ptr.ref.CallDoubleMethodA.asFunction<
+      JniResult Function(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  double CallDoubleMethodA(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallDoubleMethodA(obj, methodID, args).doubleFloat;
+
+  late final _CallVoidMethod = ptr.ref.CallVoidMethod.asFunction<
+      JThrowablePtr Function(JObjectPtr obj, JMethodIDPtr methodID)>();
+
+  void CallVoidMethod(JObjectPtr obj, JMethodIDPtr methodID) =>
+      _CallVoidMethod(obj, methodID).check();
+
+  late final _CallVoidMethodA = ptr.ref.CallVoidMethodA.asFunction<
+      JThrowablePtr Function(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  void CallVoidMethodA(
+          JObjectPtr obj, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallVoidMethodA(obj, methodID, args).check();
+
+  late final _CallNonvirtualObjectMethod = ptr.ref.CallNonvirtualObjectMethod
+      .asFunction<
+          JniResult Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  JObjectPtr CallNonvirtualObjectMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualObjectMethod(obj, clazz, methodID).object;
+
+  late final _CallNonvirtualObjectMethodA = ptr.ref.CallNonvirtualObjectMethodA
+      .asFunction<
+          JniResult Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  JObjectPtr CallNonvirtualObjectMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualObjectMethodA(obj, clazz, methodID, args).object;
+
+  late final _CallNonvirtualBooleanMethod = ptr.ref.CallNonvirtualBooleanMethod
+      .asFunction<
+          JniResult Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  bool CallNonvirtualBooleanMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualBooleanMethod(obj, clazz, methodID).boolean;
+
+  late final _CallNonvirtualBooleanMethodA =
+      ptr.ref.CallNonvirtualBooleanMethodA.asFunction<
+          JniResult Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  bool CallNonvirtualBooleanMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualBooleanMethodA(obj, clazz, methodID, args).boolean;
+
+  late final _CallNonvirtualByteMethod = ptr.ref.CallNonvirtualByteMethod
+      .asFunction<
+          JniResult Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallNonvirtualByteMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualByteMethod(obj, clazz, methodID).byte;
+
+  late final _CallNonvirtualByteMethodA = ptr.ref.CallNonvirtualByteMethodA
+      .asFunction<
+          JniResult Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallNonvirtualByteMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualByteMethodA(obj, clazz, methodID, args).byte;
+
+  late final _CallNonvirtualCharMethod = ptr.ref.CallNonvirtualCharMethod
+      .asFunction<
+          JniResult Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallNonvirtualCharMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualCharMethod(obj, clazz, methodID).char;
+
+  late final _CallNonvirtualCharMethodA = ptr.ref.CallNonvirtualCharMethodA
+      .asFunction<
+          JniResult Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallNonvirtualCharMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualCharMethodA(obj, clazz, methodID, args).char;
+
+  late final _CallNonvirtualShortMethod = ptr.ref.CallNonvirtualShortMethod
+      .asFunction<
+          JniResult Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallNonvirtualShortMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualShortMethod(obj, clazz, methodID).short;
+
+  late final _CallNonvirtualShortMethodA = ptr.ref.CallNonvirtualShortMethodA
+      .asFunction<
+          JniResult Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallNonvirtualShortMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualShortMethodA(obj, clazz, methodID, args).short;
+
+  late final _CallNonvirtualIntMethod = ptr.ref.CallNonvirtualIntMethod
+      .asFunction<
+          JniResult Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallNonvirtualIntMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualIntMethod(obj, clazz, methodID).integer;
+
+  late final _CallNonvirtualIntMethodA = ptr.ref.CallNonvirtualIntMethodA
+      .asFunction<
+          JniResult Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallNonvirtualIntMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualIntMethodA(obj, clazz, methodID, args).integer;
+
+  late final _CallNonvirtualLongMethod = ptr.ref.CallNonvirtualLongMethod
+      .asFunction<
+          JniResult Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallNonvirtualLongMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualLongMethod(obj, clazz, methodID).long;
+
+  late final _CallNonvirtualLongMethodA = ptr.ref.CallNonvirtualLongMethodA
+      .asFunction<
+          JniResult Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallNonvirtualLongMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualLongMethodA(obj, clazz, methodID, args).long;
+
+  late final _CallNonvirtualFloatMethod = ptr.ref.CallNonvirtualFloatMethod
+      .asFunction<
+          JniResult Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  double CallNonvirtualFloatMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualFloatMethod(obj, clazz, methodID).float;
+
+  late final _CallNonvirtualFloatMethodA = ptr.ref.CallNonvirtualFloatMethodA
+      .asFunction<
+          JniResult Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  double CallNonvirtualFloatMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualFloatMethodA(obj, clazz, methodID, args).float;
+
+  late final _CallNonvirtualDoubleMethod = ptr.ref.CallNonvirtualDoubleMethod
+      .asFunction<
+          JniResult Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  double CallNonvirtualDoubleMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualDoubleMethod(obj, clazz, methodID).doubleFloat;
+
+  late final _CallNonvirtualDoubleMethodA = ptr.ref.CallNonvirtualDoubleMethodA
+      .asFunction<
+          JniResult Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  double CallNonvirtualDoubleMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualDoubleMethodA(obj, clazz, methodID, args).doubleFloat;
+
+  late final _CallNonvirtualVoidMethod = ptr.ref.CallNonvirtualVoidMethod
+      .asFunction<
+          JThrowablePtr Function(
+              JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  void CallNonvirtualVoidMethod(
+          JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallNonvirtualVoidMethod(obj, clazz, methodID).check();
+
+  late final _CallNonvirtualVoidMethodA = ptr.ref.CallNonvirtualVoidMethodA
+      .asFunction<
+          JThrowablePtr Function(JObjectPtr obj, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  void CallNonvirtualVoidMethodA(JObjectPtr obj, JClassPtr clazz,
+          JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallNonvirtualVoidMethodA(obj, clazz, methodID, args).check();
+
+  late final _GetFieldID = ptr.ref.GetFieldID.asFunction<
+      JniPointerResult Function(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+          ffi.Pointer<ffi.Char> sig)>();
+
+  JFieldIDPtr GetFieldID(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+          ffi.Pointer<ffi.Char> sig) =>
+      _GetFieldID(clazz, name, sig).fieldID;
+
+  late final _GetObjectField = ptr.ref.GetObjectField
+      .asFunction<JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>();
+
+  JObjectPtr GetObjectField(JObjectPtr obj, JFieldIDPtr fieldID) =>
+      _GetObjectField(obj, fieldID).object;
+
+  late final _GetBooleanField = ptr.ref.GetBooleanField
+      .asFunction<JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>();
+
+  bool GetBooleanField(JObjectPtr obj, JFieldIDPtr fieldID) =>
+      _GetBooleanField(obj, fieldID).boolean;
+
+  late final _GetByteField = ptr.ref.GetByteField
+      .asFunction<JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>();
+
+  int GetByteField(JObjectPtr obj, JFieldIDPtr fieldID) =>
+      _GetByteField(obj, fieldID).byte;
+
+  late final _GetCharField = ptr.ref.GetCharField
+      .asFunction<JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>();
+
+  int GetCharField(JObjectPtr obj, JFieldIDPtr fieldID) =>
+      _GetCharField(obj, fieldID).char;
+
+  late final _GetShortField = ptr.ref.GetShortField
+      .asFunction<JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>();
+
+  int GetShortField(JObjectPtr obj, JFieldIDPtr fieldID) =>
+      _GetShortField(obj, fieldID).short;
+
+  late final _GetIntField = ptr.ref.GetIntField
+      .asFunction<JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>();
+
+  int GetIntField(JObjectPtr obj, JFieldIDPtr fieldID) =>
+      _GetIntField(obj, fieldID).integer;
+
+  late final _GetLongField = ptr.ref.GetLongField
+      .asFunction<JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>();
+
+  int GetLongField(JObjectPtr obj, JFieldIDPtr fieldID) =>
+      _GetLongField(obj, fieldID).long;
+
+  late final _GetFloatField = ptr.ref.GetFloatField
+      .asFunction<JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>();
+
+  double GetFloatField(JObjectPtr obj, JFieldIDPtr fieldID) =>
+      _GetFloatField(obj, fieldID).float;
+
+  late final _GetDoubleField = ptr.ref.GetDoubleField
+      .asFunction<JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>();
+
+  double GetDoubleField(JObjectPtr obj, JFieldIDPtr fieldID) =>
+      _GetDoubleField(obj, fieldID).doubleFloat;
+
+  late final _SetObjectField = ptr.ref.SetObjectField.asFunction<
+      JThrowablePtr Function(
+          JObjectPtr obj, JFieldIDPtr fieldID, JObjectPtr val)>();
+
+  void SetObjectField(JObjectPtr obj, JFieldIDPtr fieldID, JObjectPtr val) =>
+      _SetObjectField(obj, fieldID, val).check();
+
+  late final _SetBooleanField = ptr.ref.SetBooleanField.asFunction<
+      JThrowablePtr Function(JObjectPtr obj, JFieldIDPtr fieldID, int val)>();
+
+  void SetBooleanField(JObjectPtr obj, JFieldIDPtr fieldID, int val) =>
+      _SetBooleanField(obj, fieldID, val).check();
+
+  late final _SetByteField = ptr.ref.SetByteField.asFunction<
+      JThrowablePtr Function(JObjectPtr obj, JFieldIDPtr fieldID, int val)>();
+
+  void SetByteField(JObjectPtr obj, JFieldIDPtr fieldID, int val) =>
+      _SetByteField(obj, fieldID, val).check();
+
+  late final _SetCharField = ptr.ref.SetCharField.asFunction<
+      JThrowablePtr Function(JObjectPtr obj, JFieldIDPtr fieldID, int val)>();
+
+  void SetCharField(JObjectPtr obj, JFieldIDPtr fieldID, int val) =>
+      _SetCharField(obj, fieldID, val).check();
+
+  late final _SetShortField = ptr.ref.SetShortField.asFunction<
+      JThrowablePtr Function(JObjectPtr obj, JFieldIDPtr fieldID, int val)>();
+
+  void SetShortField(JObjectPtr obj, JFieldIDPtr fieldID, int val) =>
+      _SetShortField(obj, fieldID, val).check();
+
+  late final _SetIntField = ptr.ref.SetIntField.asFunction<
+      JThrowablePtr Function(JObjectPtr obj, JFieldIDPtr fieldID, int val)>();
+
+  void SetIntField(JObjectPtr obj, JFieldIDPtr fieldID, int val) =>
+      _SetIntField(obj, fieldID, val).check();
+
+  late final _SetLongField = ptr.ref.SetLongField.asFunction<
+      JThrowablePtr Function(JObjectPtr obj, JFieldIDPtr fieldID, int val)>();
+
+  void SetLongField(JObjectPtr obj, JFieldIDPtr fieldID, int val) =>
+      _SetLongField(obj, fieldID, val).check();
+
+  late final _SetFloatField = ptr.ref.SetFloatField.asFunction<
+      JThrowablePtr Function(
+          JObjectPtr obj, JFieldIDPtr fieldID, double val)>();
+
+  void SetFloatField(JObjectPtr obj, JFieldIDPtr fieldID, double val) =>
+      _SetFloatField(obj, fieldID, val).check();
+
+  late final _SetDoubleField = ptr.ref.SetDoubleField.asFunction<
+      JThrowablePtr Function(
+          JObjectPtr obj, JFieldIDPtr fieldID, double val)>();
+
+  void SetDoubleField(JObjectPtr obj, JFieldIDPtr fieldID, double val) =>
+      _SetDoubleField(obj, fieldID, val).check();
+
+  late final _GetStaticMethodID = ptr.ref.GetStaticMethodID.asFunction<
+      JniPointerResult Function(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+          ffi.Pointer<ffi.Char> sig)>();
+
+  JMethodIDPtr GetStaticMethodID(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+          ffi.Pointer<ffi.Char> sig) =>
+      _GetStaticMethodID(clazz, name, sig).methodID;
+
+  late final _CallStaticObjectMethod = ptr.ref.CallStaticObjectMethod
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  JObjectPtr CallStaticObjectMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticObjectMethod(clazz, methodID).object;
+
+  late final _CallStaticObjectMethodA = ptr.ref.CallStaticObjectMethodA
+      .asFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>();
+
+  JObjectPtr CallStaticObjectMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticObjectMethodA(clazz, methodID, args).object;
+
+  late final _CallStaticBooleanMethod = ptr.ref.CallStaticBooleanMethod
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  bool CallStaticBooleanMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticBooleanMethod(clazz, methodID).boolean;
+
+  late final _CallStaticBooleanMethodA = ptr.ref.CallStaticBooleanMethodA
+      .asFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>();
+
+  bool CallStaticBooleanMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticBooleanMethodA(clazz, methodID, args).boolean;
+
+  late final _CallStaticByteMethod = ptr.ref.CallStaticByteMethod
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallStaticByteMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticByteMethod(clazz, methodID).byte;
+
+  late final _CallStaticByteMethodA = ptr.ref.CallStaticByteMethodA.asFunction<
+      JniResult Function(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallStaticByteMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticByteMethodA(clazz, methodID, args).byte;
+
+  late final _CallStaticCharMethod = ptr.ref.CallStaticCharMethod
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallStaticCharMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticCharMethod(clazz, methodID).char;
+
+  late final _CallStaticCharMethodA = ptr.ref.CallStaticCharMethodA.asFunction<
+      JniResult Function(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallStaticCharMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticCharMethodA(clazz, methodID, args).char;
+
+  late final _CallStaticShortMethod = ptr.ref.CallStaticShortMethod
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallStaticShortMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticShortMethod(clazz, methodID).short;
+
+  late final _CallStaticShortMethodA = ptr.ref.CallStaticShortMethodA
+      .asFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>();
+
+  int CallStaticShortMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticShortMethodA(clazz, methodID, args).short;
+
+  late final _CallStaticIntMethod = ptr.ref.CallStaticIntMethod
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallStaticIntMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticIntMethod(clazz, methodID).integer;
+
+  late final _CallStaticIntMethodA = ptr.ref.CallStaticIntMethodA.asFunction<
+      JniResult Function(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallStaticIntMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticIntMethodA(clazz, methodID, args).integer;
+
+  late final _CallStaticLongMethod = ptr.ref.CallStaticLongMethod
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  int CallStaticLongMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticLongMethod(clazz, methodID).long;
+
+  late final _CallStaticLongMethodA = ptr.ref.CallStaticLongMethodA.asFunction<
+      JniResult Function(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  int CallStaticLongMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticLongMethodA(clazz, methodID, args).long;
+
+  late final _CallStaticFloatMethod = ptr.ref.CallStaticFloatMethod
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  double CallStaticFloatMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticFloatMethod(clazz, methodID).float;
+
+  late final _CallStaticFloatMethodA = ptr.ref.CallStaticFloatMethodA
+      .asFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>();
+
+  double CallStaticFloatMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticFloatMethodA(clazz, methodID, args).float;
+
+  late final _CallStaticDoubleMethod = ptr.ref.CallStaticDoubleMethod
+      .asFunction<JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  double CallStaticDoubleMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticDoubleMethod(clazz, methodID).doubleFloat;
+
+  late final _CallStaticDoubleMethodA = ptr.ref.CallStaticDoubleMethodA
+      .asFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>();
+
+  double CallStaticDoubleMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticDoubleMethodA(clazz, methodID, args).doubleFloat;
+
+  late final _CallStaticVoidMethod = ptr.ref.CallStaticVoidMethod.asFunction<
+      JThrowablePtr Function(JClassPtr clazz, JMethodIDPtr methodID)>();
+
+  void CallStaticVoidMethod(JClassPtr clazz, JMethodIDPtr methodID) =>
+      _CallStaticVoidMethod(clazz, methodID).check();
+
+  late final _CallStaticVoidMethodA = ptr.ref.CallStaticVoidMethodA.asFunction<
+      JThrowablePtr Function(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args)>();
+
+  void CallStaticVoidMethodA(
+          JClassPtr clazz, JMethodIDPtr methodID, ffi.Pointer<JValue> args) =>
+      _CallStaticVoidMethodA(clazz, methodID, args).check();
+
+  late final _GetStaticFieldID = ptr.ref.GetStaticFieldID.asFunction<
+      JniPointerResult Function(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+          ffi.Pointer<ffi.Char> sig)>();
+
+  JFieldIDPtr GetStaticFieldID(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+          ffi.Pointer<ffi.Char> sig) =>
+      _GetStaticFieldID(clazz, name, sig).fieldID;
+
+  late final _GetStaticObjectField = ptr.ref.GetStaticObjectField
+      .asFunction<JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>();
+
+  JObjectPtr GetStaticObjectField(JClassPtr clazz, JFieldIDPtr fieldID) =>
+      _GetStaticObjectField(clazz, fieldID).object;
+
+  late final _GetStaticBooleanField = ptr.ref.GetStaticBooleanField
+      .asFunction<JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>();
+
+  bool GetStaticBooleanField(JClassPtr clazz, JFieldIDPtr fieldID) =>
+      _GetStaticBooleanField(clazz, fieldID).boolean;
+
+  late final _GetStaticByteField = ptr.ref.GetStaticByteField
+      .asFunction<JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>();
+
+  int GetStaticByteField(JClassPtr clazz, JFieldIDPtr fieldID) =>
+      _GetStaticByteField(clazz, fieldID).byte;
+
+  late final _GetStaticCharField = ptr.ref.GetStaticCharField
+      .asFunction<JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>();
+
+  int GetStaticCharField(JClassPtr clazz, JFieldIDPtr fieldID) =>
+      _GetStaticCharField(clazz, fieldID).char;
+
+  late final _GetStaticShortField = ptr.ref.GetStaticShortField
+      .asFunction<JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>();
+
+  int GetStaticShortField(JClassPtr clazz, JFieldIDPtr fieldID) =>
+      _GetStaticShortField(clazz, fieldID).short;
+
+  late final _GetStaticIntField = ptr.ref.GetStaticIntField
+      .asFunction<JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>();
+
+  int GetStaticIntField(JClassPtr clazz, JFieldIDPtr fieldID) =>
+      _GetStaticIntField(clazz, fieldID).integer;
+
+  late final _GetStaticLongField = ptr.ref.GetStaticLongField
+      .asFunction<JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>();
+
+  int GetStaticLongField(JClassPtr clazz, JFieldIDPtr fieldID) =>
+      _GetStaticLongField(clazz, fieldID).long;
+
+  late final _GetStaticFloatField = ptr.ref.GetStaticFloatField
+      .asFunction<JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>();
+
+  double GetStaticFloatField(JClassPtr clazz, JFieldIDPtr fieldID) =>
+      _GetStaticFloatField(clazz, fieldID).float;
+
+  late final _GetStaticDoubleField = ptr.ref.GetStaticDoubleField
+      .asFunction<JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>();
+
+  double GetStaticDoubleField(JClassPtr clazz, JFieldIDPtr fieldID) =>
+      _GetStaticDoubleField(clazz, fieldID).doubleFloat;
+
+  late final _SetStaticObjectField = ptr.ref.SetStaticObjectField.asFunction<
+      JThrowablePtr Function(
+          JClassPtr clazz, JFieldIDPtr fieldID, JObjectPtr val)>();
+
+  void SetStaticObjectField(
+          JClassPtr clazz, JFieldIDPtr fieldID, JObjectPtr val) =>
+      _SetStaticObjectField(clazz, fieldID, val).check();
+
+  late final _SetStaticBooleanField = ptr.ref.SetStaticBooleanField.asFunction<
+      JThrowablePtr Function(JClassPtr clazz, JFieldIDPtr fieldID, int val)>();
+
+  void SetStaticBooleanField(JClassPtr clazz, JFieldIDPtr fieldID, int val) =>
+      _SetStaticBooleanField(clazz, fieldID, val).check();
+
+  late final _SetStaticByteField = ptr.ref.SetStaticByteField.asFunction<
+      JThrowablePtr Function(JClassPtr clazz, JFieldIDPtr fieldID, int val)>();
+
+  void SetStaticByteField(JClassPtr clazz, JFieldIDPtr fieldID, int val) =>
+      _SetStaticByteField(clazz, fieldID, val).check();
+
+  late final _SetStaticCharField = ptr.ref.SetStaticCharField.asFunction<
+      JThrowablePtr Function(JClassPtr clazz, JFieldIDPtr fieldID, int val)>();
+
+  void SetStaticCharField(JClassPtr clazz, JFieldIDPtr fieldID, int val) =>
+      _SetStaticCharField(clazz, fieldID, val).check();
+
+  late final _SetStaticShortField = ptr.ref.SetStaticShortField.asFunction<
+      JThrowablePtr Function(JClassPtr clazz, JFieldIDPtr fieldID, int val)>();
+
+  void SetStaticShortField(JClassPtr clazz, JFieldIDPtr fieldID, int val) =>
+      _SetStaticShortField(clazz, fieldID, val).check();
+
+  late final _SetStaticIntField = ptr.ref.SetStaticIntField.asFunction<
+      JThrowablePtr Function(JClassPtr clazz, JFieldIDPtr fieldID, int val)>();
+
+  void SetStaticIntField(JClassPtr clazz, JFieldIDPtr fieldID, int val) =>
+      _SetStaticIntField(clazz, fieldID, val).check();
+
+  late final _SetStaticLongField = ptr.ref.SetStaticLongField.asFunction<
+      JThrowablePtr Function(JClassPtr clazz, JFieldIDPtr fieldID, int val)>();
+
+  void SetStaticLongField(JClassPtr clazz, JFieldIDPtr fieldID, int val) =>
+      _SetStaticLongField(clazz, fieldID, val).check();
+
+  late final _SetStaticFloatField = ptr.ref.SetStaticFloatField.asFunction<
+      JThrowablePtr Function(
+          JClassPtr clazz, JFieldIDPtr fieldID, double val)>();
+
+  void SetStaticFloatField(JClassPtr clazz, JFieldIDPtr fieldID, double val) =>
+      _SetStaticFloatField(clazz, fieldID, val).check();
+
+  late final _SetStaticDoubleField = ptr.ref.SetStaticDoubleField.asFunction<
+      JThrowablePtr Function(
+          JClassPtr clazz, JFieldIDPtr fieldID, double val)>();
+
+  void SetStaticDoubleField(JClassPtr clazz, JFieldIDPtr fieldID, double val) =>
+      _SetStaticDoubleField(clazz, fieldID, val).check();
+
+  late final _NewString = ptr.ref.NewString.asFunction<
+      JniResult Function(ffi.Pointer<JCharMarker> unicodeChars, int len)>();
+
+  JStringPtr NewString(ffi.Pointer<JCharMarker> unicodeChars, int len) =>
+      _NewString(unicodeChars, len).object;
+
+  late final _GetStringLength = ptr.ref.GetStringLength
+      .asFunction<JniResult Function(JStringPtr string)>();
+
+  int GetStringLength(JStringPtr string) => _GetStringLength(string).integer;
+
+  late final _GetStringChars = ptr.ref.GetStringChars.asFunction<
+      JniPointerResult Function(
+          JStringPtr string, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JCharMarker> GetStringChars(
+          JStringPtr string, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetStringChars(string, isCopy).getPointer<JCharMarker>();
+
+  late final _ReleaseStringChars = ptr.ref.ReleaseStringChars.asFunction<
+      JThrowablePtr Function(
+          JStringPtr string, ffi.Pointer<JCharMarker> isCopy)>();
+
+  void ReleaseStringChars(JStringPtr string, ffi.Pointer<JCharMarker> isCopy) =>
+      _ReleaseStringChars(string, isCopy).check();
+
+  late final _NewStringUTF = ptr.ref.NewStringUTF
+      .asFunction<JniResult Function(ffi.Pointer<ffi.Char> bytes)>();
+
+  JStringPtr NewStringUTF(ffi.Pointer<ffi.Char> bytes) =>
+      _NewStringUTF(bytes).object;
+
+  late final _GetStringUTFLength = ptr.ref.GetStringUTFLength
+      .asFunction<JniResult Function(JStringPtr string)>();
+
+  int GetStringUTFLength(JStringPtr string) =>
+      _GetStringUTFLength(string).integer;
+
+  late final _GetStringUTFChars = ptr.ref.GetStringUTFChars.asFunction<
+      JniPointerResult Function(
+          JStringPtr string, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<ffi.Char> GetStringUTFChars(
+          JStringPtr string, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetStringUTFChars(string, isCopy).getPointer<ffi.Char>();
+
+  late final _ReleaseStringUTFChars = ptr.ref.ReleaseStringUTFChars.asFunction<
+      JThrowablePtr Function(JStringPtr string, ffi.Pointer<ffi.Char> utf)>();
+
+  void ReleaseStringUTFChars(JStringPtr string, ffi.Pointer<ffi.Char> utf) =>
+      _ReleaseStringUTFChars(string, utf).check();
+
+  late final _GetArrayLength =
+      ptr.ref.GetArrayLength.asFunction<JniResult Function(JArrayPtr array)>();
+
+  int GetArrayLength(JArrayPtr array) => _GetArrayLength(array).integer;
+
+  late final _NewObjectArray = ptr.ref.NewObjectArray.asFunction<
+      JniResult Function(
+          int length, JClassPtr elementClass, JObjectPtr initialElement)>();
+
+  JObjectArrayPtr NewObjectArray(
+          int length, JClassPtr elementClass, JObjectPtr initialElement) =>
+      _NewObjectArray(length, elementClass, initialElement).object;
+
+  late final _GetObjectArrayElement = ptr.ref.GetObjectArrayElement
+      .asFunction<JniResult Function(JObjectArrayPtr array, int index)>();
+
+  JObjectPtr GetObjectArrayElement(JObjectArrayPtr array, int index) =>
+      _GetObjectArrayElement(array, index).object;
+
+  late final _SetObjectArrayElement = ptr.ref.SetObjectArrayElement.asFunction<
+      JThrowablePtr Function(
+          JObjectArrayPtr array, int index, JObjectPtr val)>();
+
+  void SetObjectArrayElement(
+          JObjectArrayPtr array, int index, JObjectPtr val) =>
+      _SetObjectArrayElement(array, index, val).check();
+
+  late final _NewBooleanArray =
+      ptr.ref.NewBooleanArray.asFunction<JniResult Function(int length)>();
+
+  JBooleanArrayPtr NewBooleanArray(int length) =>
+      _NewBooleanArray(length).object;
+
+  late final _NewByteArray =
+      ptr.ref.NewByteArray.asFunction<JniResult Function(int length)>();
+
+  JByteArrayPtr NewByteArray(int length) => _NewByteArray(length).object;
+
+  late final _NewCharArray =
+      ptr.ref.NewCharArray.asFunction<JniResult Function(int length)>();
+
+  JCharArrayPtr NewCharArray(int length) => _NewCharArray(length).object;
+
+  late final _NewShortArray =
+      ptr.ref.NewShortArray.asFunction<JniResult Function(int length)>();
+
+  JShortArrayPtr NewShortArray(int length) => _NewShortArray(length).object;
+
+  late final _NewIntArray =
+      ptr.ref.NewIntArray.asFunction<JniResult Function(int length)>();
+
+  JIntArrayPtr NewIntArray(int length) => _NewIntArray(length).object;
+
+  late final _NewLongArray =
+      ptr.ref.NewLongArray.asFunction<JniResult Function(int length)>();
+
+  JLongArrayPtr NewLongArray(int length) => _NewLongArray(length).object;
+
+  late final _NewFloatArray =
+      ptr.ref.NewFloatArray.asFunction<JniResult Function(int length)>();
+
+  JFloatArrayPtr NewFloatArray(int length) => _NewFloatArray(length).object;
+
+  late final _NewDoubleArray =
+      ptr.ref.NewDoubleArray.asFunction<JniResult Function(int length)>();
+
+  JDoubleArrayPtr NewDoubleArray(int length) => _NewDoubleArray(length).object;
+
+  late final _GetBooleanArrayElements = ptr.ref.GetBooleanArrayElements
+      .asFunction<
+          JniPointerResult Function(
+              JBooleanArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JBooleanMarker> GetBooleanArrayElements(
+          JBooleanArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetBooleanArrayElements(array, isCopy).getPointer<JBooleanMarker>();
+
+  late final _GetByteArrayElements = ptr.ref.GetByteArrayElements.asFunction<
+      JniPointerResult Function(
+          JByteArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JByteMarker> GetByteArrayElements(
+          JByteArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetByteArrayElements(array, isCopy).getPointer<JByteMarker>();
+
+  late final _GetCharArrayElements = ptr.ref.GetCharArrayElements.asFunction<
+      JniPointerResult Function(
+          JCharArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JCharMarker> GetCharArrayElements(
+          JCharArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetCharArrayElements(array, isCopy).getPointer<JCharMarker>();
+
+  late final _GetShortArrayElements = ptr.ref.GetShortArrayElements.asFunction<
+      JniPointerResult Function(
+          JShortArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JShortMarker> GetShortArrayElements(
+          JShortArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetShortArrayElements(array, isCopy).getPointer<JShortMarker>();
+
+  late final _GetIntArrayElements = ptr.ref.GetIntArrayElements.asFunction<
+      JniPointerResult Function(
+          JIntArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JIntMarker> GetIntArrayElements(
+          JIntArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetIntArrayElements(array, isCopy).getPointer<JIntMarker>();
+
+  late final _GetLongArrayElements = ptr.ref.GetLongArrayElements.asFunction<
+      JniPointerResult Function(
+          JLongArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JLongMarker> GetLongArrayElements(
+          JLongArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetLongArrayElements(array, isCopy).getPointer<JLongMarker>();
+
+  late final _GetFloatArrayElements = ptr.ref.GetFloatArrayElements.asFunction<
+      JniPointerResult Function(
+          JFloatArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JFloatMarker> GetFloatArrayElements(
+          JFloatArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetFloatArrayElements(array, isCopy).getPointer<JFloatMarker>();
+
+  late final _GetDoubleArrayElements = ptr.ref.GetDoubleArrayElements
+      .asFunction<
+          JniPointerResult Function(
+              JDoubleArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JDoubleMarker> GetDoubleArrayElements(
+          JDoubleArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetDoubleArrayElements(array, isCopy).getPointer<JDoubleMarker>();
+
+  late final _ReleaseBooleanArrayElements = ptr.ref.ReleaseBooleanArrayElements
+      .asFunction<
+          JThrowablePtr Function(JBooleanArrayPtr array,
+              ffi.Pointer<JBooleanMarker> elems, int mode)>();
+
+  void ReleaseBooleanArrayElements(JBooleanArrayPtr array,
+          ffi.Pointer<JBooleanMarker> elems, int mode) =>
+      _ReleaseBooleanArrayElements(array, elems, mode).check();
+
+  late final _ReleaseByteArrayElements = ptr.ref.ReleaseByteArrayElements
+      .asFunction<
+          JThrowablePtr Function(
+              JByteArrayPtr array, ffi.Pointer<JByteMarker> elems, int mode)>();
+
+  void ReleaseByteArrayElements(
+          JByteArrayPtr array, ffi.Pointer<JByteMarker> elems, int mode) =>
+      _ReleaseByteArrayElements(array, elems, mode).check();
+
+  late final _ReleaseCharArrayElements = ptr.ref.ReleaseCharArrayElements
+      .asFunction<
+          JThrowablePtr Function(
+              JCharArrayPtr array, ffi.Pointer<JCharMarker> elems, int mode)>();
+
+  void ReleaseCharArrayElements(
+          JCharArrayPtr array, ffi.Pointer<JCharMarker> elems, int mode) =>
+      _ReleaseCharArrayElements(array, elems, mode).check();
+
+  late final _ReleaseShortArrayElements = ptr.ref.ReleaseShortArrayElements
+      .asFunction<
+          JThrowablePtr Function(JShortArrayPtr array,
+              ffi.Pointer<JShortMarker> elems, int mode)>();
+
+  void ReleaseShortArrayElements(
+          JShortArrayPtr array, ffi.Pointer<JShortMarker> elems, int mode) =>
+      _ReleaseShortArrayElements(array, elems, mode).check();
+
+  late final _ReleaseIntArrayElements = ptr.ref.ReleaseIntArrayElements
+      .asFunction<
+          JThrowablePtr Function(
+              JIntArrayPtr array, ffi.Pointer<JIntMarker> elems, int mode)>();
+
+  void ReleaseIntArrayElements(
+          JIntArrayPtr array, ffi.Pointer<JIntMarker> elems, int mode) =>
+      _ReleaseIntArrayElements(array, elems, mode).check();
+
+  late final _ReleaseLongArrayElements = ptr.ref.ReleaseLongArrayElements
+      .asFunction<
+          JThrowablePtr Function(
+              JLongArrayPtr array, ffi.Pointer<JLongMarker> elems, int mode)>();
+
+  void ReleaseLongArrayElements(
+          JLongArrayPtr array, ffi.Pointer<JLongMarker> elems, int mode) =>
+      _ReleaseLongArrayElements(array, elems, mode).check();
+
+  late final _ReleaseFloatArrayElements = ptr.ref.ReleaseFloatArrayElements
+      .asFunction<
+          JThrowablePtr Function(JFloatArrayPtr array,
+              ffi.Pointer<JFloatMarker> elems, int mode)>();
+
+  void ReleaseFloatArrayElements(
+          JFloatArrayPtr array, ffi.Pointer<JFloatMarker> elems, int mode) =>
+      _ReleaseFloatArrayElements(array, elems, mode).check();
+
+  late final _ReleaseDoubleArrayElements = ptr.ref.ReleaseDoubleArrayElements
+      .asFunction<
+          JThrowablePtr Function(JDoubleArrayPtr array,
+              ffi.Pointer<JDoubleMarker> elems, int mode)>();
+
+  void ReleaseDoubleArrayElements(
+          JDoubleArrayPtr array, ffi.Pointer<JDoubleMarker> elems, int mode) =>
+      _ReleaseDoubleArrayElements(array, elems, mode).check();
+
+  late final _GetBooleanArrayRegion = ptr.ref.GetBooleanArrayRegion.asFunction<
+      JThrowablePtr Function(JBooleanArrayPtr array, int start, int len,
+          ffi.Pointer<JBooleanMarker> buf)>();
+
+  void GetBooleanArrayRegion(JBooleanArrayPtr array, int start, int len,
+          ffi.Pointer<JBooleanMarker> buf) =>
+      _GetBooleanArrayRegion(array, start, len, buf).check();
+
+  late final _GetByteArrayRegion = ptr.ref.GetByteArrayRegion.asFunction<
+      JThrowablePtr Function(JByteArrayPtr array, int start, int len,
+          ffi.Pointer<JByteMarker> buf)>();
+
+  void GetByteArrayRegion(JByteArrayPtr array, int start, int len,
+          ffi.Pointer<JByteMarker> buf) =>
+      _GetByteArrayRegion(array, start, len, buf).check();
+
+  late final _GetCharArrayRegion = ptr.ref.GetCharArrayRegion.asFunction<
+      JThrowablePtr Function(JCharArrayPtr array, int start, int len,
+          ffi.Pointer<JCharMarker> buf)>();
+
+  void GetCharArrayRegion(JCharArrayPtr array, int start, int len,
+          ffi.Pointer<JCharMarker> buf) =>
+      _GetCharArrayRegion(array, start, len, buf).check();
+
+  late final _GetShortArrayRegion = ptr.ref.GetShortArrayRegion.asFunction<
+      JThrowablePtr Function(JShortArrayPtr array, int start, int len,
+          ffi.Pointer<JShortMarker> buf)>();
+
+  void GetShortArrayRegion(JShortArrayPtr array, int start, int len,
+          ffi.Pointer<JShortMarker> buf) =>
+      _GetShortArrayRegion(array, start, len, buf).check();
+
+  late final _GetIntArrayRegion = ptr.ref.GetIntArrayRegion.asFunction<
+      JThrowablePtr Function(JIntArrayPtr array, int start, int len,
+          ffi.Pointer<JIntMarker> buf)>();
+
+  void GetIntArrayRegion(JIntArrayPtr array, int start, int len,
+          ffi.Pointer<JIntMarker> buf) =>
+      _GetIntArrayRegion(array, start, len, buf).check();
+
+  late final _GetLongArrayRegion = ptr.ref.GetLongArrayRegion.asFunction<
+      JThrowablePtr Function(JLongArrayPtr array, int start, int len,
+          ffi.Pointer<JLongMarker> buf)>();
+
+  void GetLongArrayRegion(JLongArrayPtr array, int start, int len,
+          ffi.Pointer<JLongMarker> buf) =>
+      _GetLongArrayRegion(array, start, len, buf).check();
+
+  late final _GetFloatArrayRegion = ptr.ref.GetFloatArrayRegion.asFunction<
+      JThrowablePtr Function(JFloatArrayPtr array, int start, int len,
+          ffi.Pointer<JFloatMarker> buf)>();
+
+  void GetFloatArrayRegion(JFloatArrayPtr array, int start, int len,
+          ffi.Pointer<JFloatMarker> buf) =>
+      _GetFloatArrayRegion(array, start, len, buf).check();
+
+  late final _GetDoubleArrayRegion = ptr.ref.GetDoubleArrayRegion.asFunction<
+      JThrowablePtr Function(JDoubleArrayPtr array, int start, int len,
+          ffi.Pointer<JDoubleMarker> buf)>();
+
+  void GetDoubleArrayRegion(JDoubleArrayPtr array, int start, int len,
+          ffi.Pointer<JDoubleMarker> buf) =>
+      _GetDoubleArrayRegion(array, start, len, buf).check();
+
+  late final _SetBooleanArrayRegion = ptr.ref.SetBooleanArrayRegion.asFunction<
+      JThrowablePtr Function(JBooleanArrayPtr array, int start, int len,
+          ffi.Pointer<JBooleanMarker> buf)>();
+
+  void SetBooleanArrayRegion(JBooleanArrayPtr array, int start, int len,
+          ffi.Pointer<JBooleanMarker> buf) =>
+      _SetBooleanArrayRegion(array, start, len, buf).check();
+
+  late final _SetByteArrayRegion = ptr.ref.SetByteArrayRegion.asFunction<
+      JThrowablePtr Function(JByteArrayPtr array, int start, int len,
+          ffi.Pointer<JByteMarker> buf)>();
+
+  void SetByteArrayRegion(JByteArrayPtr array, int start, int len,
+          ffi.Pointer<JByteMarker> buf) =>
+      _SetByteArrayRegion(array, start, len, buf).check();
+
+  late final _SetCharArrayRegion = ptr.ref.SetCharArrayRegion.asFunction<
+      JThrowablePtr Function(JCharArrayPtr array, int start, int len,
+          ffi.Pointer<JCharMarker> buf)>();
+
+  void SetCharArrayRegion(JCharArrayPtr array, int start, int len,
+          ffi.Pointer<JCharMarker> buf) =>
+      _SetCharArrayRegion(array, start, len, buf).check();
+
+  late final _SetShortArrayRegion = ptr.ref.SetShortArrayRegion.asFunction<
+      JThrowablePtr Function(JShortArrayPtr array, int start, int len,
+          ffi.Pointer<JShortMarker> buf)>();
+
+  void SetShortArrayRegion(JShortArrayPtr array, int start, int len,
+          ffi.Pointer<JShortMarker> buf) =>
+      _SetShortArrayRegion(array, start, len, buf).check();
+
+  late final _SetIntArrayRegion = ptr.ref.SetIntArrayRegion.asFunction<
+      JThrowablePtr Function(JIntArrayPtr array, int start, int len,
+          ffi.Pointer<JIntMarker> buf)>();
+
+  void SetIntArrayRegion(JIntArrayPtr array, int start, int len,
+          ffi.Pointer<JIntMarker> buf) =>
+      _SetIntArrayRegion(array, start, len, buf).check();
+
+  late final _SetLongArrayRegion = ptr.ref.SetLongArrayRegion.asFunction<
+      JThrowablePtr Function(JLongArrayPtr array, int start, int len,
+          ffi.Pointer<JLongMarker> buf)>();
+
+  void SetLongArrayRegion(JLongArrayPtr array, int start, int len,
+          ffi.Pointer<JLongMarker> buf) =>
+      _SetLongArrayRegion(array, start, len, buf).check();
+
+  late final _SetFloatArrayRegion = ptr.ref.SetFloatArrayRegion.asFunction<
+      JThrowablePtr Function(JFloatArrayPtr array, int start, int len,
+          ffi.Pointer<JFloatMarker> buf)>();
+
+  void SetFloatArrayRegion(JFloatArrayPtr array, int start, int len,
+          ffi.Pointer<JFloatMarker> buf) =>
+      _SetFloatArrayRegion(array, start, len, buf).check();
+
+  late final _SetDoubleArrayRegion = ptr.ref.SetDoubleArrayRegion.asFunction<
+      JThrowablePtr Function(JDoubleArrayPtr array, int start, int len,
+          ffi.Pointer<JDoubleMarker> buf)>();
+
+  void SetDoubleArrayRegion(JDoubleArrayPtr array, int start, int len,
+          ffi.Pointer<JDoubleMarker> buf) =>
+      _SetDoubleArrayRegion(array, start, len, buf).check();
+
+  late final _RegisterNatives = ptr.ref.RegisterNatives.asFunction<
+      JniResult Function(JClassPtr clazz, ffi.Pointer<JNINativeMethod> methods,
+          int nMethods)>();
+
+  int RegisterNatives(JClassPtr clazz, ffi.Pointer<JNINativeMethod> methods,
+          int nMethods) =>
+      _RegisterNatives(clazz, methods, nMethods).integer;
+
+  late final _UnregisterNatives = ptr.ref.UnregisterNatives
+      .asFunction<JniResult Function(JClassPtr clazz)>();
+
+  int UnregisterNatives(JClassPtr clazz) => _UnregisterNatives(clazz).integer;
+
+  late final _MonitorEnter =
+      ptr.ref.MonitorEnter.asFunction<JniResult Function(JObjectPtr obj)>();
+
+  int MonitorEnter(JObjectPtr obj) => _MonitorEnter(obj).integer;
+
+  late final _MonitorExit =
+      ptr.ref.MonitorExit.asFunction<JniResult Function(JObjectPtr obj)>();
+
+  int MonitorExit(JObjectPtr obj) => _MonitorExit(obj).integer;
+
+  late final _GetJavaVM = ptr.ref.GetJavaVM
+      .asFunction<JniResult Function(ffi.Pointer<ffi.Pointer<JavaVM1>> vm)>();
+
+  int GetJavaVM(ffi.Pointer<ffi.Pointer<JavaVM1>> vm) => _GetJavaVM(vm).integer;
+
+  late final _GetStringRegion = ptr.ref.GetStringRegion.asFunction<
+      JThrowablePtr Function(
+          JStringPtr str, int start, int len, ffi.Pointer<JCharMarker> buf)>();
+
+  void GetStringRegion(
+          JStringPtr str, int start, int len, ffi.Pointer<JCharMarker> buf) =>
+      _GetStringRegion(str, start, len, buf).check();
+
+  late final _GetStringUTFRegion = ptr.ref.GetStringUTFRegion.asFunction<
+      JThrowablePtr Function(
+          JStringPtr str, int start, int len, ffi.Pointer<ffi.Char> buf)>();
+
+  void GetStringUTFRegion(
+          JStringPtr str, int start, int len, ffi.Pointer<ffi.Char> buf) =>
+      _GetStringUTFRegion(str, start, len, buf).check();
+
+  late final _GetPrimitiveArrayCritical = ptr.ref.GetPrimitiveArrayCritical
+      .asFunction<
+          JniPointerResult Function(
+              JArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<ffi.Void> GetPrimitiveArrayCritical(
+          JArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetPrimitiveArrayCritical(array, isCopy).getPointer<ffi.Void>();
+
+  late final _ReleasePrimitiveArrayCritical =
+      ptr.ref.ReleasePrimitiveArrayCritical.asFunction<
+          JThrowablePtr Function(
+              JArrayPtr array, ffi.Pointer<ffi.Void> carray, int mode)>();
+
+  void ReleasePrimitiveArrayCritical(
+          JArrayPtr array, ffi.Pointer<ffi.Void> carray, int mode) =>
+      _ReleasePrimitiveArrayCritical(array, carray, mode).check();
+
+  late final _GetStringCritical = ptr.ref.GetStringCritical.asFunction<
+      JniPointerResult Function(
+          JStringPtr str, ffi.Pointer<JBooleanMarker> isCopy)>();
+
+  ffi.Pointer<JCharMarker> GetStringCritical(
+          JStringPtr str, ffi.Pointer<JBooleanMarker> isCopy) =>
+      _GetStringCritical(str, isCopy).getPointer<JCharMarker>();
+
+  late final _ReleaseStringCritical = ptr.ref.ReleaseStringCritical.asFunction<
+      JThrowablePtr Function(
+          JStringPtr str, ffi.Pointer<JCharMarker> carray)>();
+
+  void ReleaseStringCritical(JStringPtr str, ffi.Pointer<JCharMarker> carray) =>
+      _ReleaseStringCritical(str, carray).check();
+
+  late final _NewWeakGlobalRef =
+      ptr.ref.NewWeakGlobalRef.asFunction<JniResult Function(JObjectPtr obj)>();
+
+  JWeakPtr NewWeakGlobalRef(JObjectPtr obj) => _NewWeakGlobalRef(obj).object;
+
+  late final _DeleteWeakGlobalRef = ptr.ref.DeleteWeakGlobalRef
+      .asFunction<JThrowablePtr Function(JWeakPtr obj)>();
+
+  void DeleteWeakGlobalRef(JWeakPtr obj) => _DeleteWeakGlobalRef(obj).check();
+
+  late final _ExceptionCheck =
+      ptr.ref.ExceptionCheck.asFunction<JniResult Function()>();
+
+  bool ExceptionCheck() => _ExceptionCheck().boolean;
+
+  late final _NewDirectByteBuffer = ptr.ref.NewDirectByteBuffer.asFunction<
+      JniResult Function(ffi.Pointer<ffi.Void> address, int capacity)>();
+
+  JObjectPtr NewDirectByteBuffer(ffi.Pointer<ffi.Void> address, int capacity) =>
+      _NewDirectByteBuffer(address, capacity).object;
+
+  late final _GetDirectBufferAddress = ptr.ref.GetDirectBufferAddress
+      .asFunction<JniPointerResult Function(JObjectPtr buf)>();
+
+  ffi.Pointer<ffi.Void> GetDirectBufferAddress(JObjectPtr buf) =>
+      _GetDirectBufferAddress(buf).getPointer<ffi.Void>();
+
+  late final _GetDirectBufferCapacity = ptr.ref.GetDirectBufferCapacity
+      .asFunction<JniResult Function(JObjectPtr buf)>();
+
+  int GetDirectBufferCapacity(JObjectPtr buf) =>
+      _GetDirectBufferCapacity(buf).long;
+
+  late final _GetObjectRefType =
+      ptr.ref.GetObjectRefType.asFunction<JniResult Function(JObjectPtr obj)>();
+
+  int GetObjectRefType(JObjectPtr obj) => _GetObjectRefType(obj).integer;
+}
+
+/// Wraps over the function pointers in JniAccessorsStruct and exposes them as methods.
+class JniAccessors {
+  final ffi.Pointer<JniAccessorsStruct> ptr;
+  JniAccessors(this.ptr);
+
+  late final _getClass = ptr.ref.getClass.asFunction<
+      JniClassLookupResult Function(ffi.Pointer<ffi.Char> internalName)>();
+  JniClassLookupResult getClass(ffi.Pointer<ffi.Char> internalName) =>
+      _getClass(internalName);
+
+  late final _getFieldID = ptr.ref.getFieldID.asFunction<
+      JniPointerResult Function(JClassPtr cls, ffi.Pointer<ffi.Char> fieldName,
+          ffi.Pointer<ffi.Char> signature)>();
+  JniPointerResult getFieldID(JClassPtr cls, ffi.Pointer<ffi.Char> fieldName,
+          ffi.Pointer<ffi.Char> signature) =>
+      _getFieldID(cls, fieldName, signature);
+
+  late final _getStaticFieldID = ptr.ref.getStaticFieldID.asFunction<
+      JniPointerResult Function(JClassPtr cls, ffi.Pointer<ffi.Char> fieldName,
+          ffi.Pointer<ffi.Char> signature)>();
+  JniPointerResult getStaticFieldID(JClassPtr cls,
+          ffi.Pointer<ffi.Char> fieldName, ffi.Pointer<ffi.Char> signature) =>
+      _getStaticFieldID(cls, fieldName, signature);
+
+  late final _getMethodID = ptr.ref.getMethodID.asFunction<
+      JniPointerResult Function(JClassPtr cls, ffi.Pointer<ffi.Char> methodName,
+          ffi.Pointer<ffi.Char> signature)>();
+  JniPointerResult getMethodID(JClassPtr cls, ffi.Pointer<ffi.Char> methodName,
+          ffi.Pointer<ffi.Char> signature) =>
+      _getMethodID(cls, methodName, signature);
+
+  late final _getStaticMethodID = ptr.ref.getStaticMethodID.asFunction<
+      JniPointerResult Function(JClassPtr cls, ffi.Pointer<ffi.Char> methodName,
+          ffi.Pointer<ffi.Char> signature)>();
+  JniPointerResult getStaticMethodID(JClassPtr cls,
+          ffi.Pointer<ffi.Char> methodName, ffi.Pointer<ffi.Char> signature) =>
+      _getStaticMethodID(cls, methodName, signature);
+
+  late final _newObject = ptr.ref.newObject.asFunction<
+      JniResult Function(
+          JClassPtr cls, JMethodIDPtr ctor, ffi.Pointer<JValue> args)>();
+  JniResult newObject(
+          JClassPtr cls, JMethodIDPtr ctor, ffi.Pointer<JValue> args) =>
+      _newObject(cls, ctor, args);
+
+  late final _newPrimitiveArray = ptr.ref.newPrimitiveArray
+      .asFunction<JniResult Function(int length, int type)>();
+  JniResult newPrimitiveArray(int length, int type) =>
+      _newPrimitiveArray(length, type);
+
+  late final _newObjectArray = ptr.ref.newObjectArray.asFunction<
+      JniResult Function(
+          int length, JClassPtr elementClass, JObjectPtr initialElement)>();
+  JniResult newObjectArray(
+          int length, JClassPtr elementClass, JObjectPtr initialElement) =>
+      _newObjectArray(length, elementClass, initialElement);
+
+  late final _getArrayElement = ptr.ref.getArrayElement
+      .asFunction<JniResult Function(JArrayPtr array, int index, int type)>();
+  JniResult getArrayElement(JArrayPtr array, int index, int type) =>
+      _getArrayElement(array, index, type);
+
+  late final _callMethod = ptr.ref.callMethod.asFunction<
+      JniResult Function(JObjectPtr obj, JMethodIDPtr methodID, int callType,
+          ffi.Pointer<JValue> args)>();
+  JniResult callMethod(JObjectPtr obj, JMethodIDPtr methodID, int callType,
+          ffi.Pointer<JValue> args) =>
+      _callMethod(obj, methodID, callType, args);
+
+  late final _callStaticMethod = ptr.ref.callStaticMethod.asFunction<
+      JniResult Function(JClassPtr cls, JMethodIDPtr methodID, int callType,
+          ffi.Pointer<JValue> args)>();
+  JniResult callStaticMethod(JClassPtr cls, JMethodIDPtr methodID, int callType,
+          ffi.Pointer<JValue> args) =>
+      _callStaticMethod(cls, methodID, callType, args);
+
+  late final _getField = ptr.ref.getField.asFunction<
+      JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID, int callType)>();
+  JniResult getField(JObjectPtr obj, JFieldIDPtr fieldID, int callType) =>
+      _getField(obj, fieldID, callType);
+
+  late final _getStaticField = ptr.ref.getStaticField.asFunction<
+      JniResult Function(JClassPtr cls, JFieldIDPtr fieldID, int callType)>();
+  JniResult getStaticField(JClassPtr cls, JFieldIDPtr fieldID, int callType) =>
+      _getStaticField(cls, fieldID, callType);
+
+  late final _getExceptionDetails = ptr.ref.getExceptionDetails
+      .asFunction<JniExceptionDetails Function(JThrowablePtr exception)>();
+  JniExceptionDetails getExceptionDetails(JThrowablePtr exception) =>
+      _getExceptionDetails(exception);
+}
diff --git a/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart b/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart
new file mode 100644
index 0000000..892f69e
--- /dev/null
+++ b/pkgs/jni/lib/src/third_party/jni_bindings_generated.dart
@@ -0,0 +1,3332 @@
+// Autogenerated file. Do not edit.
+// Generated from an annotated version of jni.h provided in Android NDK.
+// (NDK Version 23.1.7779620)
+// The license for original file is provided below:
+
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/*
+ * JNI specification, as defined by Sun:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
+ *
+ * Everything here is expected to be VM-neutral.
+ */
+
+// ignore_for_file: always_specify_types
+// ignore_for_file: camel_case_types
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: unused_field
+// ignore_for_file: unused_element
+// coverage:ignore-file
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+// ignore_for_file: type=lint
+import 'dart:ffi' as ffi;
+
+/// Bindings for libdartjni.so which is part of jni plugin.
+///
+/// It also transitively includes type definitions such as JNIEnv from third_party/jni.h;
+///
+/// However, functions prefixed JNI_ are not usable because they are in a different shared library.
+///
+/// Regenerate bindings with `flutter pub run ffigen.dart --config ffigen.yaml`.
+///
+class JniBindings {
+  /// Holds the symbol lookup function.
+  final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+      _lookup;
+
+  /// The symbols are looked up in [dynamicLibrary].
+  JniBindings(ffi.DynamicLibrary dynamicLibrary)
+      : _lookup = dynamicLibrary.lookup;
+
+  /// The symbols are looked up with [lookup].
+  JniBindings.fromLookup(
+      ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+          lookup)
+      : _lookup = lookup;
+
+  ffi.Pointer<JniAccessorsStruct> GetAccessors() {
+    return _GetAccessors();
+  }
+
+  late final _GetAccessorsPtr =
+      _lookup<ffi.NativeFunction<ffi.Pointer<JniAccessorsStruct> Function()>>(
+          'GetAccessors');
+  late final _GetAccessors =
+      _GetAccessorsPtr.asFunction<ffi.Pointer<JniAccessorsStruct> Function()>();
+
+  ffi.Pointer<JavaVM> GetJavaVM() {
+    return _GetJavaVM();
+  }
+
+  late final _GetJavaVMPtr =
+      _lookup<ffi.NativeFunction<ffi.Pointer<JavaVM> Function()>>('GetJavaVM');
+  late final _GetJavaVM =
+      _GetJavaVMPtr.asFunction<ffi.Pointer<JavaVM> Function()>();
+
+  ffi.Pointer<JniEnv> GetJniEnv() {
+    return _GetJniEnv();
+  }
+
+  late final _GetJniEnvPtr =
+      _lookup<ffi.NativeFunction<ffi.Pointer<JniEnv> Function()>>('GetJniEnv');
+  late final _GetJniEnv =
+      _GetJniEnvPtr.asFunction<ffi.Pointer<JniEnv> Function()>();
+
+  /// Spawn a JVM with given arguments.
+  ///
+  /// Returns JNI_OK on success, and one of the documented JNI error codes on
+  /// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple
+  /// JVMs is made, even if the underlying API potentially supports multiple VMs.
+  int SpawnJvm(
+    ffi.Pointer<JavaVMInitArgs> args,
+  ) {
+    return _SpawnJvm(
+      args,
+    );
+  }
+
+  late final _SpawnJvmPtr = _lookup<
+          ffi.NativeFunction<ffi.Int Function(ffi.Pointer<JavaVMInitArgs>)>>(
+      'SpawnJvm');
+  late final _SpawnJvm =
+      _SpawnJvmPtr.asFunction<int Function(ffi.Pointer<JavaVMInitArgs>)>();
+
+  /// Load class through platform-specific mechanism.
+  ///
+  /// Currently uses application classloader on android,
+  /// and JNIEnv->FindClass on other platforms.
+  JClassPtr FindClass(
+    ffi.Pointer<ffi.Char> name,
+  ) {
+    return _FindClass(
+      name,
+    );
+  }
+
+  late final _FindClassPtr =
+      _lookup<ffi.NativeFunction<JClassPtr Function(ffi.Pointer<ffi.Char>)>>(
+          'FindClass');
+  late final _FindClass =
+      _FindClassPtr.asFunction<JClassPtr Function(ffi.Pointer<ffi.Char>)>();
+
+  /// Returns Application classLoader (on Android),
+  /// which can be used to load application and platform classes.
+  ///
+  /// On other platforms, NULL is returned.
+  JObjectPtr GetClassLoader() {
+    return _GetClassLoader();
+  }
+
+  late final _GetClassLoaderPtr =
+      _lookup<ffi.NativeFunction<JObjectPtr Function()>>('GetClassLoader');
+  late final _GetClassLoader =
+      _GetClassLoaderPtr.asFunction<JObjectPtr Function()>();
+
+  /// Returns application context on Android.
+  ///
+  /// On other platforms, NULL is returned.
+  JObjectPtr GetApplicationContext() {
+    return _GetApplicationContext();
+  }
+
+  late final _GetApplicationContextPtr =
+      _lookup<ffi.NativeFunction<JObjectPtr Function()>>(
+          'GetApplicationContext');
+  late final _GetApplicationContext =
+      _GetApplicationContextPtr.asFunction<JObjectPtr Function()>();
+
+  /// Returns current activity of the app on Android.
+  JObjectPtr GetCurrentActivity() {
+    return _GetCurrentActivity();
+  }
+
+  late final _GetCurrentActivityPtr =
+      _lookup<ffi.NativeFunction<JObjectPtr Function()>>('GetCurrentActivity');
+  late final _GetCurrentActivity =
+      _GetCurrentActivityPtr.asFunction<JObjectPtr Function()>();
+
+  int InitDartApiDL(
+    ffi.Pointer<ffi.Void> data,
+  ) {
+    return _InitDartApiDL(
+      data,
+    );
+  }
+
+  late final _InitDartApiDLPtr =
+      _lookup<ffi.NativeFunction<ffi.IntPtr Function(ffi.Pointer<ffi.Void>)>>(
+          'InitDartApiDL');
+  late final _InitDartApiDL =
+      _InitDartApiDLPtr.asFunction<int Function(ffi.Pointer<ffi.Void>)>();
+
+  JniResult DartException__ctor(
+    JStringPtr message,
+  ) {
+    return _DartException__ctor(
+      message,
+    );
+  }
+
+  late final _DartException__ctorPtr =
+      _lookup<ffi.NativeFunction<JniResult Function(JStringPtr)>>(
+          'DartException__ctor');
+  late final _DartException__ctor =
+      _DartException__ctorPtr.asFunction<JniResult Function(JStringPtr)>();
+
+  JniResult PortContinuation__ctor(
+    int j,
+  ) {
+    return _PortContinuation__ctor(
+      j,
+    );
+  }
+
+  late final _PortContinuation__ctorPtr =
+      _lookup<ffi.NativeFunction<JniResult Function(ffi.Int64)>>(
+          'PortContinuation__ctor');
+  late final _PortContinuation__ctor =
+      _PortContinuation__ctorPtr.asFunction<JniResult Function(int)>();
+
+  JniResult PortProxy__newInstance(
+    JObjectPtr binaryName,
+    int port,
+    int functionPtr,
+  ) {
+    return _PortProxy__newInstance(
+      binaryName,
+      port,
+      functionPtr,
+    );
+  }
+
+  late final _PortProxy__newInstancePtr = _lookup<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr, ffi.Int64, ffi.Int64)>>('PortProxy__newInstance');
+  late final _PortProxy__newInstance = _PortProxy__newInstancePtr.asFunction<
+      JniResult Function(JObjectPtr, int, int)>();
+
+  void resultFor(
+    ffi.Pointer<CallbackResult> result,
+    JObjectPtr object,
+  ) {
+    return _resultFor(
+      result,
+      object,
+    );
+  }
+
+  late final _resultForPtr = _lookup<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<CallbackResult>, JObjectPtr)>>('resultFor');
+  late final _resultFor = _resultForPtr
+      .asFunction<void Function(ffi.Pointer<CallbackResult>, JObjectPtr)>();
+
+  ffi.Pointer<GlobalJniEnvStruct> GetGlobalEnv() {
+    return _GetGlobalEnv();
+  }
+
+  late final _GetGlobalEnvPtr =
+      _lookup<ffi.NativeFunction<ffi.Pointer<GlobalJniEnvStruct> Function()>>(
+          'GetGlobalEnv');
+  late final _GetGlobalEnv =
+      _GetGlobalEnvPtr.asFunction<ffi.Pointer<GlobalJniEnvStruct> Function()>();
+}
+
+/// Types used by JNI API to distinguish between primitive types.
+abstract class JniCallType {
+  static const int booleanType = 0;
+  static const int byteType = 1;
+  static const int shortType = 2;
+  static const int charType = 3;
+  static const int intType = 4;
+  static const int longType = 5;
+  static const int floatType = 6;
+  static const int doubleType = 7;
+  static const int objectType = 8;
+  static const int voidType = 9;
+}
+
+/// Result type for use by JNI.
+///
+/// If [exception] is null, it means the result is valid.
+/// It's assumed that the caller knows the expected type in [result].
+final class JniResult extends ffi.Struct {
+  external JValue value;
+
+  external JThrowablePtr exception;
+}
+
+final class JValue extends ffi.Union {
+  @JBooleanMarker()
+  external int z;
+
+  @JByteMarker()
+  external int b;
+
+  @JCharMarker()
+  external int c;
+
+  @JShortMarker()
+  external int s;
+
+  @JIntMarker()
+  external int i;
+
+  @JLongMarker()
+  external int j;
+
+  @JFloatMarker()
+  external double f;
+
+  @JDoubleMarker()
+  external double d;
+
+  external JObjectPtr l;
+}
+
+/// Primitive types that match up with Java equivalents.
+typedef JBooleanMarker = ffi.Uint8;
+typedef JByteMarker = ffi.Int8;
+typedef JCharMarker = ffi.Uint16;
+typedef JShortMarker = ffi.Int16;
+typedef JIntMarker = ffi.Int32;
+typedef JLongMarker = ffi.Int64;
+typedef JFloatMarker = ffi.Float;
+typedef JDoubleMarker = ffi.Double;
+
+/// Reference types, in C.
+typedef JObjectPtr = ffi.Pointer<ffi.Void>;
+typedef JThrowablePtr = JObjectPtr;
+
+/// Similar to [JniResult] but for class lookups.
+final class JniClassLookupResult extends ffi.Struct {
+  external JClassPtr value;
+
+  external JThrowablePtr exception;
+}
+
+typedef JClassPtr = JObjectPtr;
+
+/// Similar to [JniResult] but for method/field ID lookups.
+final class JniPointerResult extends ffi.Struct {
+  external ffi.Pointer<ffi.Void> value;
+
+  external JThrowablePtr exception;
+}
+
+/// JniExceptionDetails holds 2 jstring objects, one is the result of
+/// calling `toString` on exception object, other is stack trace;
+final class JniExceptionDetails extends ffi.Struct {
+  external JStringPtr message;
+
+  external JStringPtr stacktrace;
+}
+
+typedef JStringPtr = JObjectPtr;
+
+/// This struct contains functions which wrap method call / field access conveniently along with
+/// exception checking.
+///
+/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us
+/// to check for and clear the exception before returning to dart code, which requires these functions
+/// to return result types.
+final class JniAccessorsStruct extends ffi.Struct {
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniClassLookupResult Function(
+              ffi.Pointer<ffi.Char> internalName)>> getClass;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniPointerResult Function(
+              JClassPtr cls,
+              ffi.Pointer<ffi.Char> fieldName,
+              ffi.Pointer<ffi.Char> signature)>> getFieldID;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniPointerResult Function(
+              JClassPtr cls,
+              ffi.Pointer<ffi.Char> fieldName,
+              ffi.Pointer<ffi.Char> signature)>> getStaticFieldID;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniPointerResult Function(
+              JClassPtr cls,
+              ffi.Pointer<ffi.Char> methodName,
+              ffi.Pointer<ffi.Char> signature)>> getMethodID;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniPointerResult Function(
+              JClassPtr cls,
+              ffi.Pointer<ffi.Char> methodName,
+              ffi.Pointer<ffi.Char> signature)>> getStaticMethodID;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr cls, JMethodIDPtr ctor, ffi.Pointer<JValue> args)>>
+      newObject;
+
+  external ffi.Pointer<
+          ffi
+          .NativeFunction<JniResult Function(JSizeMarker length, ffi.Int type)>>
+      newPrimitiveArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JSizeMarker length, JClassPtr elementClass,
+              JObjectPtr initialElement)>> newObjectArray;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JArrayPtr array, ffi.Int index, ffi.Int type)>>
+      getArrayElement;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Int callType, ffi.Pointer<JValue> args)>> callMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr cls, JMethodIDPtr methodID,
+              ffi.Int callType, ffi.Pointer<JValue> args)>> callStaticMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj, JFieldIDPtr fieldID, ffi.Int callType)>> getField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr cls, JFieldIDPtr fieldID, ffi.Int callType)>>
+      getStaticField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniExceptionDetails Function(JThrowablePtr exception)>>
+      getExceptionDetails;
+}
+
+typedef JMethodIDPtr = ffi.Pointer<jmethodID_>;
+
+final class jmethodID_ extends ffi.Opaque {}
+
+/// "cardinal indices and sizes"
+typedef JSizeMarker = JIntMarker;
+typedef JArrayPtr = JObjectPtr;
+typedef JFieldIDPtr = ffi.Pointer<jfieldID_>;
+
+final class jfieldID_ extends ffi.Opaque {}
+
+typedef JavaVM = ffi.Pointer<JNIInvokeInterface>;
+
+/// JNI invocation interface.
+final class JNIInvokeInterface extends ffi.Struct {
+  external ffi.Pointer<ffi.Void> reserved0;
+
+  external ffi.Pointer<ffi.Void> reserved1;
+
+  external ffi.Pointer<ffi.Void> reserved2;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JIntMarker Function(ffi.Pointer<JavaVM1> vm)>>
+      DestroyJavaVM;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(
+              ffi.Pointer<JavaVM1> vm,
+              ffi.Pointer<ffi.Pointer<JniEnv>> p_env,
+              ffi.Pointer<ffi.Void> thr_args)>> AttachCurrentThread;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JIntMarker Function(ffi.Pointer<JavaVM1> vm)>>
+      DetachCurrentThread;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(
+              ffi.Pointer<JavaVM1> vm,
+              ffi.Pointer<ffi.Pointer<ffi.Void>> p_env,
+              JIntMarker version)>> GetEnv;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(
+              ffi.Pointer<JavaVM1> vm,
+              ffi.Pointer<ffi.Pointer<JniEnv>> p_env,
+              ffi.Pointer<ffi.Void> thr_args)>> AttachCurrentThreadAsDaemon;
+}
+
+typedef JavaVM1 = ffi.Pointer<JNIInvokeInterface>;
+typedef JniEnv = ffi.Pointer<JNINativeInterface>;
+
+/// Table of interface function pointers.
+final class JNINativeInterface extends ffi.Struct {
+  external ffi.Pointer<ffi.Void> reserved0;
+
+  external ffi.Pointer<ffi.Void> reserved1;
+
+  external ffi.Pointer<ffi.Void> reserved2;
+
+  external ffi.Pointer<ffi.Void> reserved3;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<JIntMarker Function(ffi.Pointer<JniEnv1> env)>>
+      GetVersion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JClassPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              ffi.Pointer<ffi.Char> name,
+              JObjectPtr loader,
+              ffi.Pointer<JByteMarker> buf,
+              JSizeMarker bufLen)>> DefineClass;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JClassPtr Function(
+              ffi.Pointer<JniEnv1> env, ffi.Pointer<ffi.Char> name)>> FindClass;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JMethodIDPtr Function(
+                  ffi.Pointer<JniEnv1> env, JObjectPtr method)>>
+      FromReflectedMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JFieldIDPtr Function(ffi.Pointer<JniEnv1> env, JObjectPtr field)>>
+      FromReflectedField;
+
+  /// spec doesn't show jboolean parameter
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr cls,
+              JMethodIDPtr methodId,
+              JBooleanMarker isStatic)>> ToReflectedMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JClassPtr Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz)>>
+      GetSuperclass;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz1,
+              JClassPtr clazz2)>> IsAssignableFrom;
+
+  /// spec doesn't show jboolean parameter
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1> env, JClassPtr cls,
+              JFieldIDPtr fieldID, JBooleanMarker isStatic)>> ToReflectedField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JIntMarker Function(ffi.Pointer<JniEnv1> env, JThrowablePtr obj)>>
+      Throw;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              ffi.Pointer<ffi.Char> message)>> ThrowNew;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<JThrowablePtr Function(ffi.Pointer<JniEnv1> env)>>
+      ExceptionOccurred;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<JniEnv1> env)>>
+      ExceptionDescribe;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<JniEnv1> env)>>
+      ExceptionClear;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env, ffi.Pointer<ffi.Char> msg)>> FatalError;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(
+              ffi.Pointer<JniEnv1> env, JIntMarker capacity)>> PushLocalFrame;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JObjectPtr Function(ffi.Pointer<JniEnv1> env, JObjectPtr result)>>
+      PopLocalFrame;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JObjectPtr Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj)>>
+      NewGlobalRef;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env, JObjectPtr globalRef)>> DeleteGlobalRef;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr localRef)>>
+      DeleteLocalRef;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JBooleanMarker Function(
+                  ffi.Pointer<JniEnv1> env, JObjectPtr ref1, JObjectPtr ref2)>>
+      IsSameObject;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JObjectPtr Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj)>>
+      NewLocalRef;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JIntMarker Function(
+                  ffi.Pointer<JniEnv1> env, JIntMarker capacity)>>
+      EnsureLocalCapacity;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JObjectPtr Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz)>>
+      AllocObject;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> NewObject;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> NewObjectV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>> NewObjectA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JClassPtr Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj)>>
+      GetObjectClass;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JBooleanMarker Function(
+                  ffi.Pointer<JniEnv1> env, JObjectPtr obj, JClassPtr clazz)>>
+      IsInstanceOf;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JMethodIDPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              ffi.Pointer<ffi.Char> name,
+              ffi.Pointer<ffi.Char> sig)>> GetMethodID;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallObjectMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1>, JObjectPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallObjectMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallObjectMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallBooleanMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr,
+              JMethodIDPtr, ffi.Pointer<ffi.Void>)>> CallBooleanMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JMethodIDPtr methodId,
+              ffi.Pointer<JValue> args)>> CallBooleanMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallByteMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallByteMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallByteMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallCharMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallCharMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallCharMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallShortMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallShortMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallShortMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallIntMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallIntMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID, ffi.Pointer<JValue> args)>> CallIntMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallLongMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallLongMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallLongMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallFloatMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallFloatMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallFloatMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallDoubleMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallDoubleMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallDoubleMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JMethodIDPtr methodID)>> CallVoidMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1>, JObjectPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallVoidMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallVoidMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallNonvirtualObjectMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(
+              ffi.Pointer<JniEnv1>,
+              JObjectPtr,
+              JClassPtr,
+              JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallNonvirtualObjectMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualObjectMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallNonvirtualBooleanMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(
+              ffi.Pointer<JniEnv1>,
+              JObjectPtr,
+              JClassPtr,
+              JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallNonvirtualBooleanMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualBooleanMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallNonvirtualByteMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JClassPtr,
+              JMethodIDPtr, ffi.Pointer<ffi.Void>)>> CallNonvirtualByteMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualByteMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallNonvirtualCharMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JClassPtr,
+              JMethodIDPtr, ffi.Pointer<ffi.Void>)>> CallNonvirtualCharMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualCharMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallNonvirtualShortMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JClassPtr,
+              JMethodIDPtr, ffi.Pointer<ffi.Void>)>> CallNonvirtualShortMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualShortMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JClassPtr clazz, JMethodIDPtr methodID)>> CallNonvirtualIntMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JClassPtr,
+              JMethodIDPtr, ffi.Pointer<ffi.Void>)>> CallNonvirtualIntMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualIntMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallNonvirtualLongMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JClassPtr,
+              JMethodIDPtr, ffi.Pointer<ffi.Void>)>> CallNonvirtualLongMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualLongMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallNonvirtualFloatMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(ffi.Pointer<JniEnv1>, JObjectPtr, JClassPtr,
+              JMethodIDPtr, ffi.Pointer<ffi.Void>)>> CallNonvirtualFloatMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualFloatMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallNonvirtualDoubleMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(
+              ffi.Pointer<JniEnv1>,
+              JObjectPtr,
+              JClassPtr,
+              JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallNonvirtualDoubleMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualDoubleMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallNonvirtualVoidMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1>, JObjectPtr, JClassPtr,
+              JMethodIDPtr, ffi.Pointer<ffi.Void>)>> CallNonvirtualVoidMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualVoidMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFieldIDPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              ffi.Pointer<ffi.Char> name,
+              ffi.Pointer<ffi.Char> sig)>> GetFieldID;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID)>> GetObjectField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID)>> GetBooleanField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID)>> GetByteField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID)>> GetCharField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID)>> GetShortField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID)>> GetIntField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID)>> GetLongField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID)>> GetFloatField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID)>> GetDoubleField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID, JObjectPtr val)>> SetObjectField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID, JBooleanMarker val)>> SetBooleanField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID, JByteMarker val)>> SetByteField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID, JCharMarker val)>> SetCharField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID, JShortMarker val)>> SetShortField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID, JIntMarker val)>> SetIntField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID, JLongMarker val)>> SetLongField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID, JFloatMarker val)>> SetFloatField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj,
+              JFieldIDPtr fieldID, JDoubleMarker val)>> SetDoubleField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JMethodIDPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              ffi.Pointer<ffi.Char> name,
+              ffi.Pointer<ffi.Char> sig)>> GetStaticMethodID;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticObjectMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticObjectMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticObjectMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticBooleanMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticBooleanMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticBooleanMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticByteMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticByteMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticByteMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticCharMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticCharMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticCharMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticShortMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticShortMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticShortMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticIntMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticIntMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticIntMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticLongMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticLongMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticLongMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticFloatMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticFloatMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticFloatMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticDoubleMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticDoubleMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticDoubleMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JMethodIDPtr methodID)>> CallStaticVoidMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1>, JClassPtr, JMethodIDPtr,
+              ffi.Pointer<ffi.Void>)>> CallStaticVoidMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticVoidMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFieldIDPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              ffi.Pointer<ffi.Char> name,
+              ffi.Pointer<ffi.Char> sig)>> GetStaticFieldID;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID)>> GetStaticObjectField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID)>> GetStaticBooleanField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID)>> GetStaticByteField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID)>> GetStaticCharField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID)>> GetStaticShortField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID)>> GetStaticIntField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID)>> GetStaticLongField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID)>> GetStaticFloatField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID)>> GetStaticDoubleField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID, JObjectPtr val)>> SetStaticObjectField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID, JBooleanMarker val)>> SetStaticBooleanField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID, JByteMarker val)>> SetStaticByteField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID, JCharMarker val)>> SetStaticCharField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID, JShortMarker val)>> SetStaticShortField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID, JIntMarker val)>> SetStaticIntField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID, JLongMarker val)>> SetStaticLongField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID, JFloatMarker val)>> SetStaticFloatField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz,
+              JFieldIDPtr fieldID, JDoubleMarker val)>> SetStaticDoubleField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JStringPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              ffi.Pointer<JCharMarker> unicodeChars,
+              JSizeMarker len)>> NewString;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JSizeMarker Function(
+              ffi.Pointer<JniEnv1> env, JStringPtr string)>> GetStringLength;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JCharMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JStringPtr string,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetStringChars;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JStringPtr string,
+              ffi.Pointer<JCharMarker> isCopy)>> ReleaseStringChars;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JStringPtr Function(
+                  ffi.Pointer<JniEnv1> env, ffi.Pointer<ffi.Char> bytes)>>
+      NewStringUTF;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JSizeMarker Function(
+              ffi.Pointer<JniEnv1> env, JStringPtr string)>> GetStringUTFLength;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<ffi.Char> Function(
+              ffi.Pointer<JniEnv1> env,
+              JStringPtr string,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetStringUTFChars;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JStringPtr string,
+              ffi.Pointer<ffi.Char> utf)>> ReleaseStringUTFChars;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JSizeMarker Function(ffi.Pointer<JniEnv1> env, JArrayPtr array)>>
+      GetArrayLength;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectArrayPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              JSizeMarker length,
+              JClassPtr elementClass,
+              JObjectPtr initialElement)>> NewObjectArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(ffi.Pointer<JniEnv1> env, JObjectArrayPtr array,
+              JSizeMarker index)>> GetObjectArrayElement;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JObjectArrayPtr array,
+              JSizeMarker index, JObjectPtr val)>> SetObjectArrayElement;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JBooleanArrayPtr Function(
+              ffi.Pointer<JniEnv1> env, JSizeMarker length)>> NewBooleanArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JByteArrayPtr Function(
+              ffi.Pointer<JniEnv1> env, JSizeMarker length)>> NewByteArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JCharArrayPtr Function(
+              ffi.Pointer<JniEnv1> env, JSizeMarker length)>> NewCharArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JShortArrayPtr Function(
+              ffi.Pointer<JniEnv1> env, JSizeMarker length)>> NewShortArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntArrayPtr Function(
+              ffi.Pointer<JniEnv1> env, JSizeMarker length)>> NewIntArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JLongArrayPtr Function(
+              ffi.Pointer<JniEnv1> env, JSizeMarker length)>> NewLongArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JFloatArrayPtr Function(
+              ffi.Pointer<JniEnv1> env, JSizeMarker length)>> NewFloatArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JDoubleArrayPtr Function(
+              ffi.Pointer<JniEnv1> env, JSizeMarker length)>> NewDoubleArray;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JBooleanMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JBooleanArrayPtr array,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetBooleanArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JByteMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JByteArrayPtr array,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetByteArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JCharMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JCharArrayPtr array,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetCharArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JShortMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JShortArrayPtr array,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetShortArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JIntMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JIntArrayPtr array,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetIntArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JLongMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JLongArrayPtr array,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetLongArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JFloatMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JFloatArrayPtr array,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetFloatArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JDoubleMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JDoubleArrayPtr array,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetDoubleArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JBooleanArrayPtr array,
+              ffi.Pointer<JBooleanMarker> elems,
+              JIntMarker mode)>> ReleaseBooleanArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JByteArrayPtr array,
+              ffi.Pointer<JByteMarker> elems,
+              JIntMarker mode)>> ReleaseByteArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JCharArrayPtr array,
+              ffi.Pointer<JCharMarker> elems,
+              JIntMarker mode)>> ReleaseCharArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JShortArrayPtr array,
+              ffi.Pointer<JShortMarker> elems,
+              JIntMarker mode)>> ReleaseShortArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JIntArrayPtr array,
+              ffi.Pointer<JIntMarker> elems,
+              JIntMarker mode)>> ReleaseIntArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JLongArrayPtr array,
+              ffi.Pointer<JLongMarker> elems,
+              JIntMarker mode)>> ReleaseLongArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JFloatArrayPtr array,
+              ffi.Pointer<JFloatMarker> elems,
+              JIntMarker mode)>> ReleaseFloatArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JDoubleArrayPtr array,
+              ffi.Pointer<JDoubleMarker> elems,
+              JIntMarker mode)>> ReleaseDoubleArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JBooleanArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JBooleanMarker> buf)>> GetBooleanArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JByteArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JByteMarker> buf)>> GetByteArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JCharArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JCharMarker> buf)>> GetCharArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JShortArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JShortMarker> buf)>> GetShortArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JIntArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JIntMarker> buf)>> GetIntArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JLongArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JLongMarker> buf)>> GetLongArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JFloatArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JFloatMarker> buf)>> GetFloatArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JDoubleArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JDoubleMarker> buf)>> GetDoubleArrayRegion;
+
+  /// spec shows these without const; some jni.h do, some don't
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JBooleanArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JBooleanMarker> buf)>> SetBooleanArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JByteArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JByteMarker> buf)>> SetByteArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JCharArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JCharMarker> buf)>> SetCharArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JShortArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JShortMarker> buf)>> SetShortArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JIntArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JIntMarker> buf)>> SetIntArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JLongArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JLongMarker> buf)>> SetLongArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JFloatArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JFloatMarker> buf)>> SetFloatArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JDoubleArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JDoubleMarker> buf)>> SetDoubleArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(
+              ffi.Pointer<JniEnv1> env,
+              JClassPtr clazz,
+              ffi.Pointer<JNINativeMethod> methods,
+              JIntMarker nMethods)>> RegisterNatives;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JIntMarker Function(ffi.Pointer<JniEnv1> env, JClassPtr clazz)>>
+      UnregisterNatives;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JIntMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj)>>
+      MonitorEnter;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JIntMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj)>>
+      MonitorExit;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JIntMarker Function(ffi.Pointer<JniEnv1> env,
+              ffi.Pointer<ffi.Pointer<JavaVM1>> vm)>> GetJavaVM;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JStringPtr str,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JCharMarker> buf)>> GetStringRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JStringPtr str,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<ffi.Char> buf)>> GetStringUTFRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<ffi.Void> Function(
+              ffi.Pointer<JniEnv1> env,
+              JArrayPtr array,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetPrimitiveArrayCritical;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(
+              ffi.Pointer<JniEnv1> env,
+              JArrayPtr array,
+              ffi.Pointer<ffi.Void> carray,
+              JIntMarker mode)>> ReleasePrimitiveArrayCritical;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Pointer<JCharMarker> Function(
+              ffi.Pointer<JniEnv1> env,
+              JStringPtr str,
+              ffi.Pointer<JBooleanMarker> isCopy)>> GetStringCritical;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          ffi.Void Function(ffi.Pointer<JniEnv1> env, JStringPtr str,
+              ffi.Pointer<JCharMarker> carray)>> ReleaseStringCritical;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JWeakPtr Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj)>>
+      NewWeakGlobalRef;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              ffi.Void Function(ffi.Pointer<JniEnv1> env, JWeakPtr obj)>>
+      DeleteWeakGlobalRef;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<JBooleanMarker Function(ffi.Pointer<JniEnv1> env)>>
+      ExceptionCheck;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JObjectPtr Function(
+              ffi.Pointer<JniEnv1> env,
+              ffi.Pointer<ffi.Void> address,
+              JLongMarker capacity)>> NewDirectByteBuffer;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              ffi.Pointer<ffi.Void> Function(
+                  ffi.Pointer<JniEnv1> env, JObjectPtr buf)>>
+      GetDirectBufferAddress;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JLongMarker Function(ffi.Pointer<JniEnv1> env, JObjectPtr buf)>>
+      GetDirectBufferCapacity;
+
+  /// added in JNI 1.6
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              ffi.Int32 Function(ffi.Pointer<JniEnv1> env, JObjectPtr obj)>>
+      GetObjectRefType;
+}
+
+typedef JniEnv1 = ffi.Pointer<JNINativeInterface>;
+typedef JObjectArrayPtr = JArrayPtr;
+typedef JBooleanArrayPtr = JArrayPtr;
+typedef JByteArrayPtr = JArrayPtr;
+typedef JCharArrayPtr = JArrayPtr;
+typedef JShortArrayPtr = JArrayPtr;
+typedef JIntArrayPtr = JArrayPtr;
+typedef JLongArrayPtr = JArrayPtr;
+typedef JFloatArrayPtr = JArrayPtr;
+typedef JDoubleArrayPtr = JArrayPtr;
+
+final class JNINativeMethod extends ffi.Struct {
+  external ffi.Pointer<ffi.Char> name;
+
+  external ffi.Pointer<ffi.Char> signature;
+
+  external ffi.Pointer<ffi.Void> fnPtr;
+}
+
+typedef JWeakPtr = JObjectPtr;
+
+abstract class JObjectRefType {
+  static const int JNIInvalidRefType = 0;
+  static const int JNILocalRefType = 1;
+  static const int JNIGlobalRefType = 2;
+  static const int JNIWeakGlobalRefType = 3;
+}
+
+final class JavaVMInitArgs extends ffi.Struct {
+  /// use JNI_VERSION_1_2 or later
+  @JIntMarker()
+  external int version;
+
+  @JIntMarker()
+  external int nOptions;
+
+  external ffi.Pointer<JavaVMOption> options;
+
+  @JBooleanMarker()
+  external int ignoreUnrecognized;
+}
+
+/// JNI 1.2+ initialization.  (As of 1.6, the pre-1.2 structures are no
+/// longer supported.)
+final class JavaVMOption extends ffi.Struct {
+  external ffi.Pointer<ffi.Char> optionString;
+
+  external ffi.Pointer<ffi.Void> extraInfo;
+}
+
+final class CallbackResult extends ffi.Struct {
+  external MutexLock lock;
+
+  external ConditionVariable cond;
+
+  @ffi.Int()
+  external int ready;
+
+  external JObjectPtr object;
+}
+
+typedef MutexLock = pthread_mutex_t;
+typedef pthread_mutex_t = __darwin_pthread_mutex_t;
+typedef __darwin_pthread_mutex_t = _opaque_pthread_mutex_t;
+
+final class _opaque_pthread_mutex_t extends ffi.Struct {
+  @ffi.Long()
+  external int __sig;
+
+  @ffi.Array.multi([56])
+  external ffi.Array<ffi.Char> __opaque;
+}
+
+typedef ConditionVariable = pthread_cond_t;
+typedef pthread_cond_t = __darwin_pthread_cond_t;
+typedef __darwin_pthread_cond_t = _opaque_pthread_cond_t;
+
+final class _opaque_pthread_cond_t extends ffi.Struct {
+  @ffi.Long()
+  external int __sig;
+
+  @ffi.Array.multi([40])
+  external ffi.Array<ffi.Char> __opaque;
+}
+
+final class GlobalJniEnvStruct extends ffi.Struct {
+  external ffi.Pointer<ffi.Void> reserved0;
+
+  external ffi.Pointer<ffi.Void> reserved1;
+
+  external ffi.Pointer<ffi.Void> reserved2;
+
+  external ffi.Pointer<ffi.Void> reserved3;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function()>> GetVersion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniClassLookupResult Function(
+              ffi.Pointer<ffi.Char> name,
+              JObjectPtr loader,
+              ffi.Pointer<JByteMarker> buf,
+              JSizeMarker bufLen)>> DefineClass;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniClassLookupResult Function(ffi.Pointer<ffi.Char> name)>> FindClass;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniPointerResult Function(JObjectPtr method)>>
+      FromReflectedMethod;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniPointerResult Function(JObjectPtr field)>>
+      FromReflectedField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr cls, JMethodIDPtr methodId,
+              JBooleanMarker isStatic)>> ToReflectedMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<JniClassLookupResult Function(JClassPtr clazz)>>
+      GetSuperclass;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz1, JClassPtr clazz2)>>
+      IsAssignableFrom;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr cls, JFieldIDPtr fieldID, JBooleanMarker isStatic)>>
+      ToReflectedField;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JThrowablePtr obj)>> Throw;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JClassPtr clazz, ffi.Pointer<ffi.Char> message)>> ThrowNew;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function()>>
+      ExceptionOccurred;
+
+  external ffi.Pointer<ffi.NativeFunction<JThrowablePtr Function()>>
+      ExceptionDescribe;
+
+  external ffi.Pointer<ffi.NativeFunction<JThrowablePtr Function()>>
+      ExceptionClear;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<JThrowablePtr Function(ffi.Pointer<ffi.Char> msg)>>
+      FatalError;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JIntMarker capacity)>>
+      PushLocalFrame;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JObjectPtr result)>>
+      PopLocalFrame;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JObjectPtr obj)>>
+      NewGlobalRef;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JThrowablePtr Function(JObjectPtr globalRef)>>
+      DeleteGlobalRef;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JThrowablePtr Function(JObjectPtr localRef)>>
+      DeleteLocalRef;
+
+  external ffi.Pointer<
+          ffi
+          .NativeFunction<JniResult Function(JObjectPtr ref1, JObjectPtr ref2)>>
+      IsSameObject;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JObjectPtr obj)>>
+      NewLocalRef;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JIntMarker capacity)>>
+      EnsureLocalCapacity;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JClassPtr clazz)>>
+      AllocObject;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      NewObject;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>> NewObjectV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> NewObjectA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<JniClassLookupResult Function(JObjectPtr obj)>>
+      GetObjectClass;
+
+  external ffi.Pointer<
+          ffi
+          .NativeFunction<JniResult Function(JObjectPtr obj, JClassPtr clazz)>>
+      IsInstanceOf;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniPointerResult Function(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+              ffi.Pointer<ffi.Char> sig)>> GetMethodID;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallObjectMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallObjectMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallObjectMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallBooleanMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallBooleanMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodId,
+              ffi.Pointer<JValue> args)>> CallBooleanMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallByteMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallByteMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallByteMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallCharMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallCharMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallCharMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallShortMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallShortMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallShortMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallIntMethod;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>> CallIntMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallIntMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallLongMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallLongMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallLongMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallFloatMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallFloatMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallFloatMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallDoubleMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallDoubleMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallDoubleMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(JObjectPtr obj, JMethodIDPtr methodID)>>
+      CallVoidMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallVoidMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(JObjectPtr obj, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallVoidMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualObjectMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualObjectMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualObjectMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualBooleanMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualBooleanMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualBooleanMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualByteMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualByteMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualByteMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualCharMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualCharMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualCharMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualShortMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualShortMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualShortMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualIntMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualIntMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualIntMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualLongMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualLongMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualLongMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualFloatMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualFloatMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualFloatMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualDoubleMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualDoubleMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualDoubleMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallNonvirtualVoidMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr, JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallNonvirtualVoidMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JObjectPtr obj,
+              JClassPtr clazz,
+              JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallNonvirtualVoidMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniPointerResult Function(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+              ffi.Pointer<ffi.Char> sig)>> GetFieldID;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>>
+      GetObjectField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>>
+      GetBooleanField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>>
+      GetByteField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>>
+      GetCharField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>>
+      GetShortField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>> GetIntField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>>
+      GetLongField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>>
+      GetFloatField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectPtr obj, JFieldIDPtr fieldID)>>
+      GetDoubleField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JFieldIDPtr fieldID, JObjectPtr val)>>
+      SetObjectField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JFieldIDPtr fieldID, JBooleanMarker val)>>
+      SetBooleanField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JFieldIDPtr fieldID, JByteMarker val)>>
+      SetByteField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JFieldIDPtr fieldID, JCharMarker val)>>
+      SetCharField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JFieldIDPtr fieldID, JShortMarker val)>>
+      SetShortField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JFieldIDPtr fieldID, JIntMarker val)>>
+      SetIntField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JFieldIDPtr fieldID, JLongMarker val)>>
+      SetLongField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JFieldIDPtr fieldID, JFloatMarker val)>>
+      SetFloatField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectPtr obj, JFieldIDPtr fieldID, JDoubleMarker val)>>
+      SetDoubleField;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniPointerResult Function(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+              ffi.Pointer<ffi.Char> sig)>> GetStaticMethodID;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticObjectMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticObjectMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticObjectMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticBooleanMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticBooleanMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticBooleanMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticByteMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticByteMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticByteMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticCharMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticCharMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticCharMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticShortMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticShortMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticShortMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticIntMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticIntMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticIntMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticLongMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticLongMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticLongMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticFloatMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticFloatMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticFloatMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticDoubleMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticDoubleMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticDoubleMethodA;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(JClassPtr clazz, JMethodIDPtr methodID)>>
+      CallStaticVoidMethod;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr, JMethodIDPtr, ffi.Pointer<ffi.Void>)>>
+      CallStaticVoidMethodV;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(JClassPtr clazz, JMethodIDPtr methodID,
+              ffi.Pointer<JValue> args)>> CallStaticVoidMethodA;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniPointerResult Function(JClassPtr clazz, ffi.Pointer<ffi.Char> name,
+              ffi.Pointer<ffi.Char> sig)>> GetStaticFieldID;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>>
+      GetStaticObjectField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>>
+      GetStaticBooleanField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>>
+      GetStaticByteField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>>
+      GetStaticCharField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>>
+      GetStaticShortField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>>
+      GetStaticIntField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>>
+      GetStaticLongField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>>
+      GetStaticFloatField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JClassPtr clazz, JFieldIDPtr fieldID)>>
+      GetStaticDoubleField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr clazz, JFieldIDPtr fieldID, JObjectPtr val)>>
+      SetStaticObjectField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr clazz, JFieldIDPtr fieldID, JBooleanMarker val)>>
+      SetStaticBooleanField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr clazz, JFieldIDPtr fieldID, JByteMarker val)>>
+      SetStaticByteField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr clazz, JFieldIDPtr fieldID, JCharMarker val)>>
+      SetStaticCharField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr clazz, JFieldIDPtr fieldID, JShortMarker val)>>
+      SetStaticShortField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr clazz, JFieldIDPtr fieldID, JIntMarker val)>>
+      SetStaticIntField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr clazz, JFieldIDPtr fieldID, JLongMarker val)>>
+      SetStaticLongField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr clazz, JFieldIDPtr fieldID, JFloatMarker val)>>
+      SetStaticFloatField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JClassPtr clazz, JFieldIDPtr fieldID, JDoubleMarker val)>>
+      SetStaticDoubleField;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  ffi.Pointer<JCharMarker> unicodeChars, JSizeMarker len)>>
+      NewString;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JStringPtr string)>>
+      GetStringLength;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JStringPtr string, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetStringChars;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JStringPtr string, ffi.Pointer<JCharMarker> isCopy)>>
+      ReleaseStringChars;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<JniResult Function(ffi.Pointer<ffi.Char> bytes)>>
+      NewStringUTF;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JStringPtr string)>>
+      GetStringUTFLength;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JStringPtr string, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetStringUTFChars;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JStringPtr string, ffi.Pointer<ffi.Char> utf)>>
+      ReleaseStringUTFChars;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JArrayPtr array)>>
+      GetArrayLength;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(JSizeMarker length, JClassPtr elementClass,
+              JObjectPtr initialElement)>> NewObjectArray;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(JObjectArrayPtr array, JSizeMarker index)>>
+      GetObjectArrayElement;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JObjectArrayPtr array, JSizeMarker index, JObjectPtr val)>>
+      SetObjectArrayElement;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JSizeMarker length)>>
+      NewBooleanArray;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JSizeMarker length)>>
+      NewByteArray;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JSizeMarker length)>>
+      NewCharArray;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JSizeMarker length)>>
+      NewShortArray;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JSizeMarker length)>>
+      NewIntArray;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JSizeMarker length)>>
+      NewLongArray;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JSizeMarker length)>>
+      NewFloatArray;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniResult Function(JSizeMarker length)>>
+      NewDoubleArray;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JBooleanArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetBooleanArrayElements;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JByteArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetByteArrayElements;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JCharArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetCharArrayElements;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JShortArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetShortArrayElements;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JIntArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetIntArrayElements;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JLongArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetLongArrayElements;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JFloatArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetFloatArrayElements;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JDoubleArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetDoubleArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JBooleanArrayPtr array,
+              ffi.Pointer<JBooleanMarker> elems,
+              JIntMarker mode)>> ReleaseBooleanArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JByteArrayPtr array,
+              ffi.Pointer<JByteMarker> elems,
+              JIntMarker mode)>> ReleaseByteArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JCharArrayPtr array,
+              ffi.Pointer<JCharMarker> elems,
+              JIntMarker mode)>> ReleaseCharArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JShortArrayPtr array,
+              ffi.Pointer<JShortMarker> elems,
+              JIntMarker mode)>> ReleaseShortArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JIntArrayPtr array,
+              ffi.Pointer<JIntMarker> elems,
+              JIntMarker mode)>> ReleaseIntArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JLongArrayPtr array,
+              ffi.Pointer<JLongMarker> elems,
+              JIntMarker mode)>> ReleaseLongArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JFloatArrayPtr array,
+              ffi.Pointer<JFloatMarker> elems,
+              JIntMarker mode)>> ReleaseFloatArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JDoubleArrayPtr array,
+              ffi.Pointer<JDoubleMarker> elems,
+              JIntMarker mode)>> ReleaseDoubleArrayElements;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JBooleanArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JBooleanMarker> buf)>> GetBooleanArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JByteArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JByteMarker> buf)>> GetByteArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JCharArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JCharMarker> buf)>> GetCharArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JShortArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JShortMarker> buf)>> GetShortArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(JIntArrayPtr array, JSizeMarker start,
+              JSizeMarker len, ffi.Pointer<JIntMarker> buf)>> GetIntArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JLongArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JLongMarker> buf)>> GetLongArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JFloatArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JFloatMarker> buf)>> GetFloatArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JDoubleArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JDoubleMarker> buf)>> GetDoubleArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JBooleanArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JBooleanMarker> buf)>> SetBooleanArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JByteArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JByteMarker> buf)>> SetByteArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JCharArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JCharMarker> buf)>> SetCharArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JShortArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JShortMarker> buf)>> SetShortArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(JIntArrayPtr array, JSizeMarker start,
+              JSizeMarker len, ffi.Pointer<JIntMarker> buf)>> SetIntArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JLongArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JLongMarker> buf)>> SetLongArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JFloatArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JFloatMarker> buf)>> SetFloatArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(
+              JDoubleArrayPtr array,
+              JSizeMarker start,
+              JSizeMarker len,
+              ffi.Pointer<JDoubleMarker> buf)>> SetDoubleArrayRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(
+              JClassPtr clazz,
+              ffi.Pointer<JNINativeMethod> methods,
+              JIntMarker nMethods)>> RegisterNatives;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JClassPtr clazz)>>
+      UnregisterNatives;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JObjectPtr obj)>>
+      MonitorEnter;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JObjectPtr obj)>>
+      MonitorExit;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JniResult Function(ffi.Pointer<ffi.Pointer<JavaVM>> vm)>> GetJavaVM;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(JStringPtr str, JSizeMarker start,
+              JSizeMarker len, ffi.Pointer<JCharMarker> buf)>> GetStringRegion;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(JStringPtr str, JSizeMarker start,
+              JSizeMarker len, ffi.Pointer<ffi.Char> buf)>> GetStringUTFRegion;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JArrayPtr array, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetPrimitiveArrayCritical;
+
+  external ffi.Pointer<
+      ffi.NativeFunction<
+          JThrowablePtr Function(JArrayPtr array, ffi.Pointer<ffi.Void> carray,
+              JIntMarker mode)>> ReleasePrimitiveArrayCritical;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniPointerResult Function(
+                  JStringPtr str, ffi.Pointer<JBooleanMarker> isCopy)>>
+      GetStringCritical;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JThrowablePtr Function(
+                  JStringPtr str, ffi.Pointer<JCharMarker> carray)>>
+      ReleaseStringCritical;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JObjectPtr obj)>>
+      NewWeakGlobalRef;
+
+  external ffi.Pointer<ffi.NativeFunction<JThrowablePtr Function(JWeakPtr obj)>>
+      DeleteWeakGlobalRef;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function()>> ExceptionCheck;
+
+  external ffi.Pointer<
+          ffi.NativeFunction<
+              JniResult Function(
+                  ffi.Pointer<ffi.Void> address, JLongMarker capacity)>>
+      NewDirectByteBuffer;
+
+  external ffi
+      .Pointer<ffi.NativeFunction<JniPointerResult Function(JObjectPtr buf)>>
+      GetDirectBufferAddress;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JObjectPtr buf)>>
+      GetDirectBufferCapacity;
+
+  external ffi.Pointer<ffi.NativeFunction<JniResult Function(JObjectPtr obj)>>
+      GetObjectRefType;
+}
+
+/// This file re-exports some JNI constants as enum, because they are not
+/// currently being included when they are in macro form.
+abstract class JniBooleanValues {
+  static const int JNI_FALSE = 0;
+  static const int JNI_TRUE = 1;
+}
+
+abstract class JniVersions {
+  static const int JNI_VERSION_1_1 = 65537;
+  static const int JNI_VERSION_1_2 = 65538;
+  static const int JNI_VERSION_1_4 = 65540;
+  static const int JNI_VERSION_1_6 = 65542;
+}
+
+abstract class JniErrorCode {
+  /// no error
+  static const int JNI_OK = 0;
+
+  /// generic error
+  static const int JNI_ERR = -1;
+
+  /// thread detached from the VM
+  static const int JNI_EDETACHED = -2;
+
+  /// JNI version error
+  static const int JNI_EVERSION = -3;
+
+  /// Out of memory
+  static const int JNI_ENOMEM = -4;
+
+  /// VM already created
+  static const int JNI_EEXIST = -5;
+
+  /// Invalid argument
+  static const int JNI_EINVAL = -6;
+}
+
+abstract class JniBufferWriteBack {
+  /// copy content, do not free buffer
+  static const int JNI_COMMIT = 1;
+
+  /// free buffer w/o copying back
+  static const int JNI_ABORT = 2;
+}
+
+const int DART_JNI_SINGLETON_EXISTS = -99;
diff --git a/pkgs/jni/lib/src/types.dart b/pkgs/jni/lib/src/types.dart
new file mode 100644
index 0000000..d560b2d
--- /dev/null
+++ b/pkgs/jni/lib/src/types.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+
+import 'jni.dart';
+import 'jobject.dart';
+
+part 'jprimitives.dart';
+
+sealed class JType<T> {
+  const JType();
+
+  String get signature;
+}
+
+abstract class JObjType<T extends JObject> extends JType<T> {
+  /// Number of super types. Distance to the root type.
+  int get superCount;
+
+  JObjType get superType;
+
+  const JObjType();
+
+  /// Creates an object from this type using the reference.
+  T fromRef(Pointer<Void> ref);
+
+  JClass getClass() {
+    if (signature.startsWith('L') && signature.endsWith(';')) {
+      return Jni.findJClass(signature.substring(1, signature.length - 1));
+    }
+    return Jni.findJClass(signature);
+  }
+}
+
+/// Lowest common ancestor of two types in the inheritance tree.
+JObjType _lowestCommonAncestor(JObjType a, JObjType b) {
+  while (a.superCount > b.superCount) {
+    a = a.superType;
+  }
+  while (b.superCount > a.superCount) {
+    b = b.superType;
+  }
+  while (a != b) {
+    a = a.superType;
+    b = b.superType;
+  }
+  return a;
+}
+
+JObjType lowestCommonSuperType(List<JObjType> types) {
+  return types.reduce(_lowestCommonAncestor);
+}
diff --git a/pkgs/jni/lib/src/util/jiterator.dart b/pkgs/jni/lib/src/util/jiterator.dart
new file mode 100644
index 0000000..0d0122f
--- /dev/null
+++ b/pkgs/jni/lib/src/util/jiterator.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jobject.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+
+final class JIteratorType<$E extends JObject> extends JObjType<JIterator<$E>> {
+  final JObjType<$E> E;
+
+  const JIteratorType(
+    this.E,
+  );
+
+  @override
+  String get signature => r"Ljava/util/Iterator;";
+
+  @override
+  JIterator<$E> fromRef(JObjectPtr ref) => JIterator.fromRef(E, ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash(JIteratorType, E);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == (JIteratorType<$E>) &&
+        other is JIteratorType<$E> &&
+        E == other.E;
+  }
+}
+
+class JIterator<$E extends JObject> extends JObject implements Iterator<$E> {
+  @override
+  // ignore: overridden_fields
+  late final JObjType $type = type(E);
+
+  final JObjType<$E> E;
+
+  JIterator.fromRef(
+    this.E,
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = Jni.findJClass(r"java/util/Iterator");
+
+  /// The type which includes information such as the signature of this class.
+  static JIteratorType<$E> type<$E extends JObject>(
+    JObjType<$E> E,
+  ) {
+    return JIteratorType(
+      E,
+    );
+  }
+
+  $E? _current;
+
+  @override
+  $E get current => _current as $E;
+
+  static final _hasNextId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"hasNext", r"()Z");
+  bool _hasNext() {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _hasNextId, JniCallType.booleanType, []).boolean;
+  }
+
+  static final _nextId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"next", r"()Ljava/lang/Object;");
+  $E _next() {
+    return E.fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _nextId, JniCallType.objectType, []).object);
+  }
+
+  @override
+  bool moveNext() {
+    if (!_hasNext()) {
+      return false;
+    }
+    _current = _next();
+    return true;
+  }
+}
diff --git a/pkgs/jni/lib/src/util/jlist.dart b/pkgs/jni/lib/src/util/jlist.dart
new file mode 100644
index 0000000..9779032
--- /dev/null
+++ b/pkgs/jni/lib/src/util/jlist.dart
@@ -0,0 +1,279 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+import 'dart:ffi';
+
+import 'package:jni/src/util/jset.dart';
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jobject.dart';
+import '../jvalues.dart';
+import '../third_party/generated_bindings.dart';
+import '../types.dart';
+import 'jiterator.dart';
+
+final class JListType<$E extends JObject> extends JObjType<JList<$E>> {
+  final JObjType<$E> E;
+
+  const JListType(
+    this.E,
+  );
+
+  @override
+  String get signature => r"Ljava/util/List;";
+
+  @override
+  JList<$E> fromRef(JObjectPtr ref) => JList.fromRef(E, ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash(JListType, E);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == JListType && other is JListType && E == other.E;
+  }
+}
+
+class JList<$E extends JObject> extends JObject with ListMixin<$E> {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JList> $type = type(E);
+
+  final JObjType<$E> E;
+
+  JList.fromRef(
+    this.E,
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = Jni.findJClass(r"java/util/List");
+
+  /// The type which includes information such as the signature of this class.
+  static JListType<$E> type<$E extends JObject>(
+    JObjType<$E> E,
+  ) {
+    return JListType(
+      E,
+    );
+  }
+
+  static final _arrayListClassRef = Jni.findJClass(r"java/util/ArrayList");
+  static final _ctorId = Jni.accessors
+      .getMethodIDOf(_arrayListClassRef.reference, r"<init>", r"()V");
+  JList.array(this.E)
+      : super.fromRef(Jni.accessors.newObjectWithArgs(
+            _arrayListClassRef.reference, _ctorId, []).object);
+
+  static final _sizeId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"size", r"()I");
+  @override
+  int get length => Jni.accessors
+      .callMethodWithArgs(reference, _sizeId, JniCallType.intType, []).integer;
+
+  @override
+  set length(int newLength) {
+    RangeError.checkNotNegative(newLength);
+    while (length < newLength) {
+      add(E.fromRef(nullptr));
+    }
+    while (newLength < length) {
+      removeAt(length - 1);
+    }
+  }
+
+  static final _getId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"get", r"(I)Ljava/lang/Object;");
+  @override
+  $E operator [](int index) {
+    RangeError.checkValidIndex(index, this);
+    return E.fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _getId, JniCallType.objectType, [JValueInt(index)]).object);
+  }
+
+  static final _setId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"set", r"(ILjava/lang/Object;)Ljava/lang/Object;");
+  @override
+  void operator []=(int index, $E value) {
+    RangeError.checkValidIndex(index, this);
+    E.fromRef(Jni.accessors.callMethodWithArgs(reference, _setId,
+        JniCallType.objectType, [JValueInt(index), value.reference]).object);
+  }
+
+  static final _addId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"add", r"(Ljava/lang/Object;)Z");
+  @override
+  void add($E element) {
+    Jni.accessors.callMethodWithArgs(reference, _addId, JniCallType.booleanType,
+        [element.reference]).check();
+  }
+
+  static final _collectionClass = Jni.findJClass("java/util/Collection");
+  static final _addAllId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"addAll", r"(Ljava/util/Collection;)Z");
+  @override
+  void addAll(Iterable<$E> iterable) {
+    if (iterable is JObject &&
+        Jni.env.IsInstanceOf(
+            (iterable as JObject).reference, _collectionClass.reference)) {
+      Jni.accessors.callMethodWithArgs(reference, _addAllId,
+          JniCallType.booleanType, [(iterable as JObject).reference]).boolean;
+      return;
+    }
+    return super.addAll(iterable);
+  }
+
+  static final _clearId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"clear", r"()V");
+  @override
+  void clear() {
+    Jni.accessors.callMethodWithArgs(
+        reference, _clearId, JniCallType.voidType, []).check();
+  }
+
+  static final _containsId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"contains", r"(Ljava/lang/Object;)Z");
+  @override
+  bool contains(Object? element) {
+    if (element is! JObject) return false;
+    return Jni.accessors.callMethodWithArgs(reference, _containsId,
+        JniCallType.booleanType, [element.reference]).boolean;
+  }
+
+  static final _getRangeId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"subList", r"(II)Ljava/util/List;");
+  @override
+  JList<$E> getRange(int start, int end) {
+    RangeError.checkValidRange(start, end, this.length);
+    return JListType(E).fromRef(
+      Jni.accessors.callMethodWithArgs(reference, _getRangeId,
+          JniCallType.objectType, [JValueInt(start), JValueInt(end)]).object,
+    );
+  }
+
+  static final _indexOfId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"indexOf", r"(Ljava/lang/Object;)I");
+  @override
+  int indexOf(Object? element, [int start = 0]) {
+    if (element is! JObject) return -1;
+    if (start < 0) start = 0;
+    if (start == 0) {
+      return Jni.accessors.callMethodWithArgs(reference, _indexOfId,
+          JniCallType.intType, [element.reference]).integer;
+    }
+    return Jni.accessors.callMethodWithArgs(
+      getRange(start, length).reference,
+      _indexOfId,
+      JniCallType.intType,
+      [element.reference],
+    ).integer;
+  }
+
+  static final _insertId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"add", r"(ILjava/lang/Object;)V");
+  @override
+  void insert(int index, $E element) {
+    Jni.accessors.callMethodWithArgs(reference, _insertId, JniCallType.voidType,
+        [JValueInt(index), element.reference]).check();
+  }
+
+  static final _insertAllId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"addAll", r"(ILjava/util/Collection;)Z");
+  @override
+  void insertAll(int index, Iterable<$E> iterable) {
+    if (iterable is JObject &&
+        Jni.env.IsInstanceOf(
+            (iterable as JObject).reference, _collectionClass.reference)) {
+      Jni.accessors.callMethodWithArgs(
+        reference,
+        _insertAllId,
+        JniCallType.booleanType,
+        [JValueInt(index), (iterable as JObject).reference],
+      ).boolean;
+      return;
+    }
+    super.insertAll(index, iterable);
+  }
+
+  static final _isEmptyId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"isEmpty", r"()Z");
+  @override
+  bool get isEmpty => Jni.accessors.callMethodWithArgs(
+      reference, _isEmptyId, JniCallType.booleanType, []).boolean;
+
+  @override
+  bool get isNotEmpty => !isEmpty;
+
+  static final _iteratorId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"iterator", r"()Ljava/util/Iterator;");
+  @override
+  JIterator<$E> get iterator =>
+      JIteratorType(E).fromRef(Jni.accessors.callMethodWithArgs(
+          reference, _iteratorId, JniCallType.objectType, []).object);
+
+  static final _lastIndexOfId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"lastIndexOf", r"(Ljava/lang/Object;)I");
+  @override
+  int lastIndexOf(Object? element, [int? start]) {
+    if (element is! JObject) return -1;
+    if (start == null || start >= this.length) start = this.length - 1;
+    if (start == this.length - 1) {
+      return Jni.accessors.callMethodWithArgs(reference, _lastIndexOfId,
+          JniCallType.intType, [element.reference]).integer;
+    }
+    final range = getRange(start, length);
+    final res = Jni.accessors.callMethodWithArgs(
+      range.reference,
+      _lastIndexOfId,
+      JniCallType.intType,
+      [element.reference],
+    ).integer;
+    range.release();
+    return res;
+  }
+
+  static final _removeId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"remove", r"(Ljava/lang/Object;)Z");
+  @override
+  bool remove(Object? element) {
+    if (element is! JObject) return false;
+    return Jni.accessors.callMethodWithArgs(reference, _removeId,
+        JniCallType.booleanType, [element.reference]).boolean;
+  }
+
+  static final _removeAtId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"remove", r"(I)Ljava/lang/Object;");
+  @override
+  $E removeAt(int index) {
+    return E.fromRef(Jni.accessors.callMethodWithArgs(reference, _removeAtId,
+        JniCallType.objectType, [JValueInt(index)]).object);
+  }
+
+  @override
+  void removeRange(int start, int end) {
+    final range = getRange(start, end);
+    range.clear();
+    range.release();
+  }
+
+  @override
+  JSet<$E> toSet() {
+    return toJSet(E);
+  }
+}
+
+extension ToJavaList<E extends JObject> on Iterable<E> {
+  JList<E> toJList(JObjType<E> type) {
+    final list = JList.array(type);
+    list.addAll(this);
+    return list;
+  }
+}
diff --git a/pkgs/jni/lib/src/util/jmap.dart b/pkgs/jni/lib/src/util/jmap.dart
new file mode 100644
index 0000000..73a6d62
--- /dev/null
+++ b/pkgs/jni/lib/src/util/jmap.dart
@@ -0,0 +1,185 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jobject.dart';
+import '../third_party/jni_bindings_generated.dart';
+import '../types.dart';
+import 'jset.dart';
+
+final class JMapType<$K extends JObject, $V extends JObject>
+    extends JObjType<JMap<$K, $V>> {
+  final JObjType<$K> K;
+  final JObjType<$V> V;
+
+  const JMapType(
+    this.K,
+    this.V,
+  );
+
+  @override
+  String get signature => r"Ljava/util/Map;";
+
+  @override
+  JMap<$K, $V> fromRef(JObjectPtr ref) => JMap.fromRef(K, V, ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash(JMapType, K, V);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == (JMapType<$K, $V>) &&
+        other is JMapType<$K, $V> &&
+        K == other.K &&
+        V == other.V;
+  }
+}
+
+class JMap<$K extends JObject, $V extends JObject> extends JObject
+    with MapMixin<$K, $V> {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JMap> $type = type(K, V);
+
+  final JObjType<$K> K;
+  final JObjType<$V> V;
+
+  JMap.fromRef(
+    this.K,
+    this.V,
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = Jni.findJClass(r"java/util/Map");
+
+  /// The type which includes information such as the signature of this class.
+  static JMapType<$K, $V> type<$K extends JObject, $V extends JObject>(
+    JObjType<$K> K,
+    JObjType<$V> V,
+  ) {
+    return JMapType(
+      K,
+      V,
+    );
+  }
+
+  static final _hashMapClass = Jni.findJClass(r"java/util/HashMap");
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_hashMapClass.reference, r"<init>", r"()V");
+  JMap.hash(this.K, this.V)
+      : super.fromRef(Jni.accessors
+            .newObjectWithArgs(_hashMapClass.reference, _ctorId, []).object);
+
+  static final _getId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"get", r"(Ljava/lang/Object;)Ljava/lang/Object;");
+  @override
+  $V? operator [](Object? key) {
+    if (key is! JObject) {
+      return null;
+    }
+    final value = V.fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _getId, JniCallType.objectType, [key.reference]).object);
+    return value.isNull ? null : value;
+  }
+
+  static final _putId = Jni.accessors.getMethodIDOf(_class.reference, r"put",
+      r"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+  @override
+  void operator []=($K key, $V value) {
+    Jni.accessors.callMethodWithArgs(reference, _putId, JniCallType.objectType,
+        [key.reference, value.reference]).object;
+  }
+
+  static final _addAllId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"putAll", r"(Ljava/util/Map;)V");
+  @override
+  void addAll(Map<$K, $V> other) {
+    if (other is JMap<$K, $V>) {
+      Jni.accessors.callMethodWithArgs(reference, _addAllId,
+          JniCallType.voidType, [other.reference]).check();
+      return;
+    }
+    super.addAll(other);
+  }
+
+  static final _clearId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"clear", r"()V");
+  @override
+  void clear() {
+    Jni.accessors.callMethodWithArgs(
+        reference, _clearId, JniCallType.voidType, []).check();
+  }
+
+  static final _containsKeyId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"containsKey", r"(Ljava/lang/Object;)Z");
+  @override
+  bool containsKey(Object? key) {
+    if (key is! JObject) {
+      return false;
+    }
+    return Jni.accessors.callMethodWithArgs(reference, _containsKeyId,
+        JniCallType.booleanType, [key.reference]).boolean;
+  }
+
+  static final _containsValueId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"containsValue", r"(Ljava/lang/Object;)Z");
+  @override
+  bool containsValue(Object? value) {
+    if (value is! JObject) {
+      return false;
+    }
+    return Jni.accessors.callMethodWithArgs(reference, _containsValueId,
+        JniCallType.booleanType, [value.reference]).boolean;
+  }
+
+  static final isEmptyId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"isEmpty", r"()Z");
+  @override
+  bool get isEmpty => Jni.accessors.callMethodWithArgs(
+      reference, isEmptyId, JniCallType.booleanType, []).boolean;
+
+  @override
+  bool get isNotEmpty => !isEmpty;
+
+  static final _keysId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"keySet", r"()Ljava/util/Set;");
+  @override
+  JSet<$K> get keys => JSetType(K).fromRef(Jni.accessors.callMethodWithArgs(
+      reference, _keysId, JniCallType.objectType, []).object);
+
+  static final _sizeId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"size", r"()I");
+  @override
+  int get length => Jni.accessors
+      .callMethodWithArgs(reference, _sizeId, JniCallType.intType, []).integer;
+
+  static final _removeId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"remove", r"(Ljava/lang/Object;)Ljava/lang/Object;");
+  @override
+  $V? remove(Object? key) {
+    if (key is! JObject) {
+      return null;
+    }
+    final value = V.fromRef(Jni.accessors.callMethodWithArgs(
+        reference, _removeId, JniCallType.objectType, [key.reference]).object);
+    return value.isNull ? null : value;
+  }
+}
+
+extension ToJavaMap<K extends JObject, V extends JObject> on Map<K, V> {
+  JMap<K, V> toJMap(JObjType<K> keyType, JObjType<V> valueType) {
+    final map = JMap.hash(keyType, valueType);
+    map.addAll(this);
+    return map;
+  }
+}
diff --git a/pkgs/jni/lib/src/util/jset.dart b/pkgs/jni/lib/src/util/jset.dart
new file mode 100644
index 0000000..df51003
--- /dev/null
+++ b/pkgs/jni/lib/src/util/jset.dart
@@ -0,0 +1,214 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import 'package:jni/src/third_party/generated_bindings.dart';
+
+import '../accessors.dart';
+import '../jni.dart';
+import '../jobject.dart';
+import '../types.dart';
+import 'jiterator.dart';
+
+final class JSetType<$E extends JObject> extends JObjType<JSet<$E>> {
+  final JObjType<$E> E;
+
+  const JSetType(
+    this.E,
+  );
+
+  @override
+  String get signature => r"Ljava/util/Set;";
+
+  @override
+  JSet<$E> fromRef(JObjectPtr ref) => JSet.fromRef(E, ref);
+
+  @override
+  JObjType get superType => const JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash(JSetType, E);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == (JSetType<$E>) &&
+        other is JSetType<$E> &&
+        E == other.E;
+  }
+}
+
+class JSet<$E extends JObject> extends JObject with SetMixin<$E> {
+  @override
+  // ignore: overridden_fields
+  late final JObjType<JSet> $type = type(E);
+
+  final JObjType<$E> E;
+
+  JSet.fromRef(
+    this.E,
+    JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = Jni.findJClass(r"java/util/Set");
+
+  /// The type which includes information such as the signature of this class.
+  static JSetType<$E> type<$E extends JObject>(
+    JObjType<$E> E,
+  ) {
+    return JSetType(
+      E,
+    );
+  }
+
+  static final _hashSetClass = Jni.findJClass(r"java/util/HashSet");
+  static final _ctorId =
+      Jni.accessors.getMethodIDOf(_hashSetClass.reference, r"<init>", r"()V");
+  JSet.hash(this.E)
+      : super.fromRef(Jni.accessors
+            .newObjectWithArgs(_hashSetClass.reference, _ctorId, []).object);
+
+  static final _addId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"add", r"(Ljava/lang/Object;)Z");
+  @override
+  bool add($E value) {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _addId, JniCallType.booleanType, [value.reference]).boolean;
+  }
+
+  static final _addAllId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"addAll", r"(Ljava/util/Collection;)Z");
+  @override
+  void addAll(Iterable<$E> elements) {
+    if (elements is JObject &&
+        Jni.env.IsInstanceOf(
+            (elements as JObject).reference, _collectionClass.reference)) {
+      Jni.accessors.callMethodWithArgs(
+        reference,
+        _addAllId,
+        JniCallType.booleanType,
+        [(elements as JObject).reference],
+      ).boolean;
+      return;
+    }
+    return super.addAll(elements);
+  }
+
+  static final _clearId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"clear", r"()V");
+  @override
+  void clear() {
+    return Jni.accessors.callMethodWithArgs(
+        reference, _clearId, JniCallType.voidType, []).check();
+  }
+
+  static final _containsId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"contains", r"(Ljava/lang/Object;)Z");
+
+  @override
+  bool contains(Object? element) {
+    if (element is! JObject) {
+      return false;
+    }
+    return Jni.accessors.callMethodWithArgs(reference, _containsId,
+        JniCallType.booleanType, [element.reference]).boolean;
+  }
+
+  static final _containsAllId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"containsAll", r"(Ljava/util/Collection;)Z");
+  static final _collectionClass = Jni.findJClass("java/util/Collection");
+  @override
+  bool containsAll(Iterable<Object?> other) {
+    if (other is JObject &&
+        Jni.env.IsInstanceOf(
+            (other as JObject).reference, _collectionClass.reference)) {
+      return Jni.accessors.callMethodWithArgs(reference, _containsAllId,
+          JniCallType.booleanType, [(other as JObject).reference]).boolean;
+    }
+    return super.containsAll(other);
+  }
+
+  static final _isEmptyId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"isEmpty", r"()Z");
+  @override
+  bool get isEmpty => Jni.accessors.callMethodWithArgs(
+      reference, _isEmptyId, JniCallType.booleanType, []).boolean;
+
+  @override
+  bool get isNotEmpty => !isEmpty;
+
+  static final _iteratorId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"iterator", r"()Ljava/util/Iterator;");
+  @override
+  JIterator<$E> get iterator =>
+      JIteratorType(E).fromRef(Jni.accessors.callMethodWithArgs(
+          reference, _iteratorId, JniCallType.objectType, []).object);
+
+  static final _sizeId =
+      Jni.accessors.getMethodIDOf(_class.reference, r"size", r"()I");
+  @override
+  int get length => Jni.accessors
+      .callMethodWithArgs(reference, _sizeId, JniCallType.intType, []).integer;
+
+  static final _removeId = Jni.accessors
+      .getMethodIDOf(_class.reference, r"remove", r"(Ljava/lang/Object;)Z");
+  @override
+  bool remove(Object? value) {
+    if (value is! $E) {
+      return false;
+    }
+    return Jni.accessors.callMethodWithArgs(reference, _removeId,
+        JniCallType.booleanType, [value.reference]).boolean;
+  }
+
+  static final _removeAllId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"removeAll", r"(Ljava/util/Collection;)Z");
+  @override
+  void removeAll(Iterable<Object?> elements) {
+    if (elements is JObject &&
+        Jni.env.IsInstanceOf(
+            (elements as JObject).reference, _collectionClass.reference)) {
+      Jni.accessors.callMethodWithArgs(reference, _removeAllId,
+          JniCallType.booleanType, [(elements as JObject).reference]).boolean;
+      return;
+    }
+    return super.removeAll(elements);
+  }
+
+  static final _retainAllId = Jni.accessors.getMethodIDOf(
+      _class.reference, r"retainAll", r"(Ljava/util/Collection;)Z");
+  @override
+  void retainAll(Iterable<Object?> elements) {
+    if (elements is JObject &&
+        Jni.env.IsInstanceOf(
+            (elements as JObject).reference, _collectionClass.reference)) {
+      Jni.accessors.callMethodWithArgs(reference, _retainAllId,
+          JniCallType.booleanType, [(elements as JObject).reference]).boolean;
+      return;
+    }
+    return super.retainAll(elements);
+  }
+
+  @override
+  $E? lookup(Object? element) {
+    if (contains(element)) return element as $E;
+    return null;
+  }
+
+  @override
+  JSet<$E> toSet() {
+    return toJSet(E);
+  }
+}
+
+extension ToJavaSet<E extends JObject> on Iterable<E> {
+  JSet<E> toJSet(JObjType<E> type) {
+    final set = JSet.hash(type);
+    set.addAll(this);
+    return set;
+  }
+}
diff --git a/pkgs/jni/lib/src/util/util.dart b/pkgs/jni/lib/src/util/util.dart
new file mode 100644
index 0000000..d0a0dc5
--- /dev/null
+++ b/pkgs/jni/lib/src/util/util.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'jiterator.dart';
+export 'jlist.dart';
+export 'jmap.dart';
+export 'jset.dart';
diff --git a/pkgs/jni/linux/CMakeLists.txt b/pkgs/jni/linux/CMakeLists.txt
new file mode 100644
index 0000000..953cb0f
--- /dev/null
+++ b/pkgs/jni/linux/CMakeLists.txt
@@ -0,0 +1,22 @@
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+# Project-level configuration.
+set(PROJECT_NAME "jni")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+# Invoke the build for native code shared with the other target platforms.
+# This can be changed to accomodate different builds.
+add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
+
+# List of absolute paths to libraries that should be bundled with the plugin.
+# This list could contain prebuilt libraries, or libraries created by an
+# external build triggered from this build file.
+set(jni_bundled_libraries
+  # Defined in ../src/CMakeLists.txt.
+  # This can be changed to accomodate different builds.
+  $<TARGET_FILE:jni>
+  PARENT_SCOPE
+)
diff --git a/pkgs/jni/macos/Classes/jni.c b/pkgs/jni/macos/Classes/jni.c
new file mode 100644
index 0000000..28e68ae
--- /dev/null
+++ b/pkgs/jni/macos/Classes/jni.c
@@ -0,0 +1,3 @@
+// Relative import to be able to reuse the C sources.
+// See the comment in ../{projectName}}.podspec for more information.
+#include "../../src/jni.c"
diff --git a/pkgs/jni/macos/jni.podspec b/pkgs/jni/macos/jni.podspec
new file mode 100644
index 0000000..582861a
--- /dev/null
+++ b/pkgs/jni/macos/jni.podspec
@@ -0,0 +1,27 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint jni.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+  s.name             = 'jni'
+  s.version          = '0.0.1'
+  s.summary          = 'A new Flutter FFI plugin project.'
+  s.description      = <<-DESC
+A new Flutter FFI plugin project.
+                       DESC
+  s.homepage         = 'http://example.com'
+  s.license          = { :file => '../LICENSE' }
+  s.author           = { 'Your Company' => 'email@example.com' }
+
+  # This will ensure the source files in Classes/ are included in the native
+  # builds of apps using this FFI plugin. Podspec does not support relative
+  # paths, so Classes contains a forwarder C file that relatively imports
+  # `../src/*` so that the C sources can be shared among all target platforms.
+  s.source           = { :path => '.' }
+  s.source_files     = 'Classes/**/*'
+  s.dependency 'FlutterMacOS'
+
+  s.platform = :osx, '10.11'
+  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
+  s.swift_version = '5.0'
+end
diff --git a/pkgs/jni/pubspec.yaml b/pkgs/jni/pubspec.yaml
new file mode 100644
index 0000000..6efae3d
--- /dev/null
+++ b/pkgs/jni/pubspec.yaml
@@ -0,0 +1,47 @@
+# Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+name: jni
+description: A library to access JNI from Dart and Flutter that acts as a support library for package:jnigen.
+version: 0.8.0-wip
+repository: https://github.com/dart-lang/jnigen/tree/main/jni
+
+topics:
+  - interop
+  - ffi
+  - java
+  - kotlin
+  - jni
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+  flutter: '>=2.11.0'
+
+dependencies:
+  collection: ^1.0.0
+  plugin_platform_interface: ^2.0.2
+  ffi: ^2.0.1
+  path: ^1.8.0
+  package_config: ^2.1.0
+  args: ^2.3.1
+
+dev_dependencies:
+  ## Pin ffigen version because we are depending on internal APIs.
+  ffigen: 8.0.2
+  flutter_lints: ^2.0.0
+  test: ^1.21.1
+  logging: ^1.1.1
+
+# The following section is specific to Flutter packages.
+flutter:
+  plugin:
+    platforms:
+      linux:
+        ffiPlugin: true
+      windows:
+        ffiPlugin: true
+      android:
+        ffiPlugin: true
+        package: com.github.dart_lang.jni
+        pluginClass: JniPlugin
diff --git a/pkgs/jni/src/.clang-format b/pkgs/jni/src/.clang-format
new file mode 100644
index 0000000..a256c2f
--- /dev/null
+++ b/pkgs/jni/src/.clang-format
@@ -0,0 +1,15 @@
+# From dart SDK: https://github.com/dart-lang/sdk/blob/main/.clang-format
+
+# Defines the Chromium style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
+
+# clang-format doesn't seem to do a good job of this for longer comments.
+ReflowComments: 'false'
+
+# We have lots of these. Though we need to put them all in curly braces,
+# clang-format can't do that.
+AllowShortIfStatementsOnASingleLine: 'true'
+
+# Put escaped newlines into the rightmost column.
+AlignEscapedNewlinesLeft: false
diff --git a/pkgs/jni/src/.gitignore b/pkgs/jni/src/.gitignore
new file mode 100644
index 0000000..cf9756b
--- /dev/null
+++ b/pkgs/jni/src/.gitignore
@@ -0,0 +1,8 @@
+CMakeFiles/
+Makefile
+cmake_install.cmake
+libdartjni.so
+CMakeCache.txt
+.cache
+compile_commands.json
+!.clang-format
diff --git a/pkgs/jni/src/CMakeLists.txt b/pkgs/jni/src/CMakeLists.txt
new file mode 100644
index 0000000..2cdb3f7
--- /dev/null
+++ b/pkgs/jni/src/CMakeLists.txt
@@ -0,0 +1,35 @@
+# jni_native_build (Build with jni:setup. Do not delete this line.)
+
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project(jni_library VERSION 0.0.1 LANGUAGES C)
+
+add_library(jni SHARED
+  "dartjni.c"
+  "third_party/global_jni_env.c"
+	"include/dart_api_dl.c"
+)
+
+set_target_properties(jni PROPERTIES
+  PUBLIC_HEADER dartjni.h
+  OUTPUT_NAME "dartjni"
+)
+
+target_compile_definitions(jni PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries(jni log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries(jni ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jni/src/README.md b/pkgs/jni/src/README.md
new file mode 100644
index 0000000..6516f65
--- /dev/null
+++ b/pkgs/jni/src/README.md
@@ -0,0 +1,5 @@
+Native code for jni library.
+
+## LSP instructions
+
+see https://releases.llvm.org/8.0.0/tools/clang/tools/extra/docs/clangd/Installation.html#project-setup for creating compile_commands.json for use with LSP based editor plugins (in VS Code, VIM etc..).
\ No newline at end of file
diff --git a/pkgs/jni/src/dartjni.c b/pkgs/jni/src/dartjni.c
new file mode 100644
index 0000000..7c5951b
--- /dev/null
+++ b/pkgs/jni/src/dartjni.c
@@ -0,0 +1,749 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include <jni.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include "dartjni.h"
+
+#include "include/dart_api_dl.h"
+
+void initAllLocks(JniLocks* locks) {
+  init_lock(&locks->classLoadingLock);
+  init_lock(&locks->fieldLoadingLock);
+  init_lock(&locks->methodLoadingLock);
+}
+
+/// Stores class and method references for obtaining exception details
+typedef struct JniExceptionMethods {
+  jclass objectClass, exceptionClass, printStreamClass;
+  jclass byteArrayOutputStreamClass;
+  jmethodID toStringMethod, printStackTraceMethod;
+  jmethodID byteArrayOutputStreamCtor, printStreamCtor;
+} JniExceptionMethods;
+
+// Context and shared global state. Initialized once or if thread-local, initialized once in a thread.
+JniContext jni_context = {
+    .jvm = NULL,
+    .classLoader = NULL,
+    .loadClassMethod = NULL,
+    .appContext = NULL,
+    .currentActivity = NULL,
+};
+
+JniContext* jni = &jni_context;
+
+thread_local JNIEnv* jniEnv = NULL;
+
+JniExceptionMethods exceptionMethods;
+
+void initExceptionHandling(JniExceptionMethods* methods) {
+  methods->objectClass = FindClass("java/lang/Object");
+  methods->exceptionClass = FindClass("java/lang/Exception");
+  methods->printStreamClass = FindClass("java/io/PrintStream");
+  methods->byteArrayOutputStreamClass =
+      FindClass("java/io/ByteArrayOutputStream");
+  load_method(methods->objectClass, &methods->toStringMethod, "toString",
+              "()Ljava/lang/String;");
+  load_method(methods->exceptionClass, &methods->printStackTraceMethod,
+              "printStackTrace", "(Ljava/io/PrintStream;)V");
+  load_method(methods->byteArrayOutputStreamClass,
+              &methods->byteArrayOutputStreamCtor, "<init>", "()V");
+  load_method(methods->printStreamClass, &methods->printStreamCtor, "<init>",
+              "(Ljava/io/OutputStream;)V");
+}
+
+/// Get JVM associated with current process.
+/// Returns NULL if no JVM is running.
+FFI_PLUGIN_EXPORT
+JavaVM* GetJavaVM() {
+  return jni_context.jvm;
+}
+
+FFI_PLUGIN_EXPORT
+jclass FindClass(const char* name) {
+  jclass cls = NULL;
+  attach_thread();
+  load_class_global_ref(&cls, name);
+  return cls;
+};
+
+// Android specifics
+
+FFI_PLUGIN_EXPORT
+jobject GetClassLoader() {
+  attach_thread();
+  return (*jniEnv)->NewGlobalRef(jniEnv, jni_context.classLoader);
+}
+
+FFI_PLUGIN_EXPORT
+jobject GetApplicationContext() {
+  attach_thread();
+  return (*jniEnv)->NewGlobalRef(jniEnv, jni_context.appContext);
+}
+
+FFI_PLUGIN_EXPORT
+jobject GetCurrentActivity() {
+  attach_thread();
+  return (*jniEnv)->NewGlobalRef(jniEnv, jni_context.currentActivity);
+}
+
+// JNI Initialization
+
+#ifdef __ANDROID__
+JNIEXPORT void JNICALL
+Java_com_github_dart_1lang_jni_JniPlugin_initializeJni(JNIEnv* env,
+                                                       jobject obj,
+                                                       jobject appContext,
+                                                       jobject classLoader) {
+  jniEnv = env;
+  (*env)->GetJavaVM(env, &jni_context.jvm);
+  jni_context.classLoader = (*env)->NewGlobalRef(env, classLoader);
+  jni_context.appContext = (*env)->NewGlobalRef(env, appContext);
+  jclass classLoaderClass = (*env)->GetObjectClass(env, classLoader);
+  jni_context.loadClassMethod =
+      (*env)->GetMethodID(env, classLoaderClass, "loadClass",
+                          "(Ljava/lang/String;)Ljava/lang/Class;");
+  initAllLocks(&jni_context.locks);
+  initExceptionHandling(&exceptionMethods);
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_dart_1lang_jni_JniPlugin_setJniActivity(JNIEnv* env,
+                                                        jobject obj,
+                                                        jobject activity,
+                                                        jobject context) {
+  jniEnv = env;
+  if (jni_context.currentActivity != NULL) {
+    (*env)->DeleteGlobalRef(env, jni_context.currentActivity);
+  }
+  jni_context.currentActivity = (*env)->NewGlobalRef(env, activity);
+  if (jni_context.appContext != NULL) {
+    (*env)->DeleteGlobalRef(env, jni_context.appContext);
+  }
+  jni_context.appContext = (*env)->NewGlobalRef(env, context);
+}
+
+// Sometimes you may get linker error trying to link JNI_CreateJavaVM APIs
+// on Android NDK. So IFDEF is required.
+#else
+#ifdef _WIN32
+// Pre-initialization of critical section on windows - this is required because
+// there's no coordination between multiple isolates calling Spawn.
+//
+// Taken from https://stackoverflow.com/a/12858955
+CRITICAL_SECTION spawnLock = {0};
+BOOL WINAPI DllMain(HINSTANCE hinstDLL,   // handle to DLL module
+                    DWORD fdwReason,      // reason for calling function
+                    LPVOID lpReserved) {  // reserved
+  switch (fdwReason) {
+    case DLL_PROCESS_ATTACH:
+      // Initialize once for each new process.
+      // Return FALSE to fail DLL load.
+      InitializeCriticalSection(&spawnLock);
+      break;
+    case DLL_PROCESS_DETACH:
+      // Perform any necessary cleanup.
+      DeleteCriticalSection(&spawnLock);
+      break;
+  }
+  return TRUE;  // Successful DLL_PROCESS_ATTACH.
+}
+#else
+pthread_mutex_t spawnLock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+FFI_PLUGIN_EXPORT
+int SpawnJvm(JavaVMInitArgs* initArgs) {
+  if (jni_context.jvm != NULL) {
+    return DART_JNI_SINGLETON_EXISTS;
+  }
+
+  acquire_lock(&spawnLock);
+  // Init may have happened in the meanwhile.
+  if (jni_context.jvm != NULL) {
+    release_lock(&spawnLock);
+    return DART_JNI_SINGLETON_EXISTS;
+  }
+  JavaVMOption jvmopt[1];
+  char class_path[] = "-Djava.class.path=.";
+  jvmopt[0].optionString = class_path;
+  JavaVMInitArgs vmArgs;
+  if (!initArgs) {
+    vmArgs.version = JNI_VERSION_1_6;
+    vmArgs.nOptions = 1;
+    vmArgs.options = jvmopt;
+    vmArgs.ignoreUnrecognized = JNI_TRUE;
+    initArgs = &vmArgs;
+  }
+  const long flag =
+      JNI_CreateJavaVM(&jni_context.jvm, __ENVP_CAST & jniEnv, initArgs);
+  if (flag != JNI_OK) {
+    return flag;
+  }
+  initAllLocks(&jni_context.locks);
+  initExceptionHandling(&exceptionMethods);
+  release_lock(&spawnLock);
+
+  return JNI_OK;
+}
+#endif
+
+// accessors - a bunch of functions which are directly called by jnigen generated bindings
+// and also package:jni reflective method access.
+
+JniClassLookupResult getClass(char* internalName) {
+  JniClassLookupResult result = {NULL, NULL};
+  result.value = FindClass(internalName);
+  result.exception = check_exception();
+  return result;
+}
+
+typedef void* (*MemberGetter)(JNIEnv* env, jclass clazz, char* name, char* sig);
+
+static inline JniPointerResult _getId(MemberGetter getter,
+                                      jclass cls,
+                                      char* name,
+                                      char* sig) {
+  JniPointerResult result = {NULL, NULL};
+  attach_thread();
+  result.value = getter(jniEnv, cls, name, sig);
+  result.exception = check_exception();
+  return result;
+}
+
+JniPointerResult getMethodID(jclass cls, char* name, char* sig) {
+  return _getId((MemberGetter)(*jniEnv)->GetMethodID, cls, name, sig);
+}
+
+JniPointerResult getStaticMethodID(jclass cls, char* name, char* sig) {
+  return _getId((MemberGetter)(*jniEnv)->GetStaticMethodID, cls, name, sig);
+}
+
+JniPointerResult getFieldID(jclass cls, char* name, char* sig) {
+  return _getId((MemberGetter)(*jniEnv)->GetFieldID, cls, name, sig);
+}
+
+JniPointerResult getStaticFieldID(jclass cls, char* name, char* sig) {
+  return _getId((MemberGetter)(*jniEnv)->GetStaticFieldID, cls, name, sig);
+}
+
+JniResult callMethod(jobject obj,
+                     jmethodID fieldID,
+                     int callType,
+                     jvalue* args) {
+  attach_thread();
+  jvalue result = {.j = 0};
+  switch (callType) {
+    case booleanType:
+      result.z = (*jniEnv)->CallBooleanMethodA(jniEnv, obj, fieldID, args);
+      break;
+    case byteType:
+      result.b = (*jniEnv)->CallByteMethodA(jniEnv, obj, fieldID, args);
+      break;
+    case shortType:
+      result.s = (*jniEnv)->CallShortMethodA(jniEnv, obj, fieldID, args);
+      break;
+    case charType:
+      result.c = (*jniEnv)->CallCharMethodA(jniEnv, obj, fieldID, args);
+      break;
+    case intType:
+      result.i = (*jniEnv)->CallIntMethodA(jniEnv, obj, fieldID, args);
+      break;
+    case longType:
+      result.j = (*jniEnv)->CallLongMethodA(jniEnv, obj, fieldID, args);
+      break;
+    case floatType:
+      result.f = (*jniEnv)->CallFloatMethodA(jniEnv, obj, fieldID, args);
+      break;
+    case doubleType:
+      result.d = (*jniEnv)->CallDoubleMethodA(jniEnv, obj, fieldID, args);
+      break;
+    case objectType:
+      result.l = (*jniEnv)->CallObjectMethodA(jniEnv, obj, fieldID, args);
+      break;
+    case voidType:
+      (*jniEnv)->CallVoidMethodA(jniEnv, obj, fieldID, args);
+      break;
+  }
+  jthrowable exception = check_exception();
+  if (callType == objectType && exception == NULL) {
+    result.l = to_global_ref(result.l);
+  }
+  JniResult jniResult = {.value = result, .exception = exception};
+  return jniResult;
+}
+
+// TODO(#60): Any way to reduce this boilerplate?
+JniResult callStaticMethod(jclass cls,
+                           jmethodID methodID,
+                           int callType,
+                           jvalue* args) {
+  attach_thread();
+  jvalue result = {.j = 0};
+  switch (callType) {
+    case booleanType:
+      result.z =
+          (*jniEnv)->CallStaticBooleanMethodA(jniEnv, cls, methodID, args);
+      break;
+    case byteType:
+      result.b = (*jniEnv)->CallStaticByteMethodA(jniEnv, cls, methodID, args);
+      break;
+    case shortType:
+      result.s = (*jniEnv)->CallStaticShortMethodA(jniEnv, cls, methodID, args);
+      break;
+    case charType:
+      result.c = (*jniEnv)->CallStaticCharMethodA(jniEnv, cls, methodID, args);
+      break;
+    case intType:
+      result.i = (*jniEnv)->CallStaticIntMethodA(jniEnv, cls, methodID, args);
+      break;
+    case longType:
+      result.j = (*jniEnv)->CallStaticLongMethodA(jniEnv, cls, methodID, args);
+      break;
+    case floatType:
+      result.f = (*jniEnv)->CallStaticFloatMethodA(jniEnv, cls, methodID, args);
+      break;
+    case doubleType:
+      result.d =
+          (*jniEnv)->CallStaticDoubleMethodA(jniEnv, cls, methodID, args);
+      break;
+    case objectType:
+      result.l =
+          (*jniEnv)->CallStaticObjectMethodA(jniEnv, cls, methodID, args);
+      break;
+    case voidType:
+      (*jniEnv)->CallStaticVoidMethodA(jniEnv, cls, methodID, args);
+      break;
+  }
+  jthrowable exception = check_exception();
+  if (callType == objectType && exception == NULL) {
+    result.l = to_global_ref(result.l);
+  }
+  JniResult jniResult = {.value = result, .exception = exception};
+  return jniResult;
+}
+
+JniResult getField(jobject obj, jfieldID fieldID, int callType) {
+  attach_thread();
+  jvalue result = {.j = 0};
+  switch (callType) {
+    case booleanType:
+      result.z = (*jniEnv)->GetBooleanField(jniEnv, obj, fieldID);
+      break;
+    case byteType:
+      result.b = (*jniEnv)->GetByteField(jniEnv, obj, fieldID);
+      break;
+    case shortType:
+      result.s = (*jniEnv)->GetShortField(jniEnv, obj, fieldID);
+      break;
+    case charType:
+      result.c = (*jniEnv)->GetCharField(jniEnv, obj, fieldID);
+      break;
+    case intType:
+      result.i = (*jniEnv)->GetIntField(jniEnv, obj, fieldID);
+      break;
+    case longType:
+      result.j = (*jniEnv)->GetLongField(jniEnv, obj, fieldID);
+      break;
+    case floatType:
+      result.f = (*jniEnv)->GetFloatField(jniEnv, obj, fieldID);
+      break;
+    case doubleType:
+      result.d = (*jniEnv)->GetDoubleField(jniEnv, obj, fieldID);
+      break;
+    case objectType:
+      result.l = (*jniEnv)->GetObjectField(jniEnv, obj, fieldID);
+      break;
+    case voidType:
+      // This error should have been handled in Dart.
+      break;
+  }
+  jthrowable exception = check_exception();
+  if (callType == objectType && exception == NULL) {
+    result.l = to_global_ref(result.l);
+  }
+  JniResult jniResult = {.value = result, .exception = exception};
+  return jniResult;
+}
+
+FFI_PLUGIN_EXPORT
+JniResult getStaticField(jclass cls, jfieldID fieldID, int callType) {
+  attach_thread();
+  jvalue result = {.j = 0};
+  switch (callType) {
+    case booleanType:
+      result.z = (*jniEnv)->GetStaticBooleanField(jniEnv, cls, fieldID);
+      break;
+    case byteType:
+      result.b = (*jniEnv)->GetStaticByteField(jniEnv, cls, fieldID);
+      break;
+    case shortType:
+      result.s = (*jniEnv)->GetStaticShortField(jniEnv, cls, fieldID);
+      break;
+    case charType:
+      result.c = (*jniEnv)->GetStaticCharField(jniEnv, cls, fieldID);
+      break;
+    case intType:
+      result.i = (*jniEnv)->GetStaticIntField(jniEnv, cls, fieldID);
+      break;
+    case longType:
+      result.j = (*jniEnv)->GetStaticLongField(jniEnv, cls, fieldID);
+      break;
+    case floatType:
+      result.f = (*jniEnv)->GetStaticFloatField(jniEnv, cls, fieldID);
+      break;
+    case doubleType:
+      result.d = (*jniEnv)->GetStaticDoubleField(jniEnv, cls, fieldID);
+      break;
+    case objectType:
+      result.l = (*jniEnv)->GetStaticObjectField(jniEnv, cls, fieldID);
+      break;
+    case voidType:
+      // This error should have been handled in dart.
+      // is there a way to mark this as unreachable?
+      // or throw exception in Dart using Dart's C API.
+      break;
+  }
+  jthrowable exception = check_exception();
+  if (callType == objectType && exception == NULL) {
+    result.l = to_global_ref(result.l);
+  }
+  JniResult jniResult = {.value = result, .exception = exception};
+  return jniResult;
+}
+
+JniResult newObject(jclass cls, jmethodID ctor, jvalue* args) {
+  attach_thread();
+  jobject result = (*jniEnv)->NewObjectA(jniEnv, cls, ctor, args);
+  return to_global_ref_result(result);
+}
+
+JniResult newPrimitiveArray(jsize length, int type) {
+  attach_thread();
+  jarray array;
+  switch (type) {
+    case booleanType:
+      array = (*jniEnv)->NewBooleanArray(jniEnv, length);
+      break;
+    case byteType:
+      array = (*jniEnv)->NewByteArray(jniEnv, length);
+      break;
+    case shortType:
+      array = (*jniEnv)->NewShortArray(jniEnv, length);
+      break;
+    case charType:
+      array = (*jniEnv)->NewCharArray(jniEnv, length);
+      break;
+    case intType:
+      array = (*jniEnv)->NewIntArray(jniEnv, length);
+      break;
+    case longType:
+      array = (*jniEnv)->NewLongArray(jniEnv, length);
+      break;
+    case floatType:
+      array = (*jniEnv)->NewFloatArray(jniEnv, length);
+      break;
+    case doubleType:
+      array = (*jniEnv)->NewDoubleArray(jniEnv, length);
+      break;
+    case objectType:
+    case voidType:
+      // This error should have been handled in dart.
+      // is there a way to mark this as unreachable?
+      // or throw exception in Dart using Dart's C API.
+      break;
+  }
+  return to_global_ref_result(array);
+}
+
+JniResult newObjectArray(jsize length,
+                         jclass elementClass,
+                         jobject initialElement) {
+  attach_thread();
+  jarray array =
+      (*jniEnv)->NewObjectArray(jniEnv, length, elementClass, initialElement);
+  return to_global_ref_result(array);
+}
+
+JniResult getArrayElement(jarray array, int index, int type) {
+  JniResult result = {{.l = NULL}, NULL};
+  attach_thread();
+  jvalue value;
+  switch (type) {
+    case booleanType:
+      (*jniEnv)->GetBooleanArrayRegion(jniEnv, array, index, 1, &value.z);
+      break;
+    case byteType:
+      (*jniEnv)->GetByteArrayRegion(jniEnv, array, index, 1, &value.b);
+      break;
+    case shortType:
+      (*jniEnv)->GetShortArrayRegion(jniEnv, array, index, 1, &value.s);
+      break;
+    case charType:
+      (*jniEnv)->GetCharArrayRegion(jniEnv, array, index, 1, &value.c);
+      break;
+    case intType:
+      (*jniEnv)->GetIntArrayRegion(jniEnv, array, index, 1, &value.i);
+      break;
+    case longType:
+      (*jniEnv)->GetLongArrayRegion(jniEnv, array, index, 1, &value.j);
+      break;
+    case floatType:
+      (*jniEnv)->GetFloatArrayRegion(jniEnv, array, index, 1, &value.f);
+      break;
+    case doubleType:
+      (*jniEnv)->GetDoubleArrayRegion(jniEnv, array, index, 1, &value.d);
+      break;
+    case objectType:
+      value.l = (*jniEnv)->GetObjectArrayElement(jniEnv, array, index);
+    case voidType:
+      // This error should have been handled in dart.
+      // is there a way to mark this as unreachable?
+      // or throw exception in Dart using Dart's C API.
+      break;
+  }
+  jthrowable exception = check_exception();
+  if (type == objectType && exception == NULL) {
+    value.l = to_global_ref(value.l);
+  }
+  JniResult jniResult = {.value = value, .exception = exception};
+  return jniResult;
+}
+
+JniExceptionDetails getExceptionDetails(jthrowable exception) {
+  JniExceptionDetails details;
+  details.message = (*jniEnv)->CallObjectMethod(
+      jniEnv, exception, exceptionMethods.toStringMethod);
+  jobject buffer =
+      (*jniEnv)->NewObject(jniEnv, exceptionMethods.byteArrayOutputStreamClass,
+                           exceptionMethods.byteArrayOutputStreamCtor);
+  jobject printStream =
+      (*jniEnv)->NewObject(jniEnv, exceptionMethods.printStreamClass,
+                           exceptionMethods.printStreamCtor, buffer);
+  (*jniEnv)->CallVoidMethod(
+      jniEnv, exception, exceptionMethods.printStackTraceMethod, printStream);
+  details.stacktrace = (*jniEnv)->CallObjectMethod(
+      jniEnv, buffer, exceptionMethods.toStringMethod);
+  details.message = to_global_ref(details.message);
+  details.stacktrace = to_global_ref(details.stacktrace);
+  return details;
+}
+
+JniAccessorsStruct accessors = {
+    .getClass = getClass,
+    .getFieldID = getFieldID,
+    .getStaticFieldID = getStaticFieldID,
+    .getMethodID = getMethodID,
+    .getStaticMethodID = getStaticMethodID,
+    .newObject = newObject,
+    .newPrimitiveArray = newPrimitiveArray,
+    .newObjectArray = newObjectArray,
+    .getArrayElement = getArrayElement,
+    .callMethod = callMethod,
+    .callStaticMethod = callStaticMethod,
+    .getField = getField,
+    .getStaticField = getStaticField,
+    .getExceptionDetails = getExceptionDetails,
+};
+
+FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors() {
+  return &accessors;
+}
+
+// These will not be required after migrating to Dart-only bindings.
+FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr() {
+  return jni;
+}
+
+FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv() {
+  if (jni_context.jvm == NULL) {
+    return NULL;
+  }
+  attach_thread();
+  return jniEnv;
+}
+
+FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data) {
+  return Dart_InitializeApiDL(data);
+}
+
+// com.github.dart_lang.jni.DartException
+jclass _c_DartException = NULL;
+
+jmethodID _m_DartException__ctor = NULL;
+FFI_PLUGIN_EXPORT JniResult DartException__ctor(jstring message) {
+  attach_thread();
+  load_class_global_ref(&_c_DartException,
+                        "com/github/dart_lang/jni/PortProxy$DartException");
+  if (_c_DartException == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_DartException, &_m_DartException__ctor, "<init>",
+              "(Ljava/lang/String;)V");
+  if (_m_DartException__ctor == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_DartException,
+                                         _m_DartException__ctor, message);
+  jthrowable exception = check_exception();
+  if (exception == NULL) {
+    _result = to_global_ref(_result);
+  }
+  return (JniResult){.value = {.l = _result}, .exception = check_exception()};
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_dart_1lang_jni_PortContinuation__1resumeWith(JNIEnv* env,
+                                                             jclass clazz,
+                                                             jlong port,
+                                                             jobject result) {
+  attach_thread();
+  Dart_CObject c_post;
+  c_post.type = Dart_CObject_kInt64;
+  c_post.value.as_int64 = (jlong)((*env)->NewGlobalRef(env, result));
+  Dart_PostCObject_DL(port, &c_post);
+}
+
+// com.github.dart_lang.jni.PortContinuation
+jclass _c_PortContinuation = NULL;
+
+jmethodID _m_PortContinuation__ctor = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PortContinuation__ctor(int64_t j) {
+  attach_thread();
+  load_class_global_ref(&_c_PortContinuation,
+                        "com/github/dart_lang/jni/PortContinuation");
+  if (_c_PortContinuation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PortContinuation, &_m_PortContinuation__ctor, "<init>",
+              "(J)V");
+  if (_m_PortContinuation__ctor == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_PortContinuation,
+                                         _m_PortContinuation__ctor, j);
+  jthrowable exception = check_exception();
+  if (exception == NULL) {
+    _result = to_global_ref(_result);
+  }
+  return (JniResult){.value = {.l = _result}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jni.PortProxy
+jclass _c_PortProxy = NULL;
+
+jmethodID _m_PortProxy__newInstance = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+                                 int64_t port,
+                                 int64_t functionPtr) {
+  attach_thread();
+  load_class_global_ref(&_c_PortProxy, "com/github/dart_lang/jni/PortProxy");
+  if (_c_PortProxy == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_PortProxy, &_m_PortProxy__newInstance, "newInstance",
+                     "(Ljava/lang/String;JJJ)Ljava/lang/Object;");
+  if (_m_PortProxy__newInstance == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PortProxy, _m_PortProxy__newInstance, binaryName, port,
+      (jlong)Dart_CurrentIsolate_DL(), functionPtr);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+void resultFor(CallbackResult* result, jobject object) {
+  acquire_lock(&result->lock);
+  result->ready = 1;
+  result->object = object;
+  signal_cond(&result->cond);
+  release_lock(&result->lock);
+}
+
+jclass _c_Object = NULL;
+jclass _c_Long = NULL;
+
+jmethodID _m_Long_init = NULL;
+
+JNIEXPORT jobjectArray JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1invoke(JNIEnv* env,
+                                                  jclass clazz,
+                                                  jlong port,
+                                                  jlong isolateId,
+                                                  jlong functionPtr,
+                                                  jobject proxy,
+                                                  jstring methodDescriptor,
+                                                  jobjectArray args) {
+  CallbackResult* result = (CallbackResult*)malloc(sizeof(CallbackResult));
+  if (isolateId != (jlong)Dart_CurrentIsolate_DL()) {
+    init_lock(&result->lock);
+    init_cond(&result->cond);
+    acquire_lock(&result->lock);
+    result->ready = 0;
+    result->object = NULL;
+
+    Dart_CObject c_result;
+    c_result.type = Dart_CObject_kInt64;
+    c_result.value.as_int64 = (jlong)result;
+
+    Dart_CObject c_method;
+    c_method.type = Dart_CObject_kInt64;
+    c_method.value.as_int64 =
+        (jlong)((*env)->NewGlobalRef(env, methodDescriptor));
+
+    Dart_CObject c_args;
+    c_args.type = Dart_CObject_kInt64;
+    c_args.value.as_int64 = (jlong)((*env)->NewGlobalRef(env, args));
+
+    Dart_CObject* c_post_arr[] = {&c_result, &c_method, &c_args};
+    Dart_CObject c_post;
+    c_post.type = Dart_CObject_kArray;
+    c_post.value.as_array.values = c_post_arr;
+    c_post.value.as_array.length = sizeof(c_post_arr) / sizeof(c_post_arr[0]);
+
+    Dart_PostCObject_DL(port, &c_post);
+
+    while (!result->ready) {
+      wait_for(&result->cond, &result->lock);
+    }
+
+    release_lock(&result->lock);
+    destroy_lock(&result->lock);
+    destroy_cond(&result->cond);
+  } else {
+    result->object = ((jobject(*)(uint64_t, jobject, jobject))functionPtr)(
+        port, (*env)->NewGlobalRef(env, methodDescriptor),
+        (*env)->NewGlobalRef(env, args));
+  }
+  // Returning an array of length 2.
+  // [0]: The result pointer, used for cleaning up the global reference, and
+  //      freeing the memory since we passed the ownership to Java.
+  // [1]: The returned object.
+  attach_thread();
+  load_class_global_ref(&_c_Object, "java/lang/Object");
+  load_class_global_ref(&_c_Long, "java/lang/Long");
+  load_method(_c_Long, &_m_Long_init, "<init>", "(J)V");
+  jobject first = (*env)->NewObject(env, _c_Long, _m_Long_init, (jlong)result);
+  jobject second = result->object;
+  jobjectArray arr = (*env)->NewObjectArray(env, 2, _c_Object, NULL);
+  (*env)->SetObjectArrayElement(env, arr, 0, first);
+  (*env)->SetObjectArrayElement(env, arr, 1, second);
+  return arr;
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_dart_1lang_jni_PortProxy__1cleanUp(JNIEnv* env,
+                                                   jclass clazz,
+                                                   jlong resultPtr) {
+  CallbackResult* result = (CallbackResult*)resultPtr;
+  (*env)->DeleteGlobalRef(env, result->object);
+  free(result);
+}
+
+JNIEXPORT void JNICALL
+Java_com_github_dart_1lang_jni_PortCleaner_clean(JNIEnv* env,
+                                                 jclass clazz,
+                                                 jlong port) {
+  Dart_CObject close_signal;
+  close_signal.type = Dart_CObject_kNull;
+  Dart_PostCObject_DL(port, &close_signal);
+}
diff --git a/pkgs/jni/src/dartjni.h b/pkgs/jni/src/dartjni.h
new file mode 100644
index 0000000..8f1dc74
--- /dev/null
+++ b/pkgs/jni/src/dartjni.h
@@ -0,0 +1,424 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h.
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
+#if _WIN32
+#define FFI_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FFI_PLUGIN_EXPORT
+#endif
+
+#if defined _WIN32
+#define thread_local __declspec(thread)
+#else
+#define thread_local __thread
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#ifdef __ANDROID__
+#define __ENVP_CAST (JNIEnv**)
+#else
+#define __ENVP_CAST (void**)
+#endif
+
+/// Locking functions for windows and pthread.
+
+#if defined _WIN32
+#include <windows.h>
+
+typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  InitializeCriticalSection(lock);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  EnterCriticalSection(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  LeaveCriticalSection(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  DeleteCriticalSection(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  // Not available.
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ ||         \
+    defined __GNUC__
+#include <pthread.h>
+
+typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  pthread_mutex_init(lock, NULL);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  pthread_mutex_lock(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  pthread_mutex_unlock(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  pthread_mutex_destroy(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  pthread_cond_destroy(cond);
+}
+
+#else
+
+#error "No locking/condition variable support; Possibly unsupported platform"
+
+#endif
+
+typedef struct CallbackResult {
+  MutexLock lock;
+  ConditionVariable cond;
+  int ready;
+  jobject object;
+} CallbackResult;
+
+typedef struct JniLocks {
+  MutexLock classLoadingLock;
+  MutexLock methodLoadingLock;
+  MutexLock fieldLoadingLock;
+} JniLocks;
+
+/// Represents the error when dart-jni layer has already spawned singleton VM.
+#define DART_JNI_SINGLETON_EXISTS (-99);
+
+/// Stores the global state of the JNI.
+typedef struct JniContext {
+  JavaVM* jvm;
+  jobject classLoader;
+  jmethodID loadClassMethod;
+  jobject currentActivity;
+  jobject appContext;
+  JniLocks locks;
+} JniContext;
+
+// jniEnv for this thread, used by inline functions in this header,
+// therefore declared as extern.
+extern thread_local JNIEnv* jniEnv;
+
+extern JniContext* jni;
+
+/// Types used by JNI API to distinguish between primitive types.
+enum JniType {
+  booleanType = 0,
+  byteType = 1,
+  shortType = 2,
+  charType = 3,
+  intType = 4,
+  longType = 5,
+  floatType = 6,
+  doubleType = 7,
+  objectType = 8,
+  voidType = 9,
+};
+
+/// Result type for use by JNI.
+///
+/// If [exception] is null, it means the result is valid.
+/// It's assumed that the caller knows the expected type in [result].
+typedef struct JniResult {
+  jvalue value;
+  jthrowable exception;
+} JniResult;
+
+/// Similar to [JniResult] but for class lookups.
+typedef struct JniClassLookupResult {
+  jclass value;
+  jthrowable exception;
+} JniClassLookupResult;
+
+/// Similar to [JniResult] but for method/field ID lookups.
+typedef struct JniPointerResult {
+  const void* value;
+  jthrowable exception;
+} JniPointerResult;
+
+/// JniExceptionDetails holds 2 jstring objects, one is the result of
+/// calling `toString` on exception object, other is stack trace;
+typedef struct JniExceptionDetails {
+  jstring message;
+  jstring stacktrace;
+} JniExceptionDetails;
+
+/// This struct contains functions which wrap method call / field access conveniently along with
+/// exception checking.
+///
+/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us
+/// to check for and clear the exception before returning to dart code, which requires these functions
+/// to return result types.
+typedef struct JniAccessorsStruct {
+  JniClassLookupResult (*getClass)(char* internalName);
+  JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature);
+  JniPointerResult (*getStaticFieldID)(jclass cls,
+                                       char* fieldName,
+                                       char* signature);
+  JniPointerResult (*getMethodID)(jclass cls,
+                                  char* methodName,
+                                  char* signature);
+  JniPointerResult (*getStaticMethodID)(jclass cls,
+                                        char* methodName,
+                                        char* signature);
+  JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args);
+  JniResult (*newPrimitiveArray)(jsize length, int type);
+  JniResult (*newObjectArray)(jsize length,
+                              jclass elementClass,
+                              jobject initialElement);
+  JniResult (*getArrayElement)(jarray array, int index, int type);
+  JniResult (*callMethod)(jobject obj,
+                          jmethodID methodID,
+                          int callType,
+                          jvalue* args);
+  JniResult (*callStaticMethod)(jclass cls,
+                                jmethodID methodID,
+                                int callType,
+                                jvalue* args);
+  JniResult (*getField)(jobject obj, jfieldID fieldID, int callType);
+  JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType);
+  JniExceptionDetails (*getExceptionDetails)(jthrowable exception);
+} JniAccessorsStruct;
+
+FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors();
+
+FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void);
+
+FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void);
+
+/// Spawn a JVM with given arguments.
+///
+/// Returns JNI_OK on success, and one of the documented JNI error codes on
+/// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple
+/// JVMs is made, even if the underlying API potentially supports multiple VMs.
+FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args);
+
+/// Load class through platform-specific mechanism.
+///
+/// Currently uses application classloader on android,
+/// and JNIEnv->FindClass on other platforms.
+FFI_PLUGIN_EXPORT jclass FindClass(const char* name);
+
+/// Returns Application classLoader (on Android),
+/// which can be used to load application and platform classes.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetClassLoader(void);
+
+/// Returns application context on Android.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetApplicationContext(void);
+
+/// Returns current activity of the app on Android.
+FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void);
+
+static inline void attach_thread() {
+  if (jniEnv == NULL) {
+    (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL);
+  }
+}
+
+/// Load class into [cls] using platform specific mechanism
+static inline void load_class_platform(jclass* cls, const char* name) {
+#ifdef __ANDROID__
+  jstring className = (*jniEnv)->NewStringUTF(jniEnv, name);
+  *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader,
+                                     jni->loadClassMethod, className);
+  (*jniEnv)->DeleteLocalRef(jniEnv, className);
+#else
+  *cls = (*jniEnv)->FindClass(jniEnv, name);
+#endif
+}
+
+static inline void load_class_local_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(cls, name);
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_class_global_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    jclass tmp = NULL;
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(&tmp, name);
+      if (!(*jniEnv)->ExceptionCheck(jniEnv)) {
+        *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp);
+        (*jniEnv)->DeleteLocalRef(jniEnv, tmp);
+      }
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_method(jclass cls,
+                               jmethodID* res,
+                               const char* name,
+                               const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_static_method(jclass cls,
+                                      jmethodID* res,
+                                      const char* name,
+                                      const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_field(jclass cls,
+                              jfieldID* res,
+                              const char* name,
+                              const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline void load_static_field(jclass cls,
+                                     jfieldID* res,
+                                     const char* name,
+                                     const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline jobject to_global_ref(jobject ref) {
+  jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref);
+  (*jniEnv)->DeleteLocalRef(jniEnv, ref);
+  return g;
+}
+
+// These functions are useful for C+Dart bindings, and not required for pure dart bindings.
+
+FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr();
+
+/// For use by jni_gen's generated code
+/// don't use these.
+
+// these 2 fn ptr vars will be defined by generated code library
+extern JniContext* (*context_getter)(void);
+extern JNIEnv* (*env_getter)(void);
+
+// this function will be exported by generated code library
+// it will set above 2 variables.
+FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void),
+                                     JNIEnv* (*eg)(void));
+
+static inline void load_env() {
+  if (jniEnv == NULL) {
+    jni = context_getter();
+    jniEnv = env_getter();
+  }
+}
+
+static inline jthrowable check_exception() {
+  jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv);
+  if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv);
+  if (exception == NULL) return NULL;
+  return to_global_ref(exception);
+}
+
+static inline JniResult to_global_ref_result(jobject ref) {
+  JniResult result;
+  result.exception = check_exception();
+  if (result.exception == NULL) {
+    result.value.l = to_global_ref(ref);
+  }
+  return result;
+}
+
+FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+
+FFI_PLUGIN_EXPORT
+JniResult DartException__ctor(jstring message);
+
+FFI_PLUGIN_EXPORT
+JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+                                 int64_t port,
+                                 int64_t functionPtr);
+
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
diff --git a/pkgs/jni/src/include/BUILD.gn b/pkgs/jni/src/include/BUILD.gn
new file mode 100644
index 0000000..2b10262
--- /dev/null
+++ b/pkgs/jni/src/include/BUILD.gn
@@ -0,0 +1,23 @@
+# Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import("../../sdk_args.gni")
+
+# This rule copies header files to include/
+copy("copy_headers") {
+  visibility = [ "../../sdk:copy_headers" ]
+
+  sources = [
+    "dart_api.h",
+    "dart_api_dl.c",
+    "dart_api_dl.h",
+    "dart_native_api.h",
+    "dart_tools_api.h",
+    "dart_version.h",
+    "internal/dart_api_dl_impl.h",
+  ]
+
+  outputs =
+      [ "$root_out_dir/$dart_sdk_output/include/{{source_target_relative}}" ]
+}
diff --git a/pkgs/jni/src/include/analyze_snapshot_api.h b/pkgs/jni/src/include/analyze_snapshot_api.h
new file mode 100644
index 0000000..0e68d5c
--- /dev/null
+++ b/pkgs/jni/src/include/analyze_snapshot_api.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ */
+
+#ifndef RUNTIME_INCLUDE_ANALYZE_SNAPSHOT_API_H_
+#define RUNTIME_INCLUDE_ANALYZE_SNAPSHOT_API_H_
+
+#include <stdint.h>
+
+namespace dart {
+namespace snapshot_analyzer {
+typedef struct {
+  const uint8_t* vm_snapshot_data;
+  const uint8_t* vm_snapshot_instructions;
+  const uint8_t* vm_isolate_data;
+  const uint8_t* vm_isolate_instructions;
+} Dart_SnapshotAnalyzerInformation;
+
+void Dart_DumpSnapshotInformationAsJson(char** buffer,
+                                        intptr_t* buffer_length,
+                                        Dart_SnapshotAnalyzerInformation* info);
+
+void Dart_DumpSnapshotInformationPP(Dart_SnapshotAnalyzerInformation* info);
+
+}  // namespace snapshot_analyzer
+}  // namespace dart
+
+#endif  // RUNTIME_INCLUDE_ANALYZE_SNAPSHOT_API_H_
diff --git a/pkgs/jni/src/include/bin/dart_io_api.h b/pkgs/jni/src/include/bin/dart_io_api.h
new file mode 100644
index 0000000..cc64797
--- /dev/null
+++ b/pkgs/jni/src/include/bin/dart_io_api.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_INCLUDE_BIN_DART_IO_API_H_
+#define RUNTIME_INCLUDE_BIN_DART_IO_API_H_
+
+#include "dart_tools_api.h"
+
+namespace dart {
+namespace bin {
+
+// Bootstraps 'dart:io'.
+void BootstrapDartIo();
+
+// Cleans up 'dart:io'.
+void CleanupDartIo();
+
+// Lets dart:io know where the system temporary directory is located.
+// Currently only wired up on Android.
+void SetSystemTempDirectory(const char* system_temp);
+
+// Tells the system whether to capture Stdout events.
+void SetCaptureStdout(bool value);
+
+// Tells the system whether to capture Stderr events.
+void SetCaptureStderr(bool value);
+
+// Should Stdout events be captured?
+bool ShouldCaptureStdout();
+
+// Should Stderr events be captured?
+bool ShouldCaptureStderr();
+
+// Set the executable name used by Platform.executable.
+void SetExecutableName(const char* executable_name);
+
+// Set the arguments used by Platform.executableArguments.
+void SetExecutableArguments(int script_index, char** argv);
+
+// Set dart:io implementation specific fields of Dart_EmbedderInformation.
+void GetIOEmbedderInformation(Dart_EmbedderInformation* info);
+
+// Appropriate to assign to Dart_InitializeParams.file_open/read/write/close.
+void* OpenFile(const char* name, bool write);
+void ReadFile(uint8_t** data, intptr_t* file_len, void* stream);
+void WriteFile(const void* buffer, intptr_t num_bytes, void* stream);
+void CloseFile(void* stream);
+
+// Generates 'length' random bytes into 'buffer'. Returns true on success
+// and false on failure. This is appropriate to assign to
+// Dart_InitializeParams.entropy_source.
+bool GetEntropy(uint8_t* buffer, intptr_t length);
+
+// Performs a lookup of the I/O Dart_NativeFunction with a specified 'name' and
+// 'argument_count'. Returns NULL if no I/O native function with a matching
+// name and parameter count is found.
+Dart_NativeFunction LookupIONative(Dart_Handle name,
+                                   int argument_count,
+                                   bool* auto_setup_scope);
+
+// Returns the symbol for I/O native function 'nf'. Returns NULL if 'nf' is not
+// a valid I/O native function.
+const uint8_t* LookupIONativeSymbol(Dart_NativeFunction nf);
+
+}  // namespace bin
+}  // namespace dart
+
+#endif  // RUNTIME_INCLUDE_BIN_DART_IO_API_H_
diff --git a/pkgs/jni/src/include/dart_api.h b/pkgs/jni/src/include/dart_api.h
new file mode 100644
index 0000000..99dde6f
--- /dev/null
+++ b/pkgs/jni/src/include/dart_api.h
@@ -0,0 +1,4185 @@
+/*
+ * Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ */
+
+#ifndef RUNTIME_INCLUDE_DART_API_H_
+#define RUNTIME_INCLUDE_DART_API_H_
+
+/** \mainpage Dart Embedding API Reference
+ *
+ * This reference describes the Dart Embedding API, which is used to embed the
+ * Dart Virtual Machine within C/C++ applications.
+ *
+ * This reference is generated from the header include/dart_api.h.
+ */
+
+/* __STDC_FORMAT_MACROS has to be defined before including <inttypes.h> to
+ * enable platform independent printf format specifiers. */
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#if defined(__Fuchsia__)
+#include <zircon/types.h>
+#endif
+
+#ifdef __cplusplus
+#define DART_EXTERN_C extern "C"
+#else
+#define DART_EXTERN_C extern
+#endif
+
+#if defined(__CYGWIN__)
+#error Tool chain and platform not supported.
+#elif defined(_WIN32)
+#if defined(DART_SHARED_LIB)
+#define DART_EXPORT DART_EXTERN_C __declspec(dllexport)
+#else
+#define DART_EXPORT DART_EXTERN_C
+#endif
+#else
+#if __GNUC__ >= 4
+#if defined(DART_SHARED_LIB)
+#define DART_EXPORT                                                            \
+  DART_EXTERN_C __attribute__((visibility("default"))) __attribute((used))
+#else
+#define DART_EXPORT DART_EXTERN_C
+#endif
+#else
+#error Tool chain not supported.
+#endif
+#endif
+
+#if __GNUC__
+#define DART_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#elif _MSC_VER
+#define DART_WARN_UNUSED_RESULT _Check_return_
+#else
+#define DART_WARN_UNUSED_RESULT
+#endif
+
+/*
+ * =======
+ * Handles
+ * =======
+ */
+
+/**
+ * An isolate is the unit of concurrency in Dart. Each isolate has
+ * its own memory and thread of control. No state is shared between
+ * isolates. Instead, isolates communicate by message passing.
+ *
+ * Each thread keeps track of its current isolate, which is the
+ * isolate which is ready to execute on the current thread. The
+ * current isolate may be NULL, in which case no isolate is ready to
+ * execute. Most of the Dart apis require there to be a current
+ * isolate in order to function without error. The current isolate is
+ * set by any call to Dart_CreateIsolateGroup or Dart_EnterIsolate.
+ */
+typedef struct _Dart_Isolate* Dart_Isolate;
+typedef struct _Dart_IsolateGroup* Dart_IsolateGroup;
+
+/**
+ * An object reference managed by the Dart VM garbage collector.
+ *
+ * Because the garbage collector may move objects, it is unsafe to
+ * refer to objects directly. Instead, we refer to objects through
+ * handles, which are known to the garbage collector and updated
+ * automatically when the object is moved. Handles should be passed
+ * by value (except in cases like out-parameters) and should never be
+ * allocated on the heap.
+ *
+ * Most functions in the Dart Embedding API return a handle. When a
+ * function completes normally, this will be a valid handle to an
+ * object in the Dart VM heap. This handle may represent the result of
+ * the operation or it may be a special valid handle used merely to
+ * indicate successful completion. Note that a valid handle may in
+ * some cases refer to the null object.
+ *
+ * --- Error handles ---
+ *
+ * When a function encounters a problem that prevents it from
+ * completing normally, it returns an error handle (See Dart_IsError).
+ * An error handle has an associated error message that gives more
+ * details about the problem (See Dart_GetError).
+ *
+ * There are four kinds of error handles that can be produced,
+ * depending on what goes wrong:
+ *
+ * - Api error handles are produced when an api function is misused.
+ *   This happens when a Dart embedding api function is called with
+ *   invalid arguments or in an invalid context.
+ *
+ * - Unhandled exception error handles are produced when, during the
+ *   execution of Dart code, an exception is thrown but not caught.
+ *   Prototypically this would occur during a call to Dart_Invoke, but
+ *   it can occur in any function which triggers the execution of Dart
+ *   code (for example, Dart_ToString).
+ *
+ *   An unhandled exception error provides access to an exception and
+ *   stacktrace via the functions Dart_ErrorGetException and
+ *   Dart_ErrorGetStackTrace.
+ *
+ * - Compilation error handles are produced when, during the execution
+ *   of Dart code, a compile-time error occurs.  As above, this can
+ *   occur in any function which triggers the execution of Dart code.
+ *
+ * - Fatal error handles are produced when the system wants to shut
+ *   down the current isolate.
+ *
+ * --- Propagating errors ---
+ *
+ * When an error handle is returned from the top level invocation of
+ * Dart code in a program, the embedder must handle the error as they
+ * see fit.  Often, the embedder will print the error message produced
+ * by Dart_Error and exit the program.
+ *
+ * When an error is returned while in the body of a native function,
+ * it can be propagated up the call stack by calling
+ * Dart_PropagateError, Dart_SetReturnValue, or Dart_ThrowException.
+ * Errors should be propagated unless there is a specific reason not
+ * to.  If an error is not propagated then it is ignored.  For
+ * example, if an unhandled exception error is ignored, that
+ * effectively "catches" the unhandled exception.  Fatal errors must
+ * always be propagated.
+ *
+ * When an error is propagated, any current scopes created by
+ * Dart_EnterScope will be exited.
+ *
+ * Using Dart_SetReturnValue to propagate an exception is somewhat
+ * more convenient than using Dart_PropagateError, and should be
+ * preferred for reasons discussed below.
+ *
+ * Dart_PropagateError and Dart_ThrowException do not return.  Instead
+ * they transfer control non-locally using a setjmp-like mechanism.
+ * This can be inconvenient if you have resources that you need to
+ * clean up before propagating the error.
+ *
+ * When relying on Dart_PropagateError, we often return error handles
+ * rather than propagating them from helper functions.  Consider the
+ * following contrived example:
+ *
+ * 1    Dart_Handle isLongStringHelper(Dart_Handle arg) {
+ * 2      intptr_t* length = 0;
+ * 3      result = Dart_StringLength(arg, &length);
+ * 4      if (Dart_IsError(result)) {
+ * 5        return result;
+ * 6      }
+ * 7      return Dart_NewBoolean(length > 100);
+ * 8    }
+ * 9
+ * 10   void NativeFunction_isLongString(Dart_NativeArguments args) {
+ * 11     Dart_EnterScope();
+ * 12     AllocateMyResource();
+ * 13     Dart_Handle arg = Dart_GetNativeArgument(args, 0);
+ * 14     Dart_Handle result = isLongStringHelper(arg);
+ * 15     if (Dart_IsError(result)) {
+ * 16       FreeMyResource();
+ * 17       Dart_PropagateError(result);
+ * 18       abort();  // will not reach here
+ * 19     }
+ * 20     Dart_SetReturnValue(result);
+ * 21     FreeMyResource();
+ * 22     Dart_ExitScope();
+ * 23   }
+ *
+ * In this example, we have a native function which calls a helper
+ * function to do its work.  On line 5, the helper function could call
+ * Dart_PropagateError, but that would not give the native function a
+ * chance to call FreeMyResource(), causing a leak.  Instead, the
+ * helper function returns the error handle to the caller, giving the
+ * caller a chance to clean up before propagating the error handle.
+ *
+ * When an error is propagated by calling Dart_SetReturnValue, the
+ * native function will be allowed to complete normally and then the
+ * exception will be propagated only once the native call
+ * returns. This can be convenient, as it allows the C code to clean
+ * up normally.
+ *
+ * The example can be written more simply using Dart_SetReturnValue to
+ * propagate the error.
+ *
+ * 1    Dart_Handle isLongStringHelper(Dart_Handle arg) {
+ * 2      intptr_t* length = 0;
+ * 3      result = Dart_StringLength(arg, &length);
+ * 4      if (Dart_IsError(result)) {
+ * 5        return result
+ * 6      }
+ * 7      return Dart_NewBoolean(length > 100);
+ * 8    }
+ * 9
+ * 10   void NativeFunction_isLongString(Dart_NativeArguments args) {
+ * 11     Dart_EnterScope();
+ * 12     AllocateMyResource();
+ * 13     Dart_Handle arg = Dart_GetNativeArgument(args, 0);
+ * 14     Dart_SetReturnValue(isLongStringHelper(arg));
+ * 15     FreeMyResource();
+ * 16     Dart_ExitScope();
+ * 17   }
+ *
+ * In this example, the call to Dart_SetReturnValue on line 14 will
+ * either return the normal return value or the error (potentially
+ * generated on line 3).  The call to FreeMyResource on line 15 will
+ * execute in either case.
+ *
+ * --- Local and persistent handles ---
+ *
+ * Local handles are allocated within the current scope (see
+ * Dart_EnterScope) and go away when the current scope exits. Unless
+ * otherwise indicated, callers should assume that all functions in
+ * the Dart embedding api return local handles.
+ *
+ * Persistent handles are allocated within the current isolate. They
+ * can be used to store objects across scopes. Persistent handles have
+ * the lifetime of the current isolate unless they are explicitly
+ * deallocated (see Dart_DeletePersistentHandle).
+ * The type Dart_Handle represents a handle (both local and persistent).
+ * The type Dart_PersistentHandle is a Dart_Handle and it is used to
+ * document that a persistent handle is expected as a parameter to a call
+ * or the return value from a call is a persistent handle.
+ *
+ * FinalizableHandles are persistent handles which are auto deleted when
+ * the object is garbage collected. It is never safe to use these handles
+ * unless you know the object is still reachable.
+ *
+ * WeakPersistentHandles are persistent handles which are automatically set
+ * to point Dart_Null when the object is garbage collected. They are not auto
+ * deleted, so it is safe to use them after the object has become unreachable.
+ */
+typedef struct _Dart_Handle* Dart_Handle;
+typedef Dart_Handle Dart_PersistentHandle;
+typedef struct _Dart_WeakPersistentHandle* Dart_WeakPersistentHandle;
+typedef struct _Dart_FinalizableHandle* Dart_FinalizableHandle;
+// These structs are versioned by DART_API_DL_MAJOR_VERSION, bump the
+// version when changing this struct.
+
+typedef void (*Dart_HandleFinalizer)(void* isolate_callback_data, void* peer);
+
+/**
+ * Is this an error handle?
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT bool Dart_IsError(Dart_Handle handle);
+
+/**
+ * Is this an api error handle?
+ *
+ * Api error handles are produced when an api function is misused.
+ * This happens when a Dart embedding api function is called with
+ * invalid arguments or in an invalid context.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT bool Dart_IsApiError(Dart_Handle handle);
+
+/**
+ * Is this an unhandled exception error handle?
+ *
+ * Unhandled exception error handles are produced when, during the
+ * execution of Dart code, an exception is thrown but not caught.
+ * This can occur in any function which triggers the execution of Dart
+ * code.
+ *
+ * See Dart_ErrorGetException and Dart_ErrorGetStackTrace.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT bool Dart_IsUnhandledExceptionError(Dart_Handle handle);
+
+/**
+ * Is this a compilation error handle?
+ *
+ * Compilation error handles are produced when, during the execution
+ * of Dart code, a compile-time error occurs.  This can occur in any
+ * function which triggers the execution of Dart code.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT bool Dart_IsCompilationError(Dart_Handle handle);
+
+/**
+ * Is this a fatal error handle?
+ *
+ * Fatal error handles are produced when the system wants to shut down
+ * the current isolate.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT bool Dart_IsFatalError(Dart_Handle handle);
+
+/**
+ * Gets the error message from an error handle.
+ *
+ * Requires there to be a current isolate.
+ *
+ * \return A C string containing an error message if the handle is
+ *   error. An empty C string ("") if the handle is valid. This C
+ *   String is scope allocated and is only valid until the next call
+ *   to Dart_ExitScope.
+*/
+DART_EXPORT const char* Dart_GetError(Dart_Handle handle);
+
+/**
+ * Is this an error handle for an unhandled exception?
+ */
+DART_EXPORT bool Dart_ErrorHasException(Dart_Handle handle);
+
+/**
+ * Gets the exception Object from an unhandled exception error handle.
+ */
+DART_EXPORT Dart_Handle Dart_ErrorGetException(Dart_Handle handle);
+
+/**
+ * Gets the stack trace Object from an unhandled exception error handle.
+ */
+DART_EXPORT Dart_Handle Dart_ErrorGetStackTrace(Dart_Handle handle);
+
+/**
+ * Produces an api error handle with the provided error message.
+ *
+ * Requires there to be a current isolate.
+ *
+ * \param error the error message.
+ */
+DART_EXPORT Dart_Handle Dart_NewApiError(const char* error);
+DART_EXPORT Dart_Handle Dart_NewCompilationError(const char* error);
+
+/**
+ * Produces a new unhandled exception error handle.
+ *
+ * Requires there to be a current isolate.
+ *
+ * \param exception An instance of a Dart object to be thrown or
+ *        an ApiError or CompilationError handle.
+ *        When an ApiError or CompilationError handle is passed in
+ *        a string object of the error message is created and it becomes
+ *        the Dart object to be thrown.
+ */
+DART_EXPORT Dart_Handle Dart_NewUnhandledExceptionError(Dart_Handle exception);
+
+/**
+ * Propagates an error.
+ *
+ * If the provided handle is an unhandled exception error, this
+ * function will cause the unhandled exception to be rethrown.  This
+ * will proceed in the standard way, walking up Dart frames until an
+ * appropriate 'catch' block is found, executing 'finally' blocks,
+ * etc.
+ *
+ * If the error is not an unhandled exception error, we will unwind
+ * the stack to the next C frame.  Intervening Dart frames will be
+ * discarded; specifically, 'finally' blocks will not execute.  This
+ * is the standard way that compilation errors (and the like) are
+ * handled by the Dart runtime.
+ *
+ * In either case, when an error is propagated any current scopes
+ * created by Dart_EnterScope will be exited.
+ *
+ * See the additional discussion under "Propagating Errors" at the
+ * beginning of this file.
+ *
+ * \param handle An error handle (See Dart_IsError)
+ *
+ * On success, this function does not return.  On failure, the
+ * process is terminated.
+ */
+DART_EXPORT void Dart_PropagateError(Dart_Handle handle);
+
+/**
+ * Converts an object to a string.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \return The converted string if no error occurs during
+ *   the conversion. If an error does occur, an error handle is
+ *   returned.
+ */
+DART_EXPORT Dart_Handle Dart_ToString(Dart_Handle object);
+
+/**
+ * Checks to see if two handles refer to identically equal objects.
+ *
+ * If both handles refer to instances, this is equivalent to using the top-level
+ * function identical() from dart:core. Otherwise, returns whether the two
+ * argument handles refer to the same object.
+ *
+ * \param obj1 An object to be compared.
+ * \param obj2 An object to be compared.
+ *
+ * \return True if the objects are identically equal.  False otherwise.
+ */
+DART_EXPORT bool Dart_IdentityEquals(Dart_Handle obj1, Dart_Handle obj2);
+
+/**
+ * Allocates a handle in the current scope from a persistent handle.
+ */
+DART_EXPORT Dart_Handle Dart_HandleFromPersistent(Dart_PersistentHandle object);
+
+/**
+ * Allocates a handle in the current scope from a weak persistent handle.
+ *
+ * This will be a handle to Dart_Null if the object has been garbage collected.
+ */
+DART_EXPORT Dart_Handle
+Dart_HandleFromWeakPersistent(Dart_WeakPersistentHandle object);
+
+/**
+ * Allocates a persistent handle for an object.
+ *
+ * This handle has the lifetime of the current isolate unless it is
+ * explicitly deallocated by calling Dart_DeletePersistentHandle.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT Dart_PersistentHandle Dart_NewPersistentHandle(Dart_Handle object);
+
+/**
+ * Assign value of local handle to a persistent handle.
+ *
+ * Requires there to be a current isolate.
+ *
+ * \param obj1 A persistent handle whose value needs to be set.
+ * \param obj2 An object whose value needs to be set to the persistent handle.
+ */
+DART_EXPORT void Dart_SetPersistentHandle(Dart_PersistentHandle obj1,
+                                          Dart_Handle obj2);
+
+/**
+ * Deallocates a persistent handle.
+ *
+ * Requires there to be a current isolate group.
+ */
+DART_EXPORT void Dart_DeletePersistentHandle(Dart_PersistentHandle object);
+
+/**
+ * Allocates a weak persistent handle for an object.
+ *
+ * This handle has the lifetime of the current isolate. The handle can also be
+ * explicitly deallocated by calling Dart_DeleteWeakPersistentHandle.
+ *
+ * If the object becomes unreachable the callback is invoked with the peer as
+ * argument. The callback can be executed on any thread, will have a current
+ * isolate group, but will not have a current isolate. The callback can only
+ * call Dart_DeletePersistentHandle or Dart_DeleteWeakPersistentHandle. This
+ * gives the embedder the ability to cleanup data associated with the object.
+ * The handle will point to the Dart_Null object after the finalizer has been
+ * run. It is illegal to call into the VM with any other Dart_* functions from
+ * the callback. If the handle is deleted before the object becomes
+ * unreachable, the callback is never invoked.
+ *
+ * Requires there to be a current isolate.
+ *
+ * \param object An object with identity.
+ * \param peer A pointer to a native object or NULL.  This value is
+ *   provided to callback when it is invoked.
+ * \param external_allocation_size The number of externally allocated
+ *   bytes for peer. Used to inform the garbage collector.
+ * \param callback A function pointer that will be invoked sometime
+ *   after the object is garbage collected, unless the handle has been deleted.
+ *   A valid callback needs to be specified it cannot be NULL.
+ *
+ * \return The weak persistent handle or NULL. NULL is returned in case of bad
+ *   parameters.
+ */
+DART_EXPORT Dart_WeakPersistentHandle
+Dart_NewWeakPersistentHandle(Dart_Handle object,
+                             void* peer,
+                             intptr_t external_allocation_size,
+                             Dart_HandleFinalizer callback);
+
+/**
+ * Deletes the given weak persistent [object] handle.
+ *
+ * Requires there to be a current isolate group.
+ */
+DART_EXPORT void Dart_DeleteWeakPersistentHandle(
+    Dart_WeakPersistentHandle object);
+
+/**
+ * Updates the external memory size for the given weak persistent handle.
+ *
+ * May trigger garbage collection.
+ */
+DART_EXPORT void Dart_UpdateExternalSize(Dart_WeakPersistentHandle object,
+                                         intptr_t external_allocation_size);
+
+/**
+ * Allocates a finalizable handle for an object.
+ *
+ * This handle has the lifetime of the current isolate group unless the object
+ * pointed to by the handle is garbage collected, in this case the VM
+ * automatically deletes the handle after invoking the callback associated
+ * with the handle. The handle can also be explicitly deallocated by
+ * calling Dart_DeleteFinalizableHandle.
+ *
+ * If the object becomes unreachable the callback is invoked with the
+ * the peer as argument. The callback can be executed on any thread, will have
+ * an isolate group, but will not have a current isolate. The callback can only
+ * call Dart_DeletePersistentHandle or Dart_DeleteWeakPersistentHandle.
+ * This gives the embedder the ability to cleanup data associated with the
+ * object and clear out any cached references to the handle. All references to
+ * this handle after the callback will be invalid. It is illegal to call into
+ * the VM with any other Dart_* functions from the callback. If the handle is
+ * deleted before the object becomes unreachable, the callback is never
+ * invoked.
+ *
+ * Requires there to be a current isolate.
+ *
+ * \param object An object with identity.
+ * \param peer A pointer to a native object or NULL.  This value is
+ *   provided to callback when it is invoked.
+ * \param external_allocation_size The number of externally allocated
+ *   bytes for peer. Used to inform the garbage collector.
+ * \param callback A function pointer that will be invoked sometime
+ *   after the object is garbage collected, unless the handle has been deleted.
+ *   A valid callback needs to be specified it cannot be NULL.
+ *
+ * \return The finalizable handle or NULL. NULL is returned in case of bad
+ *   parameters.
+ */
+DART_EXPORT Dart_FinalizableHandle
+Dart_NewFinalizableHandle(Dart_Handle object,
+                          void* peer,
+                          intptr_t external_allocation_size,
+                          Dart_HandleFinalizer callback);
+
+/**
+ * Deletes the given finalizable [object] handle.
+ *
+ * The caller has to provide the actual Dart object the handle was created from
+ * to prove the object (and therefore the finalizable handle) is still alive.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT void Dart_DeleteFinalizableHandle(Dart_FinalizableHandle object,
+                                              Dart_Handle strong_ref_to_object);
+
+/**
+ * Updates the external memory size for the given finalizable handle.
+ *
+ * The caller has to provide the actual Dart object the handle was created from
+ * to prove the object (and therefore the finalizable handle) is still alive.
+ *
+ * May trigger garbage collection.
+ */
+DART_EXPORT void Dart_UpdateFinalizableExternalSize(
+    Dart_FinalizableHandle object,
+    Dart_Handle strong_ref_to_object,
+    intptr_t external_allocation_size);
+
+/*
+ * ==========================
+ * Initialization and Globals
+ * ==========================
+ */
+
+/**
+ * Gets the version string for the Dart VM.
+ *
+ * The version of the Dart VM can be accessed without initializing the VM.
+ *
+ * \return The version string for the embedded Dart VM.
+ */
+DART_EXPORT const char* Dart_VersionString(void);
+
+/**
+ * Isolate specific flags are set when creating a new isolate using the
+ * Dart_IsolateFlags structure.
+ *
+ * Current version of flags is encoded in a 32-bit integer with 16 bits used
+ * for each part.
+ */
+
+#define DART_FLAGS_CURRENT_VERSION (0x0000000c)
+
+typedef struct {
+  int32_t version;
+  bool enable_asserts;
+  bool use_field_guards;
+  bool use_osr;
+  bool obfuscate;
+  bool load_vmservice_library;
+  bool copy_parent_code;
+  bool null_safety;
+  bool is_system_isolate;
+  bool snapshot_is_dontneed_safe;
+  bool branch_coverage;
+} Dart_IsolateFlags;
+
+/**
+ * Initialize Dart_IsolateFlags with correct version and default values.
+ */
+DART_EXPORT void Dart_IsolateFlagsInitialize(Dart_IsolateFlags* flags);
+
+/**
+ * An isolate creation and initialization callback function.
+ *
+ * This callback, provided by the embedder, is called when the VM
+ * needs to create an isolate. The callback should create an isolate
+ * by calling Dart_CreateIsolateGroup and load any scripts required for
+ * execution.
+ *
+ * This callback may be called on a different thread than the one
+ * running the parent isolate.
+ *
+ * When the function returns NULL, it is the responsibility of this
+ * function to ensure that Dart_ShutdownIsolate has been called if
+ * required (for example, if the isolate was created successfully by
+ * Dart_CreateIsolateGroup() but the root library fails to load
+ * successfully, then the function should call Dart_ShutdownIsolate
+ * before returning).
+ *
+ * When the function returns NULL, the function should set *error to
+ * a malloc-allocated buffer containing a useful error message.  The
+ * caller of this function (the VM) will make sure that the buffer is
+ * freed.
+ *
+ * \param script_uri The uri of the main source file or snapshot to load.
+ *   Either the URI of the parent isolate set in Dart_CreateIsolateGroup for
+ *   Isolate.spawn, or the argument to Isolate.spawnUri canonicalized by the
+ *   library tag handler of the parent isolate.
+ *   The callback is responsible for loading the program by a call to
+ *   Dart_LoadScriptFromKernel.
+ * \param main The name of the main entry point this isolate will
+ *   eventually run.  This is provided for advisory purposes only to
+ *   improve debugging messages.  The main function is not invoked by
+ *   this function.
+ * \param package_root Ignored.
+ * \param package_config Uri of the package configuration file (either in format
+ *   of .packages or .dart_tool/package_config.json) for this isolate
+ *   to resolve package imports against. If this parameter is not passed the
+ *   package resolution of the parent isolate should be used.
+ * \param flags Default flags for this isolate being spawned. Either inherited
+ *   from the spawning isolate or passed as parameters when spawning the
+ *   isolate from Dart code.
+ * \param isolate_data The isolate data which was passed to the
+ *   parent isolate when it was created by calling Dart_CreateIsolateGroup().
+ * \param error A structure into which the embedder can place a
+ *   C string containing an error message in the case of failures.
+ *
+ * \return The embedder returns NULL if the creation and
+ *   initialization was not successful and the isolate if successful.
+ */
+typedef Dart_Isolate (*Dart_IsolateGroupCreateCallback)(
+    const char* script_uri,
+    const char* main,
+    const char* package_root,
+    const char* package_config,
+    Dart_IsolateFlags* flags,
+    void* isolate_data,
+    char** error);
+
+/**
+ * An isolate initialization callback function.
+ *
+ * This callback, provided by the embedder, is called when the VM has created an
+ * isolate within an existing isolate group (i.e. from the same source as an
+ * existing isolate).
+ *
+ * The callback should setup native resolvers and might want to set a custom
+ * message handler via [Dart_SetMessageNotifyCallback] and mark the isolate as
+ * runnable.
+ *
+ * This callback may be called on a different thread than the one
+ * running the parent isolate.
+ *
+ * When the function returns `false`, it is the responsibility of this
+ * function to ensure that `Dart_ShutdownIsolate` has been called.
+ *
+ * When the function returns `false`, the function should set *error to
+ * a malloc-allocated buffer containing a useful error message.  The
+ * caller of this function (the VM) will make sure that the buffer is
+ * freed.
+ *
+ * \param child_isolate_data The callback data to associate with the new
+ *        child isolate.
+ * \param error A structure into which the embedder can place a
+ *   C string containing an error message in the case the initialization fails.
+ *
+ * \return The embedder returns true if the initialization was successful and
+ *         false otherwise (in which case the VM will terminate the isolate).
+ */
+typedef bool (*Dart_InitializeIsolateCallback)(void** child_isolate_data,
+                                               char** error);
+
+/**
+ * An isolate shutdown callback function.
+ *
+ * This callback, provided by the embedder, is called before the vm
+ * shuts down an isolate.  The isolate being shutdown will be the current
+ * isolate. It is safe to run Dart code.
+ *
+ * This function should be used to dispose of native resources that
+ * are allocated to an isolate in order to avoid leaks.
+ *
+ * \param isolate_group_data The same callback data which was passed to the
+ *   isolate group when it was created.
+ * \param isolate_data The same callback data which was passed to the isolate
+ *   when it was created.
+ */
+typedef void (*Dart_IsolateShutdownCallback)(void* isolate_group_data,
+                                             void* isolate_data);
+
+/**
+ * An isolate cleanup callback function.
+ *
+ * This callback, provided by the embedder, is called after the vm
+ * shuts down an isolate. There will be no current isolate and it is *not*
+ * safe to run Dart code.
+ *
+ * This function should be used to dispose of native resources that
+ * are allocated to an isolate in order to avoid leaks.
+ *
+ * \param isolate_group_data The same callback data which was passed to the
+ *   isolate group when it was created.
+ * \param isolate_data The same callback data which was passed to the isolate
+ *   when it was created.
+ */
+typedef void (*Dart_IsolateCleanupCallback)(void* isolate_group_data,
+                                            void* isolate_data);
+
+/**
+ * An isolate group cleanup callback function.
+ *
+ * This callback, provided by the embedder, is called after the vm
+ * shuts down an isolate group.
+ *
+ * This function should be used to dispose of native resources that
+ * are allocated to an isolate in order to avoid leaks.
+ *
+ * \param isolate_group_data The same callback data which was passed to the
+ *   isolate group when it was created.
+ *
+ */
+typedef void (*Dart_IsolateGroupCleanupCallback)(void* isolate_group_data);
+
+/**
+ * A thread start callback function.
+ * This callback, provided by the embedder, is called after a thread in the
+ * vm thread pool starts.
+ * This function could be used to adjust thread priority or attach native
+ * resources to the thread.
+ */
+typedef void (*Dart_ThreadStartCallback)(void);
+
+/**
+ * A thread death callback function.
+ * This callback, provided by the embedder, is called before a thread in the
+ * vm thread pool exits.
+ * This function could be used to dispose of native resources that
+ * are associated and attached to the thread, in order to avoid leaks.
+ */
+typedef void (*Dart_ThreadExitCallback)(void);
+
+/**
+ * Opens a file for reading or writing.
+ *
+ * Callback provided by the embedder for file operations. If the
+ * embedder does not allow file operations this callback can be
+ * NULL.
+ *
+ * \param name The name of the file to open.
+ * \param write A boolean variable which indicates if the file is to
+ *   opened for writing. If there is an existing file it needs to truncated.
+ */
+typedef void* (*Dart_FileOpenCallback)(const char* name, bool write);
+
+/**
+ * Read contents of file.
+ *
+ * Callback provided by the embedder for file operations. If the
+ * embedder does not allow file operations this callback can be
+ * NULL.
+ *
+ * \param data Buffer allocated in the callback into which the contents
+ *   of the file are read into. It is the responsibility of the caller to
+ *   free this buffer.
+ * \param file_length A variable into which the length of the file is returned.
+ *   In the case of an error this value would be -1.
+ * \param stream Handle to the opened file.
+ */
+typedef void (*Dart_FileReadCallback)(uint8_t** data,
+                                      intptr_t* file_length,
+                                      void* stream);
+
+/**
+ * Write data into file.
+ *
+ * Callback provided by the embedder for file operations. If the
+ * embedder does not allow file operations this callback can be
+ * NULL.
+ *
+ * \param data Buffer which needs to be written into the file.
+ * \param length Length of the buffer.
+ * \param stream Handle to the opened file.
+ */
+typedef void (*Dart_FileWriteCallback)(const void* data,
+                                       intptr_t length,
+                                       void* stream);
+
+/**
+ * Closes the opened file.
+ *
+ * Callback provided by the embedder for file operations. If the
+ * embedder does not allow file operations this callback can be
+ * NULL.
+ *
+ * \param stream Handle to the opened file.
+ */
+typedef void (*Dart_FileCloseCallback)(void* stream);
+
+typedef bool (*Dart_EntropySource)(uint8_t* buffer, intptr_t length);
+
+/**
+ * Callback provided by the embedder that is used by the vmservice isolate
+ * to request the asset archive. The asset archive must be an uncompressed tar
+ * archive that is stored in a Uint8List.
+ *
+ * If the embedder has no vmservice isolate assets, the callback can be NULL.
+ *
+ * \return The embedder must return a handle to a Uint8List containing an
+ *   uncompressed tar archive or null.
+ */
+typedef Dart_Handle (*Dart_GetVMServiceAssetsArchive)(void);
+
+/**
+ * The current version of the Dart_InitializeFlags. Should be incremented every
+ * time Dart_InitializeFlags changes in a binary incompatible way.
+ */
+#define DART_INITIALIZE_PARAMS_CURRENT_VERSION (0x00000008)
+
+/** Forward declaration */
+struct Dart_CodeObserver;
+
+/**
+ * Callback provided by the embedder that is used by the VM to notify on code
+ * object creation, *before* it is invoked the first time.
+ * This is useful for embedders wanting to e.g. keep track of PCs beyond
+ * the lifetime of the garbage collected code objects.
+ * Note that an address range may be used by more than one code object over the
+ * lifecycle of a process. Clients of this function should record timestamps for
+ * these compilation events and when collecting PCs to disambiguate reused
+ * address ranges.
+ */
+typedef void (*Dart_OnNewCodeCallback)(struct Dart_CodeObserver* observer,
+                                       const char* name,
+                                       uintptr_t base,
+                                       uintptr_t size);
+
+typedef struct Dart_CodeObserver {
+  void* data;
+
+  Dart_OnNewCodeCallback on_new_code;
+} Dart_CodeObserver;
+
+/**
+ * Optional callback provided by the embedder that is used by the VM to
+ * implement registration of kernel blobs for the subsequent Isolate.spawnUri
+ * If no callback is provided, the registration of kernel blobs will throw
+ * an error.
+ * 
+ * \param kernel_buffer A buffer which contains a kernel program. Callback
+ *                      should copy the contents of `kernel_buffer` as
+ *                      it may be freed immediately after registration.
+ * \param kernel_buffer_size The size of `kernel_buffer`.
+ *
+ * \return A C string representing URI which can be later used
+ *         to spawn a new isolate. This C String should be scope allocated
+ *         or owned by the embedder.
+ *         Returns NULL if embedder runs out of memory.
+ */
+typedef const char* (*Dart_RegisterKernelBlobCallback)(
+    const uint8_t* kernel_buffer,
+    intptr_t kernel_buffer_size);
+
+/**
+ * Optional callback provided by the embedder that is used by the VM to
+ * unregister kernel blobs.
+ * If no callback is provided, the unregistration of kernel blobs will throw
+ * an error.
+ * 
+ * \param kernel_blob_uri URI of the kernel blob to unregister.
+ */
+typedef void (*Dart_UnregisterKernelBlobCallback)(const char* kernel_blob_uri);
+
+/**
+ * Describes how to initialize the VM. Used with Dart_Initialize.
+ */
+typedef struct {
+  /**
+   * Identifies the version of the struct used by the client.
+   * should be initialized to DART_INITIALIZE_PARAMS_CURRENT_VERSION.
+   */
+  int32_t version;
+
+  /**
+   * A buffer containing snapshot data, or NULL if no snapshot is provided.
+   *
+   * If provided, the buffer must remain valid until Dart_Cleanup returns.
+   */
+  const uint8_t* vm_snapshot_data;
+
+  /**
+   * A buffer containing a snapshot of precompiled instructions, or NULL if
+   * no snapshot is provided.
+   *
+   * If provided, the buffer must remain valid until Dart_Cleanup returns.
+   */
+  const uint8_t* vm_snapshot_instructions;
+
+  /**
+   * A function to be called during isolate group creation.
+   * See Dart_IsolateGroupCreateCallback.
+   */
+  Dart_IsolateGroupCreateCallback create_group;
+
+  /**
+   * A function to be called during isolate
+   * initialization inside an existing isolate group.
+   * See Dart_InitializeIsolateCallback.
+   */
+  Dart_InitializeIsolateCallback initialize_isolate;
+
+  /**
+   * A function to be called right before an isolate is shutdown.
+   * See Dart_IsolateShutdownCallback.
+   */
+  Dart_IsolateShutdownCallback shutdown_isolate;
+
+  /**
+   * A function to be called after an isolate was shutdown.
+   * See Dart_IsolateCleanupCallback.
+   */
+  Dart_IsolateCleanupCallback cleanup_isolate;
+
+  /**
+   * A function to be called after an isolate group is
+   * shutdown. See Dart_IsolateGroupCleanupCallback.
+   */
+  Dart_IsolateGroupCleanupCallback cleanup_group;
+
+  Dart_ThreadStartCallback thread_start;
+  Dart_ThreadExitCallback thread_exit;
+  Dart_FileOpenCallback file_open;
+  Dart_FileReadCallback file_read;
+  Dart_FileWriteCallback file_write;
+  Dart_FileCloseCallback file_close;
+  Dart_EntropySource entropy_source;
+
+  /**
+   * A function to be called by the service isolate when it requires the
+   * vmservice assets archive. See Dart_GetVMServiceAssetsArchive.
+   */
+  Dart_GetVMServiceAssetsArchive get_service_assets;
+
+  bool start_kernel_isolate;
+
+  /**
+   * An external code observer callback function. The observer can be invoked
+   * as early as during the Dart_Initialize() call.
+   */
+  Dart_CodeObserver* code_observer;
+
+  /**
+   * Kernel blob registration callback function. See Dart_RegisterKernelBlobCallback.
+   */
+  Dart_RegisterKernelBlobCallback register_kernel_blob;
+
+  /**
+   * Kernel blob unregistration callback function. See Dart_UnregisterKernelBlobCallback.
+   */
+  Dart_UnregisterKernelBlobCallback unregister_kernel_blob;
+
+#if defined(__Fuchsia__)
+  /**
+   * The resource needed to use zx_vmo_replace_as_executable. Can be
+   * ZX_HANDLE_INVALID if the process has ambient-replace-as-executable or if
+   * executable memory is not needed (e.g., this is an AOT runtime).
+   */
+  zx_handle_t vmex_resource;
+#endif
+} Dart_InitializeParams;
+
+/**
+ * Initializes the VM.
+ *
+ * \param params A struct containing initialization information. The version
+ *   field of the struct must be DART_INITIALIZE_PARAMS_CURRENT_VERSION.
+ *
+ * \return NULL if initialization is successful. Returns an error message
+ *   otherwise. The caller is responsible for freeing the error message.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_Initialize(
+    Dart_InitializeParams* params);
+
+/**
+ * Cleanup state in the VM before process termination.
+ *
+ * \return NULL if cleanup is successful. Returns an error message otherwise.
+ *   The caller is responsible for freeing the error message.
+ *
+ * NOTE: This function must not be called on a thread that was created by the VM
+ * itself.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_Cleanup(void);
+
+/**
+ * Sets command line flags. Should be called before Dart_Initialize.
+ *
+ * \param argc The length of the arguments array.
+ * \param argv An array of arguments.
+ *
+ * \return NULL if successful. Returns an error message otherwise.
+ *  The caller is responsible for freeing the error message.
+ *
+ * NOTE: This call does not store references to the passed in c-strings.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_SetVMFlags(int argc,
+                                                          const char** argv);
+
+/**
+ * Returns true if the named VM flag is of boolean type, specified, and set to
+ * true.
+ *
+ * \param flag_name The name of the flag without leading punctuation
+ *                  (example: "enable_asserts").
+ */
+DART_EXPORT bool Dart_IsVMFlagSet(const char* flag_name);
+
+/*
+ * ========
+ * Isolates
+ * ========
+ */
+
+/**
+ * Creates a new isolate. The new isolate becomes the current isolate.
+ *
+ * A snapshot can be used to restore the VM quickly to a saved state
+ * and is useful for fast startup. If snapshot data is provided, the
+ * isolate will be started using that snapshot data. Requires a core snapshot or
+ * an app snapshot created by Dart_CreateSnapshot or
+ * Dart_CreatePrecompiledSnapshot* from a VM with the same version.
+ *
+ * Requires there to be no current isolate.
+ *
+ * \param script_uri The main source file or snapshot this isolate will load.
+ *   The VM will provide this URI to the Dart_IsolateGroupCreateCallback when a
+ *   child isolate is created by Isolate.spawn. The embedder should use a URI
+ *   that allows it to load the same program into such a child isolate.
+ * \param name A short name for the isolate to improve debugging messages.
+ *   Typically of the format 'foo.dart:main()'.
+ * \param isolate_snapshot_data Buffer containing the snapshot data of the
+ *   isolate or NULL if no snapshot is provided. If provided, the buffer must
+ *   remain valid until the isolate shuts down.
+ * \param isolate_snapshot_instructions Buffer containing the snapshot
+ *   instructions of the isolate or NULL if no snapshot is provided. If
+ *   provided, the buffer must remain valid until the isolate shuts down.
+ * \param flags Pointer to VM specific flags or NULL for default flags.
+ * \param isolate_group_data Embedder group data. This data can be obtained
+ *   by calling Dart_IsolateGroupData and will be passed to the
+ *   Dart_IsolateShutdownCallback, Dart_IsolateCleanupCallback, and
+ *   Dart_IsolateGroupCleanupCallback.
+ * \param isolate_data Embedder data.  This data will be passed to
+ *   the Dart_IsolateGroupCreateCallback when new isolates are spawned from
+ *   this parent isolate.
+ * \param error Returns NULL if creation is successful, an error message
+ *   otherwise. The caller is responsible for calling free() on the error
+ *   message.
+ *
+ * \return The new isolate on success, or NULL if isolate creation failed.
+ */
+DART_EXPORT Dart_Isolate
+Dart_CreateIsolateGroup(const char* script_uri,
+                        const char* name,
+                        const uint8_t* isolate_snapshot_data,
+                        const uint8_t* isolate_snapshot_instructions,
+                        Dart_IsolateFlags* flags,
+                        void* isolate_group_data,
+                        void* isolate_data,
+                        char** error);
+/**
+ * Creates a new isolate inside the isolate group of [group_member].
+ *
+ * Requires there to be no current isolate.
+ *
+ * \param group_member An isolate from the same group into which the newly created
+ *   isolate should be born into. Other threads may not have entered / enter this
+ *   member isolate.
+ * \param name A short name for the isolate for debugging purposes.
+ * \param shutdown_callback A callback to be called when the isolate is being
+ *   shutdown (may be NULL).
+ * \param cleanup_callback A callback to be called when the isolate is being
+ *   cleaned up (may be NULL).
+ * \param child_isolate_data The embedder-specific data associated with this isolate.
+ * \param error Set to NULL if creation is successful, set to an error
+ *   message otherwise. The caller is responsible for calling free() on the
+ *   error message.
+ *
+ * \return The newly created isolate on success, or NULL if isolate creation
+ *   failed.
+ *
+ * If successful, the newly created isolate will become the current isolate.
+ */
+DART_EXPORT Dart_Isolate
+Dart_CreateIsolateInGroup(Dart_Isolate group_member,
+                          const char* name,
+                          Dart_IsolateShutdownCallback shutdown_callback,
+                          Dart_IsolateCleanupCallback cleanup_callback,
+                          void* child_isolate_data,
+                          char** error);
+
+/* TODO(turnidge): Document behavior when there is already a current
+ * isolate. */
+
+/**
+ * Creates a new isolate from a Dart Kernel file. The new isolate
+ * becomes the current isolate.
+ *
+ * Requires there to be no current isolate.
+ *
+ * \param script_uri The main source file or snapshot this isolate will load.
+ *   The VM will provide this URI to the Dart_IsolateGroupCreateCallback when a
+ * child isolate is created by Isolate.spawn. The embedder should use a URI that
+ *   allows it to load the same program into such a child isolate.
+ * \param name A short name for the isolate to improve debugging messages.
+ *   Typically of the format 'foo.dart:main()'.
+ * \param kernel_buffer A buffer which contains a kernel/DIL program. Must
+ *   remain valid until isolate shutdown.
+ * \param kernel_buffer_size The size of `kernel_buffer`.
+ * \param flags Pointer to VM specific flags or NULL for default flags.
+ * \param isolate_group_data Embedder group data. This data can be obtained
+ *   by calling Dart_IsolateGroupData and will be passed to the
+ *   Dart_IsolateShutdownCallback, Dart_IsolateCleanupCallback, and
+ *   Dart_IsolateGroupCleanupCallback.
+ * \param isolate_data Embedder data.  This data will be passed to
+ *   the Dart_IsolateGroupCreateCallback when new isolates are spawned from
+ *   this parent isolate.
+ * \param error Returns NULL if creation is successful, an error message
+ *   otherwise. The caller is responsible for calling free() on the error
+ *   message.
+ *
+ * \return The new isolate on success, or NULL if isolate creation failed.
+ */
+DART_EXPORT Dart_Isolate
+Dart_CreateIsolateGroupFromKernel(const char* script_uri,
+                                  const char* name,
+                                  const uint8_t* kernel_buffer,
+                                  intptr_t kernel_buffer_size,
+                                  Dart_IsolateFlags* flags,
+                                  void* isolate_group_data,
+                                  void* isolate_data,
+                                  char** error);
+/**
+ * Shuts down the current isolate. After this call, the current isolate is NULL.
+ * Any current scopes created by Dart_EnterScope will be exited. Invokes the
+ * shutdown callback and any callbacks of remaining weak persistent handles.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT void Dart_ShutdownIsolate(void);
+/* TODO(turnidge): Document behavior when there is no current isolate. */
+
+/**
+ * Returns the current isolate. Will return NULL if there is no
+ * current isolate.
+ */
+DART_EXPORT Dart_Isolate Dart_CurrentIsolate(void);
+
+/**
+ * Returns the callback data associated with the current isolate. This
+ * data was set when the isolate got created or initialized.
+ */
+DART_EXPORT void* Dart_CurrentIsolateData(void);
+
+/**
+ * Returns the callback data associated with the given isolate. This
+ * data was set when the isolate got created or initialized.
+ */
+DART_EXPORT void* Dart_IsolateData(Dart_Isolate isolate);
+
+/**
+ * Returns the current isolate group. Will return NULL if there is no
+ * current isolate group.
+ */
+DART_EXPORT Dart_IsolateGroup Dart_CurrentIsolateGroup(void);
+
+/**
+ * Returns the callback data associated with the current isolate group. This
+ * data was passed to the isolate group when it was created.
+ */
+DART_EXPORT void* Dart_CurrentIsolateGroupData(void);
+
+/**
+ * Gets an id that uniquely identifies current isolate group.
+ *
+ * It is the responsibility of the caller to free the returned ID.
+ */
+typedef int64_t Dart_IsolateGroupId;
+DART_EXPORT Dart_IsolateGroupId Dart_CurrentIsolateGroupId(void);
+
+/**
+ * Returns the callback data associated with the specified isolate group. This
+ * data was passed to the isolate when it was created.
+ * The embedder is responsible for ensuring the consistency of this data
+ * with respect to the lifecycle of an isolate group.
+ */
+DART_EXPORT void* Dart_IsolateGroupData(Dart_Isolate isolate);
+
+/**
+ * Returns the debugging name for the current isolate.
+ *
+ * This name is unique to each isolate and should only be used to make
+ * debugging messages more comprehensible.
+ */
+DART_EXPORT Dart_Handle Dart_DebugName(void);
+
+/**
+ * Returns the debugging name for the current isolate.
+ *
+ * This name is unique to each isolate and should only be used to make
+ * debugging messages more comprehensible.
+ *
+ * The returned string is scope allocated and is only valid until the next call
+ * to Dart_ExitScope.
+ */
+DART_EXPORT const char* Dart_DebugNameToCString(void);
+
+/**
+ * Returns the ID for an isolate which is used to query the service protocol.
+ *
+ * It is the responsibility of the caller to free the returned ID.
+ */
+DART_EXPORT const char* Dart_IsolateServiceId(Dart_Isolate isolate);
+
+/**
+ * Enters an isolate. After calling this function,
+ * the current isolate will be set to the provided isolate.
+ *
+ * Requires there to be no current isolate. Multiple threads may not be in
+ * the same isolate at once.
+ */
+DART_EXPORT void Dart_EnterIsolate(Dart_Isolate isolate);
+
+/**
+ * Kills the given isolate.
+ *
+ * This function has the same effect as dart:isolate's
+ * Isolate.kill(priority:immediate).
+ * It can interrupt ordinary Dart code but not native code. If the isolate is
+ * in the middle of a long running native function, the isolate will not be
+ * killed until control returns to Dart.
+ *
+ * Does not require a current isolate. It is safe to kill the current isolate if
+ * there is one.
+ */
+DART_EXPORT void Dart_KillIsolate(Dart_Isolate isolate);
+
+/**
+ * Notifies the VM that the embedder expects to be idle until |deadline|. The VM
+ * may use this time to perform garbage collection or other tasks to avoid
+ * delays during execution of Dart code in the future.
+ *
+ * |deadline| is measured in microseconds against the system's monotonic time.
+ * This clock can be accessed via Dart_TimelineGetMicros().
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT void Dart_NotifyIdle(int64_t deadline);
+
+typedef void (*Dart_HeapSamplingReportCallback)(void* context,
+                                                void* data);
+
+typedef void* (*Dart_HeapSamplingCreateCallback)(
+    Dart_Isolate isolate,
+    Dart_IsolateGroup isolate_group,
+    const char* cls_name,
+    intptr_t allocation_size);
+typedef void (*Dart_HeapSamplingDeleteCallback)(void* data);
+
+/**
+ * Starts the heap sampling profiler for each thread in the VM.
+ */
+DART_EXPORT void Dart_EnableHeapSampling(void);
+
+/*
+ * Stops the heap sampling profiler for each thread in the VM.
+ */
+DART_EXPORT void Dart_DisableHeapSampling(void);
+
+/* Registers callbacks are invoked once per sampled allocation upon object
+ * allocation and garbage collection.
+ *
+ * |create_callback| can be used to associate additional data with the sampled
+ * allocation, such as a stack trace. This data pointer will be passed to
+ * |delete_callback| to allow for proper disposal when the object associated
+ * with the allocation sample is collected.
+ *
+ * The provided callbacks must not call into the VM and should do as little
+ * work as possible to avoid performance penalities during object allocation and
+ * garbage collection.
+ *
+ * NOTE: It is a fatal error to set either callback to null once they have been
+ * initialized.
+ */
+DART_EXPORT void Dart_RegisterHeapSamplingCallback(
+    Dart_HeapSamplingCreateCallback create_callback,
+    Dart_HeapSamplingDeleteCallback delete_callback);
+
+/*
+ * Reports the surviving allocation samples for all live isolate groups in the
+ * VM.
+ *
+ * When the callback is invoked:
+ *  - |context| will be the context object provided when invoking
+ *    |Dart_ReportSurvivingAllocations|. This can be safely set to null if not
+ *    required.
+ *  - |heap_size| will be equal to the size of the allocated object associated
+ *    with the sample.
+ *  - |cls_name| will be a C String representing
+ *    the class name of the allocated object. This string is valid for the
+ *    duration of the call to Dart_ReportSurvivingAllocations and can be
+ *    freed by the VM at any point after the method returns.
+ *  - |data| will be set to the data associated with the sample by
+ *    |Dart_HeapSamplingCreateCallback|.
+ *
+ * If |force_gc| is true, a full GC will be performed before reporting the
+ * allocations.
+ */
+DART_EXPORT void Dart_ReportSurvivingAllocations(
+    Dart_HeapSamplingReportCallback callback,
+    void* context,
+    bool force_gc);
+
+/*
+ * Sets the average heap sampling rate based on a number of |bytes| for each
+ * thread.
+ *
+ * In other words, approximately every |bytes| allocated will create a sample.
+ * Defaults to 512 KiB.
+ */
+DART_EXPORT void Dart_SetHeapSamplingPeriod(intptr_t bytes);
+
+/**
+ * Notifies the VM that the embedder expects the application's working set has
+ * recently shrunk significantly and is not expected to rise in the near future.
+ * The VM may spend O(heap-size) time performing clean up work.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT void Dart_NotifyDestroyed(void);
+
+/**
+ * Notifies the VM that the system is running low on memory.
+ *
+ * Does not require a current isolate. Only valid after calling Dart_Initialize.
+ */
+DART_EXPORT void Dart_NotifyLowMemory(void);
+
+typedef enum {
+  /**
+   * Balanced
+   */
+  Dart_PerformanceMode_Default,
+  /**
+   * Optimize for low latency, at the expense of throughput and memory overhead
+   * by performing work in smaller batches (requiring more overhead) or by
+   * delaying work (requiring more memory). An embedder should not remain in
+   * this mode indefinitely.
+   */
+  Dart_PerformanceMode_Latency,
+  /**
+   * Optimize for high throughput, at the expense of latency and memory overhead
+   * by performing work in larger batches with more intervening growth.
+   */
+  Dart_PerformanceMode_Throughput,
+  /**
+   * Optimize for low memory, at the expensive of throughput and latency by more
+   * frequently performing work.
+   */
+  Dart_PerformanceMode_Memory,
+} Dart_PerformanceMode;
+
+/**
+ * Set the desired performance trade-off.
+ *
+ * Requires a current isolate.
+ *
+ * Returns the previous performance mode.
+ */
+DART_EXPORT Dart_PerformanceMode
+Dart_SetPerformanceMode(Dart_PerformanceMode mode);
+
+/**
+ * Starts the CPU sampling profiler.
+ */
+DART_EXPORT void Dart_StartProfiling(void);
+
+/**
+ * Stops the CPU sampling profiler.
+ *
+ * Note that some profile samples might still be taken after this function
+ * returns due to the asynchronous nature of the implementation on some
+ * platforms.
+ */
+DART_EXPORT void Dart_StopProfiling(void);
+
+/**
+ * Notifies the VM that the current thread should not be profiled until a
+ * matching call to Dart_ThreadEnableProfiling is made.
+ *
+ * NOTE: By default, if a thread has entered an isolate it will be profiled.
+ * This function should be used when an embedder knows a thread is about
+ * to make a blocking call and wants to avoid unnecessary interrupts by
+ * the profiler.
+ */
+DART_EXPORT void Dart_ThreadDisableProfiling(void);
+
+/**
+ * Notifies the VM that the current thread should be profiled.
+ *
+ * NOTE: It is only legal to call this function *after* calling
+ *   Dart_ThreadDisableProfiling.
+ *
+ * NOTE: By default, if a thread has entered an isolate it will be profiled.
+ */
+DART_EXPORT void Dart_ThreadEnableProfiling(void);
+
+/**
+ * Register symbol information for the Dart VM's profiler and crash dumps.
+ *
+ * This consumes the output of //topaz/runtime/dart/profiler_symbols, which
+ * should be treated as opaque.
+ */
+DART_EXPORT void Dart_AddSymbols(const char* dso_name,
+                                 void* buffer,
+                                 intptr_t buffer_size);
+
+/**
+ * Exits an isolate. After this call, Dart_CurrentIsolate will
+ * return NULL.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT void Dart_ExitIsolate(void);
+/* TODO(turnidge): We don't want users of the api to be able to exit a
+ * "pure" dart isolate. Implement and document. */
+
+/**
+ * Creates a full snapshot of the current isolate heap.
+ *
+ * A full snapshot is a compact representation of the dart vm isolate heap
+ * and dart isolate heap states. These snapshots are used to initialize
+ * the vm isolate on startup and fast initialization of an isolate.
+ * A Snapshot of the heap is created before any dart code has executed.
+ *
+ * Requires there to be a current isolate. Not available in the precompiled
+ * runtime (check Dart_IsPrecompiledRuntime).
+ *
+ * \param vm_snapshot_data_buffer Returns a pointer to a buffer containing the
+ *   vm snapshot. This buffer is scope allocated and is only valid
+ *   until the next call to Dart_ExitScope.
+ * \param vm_snapshot_data_size Returns the size of vm_snapshot_data_buffer.
+ * \param isolate_snapshot_data_buffer Returns a pointer to a buffer containing
+ *   the isolate snapshot. This buffer is scope allocated and is only valid
+ *   until the next call to Dart_ExitScope.
+ * \param isolate_snapshot_data_size Returns the size of
+ *   isolate_snapshot_data_buffer.
+ * \param is_core Create a snapshot containing core libraries.
+ *   Such snapshot should be agnostic to null safety mode.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_CreateSnapshot(uint8_t** vm_snapshot_data_buffer,
+                    intptr_t* vm_snapshot_data_size,
+                    uint8_t** isolate_snapshot_data_buffer,
+                    intptr_t* isolate_snapshot_data_size,
+                    bool is_core);
+
+/**
+ * Returns whether the buffer contains a kernel file.
+ *
+ * \param buffer Pointer to a buffer that might contain a kernel binary.
+ * \param buffer_size Size of the buffer.
+ *
+ * \return Whether the buffer contains a kernel binary (full or partial).
+ */
+DART_EXPORT bool Dart_IsKernel(const uint8_t* buffer, intptr_t buffer_size);
+
+/**
+ * Make isolate runnable.
+ *
+ * When isolates are spawned, this function is used to indicate that
+ * the creation and initialization (including script loading) of the
+ * isolate is complete and the isolate can start.
+ * This function expects there to be no current isolate.
+ *
+ * \param isolate The isolate to be made runnable.
+ *
+ * \return NULL if successful. Returns an error message otherwise. The caller
+ * is responsible for freeing the error message.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_IsolateMakeRunnable(
+    Dart_Isolate isolate);
+
+/*
+ * ==================
+ * Messages and Ports
+ * ==================
+ */
+
+/**
+ * A port is used to send or receive inter-isolate messages
+ */
+typedef int64_t Dart_Port;
+
+/**
+ * ILLEGAL_PORT is a port number guaranteed never to be associated with a valid
+ * port.
+ */
+#define ILLEGAL_PORT ((Dart_Port)0)
+
+/**
+ * A message notification callback.
+ *
+ * This callback allows the embedder to provide a custom wakeup mechanism for
+ * the delivery of inter-isolate messages. This function is called once per
+ * message on an arbitrary thread. It is the responsibility of the embedder to
+ * eventually call Dart_HandleMessage once per callback received with the
+ * destination isolate set as the current isolate to process the message.
+ */
+typedef void (*Dart_MessageNotifyCallback)(Dart_Isolate destination_isolate);
+
+/**
+ * Allows embedders to provide a custom wakeup mechanism for the delivery of
+ * inter-isolate messages. This setting only applies to the current isolate.
+ *
+ * This mechanism is optional: if not provided, the isolate will be scheduled on
+ * a VM-managed thread pool. An embedder should provide this callback if it
+ * wants to run an isolate on a specific thread or to interleave handling of
+ * inter-isolate messages with other event sources.
+ *
+ * Most embedders will only call this function once, before isolate
+ * execution begins. If this function is called after isolate
+ * execution begins, the embedder is responsible for threading issues.
+ */
+DART_EXPORT void Dart_SetMessageNotifyCallback(
+    Dart_MessageNotifyCallback message_notify_callback);
+/* TODO(turnidge): Consider moving this to isolate creation so that it
+ * is impossible to mess up. */
+
+/**
+ * Query the current message notify callback for the isolate.
+ *
+ * \return The current message notify callback for the isolate.
+ */
+DART_EXPORT Dart_MessageNotifyCallback Dart_GetMessageNotifyCallback(void);
+
+/**
+ * The VM's default message handler supports pausing an isolate before it
+ * processes the first message and right after the it processes the isolate's
+ * final message. This can be controlled for all isolates by two VM flags:
+ *
+ *   `--pause-isolates-on-start`
+ *   `--pause-isolates-on-exit`
+ *
+ * Additionally, Dart_SetShouldPauseOnStart and Dart_SetShouldPauseOnExit can be
+ * used to control this behaviour on a per-isolate basis.
+ *
+ * When an embedder is using a Dart_MessageNotifyCallback the embedder
+ * needs to cooperate with the VM so that the service protocol can report
+ * accurate information about isolates and so that tools such as debuggers
+ * work reliably.
+ *
+ * The following functions can be used to implement pausing on start and exit.
+ */
+
+/**
+ * If the VM flag `--pause-isolates-on-start` was passed this will be true.
+ *
+ * \return A boolean value indicating if pause on start was requested.
+ */
+DART_EXPORT bool Dart_ShouldPauseOnStart(void);
+
+/**
+ * Override the VM flag `--pause-isolates-on-start` for the current isolate.
+ *
+ * \param should_pause Should the isolate be paused on start?
+ *
+ * NOTE: This must be called before Dart_IsolateMakeRunnable.
+ */
+DART_EXPORT void Dart_SetShouldPauseOnStart(bool should_pause);
+
+/**
+ * Is the current isolate paused on start?
+ *
+ * \return A boolean value indicating if the isolate is paused on start.
+ */
+DART_EXPORT bool Dart_IsPausedOnStart(void);
+
+/**
+ * Called when the embedder has paused the current isolate on start and when
+ * the embedder has resumed the isolate.
+ *
+ * \param paused Is the isolate paused on start?
+ */
+DART_EXPORT void Dart_SetPausedOnStart(bool paused);
+
+/**
+ * If the VM flag `--pause-isolates-on-exit` was passed this will be true.
+ *
+ * \return A boolean value indicating if pause on exit was requested.
+ */
+DART_EXPORT bool Dart_ShouldPauseOnExit(void);
+
+/**
+ * Override the VM flag `--pause-isolates-on-exit` for the current isolate.
+ *
+ * \param should_pause Should the isolate be paused on exit?
+ *
+ */
+DART_EXPORT void Dart_SetShouldPauseOnExit(bool should_pause);
+
+/**
+ * Is the current isolate paused on exit?
+ *
+ * \return A boolean value indicating if the isolate is paused on exit.
+ */
+DART_EXPORT bool Dart_IsPausedOnExit(void);
+
+/**
+ * Called when the embedder has paused the current isolate on exit and when
+ * the embedder has resumed the isolate.
+ *
+ * \param paused Is the isolate paused on exit?
+ */
+DART_EXPORT void Dart_SetPausedOnExit(bool paused);
+
+/**
+ * Called when the embedder has caught a top level unhandled exception error
+ * in the current isolate.
+ *
+ * NOTE: It is illegal to call this twice on the same isolate without first
+ * clearing the sticky error to null.
+ *
+ * \param error The unhandled exception error.
+ */
+DART_EXPORT void Dart_SetStickyError(Dart_Handle error);
+
+/**
+ * Does the current isolate have a sticky error?
+ */
+DART_EXPORT bool Dart_HasStickyError(void);
+
+/**
+ * Gets the sticky error for the current isolate.
+ *
+ * \return A handle to the sticky error object or null.
+ */
+DART_EXPORT Dart_Handle Dart_GetStickyError(void);
+
+/**
+ * Handles the next pending message for the current isolate.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_HandleMessage(void);
+
+/**
+ * Drains the microtask queue, then blocks the calling thread until the current
+ * isolate receives a message, then handles all messages.
+ *
+ * \param timeout_millis When non-zero, the call returns after the indicated
+          number of milliseconds even if no message was received.
+ * \return A valid handle if no error occurs, otherwise an error handle.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_WaitForEvent(int64_t timeout_millis);
+
+/**
+ * Handles any pending messages for the vm service for the current
+ * isolate.
+ *
+ * This function may be used by an embedder at a breakpoint to avoid
+ * pausing the vm service.
+ *
+ * This function can indirectly cause the message notify callback to
+ * be called.
+ *
+ * \return true if the vm service requests the program resume
+ * execution, false otherwise
+ */
+DART_EXPORT bool Dart_HandleServiceMessages(void);
+
+/**
+ * Does the current isolate have pending service messages?
+ *
+ * \return true if the isolate has pending service messages, false otherwise.
+ */
+DART_EXPORT bool Dart_HasServiceMessages(void);
+
+/**
+ * Processes any incoming messages for the current isolate.
+ *
+ * This function may only be used when the embedder has not provided
+ * an alternate message delivery mechanism with
+ * Dart_SetMessageCallbacks. It is provided for convenience.
+ *
+ * This function waits for incoming messages for the current
+ * isolate. As new messages arrive, they are handled using
+ * Dart_HandleMessage. The routine exits when all ports to the
+ * current isolate are closed.
+ *
+ * \return A valid handle if the run loop exited successfully.  If an
+ *   exception or other error occurs while processing messages, an
+ *   error handle is returned.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_RunLoop(void);
+
+/**
+ * Lets the VM run message processing for the isolate.
+ *
+ * This function expects there to a current isolate and the current isolate
+ * must not have an active api scope. The VM will take care of making the
+ * isolate runnable (if not already), handles its message loop and will take
+ * care of shutting the isolate down once it's done.
+ *
+ * \param errors_are_fatal Whether uncaught errors should be fatal.
+ * \param on_error_port A port to notify on uncaught errors (or ILLEGAL_PORT).
+ * \param on_exit_port A port to notify on exit (or ILLEGAL_PORT).
+ * \param error A non-NULL pointer which will hold an error message if the call
+ *   fails. The error has to be free()ed by the caller.
+ *
+ * \return If successful the VM takes ownership of the isolate and takes care
+ *   of its message loop. If not successful the caller retains ownership of the
+ *   isolate.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT bool Dart_RunLoopAsync(
+    bool errors_are_fatal,
+    Dart_Port on_error_port,
+    Dart_Port on_exit_port,
+    char** error);
+
+/* TODO(turnidge): Should this be removed from the public api? */
+
+/**
+ * Gets the main port id for the current isolate.
+ */
+DART_EXPORT Dart_Port Dart_GetMainPortId(void);
+
+/**
+ * Does the current isolate have live ReceivePorts?
+ *
+ * A ReceivePort is live when it has not been closed.
+ */
+DART_EXPORT bool Dart_HasLivePorts(void);
+
+/**
+ * Posts a message for some isolate. The message is a serialized
+ * object.
+ *
+ * Requires there to be a current isolate.
+ *
+ * For posting messages outside of an isolate see \ref Dart_PostCObject.
+ *
+ * \param port_id The destination port.
+ * \param object An object from the current isolate.
+ *
+ * \return True if the message was posted.
+ */
+DART_EXPORT bool Dart_Post(Dart_Port port_id, Dart_Handle object);
+
+/**
+ * Returns a new SendPort with the provided port id.
+ *
+ * \param port_id The destination port.
+ *
+ * \return A new SendPort if no errors occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewSendPort(Dart_Port port_id);
+
+/**
+ * Gets the SendPort id for the provided SendPort.
+ * \param port A SendPort object whose id is desired.
+ * \param port_id Returns the id of the SendPort.
+ * \return Success if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_SendPortGetId(Dart_Handle port,
+                                           Dart_Port* port_id);
+
+/*
+ * ======
+ * Scopes
+ * ======
+ */
+
+/**
+ * Enters a new scope.
+ *
+ * All new local handles will be created in this scope. Additionally,
+ * some functions may return "scope allocated" memory which is only
+ * valid within this scope.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT void Dart_EnterScope(void);
+
+/**
+ * Exits a scope.
+ *
+ * The previous scope (if any) becomes the current scope.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT void Dart_ExitScope(void);
+
+/**
+ * The Dart VM uses "zone allocation" for temporary structures. Zones
+ * support very fast allocation of small chunks of memory. The chunks
+ * cannot be deallocated individually, but instead zones support
+ * deallocating all chunks in one fast operation.
+ *
+ * This function makes it possible for the embedder to allocate
+ * temporary data in the VMs zone allocator.
+ *
+ * Zone allocation is possible:
+ *   1. when inside a scope where local handles can be allocated
+ *   2. when processing a message from a native port in a native port
+ *      handler
+ *
+ * All the memory allocated this way will be reclaimed either on the
+ * next call to Dart_ExitScope or when the native port handler exits.
+ *
+ * \param size Size of the memory to allocate.
+ *
+ * \return A pointer to the allocated memory. NULL if allocation
+ *   failed. Failure might due to is no current VM zone.
+ */
+DART_EXPORT uint8_t* Dart_ScopeAllocate(intptr_t size);
+
+/*
+ * =======
+ * Objects
+ * =======
+ */
+
+/**
+ * Returns the null object.
+ *
+ * \return A handle to the null object.
+ */
+DART_EXPORT Dart_Handle Dart_Null(void);
+
+/**
+ * Is this object null?
+ */
+DART_EXPORT bool Dart_IsNull(Dart_Handle object);
+
+/**
+ * Returns the empty string object.
+ *
+ * \return A handle to the empty string object.
+ */
+DART_EXPORT Dart_Handle Dart_EmptyString(void);
+
+/**
+ * Returns types that are not classes, and which therefore cannot be looked up
+ * as library members by Dart_GetType.
+ *
+ * \return A handle to the dynamic, void or Never type.
+ */
+DART_EXPORT Dart_Handle Dart_TypeDynamic(void);
+DART_EXPORT Dart_Handle Dart_TypeVoid(void);
+DART_EXPORT Dart_Handle Dart_TypeNever(void);
+
+/**
+ * Checks if the two objects are equal.
+ *
+ * The result of the comparison is returned through the 'equal'
+ * parameter. The return value itself is used to indicate success or
+ * failure, not equality.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param obj1 An object to be compared.
+ * \param obj2 An object to be compared.
+ * \param equal Returns the result of the equality comparison.
+ *
+ * \return A valid handle if no error occurs during the comparison.
+ */
+DART_EXPORT Dart_Handle Dart_ObjectEquals(Dart_Handle obj1,
+                                          Dart_Handle obj2,
+                                          bool* equal);
+
+/**
+ * Is this object an instance of some type?
+ *
+ * The result of the test is returned through the 'instanceof' parameter.
+ * The return value itself is used to indicate success or failure.
+ *
+ * \param object An object.
+ * \param type A type.
+ * \param instanceof Return true if 'object' is an instance of type 'type'.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_ObjectIsType(Dart_Handle object,
+                                          Dart_Handle type,
+                                          bool* instanceof);
+
+/**
+ * Query object type.
+ *
+ * \param object Some Object.
+ *
+ * \return true if Object is of the specified type.
+ */
+DART_EXPORT bool Dart_IsInstance(Dart_Handle object);
+DART_EXPORT bool Dart_IsNumber(Dart_Handle object);
+DART_EXPORT bool Dart_IsInteger(Dart_Handle object);
+DART_EXPORT bool Dart_IsDouble(Dart_Handle object);
+DART_EXPORT bool Dart_IsBoolean(Dart_Handle object);
+DART_EXPORT bool Dart_IsString(Dart_Handle object);
+DART_EXPORT bool Dart_IsStringLatin1(Dart_Handle object); /* (ISO-8859-1) */
+DART_EXPORT bool Dart_IsExternalString(Dart_Handle object);
+DART_EXPORT bool Dart_IsList(Dart_Handle object);
+DART_EXPORT bool Dart_IsMap(Dart_Handle object);
+DART_EXPORT bool Dart_IsLibrary(Dart_Handle object);
+DART_EXPORT bool Dart_IsType(Dart_Handle handle);
+DART_EXPORT bool Dart_IsFunction(Dart_Handle handle);
+DART_EXPORT bool Dart_IsVariable(Dart_Handle handle);
+DART_EXPORT bool Dart_IsTypeVariable(Dart_Handle handle);
+DART_EXPORT bool Dart_IsClosure(Dart_Handle object);
+DART_EXPORT bool Dart_IsTypedData(Dart_Handle object);
+DART_EXPORT bool Dart_IsByteBuffer(Dart_Handle object);
+DART_EXPORT bool Dart_IsFuture(Dart_Handle object);
+
+/*
+ * =========
+ * Instances
+ * =========
+ */
+
+/*
+ * For the purposes of the embedding api, not all objects returned are
+ * Dart language objects.  Within the api, we use the term 'Instance'
+ * to indicate handles which refer to true Dart language objects.
+ *
+ * TODO(turnidge): Reorganize the "Object" section above, pulling down
+ * any functions that more properly belong here. */
+
+/**
+ * Gets the type of a Dart language object.
+ *
+ * \param instance Some Dart object.
+ *
+ * \return If no error occurs, the type is returned. Otherwise an
+ *   error handle is returned.
+ */
+DART_EXPORT Dart_Handle Dart_InstanceGetType(Dart_Handle instance);
+
+/**
+ * Returns the name for the provided class type.
+ *
+ * \return A valid string handle if no error occurs during the
+ *   operation.
+ */
+DART_EXPORT Dart_Handle Dart_ClassName(Dart_Handle cls_type);
+
+/**
+ * Returns the name for the provided function or method.
+ *
+ * \return A valid string handle if no error occurs during the
+ *   operation.
+ */
+DART_EXPORT Dart_Handle Dart_FunctionName(Dart_Handle function);
+
+/**
+ * Returns a handle to the owner of a function.
+ *
+ * The owner of an instance method or a static method is its defining
+ * class. The owner of a top-level function is its defining
+ * library. The owner of the function of a non-implicit closure is the
+ * function of the method or closure that defines the non-implicit
+ * closure.
+ *
+ * \return A valid handle to the owner of the function, or an error
+ *   handle if the argument is not a valid handle to a function.
+ */
+DART_EXPORT Dart_Handle Dart_FunctionOwner(Dart_Handle function);
+
+/**
+ * Determines whether a function handle refers to a static function
+ * of method.
+ *
+ * For the purposes of the embedding API, a top-level function is
+ * implicitly declared static.
+ *
+ * \param function A handle to a function or method declaration.
+ * \param is_static Returns whether the function or method is declared static.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_FunctionIsStatic(Dart_Handle function,
+                                              bool* is_static);
+
+/**
+ * Is this object a closure resulting from a tear-off (closurized method)?
+ *
+ * Returns true for closures produced when an ordinary method is accessed
+ * through a getter call. Returns false otherwise, in particular for closures
+ * produced from local function declarations.
+ *
+ * \param object Some Object.
+ *
+ * \return true if Object is a tear-off.
+ */
+DART_EXPORT bool Dart_IsTearOff(Dart_Handle object);
+
+/**
+ * Retrieves the function of a closure.
+ *
+ * \return A handle to the function of the closure, or an error handle if the
+ *   argument is not a closure.
+ */
+DART_EXPORT Dart_Handle Dart_ClosureFunction(Dart_Handle closure);
+
+/**
+ * Returns a handle to the library which contains class.
+ *
+ * \return A valid handle to the library with owns class, null if the class
+ *   has no library or an error handle if the argument is not a valid handle
+ *   to a class type.
+ */
+DART_EXPORT Dart_Handle Dart_ClassLibrary(Dart_Handle cls_type);
+
+/*
+ * =============================
+ * Numbers, Integers and Doubles
+ * =============================
+ */
+
+/**
+ * Does this Integer fit into a 64-bit signed integer?
+ *
+ * \param integer An integer.
+ * \param fits Returns true if the integer fits into a 64-bit signed integer.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_IntegerFitsIntoInt64(Dart_Handle integer,
+                                                  bool* fits);
+
+/**
+ * Does this Integer fit into a 64-bit unsigned integer?
+ *
+ * \param integer An integer.
+ * \param fits Returns true if the integer fits into a 64-bit unsigned integer.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_IntegerFitsIntoUint64(Dart_Handle integer,
+                                                   bool* fits);
+
+/**
+ * Returns an Integer with the provided value.
+ *
+ * \param value The value of the integer.
+ *
+ * \return The Integer object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewInteger(int64_t value);
+
+/**
+ * Returns an Integer with the provided value.
+ *
+ * \param value The unsigned value of the integer.
+ *
+ * \return The Integer object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewIntegerFromUint64(uint64_t value);
+
+/**
+ * Returns an Integer with the provided value.
+ *
+ * \param value The value of the integer represented as a C string
+ *   containing a hexadecimal number.
+ *
+ * \return The Integer object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewIntegerFromHexCString(const char* value);
+
+/**
+ * Gets the value of an Integer.
+ *
+ * The integer must fit into a 64-bit signed integer, otherwise an error occurs.
+ *
+ * \param integer An Integer.
+ * \param value Returns the value of the Integer.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_IntegerToInt64(Dart_Handle integer,
+                                            int64_t* value);
+
+/**
+ * Gets the value of an Integer.
+ *
+ * The integer must fit into a 64-bit unsigned integer, otherwise an
+ * error occurs.
+ *
+ * \param integer An Integer.
+ * \param value Returns the value of the Integer.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_IntegerToUint64(Dart_Handle integer,
+                                             uint64_t* value);
+
+/**
+ * Gets the value of an integer as a hexadecimal C string.
+ *
+ * \param integer An Integer.
+ * \param value Returns the value of the Integer as a hexadecimal C
+ *   string. This C string is scope allocated and is only valid until
+ *   the next call to Dart_ExitScope.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_IntegerToHexCString(Dart_Handle integer,
+                                                 const char** value);
+
+/**
+ * Returns a Double with the provided value.
+ *
+ * \param value A double.
+ *
+ * \return The Double object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewDouble(double value);
+
+/**
+ * Gets the value of a Double
+ *
+ * \param double_obj A Double
+ * \param value Returns the value of the Double.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_DoubleValue(Dart_Handle double_obj, double* value);
+
+/**
+ * Returns a closure of static function 'function_name' in the class 'class_name'
+ * in the exported namespace of specified 'library'.
+ *
+ * \param library Library object
+ * \param cls_type Type object representing a Class
+ * \param function_name Name of the static function in the class
+ *
+ * \return A valid Dart instance if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_GetStaticMethodClosure(Dart_Handle library,
+                                                    Dart_Handle cls_type,
+                                                    Dart_Handle function_name);
+
+/*
+ * ========
+ * Booleans
+ * ========
+ */
+
+/**
+ * Returns the True object.
+ *
+ * Requires there to be a current isolate.
+ *
+ * \return A handle to the True object.
+ */
+DART_EXPORT Dart_Handle Dart_True(void);
+
+/**
+ * Returns the False object.
+ *
+ * Requires there to be a current isolate.
+ *
+ * \return A handle to the False object.
+ */
+DART_EXPORT Dart_Handle Dart_False(void);
+
+/**
+ * Returns a Boolean with the provided value.
+ *
+ * \param value true or false.
+ *
+ * \return The Boolean object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewBoolean(bool value);
+
+/**
+ * Gets the value of a Boolean
+ *
+ * \param boolean_obj A Boolean
+ * \param value Returns the value of the Boolean.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_BooleanValue(Dart_Handle boolean_obj, bool* value);
+
+/*
+ * =======
+ * Strings
+ * =======
+ */
+
+/**
+ * Gets the length of a String.
+ *
+ * \param str A String.
+ * \param length Returns the length of the String.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_StringLength(Dart_Handle str, intptr_t* length);
+
+/**
+ * Returns a String built from the provided C string
+ * (There is an implicit assumption that the C string passed in contains
+ *  UTF-8 encoded characters and '\0' is considered as a termination
+ *  character).
+ *
+ * \param str A C String
+ *
+ * \return The String object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewStringFromCString(const char* str);
+/* TODO(turnidge): Document what happens when we run out of memory
+ * during this call. */
+
+/**
+ * Returns a String built from an array of UTF-8 encoded characters.
+ *
+ * \param utf8_array An array of UTF-8 encoded characters.
+ * \param length The length of the codepoints array.
+ *
+ * \return The String object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewStringFromUTF8(const uint8_t* utf8_array,
+                                               intptr_t length);
+
+/**
+ * Returns a String built from an array of UTF-16 encoded characters.
+ *
+ * \param utf16_array An array of UTF-16 encoded characters.
+ * \param length The length of the codepoints array.
+ *
+ * \return The String object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewStringFromUTF16(const uint16_t* utf16_array,
+                                                intptr_t length);
+
+/**
+ * Returns a String built from an array of UTF-32 encoded characters.
+ *
+ * \param utf32_array An array of UTF-32 encoded characters.
+ * \param length The length of the codepoints array.
+ *
+ * \return The String object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewStringFromUTF32(const int32_t* utf32_array,
+                                                intptr_t length);
+
+/**
+ * Returns a String which references an external array of
+ * Latin-1 (ISO-8859-1) encoded characters.
+ *
+ * \param latin1_array Array of Latin-1 encoded characters. This must not move.
+ * \param length The length of the characters array.
+ * \param peer An external pointer to associate with this string.
+ * \param external_allocation_size The number of externally allocated
+ *   bytes for peer. Used to inform the garbage collector.
+ * \param callback A callback to be called when this string is finalized.
+ *
+ * \return The String object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle
+Dart_NewExternalLatin1String(const uint8_t* latin1_array,
+                             intptr_t length,
+                             void* peer,
+                             intptr_t external_allocation_size,
+                             Dart_HandleFinalizer callback);
+
+/**
+ * Returns a String which references an external array of UTF-16 encoded
+ * characters.
+ *
+ * \param utf16_array An array of UTF-16 encoded characters. This must not move.
+ * \param length The length of the characters array.
+ * \param peer An external pointer to associate with this string.
+ * \param external_allocation_size The number of externally allocated
+ *   bytes for peer. Used to inform the garbage collector.
+ * \param callback A callback to be called when this string is finalized.
+ *
+ * \return The String object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle
+Dart_NewExternalUTF16String(const uint16_t* utf16_array,
+                            intptr_t length,
+                            void* peer,
+                            intptr_t external_allocation_size,
+                            Dart_HandleFinalizer callback);
+
+/**
+ * Gets the C string representation of a String.
+ * (It is a sequence of UTF-8 encoded values with a '\0' termination.)
+ *
+ * \param str A string.
+ * \param cstr Returns the String represented as a C string.
+ *   This C string is scope allocated and is only valid until
+ *   the next call to Dart_ExitScope.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_StringToCString(Dart_Handle str,
+                                             const char** cstr);
+
+/**
+ * Gets a UTF-8 encoded representation of a String.
+ *
+ * Any unpaired surrogate code points in the string will be converted as
+ * replacement characters (U+FFFD, 0xEF 0xBF 0xBD in UTF-8). If you need
+ * to preserve unpaired surrogates, use the Dart_StringToUTF16 function.
+ *
+ * \param str A string.
+ * \param utf8_array Returns the String represented as UTF-8 code
+ *   units.  This UTF-8 array is scope allocated and is only valid
+ *   until the next call to Dart_ExitScope.
+ * \param length Used to return the length of the array which was
+ *   actually used.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_StringToUTF8(Dart_Handle str,
+                                          uint8_t** utf8_array,
+                                          intptr_t* length);
+
+/**
+ * Gets the data corresponding to the string object. This function returns
+ * the data only for Latin-1 (ISO-8859-1) string objects. For all other
+ * string objects it returns an error.
+ *
+ * \param str A string.
+ * \param latin1_array An array allocated by the caller, used to return
+ *   the string data.
+ * \param length Used to pass in the length of the provided array.
+ *   Used to return the length of the array which was actually used.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_StringToLatin1(Dart_Handle str,
+                                            uint8_t* latin1_array,
+                                            intptr_t* length);
+
+/**
+ * Gets the UTF-16 encoded representation of a string.
+ *
+ * \param str A string.
+ * \param utf16_array An array allocated by the caller, used to return
+ *   the array of UTF-16 encoded characters.
+ * \param length Used to pass in the length of the provided array.
+ *   Used to return the length of the array which was actually used.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_StringToUTF16(Dart_Handle str,
+                                           uint16_t* utf16_array,
+                                           intptr_t* length);
+
+/**
+ * Gets the storage size in bytes of a String.
+ *
+ * \param str A String.
+ * \param size Returns the storage size in bytes of the String.
+ *  This is the size in bytes needed to store the String.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_StringStorageSize(Dart_Handle str, intptr_t* size);
+
+/**
+ * Retrieves some properties associated with a String.
+ * Properties retrieved are:
+ * - character size of the string (one or two byte)
+ * - length of the string
+ * - peer pointer of string if it is an external string.
+ * \param str A String.
+ * \param char_size Returns the character size of the String.
+ * \param str_len Returns the length of the String.
+ * \param peer Returns the peer pointer associated with the String or 0 if
+ *   there is no peer pointer for it.
+ * \return Success if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_StringGetProperties(Dart_Handle str,
+                                                 intptr_t* char_size,
+                                                 intptr_t* str_len,
+                                                 void** peer);
+
+/*
+ * =====
+ * Lists
+ * =====
+ */
+
+/**
+ * Returns a List<dynamic> of the desired length.
+ *
+ * \param length The length of the list.
+ *
+ * \return The List object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewList(intptr_t length);
+
+typedef enum {
+  Dart_CoreType_Dynamic,
+  Dart_CoreType_Int,
+  Dart_CoreType_String,
+} Dart_CoreType_Id;
+
+// TODO(bkonyi): convert this to use nullable types once NNBD is enabled.
+/**
+ * Returns a List of the desired length with the desired legacy element type.
+ *
+ * \param element_type_id The type of elements of the list.
+ * \param length The length of the list.
+ *
+ * \return The List object if no error occurs. Otherwise returns an error
+ * handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewListOf(Dart_CoreType_Id element_type_id,
+                                       intptr_t length);
+
+/**
+ * Returns a List of the desired length with the desired element type.
+ *
+ * \param element_type Handle to a nullable type object. E.g., from
+ * Dart_GetType or Dart_GetNullableType.
+ *
+ * \param length The length of the list.
+ *
+ * \return The List object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewListOfType(Dart_Handle element_type,
+                                           intptr_t length);
+
+/**
+ * Returns a List of the desired length with the desired element type, filled
+ * with the provided object.
+ *
+ * \param element_type Handle to a type object. E.g., from Dart_GetType.
+ *
+ * \param fill_object Handle to an object of type 'element_type' that will be
+ * used to populate the list. This parameter can only be Dart_Null() if the
+ * length of the list is 0 or 'element_type' is a nullable type.
+ *
+ * \param length The length of the list.
+ *
+ * \return The List object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewListOfTypeFilled(Dart_Handle element_type,
+                                                 Dart_Handle fill_object,
+                                                 intptr_t length);
+
+/**
+ * Gets the length of a List.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param list A List.
+ * \param length Returns the length of the List.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_ListLength(Dart_Handle list, intptr_t* length);
+
+/**
+ * Gets the Object at some index of a List.
+ *
+ * If the index is out of bounds, an error occurs.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param list A List.
+ * \param index A valid index into the List.
+ *
+ * \return The Object in the List at the specified index if no error
+ *   occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_ListGetAt(Dart_Handle list, intptr_t index);
+
+/**
+* Gets a range of Objects from a List.
+*
+* If any of the requested index values are out of bounds, an error occurs.
+*
+* May generate an unhandled exception error.
+*
+* \param list A List.
+* \param offset The offset of the first item to get.
+* \param length The number of items to get.
+* \param result A pointer to fill with the objects.
+*
+* \return Success if no error occurs during the operation.
+*/
+DART_EXPORT Dart_Handle Dart_ListGetRange(Dart_Handle list,
+                                          intptr_t offset,
+                                          intptr_t length,
+                                          Dart_Handle* result);
+
+/**
+ * Sets the Object at some index of a List.
+ *
+ * If the index is out of bounds, an error occurs.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param list A List.
+ * \param index A valid index into the List.
+ * \param value The Object to put in the List.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT Dart_Handle Dart_ListSetAt(Dart_Handle list,
+                                       intptr_t index,
+                                       Dart_Handle value);
+
+/**
+ * May generate an unhandled exception error.
+ */
+DART_EXPORT Dart_Handle Dart_ListGetAsBytes(Dart_Handle list,
+                                            intptr_t offset,
+                                            uint8_t* native_array,
+                                            intptr_t length);
+
+/**
+ * May generate an unhandled exception error.
+ */
+DART_EXPORT Dart_Handle Dart_ListSetAsBytes(Dart_Handle list,
+                                            intptr_t offset,
+                                            const uint8_t* native_array,
+                                            intptr_t length);
+
+/*
+ * ====
+ * Maps
+ * ====
+ */
+
+/**
+ * Gets the Object at some key of a Map.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param map A Map.
+ * \param key An Object.
+ *
+ * \return The value in the map at the specified key, null if the map does not
+ *   contain the key, or an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_MapGetAt(Dart_Handle map, Dart_Handle key);
+
+/**
+ * Returns whether the Map contains a given key.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param map A Map.
+ *
+ * \return A handle on a boolean indicating whether map contains the key.
+ *   Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_MapContainsKey(Dart_Handle map, Dart_Handle key);
+
+/**
+ * Gets the list of keys of a Map.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param map A Map.
+ *
+ * \return The list of key Objects if no error occurs. Otherwise returns an
+ *   error handle.
+ */
+DART_EXPORT Dart_Handle Dart_MapKeys(Dart_Handle map);
+
+/*
+ * ==========
+ * Typed Data
+ * ==========
+ */
+
+typedef enum {
+  Dart_TypedData_kByteData = 0,
+  Dart_TypedData_kInt8,
+  Dart_TypedData_kUint8,
+  Dart_TypedData_kUint8Clamped,
+  Dart_TypedData_kInt16,
+  Dart_TypedData_kUint16,
+  Dart_TypedData_kInt32,
+  Dart_TypedData_kUint32,
+  Dart_TypedData_kInt64,
+  Dart_TypedData_kUint64,
+  Dart_TypedData_kFloat32,
+  Dart_TypedData_kFloat64,
+  Dart_TypedData_kInt32x4,
+  Dart_TypedData_kFloat32x4,
+  Dart_TypedData_kFloat64x2,
+  Dart_TypedData_kInvalid
+} Dart_TypedData_Type;
+
+/**
+ * Return type if this object is a TypedData object.
+ *
+ * \return kInvalid if the object is not a TypedData object or the appropriate
+ *   Dart_TypedData_Type.
+ */
+DART_EXPORT Dart_TypedData_Type Dart_GetTypeOfTypedData(Dart_Handle object);
+
+/**
+ * Return type if this object is an external TypedData object.
+ *
+ * \return kInvalid if the object is not an external TypedData object or
+ *   the appropriate Dart_TypedData_Type.
+ */
+DART_EXPORT Dart_TypedData_Type
+Dart_GetTypeOfExternalTypedData(Dart_Handle object);
+
+/**
+ * Returns a TypedData object of the desired length and type.
+ *
+ * \param type The type of the TypedData object.
+ * \param length The length of the TypedData object (length in type units).
+ *
+ * \return The TypedData object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewTypedData(Dart_TypedData_Type type,
+                                          intptr_t length);
+
+/**
+ * Returns a TypedData object which references an external data array.
+ *
+ * \param type The type of the data array.
+ * \param data A data array. This array must not move.
+ * \param length The length of the data array (length in type units).
+ *
+ * \return The TypedData object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewExternalTypedData(Dart_TypedData_Type type,
+                                                  void* data,
+                                                  intptr_t length);
+
+/**
+ * Returns a TypedData object which references an external data array.
+ *
+ * \param type The type of the data array.
+ * \param data A data array. This array must not move.
+ * \param length The length of the data array (length in type units).
+ * \param peer A pointer to a native object or NULL.  This value is
+ *   provided to callback when it is invoked.
+ * \param external_allocation_size The number of externally allocated
+ *   bytes for peer. Used to inform the garbage collector.
+ * \param callback A function pointer that will be invoked sometime
+ *   after the object is garbage collected, unless the handle has been deleted.
+ *   A valid callback needs to be specified it cannot be NULL.
+ *
+ * \return The TypedData object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle
+Dart_NewExternalTypedDataWithFinalizer(Dart_TypedData_Type type,
+                                       void* data,
+                                       intptr_t length,
+                                       void* peer,
+                                       intptr_t external_allocation_size,
+                                       Dart_HandleFinalizer callback);
+DART_EXPORT Dart_Handle Dart_NewUnmodifiableExternalTypedDataWithFinalizer(
+    Dart_TypedData_Type type,
+    const void* data,
+    intptr_t length,
+    void* peer,
+    intptr_t external_allocation_size,
+    Dart_HandleFinalizer callback);
+
+/**
+ * Returns a ByteBuffer object for the typed data.
+ *
+ * \param typed_data The TypedData object.
+ *
+ * \return The ByteBuffer object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewByteBuffer(Dart_Handle typed_data);
+
+/**
+ * Acquires access to the internal data address of a TypedData object.
+ *
+ * \param object The typed data object whose internal data address is to
+ *    be accessed.
+ * \param type The type of the object is returned here.
+ * \param data The internal data address is returned here.
+ * \param len Size of the typed array is returned here.
+ *
+ * Notes:
+ *   When the internal address of the object is acquired any calls to a
+ *   Dart API function that could potentially allocate an object or run
+ *   any Dart code will return an error.
+ *
+ *   Any Dart API functions for accessing the data should not be called
+ *   before the corresponding release. In particular, the object should
+ *   not be acquired again before its release. This leads to undefined
+ *   behavior.
+ *
+ * \return Success if the internal data address is acquired successfully.
+ *   Otherwise, returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_TypedDataAcquireData(Dart_Handle object,
+                                                  Dart_TypedData_Type* type,
+                                                  void** data,
+                                                  intptr_t* len);
+
+/**
+ * Releases access to the internal data address that was acquired earlier using
+ * Dart_TypedDataAcquireData.
+ *
+ * \param object The typed data object whose internal data address is to be
+ *   released.
+ *
+ * \return Success if the internal data address is released successfully.
+ *   Otherwise, returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_TypedDataReleaseData(Dart_Handle object);
+
+/**
+ * Returns the TypedData object associated with the ByteBuffer object.
+ *
+ * \param byte_buffer The ByteBuffer object.
+ *
+ * \return The TypedData object if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetDataFromByteBuffer(Dart_Handle byte_buffer);
+
+/*
+ * ============================================================
+ * Invoking Constructors, Methods, Closures and Field accessors
+ * ============================================================
+ */
+
+/**
+ * Invokes a constructor, creating a new object.
+ *
+ * This function allows hidden constructors (constructors with leading
+ * underscores) to be called.
+ *
+ * \param type Type of object to be constructed.
+ * \param constructor_name The name of the constructor to invoke.  Use
+ *   Dart_Null() or Dart_EmptyString() to invoke the unnamed constructor.
+ *   This name should not include the name of the class.
+ * \param number_of_arguments Size of the arguments array.
+ * \param arguments An array of arguments to the constructor.
+ *
+ * \return If the constructor is called and completes successfully,
+ *   then the new object. If an error occurs during execution, then an
+ *   error handle is returned.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_New(Dart_Handle type,
+         Dart_Handle constructor_name,
+         int number_of_arguments,
+         Dart_Handle* arguments);
+
+/**
+ * Allocate a new object without invoking a constructor.
+ *
+ * \param type The type of an object to be allocated.
+ *
+ * \return The new object. If an error occurs during execution, then an
+ *   error handle is returned.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_Allocate(Dart_Handle type);
+
+/**
+ * Allocate a new object without invoking a constructor, and sets specified
+ *  native fields.
+ *
+ * \param type The type of an object to be allocated.
+ * \param num_native_fields The number of native fields to set.
+ * \param native_fields An array containing the value of native fields.
+ *
+ * \return The new object. If an error occurs during execution, then an
+ *   error handle is returned.
+ */
+DART_EXPORT Dart_Handle
+Dart_AllocateWithNativeFields(Dart_Handle type,
+                              intptr_t num_native_fields,
+                              const intptr_t* native_fields);
+
+/**
+ * Invokes a method or function.
+ *
+ * The 'target' parameter may be an object, type, or library.  If
+ * 'target' is an object, then this function will invoke an instance
+ * method.  If 'target' is a type, then this function will invoke a
+ * static method.  If 'target' is a library, then this function will
+ * invoke a top-level function from that library.
+ * NOTE: This API call cannot be used to invoke methods of a type object.
+ *
+ * This function ignores visibility (leading underscores in names).
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param target An object, type, or library.
+ * \param name The name of the function or method to invoke.
+ * \param number_of_arguments Size of the arguments array.
+ * \param arguments An array of arguments to the function.
+ *
+ * \return If the function or method is called and completes
+ *   successfully, then the return value is returned. If an error
+ *   occurs during execution, then an error handle is returned.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_Invoke(Dart_Handle target,
+            Dart_Handle name,
+            int number_of_arguments,
+            Dart_Handle* arguments);
+/* TODO(turnidge): Document how to invoke operators. */
+
+/**
+ * Invokes a Closure with the given arguments.
+ *
+ * May generate an unhandled exception error.
+ *
+ * \return If no error occurs during execution, then the result of
+ *   invoking the closure is returned. If an error occurs during
+ *   execution, then an error handle is returned.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_InvokeClosure(Dart_Handle closure,
+                   int number_of_arguments,
+                   Dart_Handle* arguments);
+
+/**
+ * Invokes a Generative Constructor on an object that was previously
+ * allocated using Dart_Allocate/Dart_AllocateWithNativeFields.
+ *
+ * The 'object' parameter must be an object.
+ *
+ * This function ignores visibility (leading underscores in names).
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param object An object.
+ * \param name The name of the constructor to invoke.
+ *   Use Dart_Null() or Dart_EmptyString() to invoke the unnamed constructor.
+ * \param number_of_arguments Size of the arguments array.
+ * \param arguments An array of arguments to the function.
+ *
+ * \return If the constructor is called and completes
+ *   successfully, then the object is returned. If an error
+ *   occurs during execution, then an error handle is returned.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_InvokeConstructor(Dart_Handle object,
+                       Dart_Handle name,
+                       int number_of_arguments,
+                       Dart_Handle* arguments);
+
+/**
+ * Gets the value of a field.
+ *
+ * The 'container' parameter may be an object, type, or library.  If
+ * 'container' is an object, then this function will access an
+ * instance field.  If 'container' is a type, then this function will
+ * access a static field.  If 'container' is a library, then this
+ * function will access a top-level variable.
+ * NOTE: This API call cannot be used to access fields of a type object.
+ *
+ * This function ignores field visibility (leading underscores in names).
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param container An object, type, or library.
+ * \param name A field name.
+ *
+ * \return If no error occurs, then the value of the field is
+ *   returned. Otherwise an error handle is returned.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_GetField(Dart_Handle container, Dart_Handle name);
+
+/**
+ * Sets the value of a field.
+ *
+ * The 'container' parameter may actually be an object, type, or
+ * library.  If 'container' is an object, then this function will
+ * access an instance field.  If 'container' is a type, then this
+ * function will access a static field.  If 'container' is a library,
+ * then this function will access a top-level variable.
+ * NOTE: This API call cannot be used to access fields of a type object.
+ *
+ * This function ignores field visibility (leading underscores in names).
+ *
+ * May generate an unhandled exception error.
+ *
+ * \param container An object, type, or library.
+ * \param name A field name.
+ * \param value The new field value.
+ *
+ * \return A valid handle if no error occurs.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_SetField(Dart_Handle container, Dart_Handle name, Dart_Handle value);
+
+/*
+ * ==========
+ * Exceptions
+ * ==========
+ */
+
+/*
+ * TODO(turnidge): Remove these functions from the api and replace all
+ * uses with Dart_NewUnhandledExceptionError. */
+
+/**
+ * Throws an exception.
+ *
+ * This function causes a Dart language exception to be thrown. This
+ * will proceed in the standard way, walking up Dart frames until an
+ * appropriate 'catch' block is found, executing 'finally' blocks,
+ * etc.
+ *
+ * If an error handle is passed into this function, the error is
+ * propagated immediately.  See Dart_PropagateError for a discussion
+ * of error propagation.
+ *
+ * If successful, this function does not return. Note that this means
+ * that the destructors of any stack-allocated C++ objects will not be
+ * called. If there are no Dart frames on the stack, an error occurs.
+ *
+ * \return An error handle if the exception was not thrown.
+ *   Otherwise the function does not return.
+ */
+DART_EXPORT Dart_Handle Dart_ThrowException(Dart_Handle exception);
+
+/**
+ * Rethrows an exception.
+ *
+ * Rethrows an exception, unwinding all dart frames on the stack. If
+ * successful, this function does not return. Note that this means
+ * that the destructors of any stack-allocated C++ objects will not be
+ * called. If there are no Dart frames on the stack, an error occurs.
+ *
+ * \return An error handle if the exception was not thrown.
+ *   Otherwise the function does not return.
+ */
+DART_EXPORT Dart_Handle Dart_ReThrowException(Dart_Handle exception,
+                                              Dart_Handle stacktrace);
+
+/*
+ * ===========================
+ * Native fields and functions
+ * ===========================
+ */
+
+/**
+ * Gets the number of native instance fields in an object.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeInstanceFieldCount(Dart_Handle obj,
+                                                         int* count);
+
+/**
+ * Gets the value of a native field.
+ *
+ * TODO(turnidge): Document.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeInstanceField(Dart_Handle obj,
+                                                    int index,
+                                                    intptr_t* value);
+
+/**
+ * Sets the value of a native field.
+ *
+ * TODO(turnidge): Document.
+ */
+DART_EXPORT Dart_Handle Dart_SetNativeInstanceField(Dart_Handle obj,
+                                                    int index,
+                                                    intptr_t value);
+
+/**
+ * The arguments to a native function.
+ *
+ * This object is passed to a native function to represent its
+ * arguments and return value. It allows access to the arguments to a
+ * native function by index. It also allows the return value of a
+ * native function to be set.
+ */
+typedef struct _Dart_NativeArguments* Dart_NativeArguments;
+
+/**
+ * Extracts current isolate group data from the native arguments structure.
+ */
+DART_EXPORT void* Dart_GetNativeIsolateGroupData(Dart_NativeArguments args);
+
+typedef enum {
+  Dart_NativeArgument_kBool = 0,
+  Dart_NativeArgument_kInt32,
+  Dart_NativeArgument_kUint32,
+  Dart_NativeArgument_kInt64,
+  Dart_NativeArgument_kUint64,
+  Dart_NativeArgument_kDouble,
+  Dart_NativeArgument_kString,
+  Dart_NativeArgument_kInstance,
+  Dart_NativeArgument_kNativeFields,
+} Dart_NativeArgument_Type;
+
+typedef struct _Dart_NativeArgument_Descriptor {
+  uint8_t type;
+  uint8_t index;
+} Dart_NativeArgument_Descriptor;
+
+typedef union _Dart_NativeArgument_Value {
+  bool as_bool;
+  int32_t as_int32;
+  uint32_t as_uint32;
+  int64_t as_int64;
+  uint64_t as_uint64;
+  double as_double;
+  struct {
+    Dart_Handle dart_str;
+    void* peer;
+  } as_string;
+  struct {
+    intptr_t num_fields;
+    intptr_t* values;
+  } as_native_fields;
+  Dart_Handle as_instance;
+} Dart_NativeArgument_Value;
+
+enum {
+  kNativeArgNumberPos = 0,
+  kNativeArgNumberSize = 8,
+  kNativeArgTypePos = kNativeArgNumberPos + kNativeArgNumberSize,
+  kNativeArgTypeSize = 8,
+};
+
+#define BITMASK(size) ((1 << size) - 1)
+#define DART_NATIVE_ARG_DESCRIPTOR(type, position)                             \
+  (((type & BITMASK(kNativeArgTypeSize)) << kNativeArgTypePos) |               \
+   (position & BITMASK(kNativeArgNumberSize)))
+
+/**
+ * Gets the native arguments based on the types passed in and populates
+ * the passed arguments buffer with appropriate native values.
+ *
+ * \param args the Native arguments block passed into the native call.
+ * \param num_arguments length of argument descriptor array and argument
+ *   values array passed in.
+ * \param arg_descriptors an array that describes the arguments that
+ *   need to be retrieved. For each argument to be retrieved the descriptor
+ *   contains the argument number (0, 1 etc.) and the argument type
+ *   described using Dart_NativeArgument_Type, e.g:
+ *   DART_NATIVE_ARG_DESCRIPTOR(Dart_NativeArgument_kBool, 1) indicates
+ *   that the first argument is to be retrieved and it should be a boolean.
+ * \param arg_values array into which the native arguments need to be
+ *   extracted into, the array is allocated by the caller (it could be
+ *   stack allocated to avoid the malloc/free performance overhead).
+ *
+ * \return Success if all the arguments could be extracted correctly,
+ *   returns an error handle if there were any errors while extracting the
+ *   arguments (mismatched number of arguments, incorrect types, etc.).
+ */
+DART_EXPORT Dart_Handle
+Dart_GetNativeArguments(Dart_NativeArguments args,
+                        int num_arguments,
+                        const Dart_NativeArgument_Descriptor* arg_descriptors,
+                        Dart_NativeArgument_Value* arg_values);
+
+/**
+ * Gets the native argument at some index.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeArgument(Dart_NativeArguments args,
+                                               int index);
+/* TODO(turnidge): Specify the behavior of an out-of-bounds access. */
+
+/**
+ * Gets the number of native arguments.
+ */
+DART_EXPORT int Dart_GetNativeArgumentCount(Dart_NativeArguments args);
+
+/**
+ * Gets all the native fields of the native argument at some index.
+ * \param args Native arguments structure.
+ * \param arg_index Index of the desired argument in the structure above.
+ * \param num_fields size of the intptr_t array 'field_values' passed in.
+ * \param field_values intptr_t array in which native field values are returned.
+ * \return Success if the native fields where copied in successfully. Otherwise
+ *   returns an error handle. On success the native field values are copied
+ *   into the 'field_values' array, if the argument at 'arg_index' is a
+ *   null object then 0 is copied as the native field values into the
+ *   'field_values' array.
+ */
+DART_EXPORT Dart_Handle
+Dart_GetNativeFieldsOfArgument(Dart_NativeArguments args,
+                               int arg_index,
+                               int num_fields,
+                               intptr_t* field_values);
+
+/**
+ * Gets the native field of the receiver.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeReceiver(Dart_NativeArguments args,
+                                               intptr_t* value);
+
+/**
+ * Gets a string native argument at some index.
+ * \param args Native arguments structure.
+ * \param arg_index Index of the desired argument in the structure above.
+ * \param peer Returns the peer pointer if the string argument has one.
+ * \return Success if the string argument has a peer, if it does not
+ *   have a peer then the String object is returned. Otherwise returns
+ *   an error handle (argument is not a String object).
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeStringArgument(Dart_NativeArguments args,
+                                                     int arg_index,
+                                                     void** peer);
+
+/**
+ * Gets an integer native argument at some index.
+ * \param args Native arguments structure.
+ * \param index Index of the desired argument in the structure above.
+ * \param value Returns the integer value if the argument is an Integer.
+ * \return Success if no error occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeIntegerArgument(Dart_NativeArguments args,
+                                                      int index,
+                                                      int64_t* value);
+
+/**
+ * Gets a boolean native argument at some index.
+ * \param args Native arguments structure.
+ * \param index Index of the desired argument in the structure above.
+ * \param value Returns the boolean value if the argument is a Boolean.
+ * \return Success if no error occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeBooleanArgument(Dart_NativeArguments args,
+                                                      int index,
+                                                      bool* value);
+
+/**
+ * Gets a double native argument at some index.
+ * \param args Native arguments structure.
+ * \param index Index of the desired argument in the structure above.
+ * \param value Returns the double value if the argument is a double.
+ * \return Success if no error occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeDoubleArgument(Dart_NativeArguments args,
+                                                     int index,
+                                                     double* value);
+
+/**
+ * Sets the return value for a native function.
+ *
+ * If retval is an Error handle, then error will be propagated once
+ * the native functions exits. See Dart_PropagateError for a
+ * discussion of how different types of errors are propagated.
+ */
+DART_EXPORT void Dart_SetReturnValue(Dart_NativeArguments args,
+                                     Dart_Handle retval);
+
+DART_EXPORT void Dart_SetWeakHandleReturnValue(Dart_NativeArguments args,
+                                               Dart_WeakPersistentHandle rval);
+
+DART_EXPORT void Dart_SetBooleanReturnValue(Dart_NativeArguments args,
+                                            bool retval);
+
+DART_EXPORT void Dart_SetIntegerReturnValue(Dart_NativeArguments args,
+                                            int64_t retval);
+
+DART_EXPORT void Dart_SetDoubleReturnValue(Dart_NativeArguments args,
+                                           double retval);
+
+/**
+ * A native function.
+ */
+typedef void (*Dart_NativeFunction)(Dart_NativeArguments arguments);
+
+/**
+ * Native entry resolution callback.
+ *
+ * For libraries and scripts which have native functions, the embedder
+ * can provide a native entry resolver. This callback is used to map a
+ * name/arity to a Dart_NativeFunction. If no function is found, the
+ * callback should return NULL.
+ *
+ * The parameters to the native resolver function are:
+ * \param name a Dart string which is the name of the native function.
+ * \param num_of_arguments is the number of arguments expected by the
+ *   native function.
+ * \param auto_setup_scope is a boolean flag that can be set by the resolver
+ *   to indicate if this function needs a Dart API scope (see Dart_EnterScope/
+ *   Dart_ExitScope) to be setup automatically by the VM before calling into
+ *   the native function. By default most native functions would require this
+ *   to be true but some light weight native functions which do not call back
+ *   into the VM through the Dart API may not require a Dart scope to be
+ *   setup automatically.
+ *
+ * \return A valid Dart_NativeFunction which resolves to a native entry point
+ *   for the native function.
+ *
+ * See Dart_SetNativeResolver.
+ */
+typedef Dart_NativeFunction (*Dart_NativeEntryResolver)(Dart_Handle name,
+                                                        int num_of_arguments,
+                                                        bool* auto_setup_scope);
+/* TODO(turnidge): Consider renaming to NativeFunctionResolver or
+ * NativeResolver. */
+
+/**
+ * Native entry symbol lookup callback.
+ *
+ * For libraries and scripts which have native functions, the embedder
+ * can provide a callback for mapping a native entry to a symbol. This callback
+ * maps a native function entry PC to the native function name. If no native
+ * entry symbol can be found, the callback should return NULL.
+ *
+ * The parameters to the native reverse resolver function are:
+ * \param nf A Dart_NativeFunction.
+ *
+ * \return A const UTF-8 string containing the symbol name or NULL.
+ *
+ * See Dart_SetNativeResolver.
+ */
+typedef const uint8_t* (*Dart_NativeEntrySymbol)(Dart_NativeFunction nf);
+
+/**
+ * FFI Native C function pointer resolver callback.
+ *
+ * See Dart_SetFfiNativeResolver.
+ */
+typedef void* (*Dart_FfiNativeResolver)(const char* name, uintptr_t args_n);
+
+/*
+ * ===========
+ * Environment
+ * ===========
+ */
+
+/**
+ * An environment lookup callback function.
+ *
+ * \param name The name of the value to lookup in the environment.
+ *
+ * \return A valid handle to a string if the name exists in the
+ * current environment or Dart_Null() if not.
+ */
+typedef Dart_Handle (*Dart_EnvironmentCallback)(Dart_Handle name);
+
+/**
+ * Sets the environment callback for the current isolate. This
+ * callback is used to lookup environment values by name in the
+ * current environment. This enables the embedder to supply values for
+ * the const constructors bool.fromEnvironment, int.fromEnvironment
+ * and String.fromEnvironment.
+ */
+DART_EXPORT Dart_Handle
+Dart_SetEnvironmentCallback(Dart_EnvironmentCallback callback);
+
+/**
+ * Sets the callback used to resolve native functions for a library.
+ *
+ * \param library A library.
+ * \param resolver A native entry resolver.
+ *
+ * \return A valid handle if the native resolver was set successfully.
+ */
+DART_EXPORT Dart_Handle
+Dart_SetNativeResolver(Dart_Handle library,
+                       Dart_NativeEntryResolver resolver,
+                       Dart_NativeEntrySymbol symbol);
+/* TODO(turnidge): Rename to Dart_LibrarySetNativeResolver? */
+
+/**
+ * Returns the callback used to resolve native functions for a library.
+ *
+ * \param library A library.
+ * \param resolver a pointer to a Dart_NativeEntryResolver
+ *
+ * \return A valid handle if the library was found.
+ */
+DART_EXPORT Dart_Handle
+Dart_GetNativeResolver(Dart_Handle library, Dart_NativeEntryResolver* resolver);
+
+/**
+ * Returns the callback used to resolve native function symbols for a library.
+ *
+ * \param library A library.
+ * \param resolver a pointer to a Dart_NativeEntrySymbol.
+ *
+ * \return A valid handle if the library was found.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeSymbol(Dart_Handle library,
+                                             Dart_NativeEntrySymbol* resolver);
+
+/**
+ * Sets the callback used to resolve FFI native functions for a library.
+ * The resolved functions are expected to be a C function pointer of the
+ * correct signature (as specified in the `@FfiNative<NFT>()` function
+ * annotation in Dart code).
+ *
+ * NOTE: This is an experimental feature and might change in the future.
+ *
+ * \param library A library.
+ * \param resolver A native function resolver.
+ *
+ * \return A valid handle if the native resolver was set successfully.
+ */
+DART_EXPORT Dart_Handle
+Dart_SetFfiNativeResolver(Dart_Handle library, Dart_FfiNativeResolver resolver);
+
+/*
+ * =====================
+ * Scripts and Libraries
+ * =====================
+ */
+
+typedef enum {
+  Dart_kCanonicalizeUrl = 0,
+  Dart_kImportTag,
+  Dart_kKernelTag,
+} Dart_LibraryTag;
+
+/**
+ * The library tag handler is a multi-purpose callback provided by the
+ * embedder to the Dart VM. The embedder implements the tag handler to
+ * provide the ability to load Dart scripts and imports.
+ *
+ * -- TAGS --
+ *
+ * Dart_kCanonicalizeUrl
+ *
+ * This tag indicates that the embedder should canonicalize 'url' with
+ * respect to 'library'.  For most embedders, the
+ * Dart_DefaultCanonicalizeUrl function is a sufficient implementation
+ * of this tag.  The return value should be a string holding the
+ * canonicalized url.
+ *
+ * Dart_kImportTag
+ *
+ * This tag is used to load a library from IsolateMirror.loadUri. The embedder
+ * should call Dart_LoadLibraryFromKernel to provide the library to the VM. The
+ * return value should be an error or library (the result from
+ * Dart_LoadLibraryFromKernel).
+ *
+ * Dart_kKernelTag
+ *
+ * This tag is used to load the intermediate file (kernel) generated by
+ * the Dart front end. This tag is typically used when a 'hot-reload'
+ * of an application is needed and the VM is 'use dart front end' mode.
+ * The dart front end typically compiles all the scripts, imports and part
+ * files into one intermediate file hence we don't use the source/import or
+ * script tags. The return value should be an error or a TypedData containing
+ * the kernel bytes.
+ *
+ */
+typedef Dart_Handle (*Dart_LibraryTagHandler)(
+    Dart_LibraryTag tag,
+    Dart_Handle library_or_package_map_url,
+    Dart_Handle url);
+
+/**
+ * Sets library tag handler for the current isolate. This handler is
+ * used to handle the various tags encountered while loading libraries
+ * or scripts in the isolate.
+ *
+ * \param handler Handler code to be used for handling the various tags
+ *   encountered while loading libraries or scripts in the isolate.
+ *
+ * \return If no error occurs, the handler is set for the isolate.
+ *   Otherwise an error handle is returned.
+ *
+ * TODO(turnidge): Document.
+ */
+DART_EXPORT Dart_Handle
+Dart_SetLibraryTagHandler(Dart_LibraryTagHandler handler);
+
+/**
+ * Handles deferred loading requests. When this handler is invoked, it should
+ * eventually load the deferred loading unit with the given id and call
+ * Dart_DeferredLoadComplete or Dart_DeferredLoadCompleteError. It is
+ * recommended that the loading occur asynchronously, but it is permitted to
+ * call Dart_DeferredLoadComplete or Dart_DeferredLoadCompleteError before the
+ * handler returns.
+ *
+ * If an error is returned, it will be propagated through
+ * `prefix.loadLibrary()`. This is useful for synchronous
+ * implementations, which must propagate any unwind errors from
+ * Dart_DeferredLoadComplete or Dart_DeferredLoadComplete. Otherwise the handler
+ * should return a non-error such as `Dart_Null()`.
+ */
+typedef Dart_Handle (*Dart_DeferredLoadHandler)(intptr_t loading_unit_id);
+
+/**
+ * Sets the deferred load handler for the current isolate. This handler is
+ * used to handle loading deferred imports in an AppJIT or AppAOT program.
+ */
+DART_EXPORT Dart_Handle
+Dart_SetDeferredLoadHandler(Dart_DeferredLoadHandler handler);
+
+/**
+ * Notifies the VM that a deferred load completed successfully. This function
+ * will eventually cause the corresponding `prefix.loadLibrary()` futures to
+ * complete.
+ *
+ * Requires the current isolate to be the same current isolate during the
+ * invocation of the Dart_DeferredLoadHandler.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_DeferredLoadComplete(intptr_t loading_unit_id,
+                          const uint8_t* snapshot_data,
+                          const uint8_t* snapshot_instructions);
+
+/**
+ * Notifies the VM that a deferred load failed. This function
+ * will eventually cause the corresponding `prefix.loadLibrary()` futures to
+ * complete with an error.
+ *
+ * If `transient` is true, future invocations of `prefix.loadLibrary()` will
+ * trigger new load requests. If false, futures invocation will complete with
+ * the same error.
+ *
+ * Requires the current isolate to be the same current isolate during the
+ * invocation of the Dart_DeferredLoadHandler.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_DeferredLoadCompleteError(intptr_t loading_unit_id,
+                               const char* error_message,
+                               bool transient);
+
+/**
+ * Canonicalizes a url with respect to some library.
+ *
+ * The url is resolved with respect to the library's url and some url
+ * normalizations are performed.
+ *
+ * This canonicalization function should be sufficient for most
+ * embedders to implement the Dart_kCanonicalizeUrl tag.
+ *
+ * \param base_url The base url relative to which the url is
+ *                being resolved.
+ * \param url The url being resolved and canonicalized.  This
+ *            parameter is a string handle.
+ *
+ * \return If no error occurs, a String object is returned.  Otherwise
+ *   an error handle is returned.
+ */
+DART_EXPORT Dart_Handle Dart_DefaultCanonicalizeUrl(Dart_Handle base_url,
+                                                    Dart_Handle url);
+
+/**
+ * Loads the root library for the current isolate.
+ *
+ * Requires there to be no current root library.
+ *
+ * \param kernel_buffer A buffer which contains a kernel binary (see
+ *     pkg/kernel/binary.md). Must remain valid until isolate group shutdown.
+ * \param kernel_size Length of the passed in buffer.
+ *
+ * \return A handle to the root library, or an error.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_LoadScriptFromKernel(const uint8_t* kernel_buffer, intptr_t kernel_size);
+
+/**
+ * Gets the library for the root script for the current isolate.
+ *
+ * If the root script has not yet been set for the current isolate,
+ * this function returns Dart_Null().  This function never returns an
+ * error handle.
+ *
+ * \return Returns the root Library for the current isolate or Dart_Null().
+ */
+DART_EXPORT Dart_Handle Dart_RootLibrary(void);
+
+/**
+ * Sets the root library for the current isolate.
+ *
+ * \return Returns an error handle if `library` is not a library handle.
+ */
+DART_EXPORT Dart_Handle Dart_SetRootLibrary(Dart_Handle library);
+
+/**
+ * Lookup or instantiate a legacy type by name and type arguments from a
+ * Library.
+ *
+ * \param library The library containing the class or interface.
+ * \param class_name The class name for the type.
+ * \param number_of_type_arguments Number of type arguments.
+ *   For non parametric types the number of type arguments would be 0.
+ * \param type_arguments Pointer to an array of type arguments.
+ *   For non parametric types a NULL would be passed in for this argument.
+ *
+ * \return If no error occurs, the type is returned.
+ *   Otherwise an error handle is returned.
+ */
+DART_EXPORT Dart_Handle Dart_GetType(Dart_Handle library,
+                                     Dart_Handle class_name,
+                                     intptr_t number_of_type_arguments,
+                                     Dart_Handle* type_arguments);
+
+/**
+ * Lookup or instantiate a nullable type by name and type arguments from
+ * Library.
+ *
+ * \param library The library containing the class or interface.
+ * \param class_name The class name for the type.
+ * \param number_of_type_arguments Number of type arguments.
+ *   For non parametric types the number of type arguments would be 0.
+ * \param type_arguments Pointer to an array of type arguments.
+ *   For non parametric types a NULL would be passed in for this argument.
+ *
+ * \return If no error occurs, the type is returned.
+ *   Otherwise an error handle is returned.
+ */
+DART_EXPORT Dart_Handle Dart_GetNullableType(Dart_Handle library,
+                                             Dart_Handle class_name,
+                                             intptr_t number_of_type_arguments,
+                                             Dart_Handle* type_arguments);
+
+/**
+ * Lookup or instantiate a non-nullable type by name and type arguments from
+ * Library.
+ *
+ * \param library The library containing the class or interface.
+ * \param class_name The class name for the type.
+ * \param number_of_type_arguments Number of type arguments.
+ *   For non parametric types the number of type arguments would be 0.
+ * \param type_arguments Pointer to an array of type arguments.
+ *   For non parametric types a NULL would be passed in for this argument.
+ *
+ * \return If no error occurs, the type is returned.
+ *   Otherwise an error handle is returned.
+ */
+DART_EXPORT Dart_Handle
+Dart_GetNonNullableType(Dart_Handle library,
+                        Dart_Handle class_name,
+                        intptr_t number_of_type_arguments,
+                        Dart_Handle* type_arguments);
+
+/**
+ * Creates a nullable version of the provided type.
+ *
+ * \param type The type to be converted to a nullable type.
+ *
+ * \return If no error occurs, a nullable type is returned.
+ *   Otherwise an error handle is returned.
+ */
+DART_EXPORT Dart_Handle Dart_TypeToNullableType(Dart_Handle type);
+
+/**
+ * Creates a non-nullable version of the provided type.
+ *
+ * \param type The type to be converted to a non-nullable type.
+ *
+ * \return If no error occurs, a non-nullable type is returned.
+ *   Otherwise an error handle is returned.
+ */
+DART_EXPORT Dart_Handle Dart_TypeToNonNullableType(Dart_Handle type);
+
+/**
+ * A type's nullability.
+ *
+ * \param type A Dart type.
+ * \param result An out parameter containing the result of the check. True if
+ * the type is of the specified nullability, false otherwise.
+ *
+ * \return Returns an error handle if type is not of type Type.
+ */
+DART_EXPORT Dart_Handle Dart_IsNullableType(Dart_Handle type, bool* result);
+DART_EXPORT Dart_Handle Dart_IsNonNullableType(Dart_Handle type, bool* result);
+DART_EXPORT Dart_Handle Dart_IsLegacyType(Dart_Handle type, bool* result);
+
+/**
+ * Lookup a class or interface by name from a Library.
+ *
+ * \param library The library containing the class or interface.
+ * \param class_name The name of the class or interface.
+ *
+ * \return If no error occurs, the class or interface is
+ *   returned. Otherwise an error handle is returned.
+ */
+DART_EXPORT Dart_Handle Dart_GetClass(Dart_Handle library,
+                                      Dart_Handle class_name);
+/* TODO(asiva): The above method needs to be removed once all uses
+ * of it are removed from the embedder code. */
+
+/**
+ * Returns an import path to a Library, such as "file:///test.dart" or
+ * "dart:core".
+ */
+DART_EXPORT Dart_Handle Dart_LibraryUrl(Dart_Handle library);
+
+/**
+ * Returns a URL from which a Library was loaded.
+ */
+DART_EXPORT Dart_Handle Dart_LibraryResolvedUrl(Dart_Handle library);
+
+/**
+ * \return An array of libraries.
+ */
+DART_EXPORT Dart_Handle Dart_GetLoadedLibraries(void);
+
+DART_EXPORT Dart_Handle Dart_LookupLibrary(Dart_Handle url);
+/* TODO(turnidge): Consider returning Dart_Null() when the library is
+ * not found to distinguish that from a true error case. */
+
+/**
+ * Report an loading error for the library.
+ *
+ * \param library The library that failed to load.
+ * \param error The Dart error instance containing the load error.
+ *
+ * \return If the VM handles the error, the return value is
+ * a null handle. If it doesn't handle the error, the error
+ * object is returned.
+ */
+DART_EXPORT Dart_Handle Dart_LibraryHandleError(Dart_Handle library,
+                                                Dart_Handle error);
+
+/**
+ * Called by the embedder to load a partial program. Does not set the root
+ * library.
+ *
+ * \param kernel_buffer A buffer which contains a kernel binary (see
+ *     pkg/kernel/binary.md). Must remain valid until isolate shutdown.
+ * \param kernel_buffer_size Length of the passed in buffer.
+ *
+ * \return A handle to the main library of the compilation unit, or an error.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_LoadLibraryFromKernel(const uint8_t* kernel_buffer,
+                           intptr_t kernel_buffer_size);
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_LoadLibrary(Dart_Handle kernel_buffer);
+
+/**
+ * Indicates that all outstanding load requests have been satisfied.
+ * This finalizes all the new classes loaded and optionally completes
+ * deferred library futures.
+ *
+ * Requires there to be a current isolate.
+ *
+ * \param complete_futures Specify true if all deferred library
+ *  futures should be completed, false otherwise.
+ *
+ * \return Success if all classes have been finalized and deferred library
+ *   futures are completed. Otherwise, returns an error.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_FinalizeLoading(bool complete_futures);
+
+/*
+ * =====
+ * Peers
+ * =====
+ */
+
+/**
+ * The peer field is a lazily allocated field intended for storage of
+ * an uncommonly used values.  Most instances types can have a peer
+ * field allocated.  The exceptions are subtypes of Null, num, and
+ * bool.
+ */
+
+/**
+ * Returns the value of peer field of 'object' in 'peer'.
+ *
+ * \param object An object.
+ * \param peer An out parameter that returns the value of the peer
+ *   field.
+ *
+ * \return Returns an error if 'object' is a subtype of Null, num, or
+ *   bool.
+ */
+DART_EXPORT Dart_Handle Dart_GetPeer(Dart_Handle object, void** peer);
+
+/**
+ * Sets the value of the peer field of 'object' to the value of
+ * 'peer'.
+ *
+ * \param object An object.
+ * \param peer A value to store in the peer field.
+ *
+ * \return Returns an error if 'object' is a subtype of Null, num, or
+ *   bool.
+ */
+DART_EXPORT Dart_Handle Dart_SetPeer(Dart_Handle object, void* peer);
+
+/*
+ * ======
+ * Kernel
+ * ======
+ */
+
+/**
+ * Experimental support for Dart to Kernel parser isolate.
+ *
+ * TODO(hausner): Document finalized interface.
+ *
+ */
+
+// TODO(33433): Remove kernel service from the embedding API.
+
+typedef enum {
+  Dart_KernelCompilationStatus_Unknown = -1,
+  Dart_KernelCompilationStatus_Ok = 0,
+  Dart_KernelCompilationStatus_Error = 1,
+  Dart_KernelCompilationStatus_Crash = 2,
+  Dart_KernelCompilationStatus_MsgFailed = 3,
+} Dart_KernelCompilationStatus;
+
+typedef struct {
+  Dart_KernelCompilationStatus status;
+  bool null_safety;
+  char* error;
+  uint8_t* kernel;
+  intptr_t kernel_size;
+} Dart_KernelCompilationResult;
+
+typedef enum {
+  Dart_KernelCompilationVerbosityLevel_Error = 0,
+  Dart_KernelCompilationVerbosityLevel_Warning,
+  Dart_KernelCompilationVerbosityLevel_Info,
+  Dart_KernelCompilationVerbosityLevel_All,
+} Dart_KernelCompilationVerbosityLevel;
+
+DART_EXPORT bool Dart_IsKernelIsolate(Dart_Isolate isolate);
+DART_EXPORT bool Dart_KernelIsolateIsRunning(void);
+DART_EXPORT Dart_Port Dart_KernelPort(void);
+
+/**
+ * Compiles the given `script_uri` to a kernel file.
+ *
+ * \param platform_kernel A buffer containing the kernel of the platform (e.g.
+ * `vm_platform_strong.dill`). The VM does not take ownership of this memory.
+ *
+ * \param platform_kernel_size The length of the platform_kernel buffer.
+ *
+ * \param snapshot_compile Set to `true` when the compilation is for a snapshot.
+ * This is used by the frontend to determine if compilation related information
+ * should be printed to console (e.g., null safety mode).
+ *
+ * \param verbosity Specifies the logging behavior of the kernel compilation
+ * service.
+ *
+ * \return Returns the result of the compilation.
+ *
+ * On a successful compilation the returned [Dart_KernelCompilationResult] has
+ * a status of [Dart_KernelCompilationStatus_Ok] and the `kernel`/`kernel_size`
+ * fields are set. The caller takes ownership of the malloc()ed buffer.
+ *
+ * On a failed compilation the `error` might be set describing the reason for
+ * the failed compilation. The caller takes ownership of the malloc()ed
+ * error.
+ *
+ * Requires there to be a current isolate.
+ */
+DART_EXPORT Dart_KernelCompilationResult
+Dart_CompileToKernel(const char* script_uri,
+                     const uint8_t* platform_kernel,
+                     const intptr_t platform_kernel_size,
+                     bool incremental_compile,
+                     bool snapshot_compile,
+                     const char* package_config,
+                     Dart_KernelCompilationVerbosityLevel verbosity);
+
+typedef struct {
+  const char* uri;
+  const char* source;
+} Dart_SourceFile;
+
+DART_EXPORT Dart_KernelCompilationResult Dart_KernelListDependencies(void);
+
+/**
+ * Sets the kernel buffer which will be used to load Dart SDK sources
+ * dynamically at runtime.
+ *
+ * \param platform_kernel A buffer containing kernel which has sources for the
+ * Dart SDK populated. Note: The VM does not take ownership of this memory.
+ *
+ * \param platform_kernel_size The length of the platform_kernel buffer.
+ */
+DART_EXPORT void Dart_SetDartLibrarySourcesKernel(
+    const uint8_t* platform_kernel,
+    const intptr_t platform_kernel_size);
+
+/**
+ * Detect the null safety opt-in status.
+ *
+ * When running from source, it is based on the opt-in status of `script_uri`.
+ * When running from a kernel buffer, it is based on the mode used when
+ *   generating `kernel_buffer`.
+ * When running from an appJIT or AOT snapshot, it is based on the mode used
+ *   when generating `snapshot_data`.
+ *
+ * \param script_uri Uri of the script that contains the source code
+ *
+ * \param package_config Uri of the package configuration file (either in format
+ *   of .packages or .dart_tool/package_config.json) for the null safety
+ *   detection to resolve package imports against. If this parameter is not
+ *   passed the package resolution of the parent isolate should be used.
+ *
+ * \param original_working_directory current working directory when the VM
+ *   process was launched, this is used to correctly resolve the path specified
+ *   for package_config.
+ *
+ * \param snapshot_data Buffer containing the snapshot data of the
+ *   isolate or NULL if no snapshot is provided. If provided, the buffers must
+ *   remain valid until the isolate shuts down.
+ *
+ * \param snapshot_instructions Buffer containing the snapshot instructions of
+ *   the isolate or NULL if no snapshot is provided. If provided, the buffers
+ *   must remain valid until the isolate shuts down.
+ *
+ * \param kernel_buffer A buffer which contains a kernel/DIL program. Must
+ *   remain valid until isolate shutdown.
+ *
+ * \param kernel_buffer_size The size of `kernel_buffer`.
+ *
+ * \return Returns true if the null safety is opted in by the input being
+ *   run `script_uri`, `snapshot_data` or `kernel_buffer`.
+ *
+ */
+DART_EXPORT bool Dart_DetectNullSafety(const char* script_uri,
+                                       const char* package_config,
+                                       const char* original_working_directory,
+                                       const uint8_t* snapshot_data,
+                                       const uint8_t* snapshot_instructions,
+                                       const uint8_t* kernel_buffer,
+                                       intptr_t kernel_buffer_size);
+
+#define DART_KERNEL_ISOLATE_NAME "kernel-service"
+
+/*
+ * =======
+ * Service
+ * =======
+ */
+
+#define DART_VM_SERVICE_ISOLATE_NAME "vm-service"
+
+/**
+ * Returns true if isolate is the service isolate.
+ *
+ * \param isolate An isolate
+ *
+ * \return Returns true if 'isolate' is the service isolate.
+ */
+DART_EXPORT bool Dart_IsServiceIsolate(Dart_Isolate isolate);
+
+/**
+ * Writes the CPU profile to the timeline as a series of 'instant' events.
+ *
+ * Note that this is an expensive operation.
+ *
+ * \param main_port The main port of the Isolate whose profile samples to write.
+ * \param error An optional error, must be free()ed by caller.
+ *
+ * \return Returns true if the profile is successfully written and false
+ *         otherwise.
+ */
+DART_EXPORT bool Dart_WriteProfileToTimeline(Dart_Port main_port, char** error);
+
+/*
+ * ==============
+ * Precompilation
+ * ==============
+ */
+
+/**
+ * Compiles all functions reachable from entry points and marks
+ * the isolate to disallow future compilation.
+ *
+ * Entry points should be specified using `@pragma("vm:entry-point")`
+ * annotation.
+ *
+ * \return An error handle if a compilation error or runtime error running const
+ * constructors was encountered.
+ */
+DART_EXPORT Dart_Handle Dart_Precompile(void);
+
+typedef void (*Dart_CreateLoadingUnitCallback)(
+    void* callback_data,
+    intptr_t loading_unit_id,
+    void** write_callback_data,
+    void** write_debug_callback_data);
+typedef void (*Dart_StreamingWriteCallback)(void* callback_data,
+                                            const uint8_t* buffer,
+                                            intptr_t size);
+typedef void (*Dart_StreamingCloseCallback)(void* callback_data);
+
+DART_EXPORT Dart_Handle Dart_LoadingUnitLibraryUris(intptr_t loading_unit_id);
+
+// On Darwin systems, 'dlsym' adds an '_' to the beginning of the symbol name.
+// Use the '...CSymbol' definitions for resolving through 'dlsym'. The actual
+// symbol names in the objects are given by the '...AsmSymbol' definitions.
+#if defined(__APPLE__)
+#define kSnapshotBuildIdCSymbol "kDartSnapshotBuildId"
+#define kVmSnapshotDataCSymbol "kDartVmSnapshotData"
+#define kVmSnapshotInstructionsCSymbol "kDartVmSnapshotInstructions"
+#define kVmSnapshotBssCSymbol "kDartVmSnapshotBss"
+#define kIsolateSnapshotDataCSymbol "kDartIsolateSnapshotData"
+#define kIsolateSnapshotInstructionsCSymbol "kDartIsolateSnapshotInstructions"
+#define kIsolateSnapshotBssCSymbol "kDartIsolateSnapshotBss"
+#else
+#define kSnapshotBuildIdCSymbol "_kDartSnapshotBuildId"
+#define kVmSnapshotDataCSymbol "_kDartVmSnapshotData"
+#define kVmSnapshotInstructionsCSymbol "_kDartVmSnapshotInstructions"
+#define kVmSnapshotBssCSymbol "_kDartVmSnapshotBss"
+#define kIsolateSnapshotDataCSymbol "_kDartIsolateSnapshotData"
+#define kIsolateSnapshotInstructionsCSymbol "_kDartIsolateSnapshotInstructions"
+#define kIsolateSnapshotBssCSymbol "_kDartIsolateSnapshotBss"
+#endif
+
+#define kSnapshotBuildIdAsmSymbol "_kDartSnapshotBuildId"
+#define kVmSnapshotDataAsmSymbol "_kDartVmSnapshotData"
+#define kVmSnapshotInstructionsAsmSymbol "_kDartVmSnapshotInstructions"
+#define kVmSnapshotBssAsmSymbol "_kDartVmSnapshotBss"
+#define kIsolateSnapshotDataAsmSymbol "_kDartIsolateSnapshotData"
+#define kIsolateSnapshotInstructionsAsmSymbol                                  \
+  "_kDartIsolateSnapshotInstructions"
+#define kIsolateSnapshotBssAsmSymbol "_kDartIsolateSnapshotBss"
+
+/**
+ *  Creates a precompiled snapshot.
+ *   - A root library must have been loaded.
+ *   - Dart_Precompile must have been called.
+ *
+ *  Outputs an assembly file defining the symbols listed in the definitions
+ *  above.
+ *
+ *  The assembly should be compiled as a static or shared library and linked or
+ *  loaded by the embedder. Running this snapshot requires a VM compiled with
+ *  DART_PRECOMPILED_SNAPSHOT. The kDartVmSnapshotData and
+ *  kDartVmSnapshotInstructions should be passed to Dart_Initialize. The
+ *  kDartIsolateSnapshotData and kDartIsolateSnapshotInstructions should be
+ *  passed to Dart_CreateIsolateGroup.
+ *
+ *  The callback will be invoked one or more times to provide the assembly code.
+ *
+ *  If stripped is true, then the assembly code will not include DWARF
+ *  debugging sections.
+ *
+ *  If debug_callback_data is provided, debug_callback_data will be used with
+ *  the callback to provide separate debugging information.
+ *
+ *  \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_CreateAppAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback,
+                                    void* callback_data,
+                                    bool stripped,
+                                    void* debug_callback_data);
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_CreateAppAOTSnapshotAsAssemblies(
+    Dart_CreateLoadingUnitCallback next_callback,
+    void* next_callback_data,
+    bool stripped,
+    Dart_StreamingWriteCallback write_callback,
+    Dart_StreamingCloseCallback close_callback);
+
+/**
+ *  Creates a precompiled snapshot.
+ *   - A root library must have been loaded.
+ *   - Dart_Precompile must have been called.
+ *
+ *  Outputs an ELF shared library defining the symbols
+ *   - _kDartVmSnapshotData
+ *   - _kDartVmSnapshotInstructions
+ *   - _kDartIsolateSnapshotData
+ *   - _kDartIsolateSnapshotInstructions
+ *
+ *  The shared library should be dynamically loaded by the embedder.
+ *  Running this snapshot requires a VM compiled with DART_PRECOMPILED_SNAPSHOT.
+ *  The kDartVmSnapshotData and kDartVmSnapshotInstructions should be passed to
+ *  Dart_Initialize. The kDartIsolateSnapshotData and
+ *  kDartIsolateSnapshotInstructions should be passed to Dart_CreateIsolate.
+ *
+ *  The callback will be invoked one or more times to provide the binary output.
+ *
+ *  If stripped is true, then the binary output will not include DWARF
+ *  debugging sections.
+ *
+ *  If debug_callback_data is provided, debug_callback_data will be used with
+ *  the callback to provide separate debugging information.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_CreateAppAOTSnapshotAsElf(Dart_StreamingWriteCallback callback,
+                               void* callback_data,
+                               bool stripped,
+                               void* debug_callback_data);
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_CreateAppAOTSnapshotAsElfs(Dart_CreateLoadingUnitCallback next_callback,
+                                void* next_callback_data,
+                                bool stripped,
+                                Dart_StreamingWriteCallback write_callback,
+                                Dart_StreamingCloseCallback close_callback);
+
+/**
+ *  Like Dart_CreateAppAOTSnapshotAsAssembly, but only includes
+ *  kDartVmSnapshotData and kDartVmSnapshotInstructions. It also does
+ *  not strip DWARF information from the generated assembly or allow for
+ *  separate debug information.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_CreateVMAOTSnapshotAsAssembly(Dart_StreamingWriteCallback callback,
+                                   void* callback_data);
+
+/**
+ * Sorts the class-ids in depth first traversal order of the inheritance
+ * tree. This is a costly operation, but it can make method dispatch
+ * more efficient and is done before writing snapshots.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_SortClasses(void);
+
+/**
+ *  Creates a snapshot that caches compiled code and type feedback for faster
+ *  startup and quicker warmup in a subsequent process.
+ *
+ *  Outputs a snapshot in two pieces. The pieces should be passed to
+ *  Dart_CreateIsolateGroup in a VM using the same VM snapshot pieces used in the
+ *  current VM. The instructions piece must be loaded with read and execute
+ *  permissions; the data piece may be loaded as read-only.
+ *
+ *   - Requires the VM to have not been started with --precompilation.
+ *   - Not supported when targeting IA32.
+ *   - The VM writing the snapshot and the VM reading the snapshot must be the
+ *     same version, must be built in the same DEBUG/RELEASE/PRODUCT mode, must
+ *     be targeting the same architecture, and must both be in checked mode or
+ *     both in unchecked mode.
+ *
+ *  The buffers are scope allocated and are only valid until the next call to
+ *  Dart_ExitScope.
+ *
+ * \return A valid handle if no error occurs during the operation.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_CreateAppJITSnapshotAsBlobs(uint8_t** isolate_snapshot_data_buffer,
+                                 intptr_t* isolate_snapshot_data_size,
+                                 uint8_t** isolate_snapshot_instructions_buffer,
+                                 intptr_t* isolate_snapshot_instructions_size);
+
+/**
+ * Like Dart_CreateAppJITSnapshotAsBlobs, but also creates a new VM snapshot.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_CreateCoreJITSnapshotAsBlobs(
+    uint8_t** vm_snapshot_data_buffer,
+    intptr_t* vm_snapshot_data_size,
+    uint8_t** vm_snapshot_instructions_buffer,
+    intptr_t* vm_snapshot_instructions_size,
+    uint8_t** isolate_snapshot_data_buffer,
+    intptr_t* isolate_snapshot_data_size,
+    uint8_t** isolate_snapshot_instructions_buffer,
+    intptr_t* isolate_snapshot_instructions_size);
+
+/**
+ * Get obfuscation map for precompiled code.
+ *
+ * Obfuscation map is encoded as a JSON array of pairs (original name,
+ * obfuscated name).
+ *
+ * \return Returns an error handler if the VM was built in a mode that does not
+ * support obfuscation.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
+Dart_GetObfuscationMap(uint8_t** buffer, intptr_t* buffer_length);
+
+/**
+ *  Returns whether the VM only supports running from precompiled snapshots and
+ *  not from any other kind of snapshot or from source (that is, the VM was
+ *  compiled with DART_PRECOMPILED_RUNTIME).
+ */
+DART_EXPORT bool Dart_IsPrecompiledRuntime(void);
+
+/**
+ *  Print a native stack trace. Used for crash handling.
+ *
+ *  If context is NULL, prints the current stack trace. Otherwise, context
+ *  should be a CONTEXT* (Windows) or ucontext_t* (POSIX) from a signal handler
+ *  running on the current thread.
+ */
+DART_EXPORT void Dart_DumpNativeStackTrace(void* context);
+
+/**
+ *  Indicate that the process is about to abort, and the Dart VM should not
+ *  attempt to cleanup resources.
+ */
+DART_EXPORT void Dart_PrepareToAbort(void);
+
+/**
+ * Callback provided by the embedder that is used by the VM to
+ * produce footnotes appended to DWARF stack traces.
+ *
+ * Whenever VM formats a stack trace as a string it would call this callback
+ * passing raw program counters for each frame in the stack trace.
+ *
+ * Embedder can then return a string which if not-null will be appended to the
+ * formatted stack trace.
+ *
+ * Returned string is expected to be `malloc()` allocated. VM takes ownership
+ * of the returned string and will `free()` it.
+ *
+ * \param addresses raw program counter addresses for each frame
+ * \param count number of elements in the addresses array
+ */
+typedef char* (*Dart_DwarfStackTraceFootnoteCallback)(void* addresses[],
+                                                      intptr_t count);
+
+/**
+ *  Configure DWARF stack trace footnote callback.
+ */
+DART_EXPORT void Dart_SetDwarfStackTraceFootnoteCallback(
+    Dart_DwarfStackTraceFootnoteCallback callback);
+
+#endif /* INCLUDE_DART_API_H_ */ /* NOLINT */
diff --git a/pkgs/jni/src/include/dart_api_dl.c b/pkgs/jni/src/include/dart_api_dl.c
new file mode 100644
index 0000000..c4a68f4
--- /dev/null
+++ b/pkgs/jni/src/include/dart_api_dl.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ */
+
+#include "dart_api_dl.h"               /* NOLINT */
+#include "dart_version.h"              /* NOLINT */
+#include "internal/dart_api_dl_impl.h" /* NOLINT */
+
+#include <string.h>
+
+#define DART_API_DL_DEFINITIONS(name, R, A) name##_Type name##_DL = NULL;
+
+DART_API_ALL_DL_SYMBOLS(DART_API_DL_DEFINITIONS)
+
+#undef DART_API_DL_DEFINITIONS
+
+typedef void* DartApiEntry_function;
+
+DartApiEntry_function FindFunctionPointer(const DartApiEntry* entries,
+                                          const char* name) {
+  while (entries->name != NULL) {
+    if (strcmp(entries->name, name) == 0) return entries->function;
+    entries++;
+  }
+  return NULL;
+}
+
+intptr_t Dart_InitializeApiDL(void* data) {
+  DartApi* dart_api_data = (DartApi*)data;
+
+  if (dart_api_data->major != DART_API_DL_MAJOR_VERSION) {
+    // If the DartVM we're running on does not have the same version as this
+    // file was compiled against, refuse to initialize. The symbols are not
+    // compatible.
+    return -1;
+  }
+  // Minor versions are allowed to be different.
+  // If the DartVM has a higher minor version, it will provide more symbols
+  // than we initialize here.
+  // If the DartVM has a lower minor version, it will not provide all symbols.
+  // In that case, we leave the missing symbols un-initialized. Those symbols
+  // should not be used by the Dart and native code. The client is responsible
+  // for checking the minor version number himself based on which symbols it
+  // is using.
+  // (If we would error out on this case, recompiling native code against a
+  // newer SDK would break all uses on older SDKs, which is too strict.)
+
+  const DartApiEntry* dart_api_function_pointers = dart_api_data->functions;
+
+#define DART_API_DL_INIT(name, R, A)                                           \
+  name##_DL =                                                                  \
+      (name##_Type)(FindFunctionPointer(dart_api_function_pointers, #name));
+  DART_API_ALL_DL_SYMBOLS(DART_API_DL_INIT)
+#undef DART_API_DL_INIT
+
+  return 0;
+}
diff --git a/pkgs/jni/src/include/dart_api_dl.h b/pkgs/jni/src/include/dart_api_dl.h
new file mode 100644
index 0000000..cce3450
--- /dev/null
+++ b/pkgs/jni/src/include/dart_api_dl.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ */
+
+#ifndef RUNTIME_INCLUDE_DART_API_DL_H_
+#define RUNTIME_INCLUDE_DART_API_DL_H_
+
+#include "dart_api.h"        /* NOLINT */
+#include "dart_native_api.h" /* NOLINT */
+
+/** \mainpage Dynamically Linked Dart API
+ *
+ * This exposes a subset of symbols from dart_api.h and dart_native_api.h
+ * available in every Dart embedder through dynamic linking.
+ *
+ * All symbols are postfixed with _DL to indicate that they are dynamically
+ * linked and to prevent conflicts with the original symbol.
+ *
+ * Link `dart_api_dl.c` file into your library and invoke
+ * `Dart_InitializeApiDL` with `NativeApi.initializeApiDLData`.
+ */
+
+DART_EXPORT intptr_t Dart_InitializeApiDL(void* data);
+
+// ============================================================================
+// IMPORTANT! Never update these signatures without properly updating
+// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION.
+//
+// Verbatim copy of `dart_native_api.h` and `dart_api.h` symbol names and types
+// to trigger compile-time errors if the symbols in those files are updated
+// without updating these.
+//
+// Function return and argument types, and typedefs are carbon copied. Structs
+// are typechecked nominally in C/C++, so they are not copied, instead a
+// comment is added to their definition.
+typedef int64_t Dart_Port_DL;
+
+typedef void (*Dart_NativeMessageHandler_DL)(Dart_Port_DL dest_port_id,
+                                             Dart_CObject* message);
+
+// dart_native_api.h symbols can be called on any thread.
+#define DART_NATIVE_API_DL_SYMBOLS(F)                                          \
+  /***** dart_native_api.h *****/                                              \
+  /* Dart_Port */                                                              \
+  F(Dart_PostCObject, bool, (Dart_Port_DL port_id, Dart_CObject * message))    \
+  F(Dart_PostInteger, bool, (Dart_Port_DL port_id, int64_t message))           \
+  F(Dart_NewNativePort, Dart_Port_DL,                                          \
+    (const char* name, Dart_NativeMessageHandler_DL handler,                   \
+     bool handle_concurrently))                                                \
+  F(Dart_CloseNativePort, bool, (Dart_Port_DL native_port_id))
+
+// dart_api.h symbols can only be called on Dart threads.
+#define DART_API_DL_SYMBOLS(F)                                                 \
+  /***** dart_api.h *****/                                                     \
+  /* Errors */                                                                 \
+  F(Dart_IsError, bool, (Dart_Handle handle))                                  \
+  F(Dart_IsApiError, bool, (Dart_Handle handle))                               \
+  F(Dart_IsUnhandledExceptionError, bool, (Dart_Handle handle))                \
+  F(Dart_IsCompilationError, bool, (Dart_Handle handle))                       \
+  F(Dart_IsFatalError, bool, (Dart_Handle handle))                             \
+  F(Dart_GetError, const char*, (Dart_Handle handle))                          \
+  F(Dart_ErrorHasException, bool, (Dart_Handle handle))                        \
+  F(Dart_ErrorGetException, Dart_Handle, (Dart_Handle handle))                 \
+  F(Dart_ErrorGetStackTrace, Dart_Handle, (Dart_Handle handle))                \
+  F(Dart_NewApiError, Dart_Handle, (const char* error))                        \
+  F(Dart_NewCompilationError, Dart_Handle, (const char* error))                \
+  F(Dart_NewUnhandledExceptionError, Dart_Handle, (Dart_Handle exception))     \
+  F(Dart_PropagateError, void, (Dart_Handle handle))                           \
+  /* Dart_Handle, Dart_PersistentHandle, Dart_WeakPersistentHandle */          \
+  F(Dart_HandleFromPersistent, Dart_Handle, (Dart_PersistentHandle object))    \
+  F(Dart_HandleFromWeakPersistent, Dart_Handle,                                \
+    (Dart_WeakPersistentHandle object))                                        \
+  F(Dart_NewPersistentHandle, Dart_PersistentHandle, (Dart_Handle object))     \
+  F(Dart_SetPersistentHandle, void,                                            \
+    (Dart_PersistentHandle obj1, Dart_Handle obj2))                            \
+  F(Dart_DeletePersistentHandle, void, (Dart_PersistentHandle object))         \
+  F(Dart_NewWeakPersistentHandle, Dart_WeakPersistentHandle,                   \
+    (Dart_Handle object, void* peer, intptr_t external_allocation_size,        \
+     Dart_HandleFinalizer callback))                                           \
+  F(Dart_DeleteWeakPersistentHandle, void, (Dart_WeakPersistentHandle object)) \
+  F(Dart_UpdateExternalSize, void,                                             \
+    (Dart_WeakPersistentHandle object, intptr_t external_allocation_size))     \
+  F(Dart_NewFinalizableHandle, Dart_FinalizableHandle,                         \
+    (Dart_Handle object, void* peer, intptr_t external_allocation_size,        \
+     Dart_HandleFinalizer callback))                                           \
+  F(Dart_DeleteFinalizableHandle, void,                                        \
+    (Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object))         \
+  F(Dart_UpdateFinalizableExternalSize, void,                                  \
+    (Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object,          \
+     intptr_t external_allocation_size))                                       \
+  /* Isolates */                                                               \
+  F(Dart_CurrentIsolate, Dart_Isolate, (void))                                 \
+  F(Dart_ExitIsolate, void, (void))                                            \
+  F(Dart_EnterIsolate, void, (Dart_Isolate))                                   \
+  /* Dart_Port */                                                              \
+  F(Dart_Post, bool, (Dart_Port_DL port_id, Dart_Handle object))               \
+  F(Dart_NewSendPort, Dart_Handle, (Dart_Port_DL port_id))                     \
+  F(Dart_SendPortGetId, Dart_Handle,                                           \
+    (Dart_Handle port, Dart_Port_DL * port_id))                                \
+  /* Scopes */                                                                 \
+  F(Dart_EnterScope, void, (void))                                             \
+  F(Dart_ExitScope, void, (void))                                              \
+  /* Objects */                                                                \
+  F(Dart_IsNull, bool, (Dart_Handle))
+
+#define DART_API_ALL_DL_SYMBOLS(F)                                             \
+  DART_NATIVE_API_DL_SYMBOLS(F)                                                \
+  DART_API_DL_SYMBOLS(F)
+// IMPORTANT! Never update these signatures without properly updating
+// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION.
+//
+// End of verbatim copy.
+// ============================================================================
+
+// Copy of definition of DART_EXPORT without 'used' attribute.
+//
+// The 'used' attribute cannot be used with DART_API_ALL_DL_SYMBOLS because
+// they are not function declarations, but variable declarations with a
+// function pointer type.
+//
+// The function pointer variables are initialized with the addresses of the
+// functions in the VM. If we were to use function declarations instead, we
+// would need to forward the call to the VM adding indirection.
+#if defined(__CYGWIN__)
+#error Tool chain and platform not supported.
+#elif defined(_WIN32)
+#if defined(DART_SHARED_LIB)
+#define DART_EXPORT_DL DART_EXTERN_C __declspec(dllexport)
+#else
+#define DART_EXPORT_DL DART_EXTERN_C
+#endif
+#else
+#if __GNUC__ >= 4
+#if defined(DART_SHARED_LIB)
+#define DART_EXPORT_DL DART_EXTERN_C __attribute__((visibility("default")))
+#else
+#define DART_EXPORT_DL DART_EXTERN_C
+#endif
+#else
+#error Tool chain not supported.
+#endif
+#endif
+
+#define DART_API_DL_DECLARATIONS(name, R, A)                                   \
+  typedef R(*name##_Type) A;                                                   \
+  DART_EXPORT_DL name##_Type name##_DL;
+
+DART_API_ALL_DL_SYMBOLS(DART_API_DL_DECLARATIONS)
+
+#undef DART_API_DL_DECLARATIONS
+
+#undef DART_EXPORT_DL
+
+#endif /* RUNTIME_INCLUDE_DART_API_DL_H_ */ /* NOLINT */
diff --git a/pkgs/jni/src/include/dart_embedder_api.h b/pkgs/jni/src/include/dart_embedder_api.h
new file mode 100644
index 0000000..e565ebf
--- /dev/null
+++ b/pkgs/jni/src/include/dart_embedder_api.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_INCLUDE_DART_EMBEDDER_API_H_
+#define RUNTIME_INCLUDE_DART_EMBEDDER_API_H_
+
+#include "include/dart_api.h"
+#include "include/dart_tools_api.h"
+
+namespace dart {
+namespace embedder {
+
+// Initialize all subsystems of the embedder.
+//
+// Must be called before the `Dart_Initialize()` call to initialize the
+// Dart VM.
+//
+// Returns true on success and false otherwise, in which case error would
+// contain error message.
+DART_WARN_UNUSED_RESULT bool InitOnce(char** error);
+
+// Cleans up all subsystems of the embedder.
+//
+// Must be called after the `Dart_Cleanup()` call to initialize the
+// Dart VM.
+void Cleanup();
+
+// Common arguments that are passed to isolate creation callback and to
+// API methods that create isolates.
+struct IsolateCreationData {
+  // URI for the main script that will be running in the isolate.
+  const char* script_uri;
+
+  // Advisory name of the main method that will be run by isolate.
+  // Only used for error messages.
+  const char* main;
+
+  // Isolate creation flags. Might be absent.
+  Dart_IsolateFlags* flags;
+
+  // Isolate group callback data.
+  void* isolate_group_data;
+
+  // Isolate callback data.
+  void* isolate_data;
+};
+
+// Create and initialize kernel-service isolate. This method should be used
+// when VM invokes isolate creation callback with DART_KERNEL_ISOLATE_NAME as
+// script_uri.
+// The isolate is created from the given snapshot (might be kernel data or
+// app-jit snapshot).
+DART_WARN_UNUSED_RESULT Dart_Isolate
+CreateKernelServiceIsolate(const IsolateCreationData& data,
+                           const uint8_t* buffer,
+                           intptr_t buffer_size,
+                           char** error);
+
+// Service isolate configuration.
+struct VmServiceConfiguration {
+  enum {
+    kBindHttpServerToAFreePort = 0,
+    kDoNotAutoStartHttpServer = -1
+  };
+
+  // Address to which HTTP server will be bound.
+  const char* ip;
+
+  // Default port. See enum above for special values.
+  int port;
+
+  // If non-null, connection information for the VM service will be output to a
+  // file in JSON format at the location specified.
+  const char* write_service_info_filename;
+
+  // TODO(vegorov) document these ones.
+  bool dev_mode;
+  bool deterministic;
+  bool disable_auth_codes;
+};
+
+// Create and initialize vm-service isolate from the given AOT snapshot, which
+// is expected to contain all necessary 'vm-service' libraries.
+// This method should be used when VM invokes isolate creation callback with
+// DART_VM_SERVICE_ISOLATE_NAME as script_uri.
+DART_WARN_UNUSED_RESULT Dart_Isolate
+CreateVmServiceIsolate(const IsolateCreationData& data,
+                       const VmServiceConfiguration& config,
+                       const uint8_t* isolate_data,
+                       const uint8_t* isolate_instr,
+                       char** error);
+
+// Create and initialize vm-service isolate from the given kernel binary, which
+// is expected to contain all necessary 'vm-service' libraries.
+// This method should be used when VM invokes isolate creation callback with
+// DART_VM_SERVICE_ISOLATE_NAME as script_uri.
+DART_WARN_UNUSED_RESULT Dart_Isolate
+CreateVmServiceIsolateFromKernel(const IsolateCreationData& data,
+                                 const VmServiceConfiguration& config,
+                                 const uint8_t* kernel_buffer,
+                                 intptr_t kernel_buffer_size,
+                                 char** error);
+
+}  // namespace embedder
+}  // namespace dart
+
+#endif  // RUNTIME_INCLUDE_DART_EMBEDDER_API_H_
diff --git a/pkgs/jni/src/include/dart_native_api.h b/pkgs/jni/src/include/dart_native_api.h
new file mode 100644
index 0000000..79194e0
--- /dev/null
+++ b/pkgs/jni/src/include/dart_native_api.h
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ */
+
+#ifndef RUNTIME_INCLUDE_DART_NATIVE_API_H_
+#define RUNTIME_INCLUDE_DART_NATIVE_API_H_
+
+#include "dart_api.h" /* NOLINT */
+
+/*
+ * ==========================================
+ * Message sending/receiving from native code
+ * ==========================================
+ */
+
+/**
+ * A Dart_CObject is used for representing Dart objects as native C
+ * data outside the Dart heap. These objects are totally detached from
+ * the Dart heap. Only a subset of the Dart objects have a
+ * representation as a Dart_CObject.
+ *
+ * The string encoding in the 'value.as_string' is UTF-8.
+ *
+ * All the different types from dart:typed_data are exposed as type
+ * kTypedData. The specific type from dart:typed_data is in the type
+ * field of the as_typed_data structure. The length in the
+ * as_typed_data structure is always in bytes.
+ *
+ * The data for kTypedData is copied on message send and ownership remains with
+ * the caller. The ownership of data for kExternalTyped is passed to the VM on
+ * message send and returned when the VM invokes the
+ * Dart_HandleFinalizer callback; a non-NULL callback must be provided.
+ *
+ * Note that Dart_CObject_kNativePointer is intended for internal use by
+ * dart:io implementation and has no connection to dart:ffi Pointer class.
+ * It represents a pointer to a native resource of a known type.
+ * The receiving side will only see this pointer as an integer and will not
+ * see the specified finalizer.
+ * The specified finalizer will only be invoked if the message is not delivered.
+ */
+typedef enum {
+  Dart_CObject_kNull = 0,
+  Dart_CObject_kBool,
+  Dart_CObject_kInt32,
+  Dart_CObject_kInt64,
+  Dart_CObject_kDouble,
+  Dart_CObject_kString,
+  Dart_CObject_kArray,
+  Dart_CObject_kTypedData,
+  Dart_CObject_kExternalTypedData,
+  Dart_CObject_kSendPort,
+  Dart_CObject_kCapability,
+  Dart_CObject_kNativePointer,
+  Dart_CObject_kUnsupported,
+  Dart_CObject_kUnmodifiableExternalTypedData,
+  Dart_CObject_kNumberOfTypes
+} Dart_CObject_Type;
+// This enum is versioned by DART_API_DL_MAJOR_VERSION, only add at the end
+// and bump the DART_API_DL_MINOR_VERSION.
+
+typedef struct _Dart_CObject {
+  Dart_CObject_Type type;
+  union {
+    bool as_bool;
+    int32_t as_int32;
+    int64_t as_int64;
+    double as_double;
+    const char* as_string;
+    struct {
+      Dart_Port id;
+      Dart_Port origin_id;
+    } as_send_port;
+    struct {
+      int64_t id;
+    } as_capability;
+    struct {
+      intptr_t length;
+      struct _Dart_CObject** values;
+    } as_array;
+    struct {
+      Dart_TypedData_Type type;
+      intptr_t length; /* in elements, not bytes */
+      const uint8_t* values;
+    } as_typed_data;
+    struct {
+      Dart_TypedData_Type type;
+      intptr_t length; /* in elements, not bytes */
+      uint8_t* data;
+      void* peer;
+      Dart_HandleFinalizer callback;
+    } as_external_typed_data;
+    struct {
+      intptr_t ptr;
+      intptr_t size;
+      Dart_HandleFinalizer callback;
+    } as_native_pointer;
+  } value;
+} Dart_CObject;
+// This struct is versioned by DART_API_DL_MAJOR_VERSION, bump the version when
+// changing this struct.
+
+/**
+ * Posts a message on some port. The message will contain the Dart_CObject
+ * object graph rooted in 'message'.
+ *
+ * While the message is being sent the state of the graph of Dart_CObject
+ * structures rooted in 'message' should not be accessed, as the message
+ * generation will make temporary modifications to the data. When the message
+ * has been sent the graph will be fully restored.
+ *
+ * If true is returned, the message was enqueued, and finalizers for external
+ * typed data will eventually run, even if the receiving isolate shuts down
+ * before processing the message. If false is returned, the message was not
+ * enqueued and ownership of external typed data in the message remains with the
+ * caller.
+ *
+ * This function may be called on any thread when the VM is running (that is,
+ * after Dart_Initialize has returned and before Dart_Cleanup has been called).
+ *
+ * \param port_id The destination port.
+ * \param message The message to send.
+ *
+ * \return True if the message was posted.
+ */
+DART_EXPORT bool Dart_PostCObject(Dart_Port port_id, Dart_CObject* message);
+
+/**
+ * Posts a message on some port. The message will contain the integer 'message'.
+ *
+ * \param port_id The destination port.
+ * \param message The message to send.
+ *
+ * \return True if the message was posted.
+ */
+DART_EXPORT bool Dart_PostInteger(Dart_Port port_id, int64_t message);
+
+/**
+ * A native message handler.
+ *
+ * This handler is associated with a native port by calling
+ * Dart_NewNativePort.
+ *
+ * The message received is decoded into the message structure. The
+ * lifetime of the message data is controlled by the caller. All the
+ * data references from the message are allocated by the caller and
+ * will be reclaimed when returning to it.
+ */
+typedef void (*Dart_NativeMessageHandler)(Dart_Port dest_port_id,
+                                          Dart_CObject* message);
+
+/**
+ * Creates a new native port.  When messages are received on this
+ * native port, then they will be dispatched to the provided native
+ * message handler.
+ *
+ * \param name The name of this port in debugging messages.
+ * \param handler The C handler to run when messages arrive on the port.
+ * \param handle_concurrently Is it okay to process requests on this
+ *                            native port concurrently?
+ *
+ * \return If successful, returns the port id for the native port.  In
+ *   case of error, returns ILLEGAL_PORT.
+ */
+DART_EXPORT Dart_Port Dart_NewNativePort(const char* name,
+                                         Dart_NativeMessageHandler handler,
+                                         bool handle_concurrently);
+/* TODO(turnidge): Currently handle_concurrently is ignored. */
+
+/**
+ * Closes the native port with the given id.
+ *
+ * The port must have been allocated by a call to Dart_NewNativePort.
+ *
+ * \param native_port_id The id of the native port to close.
+ *
+ * \return Returns true if the port was closed successfully.
+ */
+DART_EXPORT bool Dart_CloseNativePort(Dart_Port native_port_id);
+
+/*
+ * ==================
+ * Verification Tools
+ * ==================
+ */
+
+/**
+ * Forces all loaded classes and functions to be compiled eagerly in
+ * the current isolate..
+ *
+ * TODO(turnidge): Document.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_CompileAll(void);
+
+/**
+ * Finalizes all classes.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_FinalizeAllClasses(void);
+
+/*  This function is intentionally undocumented.
+ *
+ *  It should not be used outside internal tests.
+ */
+DART_EXPORT void* Dart_ExecuteInternalCommand(const char* command, void* arg);
+
+#endif /* INCLUDE_DART_NATIVE_API_H_ */ /* NOLINT */
diff --git a/pkgs/jni/src/include/dart_tools_api.h b/pkgs/jni/src/include/dart_tools_api.h
new file mode 100644
index 0000000..7b706bc
--- /dev/null
+++ b/pkgs/jni/src/include/dart_tools_api.h
@@ -0,0 +1,658 @@
+// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#ifndef RUNTIME_INCLUDE_DART_TOOLS_API_H_
+#define RUNTIME_INCLUDE_DART_TOOLS_API_H_
+
+#include "dart_api.h" /* NOLINT */
+
+/** \mainpage Dart Tools Embedding API Reference
+ *
+ * This reference describes the Dart embedding API for tools. Tools include
+ * a debugger, service protocol, and timeline.
+ *
+ * NOTE: The APIs described in this file are unstable and subject to change.
+ *
+ * This reference is generated from the header include/dart_tools_api.h.
+ */
+
+/*
+ * ========
+ * Debugger
+ * ========
+ */
+
+/**
+ * ILLEGAL_ISOLATE_ID is a number guaranteed never to be associated with a
+ * valid isolate.
+ */
+#define ILLEGAL_ISOLATE_ID ILLEGAL_PORT
+
+/**
+ * ILLEGAL_ISOLATE_GROUP_ID is a number guaranteed never to be associated with a
+ * valid isolate group.
+ */
+#define ILLEGAL_ISOLATE_GROUP_ID 0
+
+/*
+ * =======
+ * Service
+ * =======
+ */
+
+/**
+ * A service request callback function.
+ *
+ * These callbacks, registered by the embedder, are called when the VM receives
+ * a service request it can't handle and the service request command name
+ * matches one of the embedder registered handlers.
+ *
+ * The return value of the callback indicates whether the response
+ * should be used as a regular result or an error result.
+ * Specifically, if the callback returns true, a regular JSON-RPC
+ * response is built in the following way:
+ *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "result": <json_object>,
+ *   "id": <some sequence id>,
+ * }
+ *
+ * If the callback returns false, a JSON-RPC error is built like this:
+ *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "error": <json_object>,
+ *   "id": <some sequence id>,
+ * }
+ *
+ * \param method The rpc method name.
+ * \param param_keys Service requests can have key-value pair parameters. The
+ *   keys and values are flattened and stored in arrays.
+ * \param param_values The values associated with the keys.
+ * \param num_params The length of the param_keys and param_values arrays.
+ * \param user_data The user_data pointer registered with this handler.
+ * \param result A C string containing a valid JSON object. The returned
+ *   pointer will be freed by the VM by calling free.
+ *
+ * \return True if the result is a regular JSON-RPC response, false if the
+ *   result is a JSON-RPC error.
+ */
+typedef bool (*Dart_ServiceRequestCallback)(const char* method,
+                                            const char** param_keys,
+                                            const char** param_values,
+                                            intptr_t num_params,
+                                            void* user_data,
+                                            const char** json_object);
+
+/**
+ * Register a Dart_ServiceRequestCallback to be called to handle
+ * requests for the named rpc on a specific isolate. The callback will
+ * be invoked with the current isolate set to the request target.
+ *
+ * \param method The name of the method that this callback is responsible for.
+ * \param callback The callback to invoke.
+ * \param user_data The user data passed to the callback.
+ *
+ * NOTE: If multiple callbacks with the same name are registered, only
+ * the last callback registered will be remembered.
+ */
+DART_EXPORT void Dart_RegisterIsolateServiceRequestCallback(
+    const char* method,
+    Dart_ServiceRequestCallback callback,
+    void* user_data);
+
+/**
+ * Register a Dart_ServiceRequestCallback to be called to handle
+ * requests for the named rpc. The callback will be invoked without a
+ * current isolate.
+ *
+ * \param method The name of the command that this callback is responsible for.
+ * \param callback The callback to invoke.
+ * \param user_data The user data passed to the callback.
+ *
+ * NOTE: If multiple callbacks with the same name are registered, only
+ * the last callback registered will be remembered.
+ */
+DART_EXPORT void Dart_RegisterRootServiceRequestCallback(
+    const char* method,
+    Dart_ServiceRequestCallback callback,
+    void* user_data);
+
+/**
+ * Embedder information which can be requested by the VM for internal or
+ * reporting purposes.
+ *
+ * The pointers in this structure are not going to be cached or freed by the VM.
+ */
+
+ #define DART_EMBEDDER_INFORMATION_CURRENT_VERSION (0x00000001)
+
+typedef struct {
+  int32_t version;
+  const char* name;  // [optional] The name of the embedder
+  int64_t current_rss;  // [optional] the current RSS of the embedder
+  int64_t max_rss;  // [optional] the maximum RSS of the embedder
+} Dart_EmbedderInformation;
+
+/**
+ * Callback provided by the embedder that is used by the VM to request
+ * information.
+ *
+ * \return Returns a pointer to a Dart_EmbedderInformation structure.
+ * The embedder keeps the ownership of the structure and any field in it.
+ * The embedder must ensure that the structure will remain valid until the
+ * next invocation of the callback.
+ */
+typedef void (*Dart_EmbedderInformationCallback)(
+    Dart_EmbedderInformation* info);
+
+/**
+ * Register a Dart_ServiceRequestCallback to be called to handle
+ * requests for the named rpc. The callback will be invoked without a
+ * current isolate.
+ *
+ * \param method The name of the command that this callback is responsible for.
+ * \param callback The callback to invoke.
+ * \param user_data The user data passed to the callback.
+ *
+ * NOTE: If multiple callbacks are registered, only the last callback registered
+ * will be remembered.
+ */
+DART_EXPORT void Dart_SetEmbedderInformationCallback(
+    Dart_EmbedderInformationCallback callback);
+
+/**
+ * Invoke a vm-service method and wait for its result.
+ *
+ * \param request_json The utf8-encoded json-rpc request.
+ * \param request_json_length The length of the json-rpc request.
+ *
+ * \param response_json The returned utf8-encoded json response, must be
+ *   free()ed by caller.
+ * \param response_json_length The length of the returned json response.
+ * \param error An optional error, must be free()ed by caller.
+ *
+ * \return Whether the call was successfully performed.
+ *
+ * NOTE: This method does not need a current isolate and must not have the
+ * vm-isolate being the current isolate. It must be called after
+ * Dart_Initialize() and before Dart_Cleanup().
+ */
+DART_EXPORT bool Dart_InvokeVMServiceMethod(uint8_t* request_json,
+                                            intptr_t request_json_length,
+                                            uint8_t** response_json,
+                                            intptr_t* response_json_length,
+                                            char** error);
+
+/*
+ * ========
+ * Event Streams
+ * ========
+ */
+
+/**
+ * A callback invoked when the VM service gets a request to listen to
+ * some stream.
+ *
+ * \return Returns true iff the embedder supports the named stream id.
+ */
+typedef bool (*Dart_ServiceStreamListenCallback)(const char* stream_id);
+
+/**
+ * A callback invoked when the VM service gets a request to cancel
+ * some stream.
+ */
+typedef void (*Dart_ServiceStreamCancelCallback)(const char* stream_id);
+
+/**
+ * Adds VM service stream callbacks.
+ *
+ * \param listen_callback A function pointer to a listen callback function.
+ *   A listen callback function should not be already set when this function
+ *   is called. A NULL value removes the existing listen callback function
+ *   if any.
+ *
+ * \param cancel_callback A function pointer to a cancel callback function.
+ *   A cancel callback function should not be already set when this function
+ *   is called. A NULL value removes the existing cancel callback function
+ *   if any.
+ *
+ * \return Success if the callbacks were added.  Otherwise, returns an
+ *   error handle.
+ */
+DART_EXPORT char* Dart_SetServiceStreamCallbacks(
+    Dart_ServiceStreamListenCallback listen_callback,
+    Dart_ServiceStreamCancelCallback cancel_callback);
+
+/**
+ * Sends a data event to clients of the VM Service.
+ *
+ * A data event is used to pass an array of bytes to subscribed VM
+ * Service clients.  For example, in the standalone embedder, this is
+ * function used to provide WriteEvents on the Stdout and Stderr
+ * streams.
+ *
+ * If the embedder passes in a stream id for which no client is
+ * subscribed, then the event is ignored.
+ *
+ * \param stream_id The id of the stream on which to post the event.
+ *
+ * \param event_kind A string identifying what kind of event this is.
+ *   For example, 'WriteEvent'.
+ *
+ * \param bytes A pointer to an array of bytes.
+ *
+ * \param bytes_length The length of the byte array.
+ *
+ * \return NULL if the arguments are well formed.  Otherwise, returns an
+ *   error string. The caller is responsible for freeing the error message.
+ */
+DART_EXPORT char* Dart_ServiceSendDataEvent(const char* stream_id,
+                                            const char* event_kind,
+                                            const uint8_t* bytes,
+                                            intptr_t bytes_length);
+
+/*
+ * ========
+ * Reload support
+ * ========
+ *
+ * These functions are used to implement reloading in the Dart VM.
+ * This is an experimental feature, so embedders should be prepared
+ * for these functions to change.
+ */
+
+/**
+ * A callback which determines whether the file at some url has been
+ * modified since some time.  If the file cannot be found, true should
+ * be returned.
+ */
+typedef bool (*Dart_FileModifiedCallback)(const char* url, int64_t since);
+
+DART_EXPORT char* Dart_SetFileModifiedCallback(
+    Dart_FileModifiedCallback file_modified_callback);
+
+/**
+ * Returns true if isolate is currently reloading.
+ */
+DART_EXPORT bool Dart_IsReloading();
+
+/*
+ * ========
+ * Timeline
+ * ========
+ */
+
+/**
+ * Enable tracking of specified timeline category. This is operational
+ * only when systrace timeline functionality is turned on.
+ *
+ * \param categories A comma separated list of categories that need to
+ *   be enabled, the categories are
+ *   "all" : All categories
+ *   "API" - Execution of Dart C API functions
+ *   "Compiler" - Execution of Dart JIT compiler
+ *   "CompilerVerbose" - More detailed Execution of Dart JIT compiler
+ *   "Dart" - Execution of Dart code
+ *   "Debugger" - Execution of Dart debugger
+ *   "Embedder" - Execution of Dart embedder code
+ *   "GC" - Execution of Dart Garbage Collector
+ *   "Isolate" - Dart Isolate lifecycle execution
+ *   "VM" - Execution in Dart VM runtime code
+ *   "" - None
+ *
+ *  When "all" is specified all the categories are enabled.
+ *  When a comma separated list of categories is specified, the categories
+ *   that are specified will be enabled and the rest will be disabled.
+ *  When "" is specified all the categories are disabled.
+ *  The category names are case sensitive.
+ *  eg:  Dart_EnableTimelineCategory("all");
+ *       Dart_EnableTimelineCategory("GC,API,Isolate");
+ *       Dart_EnableTimelineCategory("GC,Debugger,Dart");
+ *
+ * \return True if the categories were successfully enabled, False otherwise.
+ */
+DART_EXPORT bool Dart_SetEnabledTimelineCategory(const char* categories);
+
+/**
+ * Returns a timestamp in microseconds. This timestamp is suitable for
+ * passing into the timeline system, and uses the same monotonic clock
+ * as dart:developer's Timeline.now.
+ *
+ * \return A timestamp that can be passed to the timeline system.
+ */
+DART_EXPORT int64_t Dart_TimelineGetMicros();
+
+/**
+ * Returns a raw timestamp in from the monotonic clock.
+ *
+ * \return A raw timestamp from the monotonic clock.
+ */
+DART_EXPORT int64_t Dart_TimelineGetTicks();
+
+/**
+ * Returns the frequency of the monotonic clock.
+ *
+ * \return The frequency of the monotonic clock.
+ */
+DART_EXPORT int64_t Dart_TimelineGetTicksFrequency();
+
+typedef enum {
+  Dart_Timeline_Event_Begin,          // Phase = 'B'.
+  Dart_Timeline_Event_End,            // Phase = 'E'.
+  Dart_Timeline_Event_Instant,        // Phase = 'i'.
+  Dart_Timeline_Event_Duration,       // Phase = 'X'.
+  Dart_Timeline_Event_Async_Begin,    // Phase = 'b'.
+  Dart_Timeline_Event_Async_End,      // Phase = 'e'.
+  Dart_Timeline_Event_Async_Instant,  // Phase = 'n'.
+  Dart_Timeline_Event_Counter,        // Phase = 'C'.
+  Dart_Timeline_Event_Flow_Begin,     // Phase = 's'.
+  Dart_Timeline_Event_Flow_Step,      // Phase = 't'.
+  Dart_Timeline_Event_Flow_End,       // Phase = 'f'.
+} Dart_Timeline_Event_Type;
+
+/**
+ * Add a timeline event to the embedder stream.
+ *
+ * DEPRECATED: this function will be removed in Dart SDK v3.2.
+ *
+ * \param label The name of the event. Its lifetime must extend at least until
+ *     Dart_Cleanup.
+ * \param timestamp0 The first timestamp of the event.
+ * \param timestamp1_or_id When reporting an event of type
+ *     |Dart_Timeline_Event_Duration|, the second (end) timestamp of the event
+ *     should be passed through |timestamp1_or_id|. When reporting an event of
+ *     type |Dart_Timeline_Event_Async_Begin|, |Dart_Timeline_Event_Async_End|,
+ *     or |Dart_Timeline_Event_Async_Instant|, the async ID associated with the
+ *     event should be passed through |timestamp1_or_id|. When reporting an
+ *     event of type |Dart_Timeline_Event_Flow_Begin|,
+ *     |Dart_Timeline_Event_Flow_Step|, or |Dart_Timeline_Event_Flow_End|, the
+ *     flow ID associated with the event should be passed through
+ *     |timestamp1_or_id|. When reporting an event of type
+ *     |Dart_Timeline_Event_Begin| or |Dart_Timeline_Event_End|, the event ID
+ *     associated with the event should be passed through |timestamp1_or_id|.
+ *     Note that this event ID will only be used by the MacOS recorder. The
+ *     argument to |timestamp1_or_id| will not be used when reporting events of
+ *     other types.
+ * \param argument_count The number of argument names and values.
+ * \param argument_names An array of names of the arguments. The lifetime of the
+ *     names must extend at least until Dart_Cleanup. The array may be reclaimed
+ *     when this call returns.
+ * \param argument_values An array of values of the arguments. The values and
+ *     the array may be reclaimed when this call returns.
+ */
+DART_EXPORT void Dart_TimelineEvent(const char* label,
+                                    int64_t timestamp0,
+                                    int64_t timestamp1_or_id,
+                                    Dart_Timeline_Event_Type type,
+                                    intptr_t argument_count,
+                                    const char** argument_names,
+                                    const char** argument_values);
+
+/**
+ * Add a timeline event to the embedder stream.
+ *
+ * Note regarding flow events: events must be associated with flow IDs in two
+ * different ways to allow flow events to be serialized correctly in both
+ * Chrome's JSON trace event format and Perfetto's proto trace format. Events
+ * of type |Dart_Timeline_Event_Flow_Begin|, |Dart_Timeline_Event_Flow_Step|,
+ * and |Dart_Timeline_Event_Flow_End| must be reported to support serialization
+ * in Chrome's trace format. The |flow_ids| argument must be supplied when
+ * reporting events of type |Dart_Timeline_Event_Begin|,
+ * |Dart_Timeline_Event_Duration|, |Dart_Timeline_Event_Instant|,
+ * |Dart_Timeline_Event_Async_Begin|, and |Dart_Timeline_Event_Async_Instant| to
+ * support serialization in Perfetto's proto format.
+ *
+ * \param label The name of the event. Its lifetime must extend at least until
+ *     Dart_Cleanup.
+ * \param timestamp0 The first timestamp of the event.
+ * \param timestamp1_or_id When reporting an event of type
+ *     |Dart_Timeline_Event_Duration|, the second (end) timestamp of the event
+ *     should be passed through |timestamp1_or_id|. When reporting an event of
+ *     type |Dart_Timeline_Event_Async_Begin|, |Dart_Timeline_Event_Async_End|,
+ *     or |Dart_Timeline_Event_Async_Instant|, the async ID associated with the
+ *     event should be passed through |timestamp1_or_id|. When reporting an
+ *     event of type |Dart_Timeline_Event_Flow_Begin|,
+ *     |Dart_Timeline_Event_Flow_Step|, or |Dart_Timeline_Event_Flow_End|, the
+ *     flow ID associated with the event should be passed through
+ *     |timestamp1_or_id|. When reporting an event of type
+ *     |Dart_Timeline_Event_Begin| or |Dart_Timeline_Event_End|, the event ID
+ *     associated with the event should be passed through |timestamp1_or_id|.
+ *     Note that this event ID will only be used by the MacOS recorder. The
+ *     argument to |timestamp1_or_id| will not be used when reporting events of
+ *     other types.
+ * \param flow_id_count The number of flow IDs associated with this event.
+ * \param flow_ids An array of flow IDs associated with this event. The array
+ *     may be reclaimed when this call returns.
+ * \param argument_count The number of argument names and values.
+ * \param argument_names An array of names of the arguments. The lifetime of the
+ *     names must extend at least until Dart_Cleanup. The array may be reclaimed
+ *     when this call returns.
+ * \param argument_values An array of values of the arguments. The values and
+ *     the array may be reclaimed when this call returns.
+ */
+DART_EXPORT void Dart_RecordTimelineEvent(const char* label,
+                                          int64_t timestamp0,
+                                          int64_t timestamp1_or_id,
+                                          intptr_t flow_id_count,
+                                          const int64_t* flow_ids,
+                                          Dart_Timeline_Event_Type type,
+                                          intptr_t argument_count,
+                                          const char** argument_names,
+                                          const char** argument_values);
+
+/**
+ * Associates a name with the current thread. This name will be used to name
+ * threads in the timeline. Can only be called after a call to Dart_Initialize.
+ *
+ * \param name The name of the thread.
+ */
+DART_EXPORT void Dart_SetThreadName(const char* name);
+
+typedef struct {
+  const char* name;
+  const char* value;
+} Dart_TimelineRecorderEvent_Argument;
+
+#define DART_TIMELINE_RECORDER_CURRENT_VERSION (0x00000002)
+
+typedef struct {
+  /* Set to DART_TIMELINE_RECORDER_CURRENT_VERSION */
+  int32_t version;
+
+  /* The event's type / phase. */
+  Dart_Timeline_Event_Type type;
+
+  /* The event's timestamp according to the same clock as
+   * Dart_TimelineGetMicros. For a duration event, this is the beginning time.
+   */
+  int64_t timestamp0;
+
+  /**
+   * For a duration event, this is the end time. For an async event, this is the
+   * async ID. For a flow event, this is the flow ID. For a begin or end event,
+   * this is the event ID (which is only referenced by the MacOS recorder).
+   */
+  int64_t timestamp1_or_id;
+
+  /* The current isolate of the event, as if by Dart_GetMainPortId, or
+   * ILLEGAL_PORT if the event had no current isolate. */
+  Dart_Port isolate;
+
+  /* The current isolate group of the event, as if by
+   * Dart_CurrentIsolateGroupId, or ILLEGAL_PORT if the event had no current
+   * isolate group. */
+  Dart_IsolateGroupId isolate_group;
+
+  /* The callback data associated with the isolate if any. */
+  void* isolate_data;
+
+  /* The callback data associated with the isolate group if any. */
+  void* isolate_group_data;
+
+  /* The name / label of the event. */
+  const char* label;
+
+  /* The stream / category of the event. */
+  const char* stream;
+
+  intptr_t argument_count;
+  Dart_TimelineRecorderEvent_Argument* arguments;
+} Dart_TimelineRecorderEvent;
+
+/**
+ * Callback provided by the embedder to handle the completion of timeline
+ * events.
+ *
+ * \param event A timeline event that has just been completed. The VM keeps
+ * ownership of the event and any field in it (i.e., the embedder should copy
+ * any values it needs after the callback returns).
+ */
+typedef void (*Dart_TimelineRecorderCallback)(
+    Dart_TimelineRecorderEvent* event);
+
+/**
+ * Register a `Dart_TimelineRecorderCallback` to be called as timeline events
+ * are completed.
+ *
+ * The callback will be invoked without a current isolate.
+ *
+ * The callback will be invoked on the thread completing the event. Because
+ * `Dart_TimelineEvent` may be called by any thread, the callback may be called
+ * on any thread.
+ *
+ * The callback may be invoked at any time after `Dart_Initialize` is called and
+ * before `Dart_Cleanup` returns.
+ *
+ * If multiple callbacks are registered, only the last callback registered
+ * will be remembered. Providing a NULL callback will clear the registration
+ * (i.e., a NULL callback produced a no-op instead of a crash).
+ *
+ * Setting a callback is insufficient to receive events through the callback. The
+ * VM flag `timeline_recorder` must also be set to `callback`.
+ */
+DART_EXPORT void Dart_SetTimelineRecorderCallback(
+    Dart_TimelineRecorderCallback callback);
+
+/*
+ * =======
+ * Metrics
+ * =======
+ */
+
+/**
+ * Return metrics gathered for the VM and individual isolates.
+ */
+DART_EXPORT int64_t
+Dart_IsolateGroupHeapOldUsedMetric(Dart_IsolateGroup group);  // Byte
+DART_EXPORT int64_t
+Dart_IsolateGroupHeapOldCapacityMetric(Dart_IsolateGroup group);  // Byte
+DART_EXPORT int64_t
+Dart_IsolateGroupHeapOldExternalMetric(Dart_IsolateGroup group);  // Byte
+DART_EXPORT int64_t
+Dart_IsolateGroupHeapNewUsedMetric(Dart_IsolateGroup group);  // Byte
+DART_EXPORT int64_t
+Dart_IsolateGroupHeapNewCapacityMetric(Dart_IsolateGroup group);  // Byte
+DART_EXPORT int64_t
+Dart_IsolateGroupHeapNewExternalMetric(Dart_IsolateGroup group);  // Byte
+
+/*
+ * ========
+ * UserTags
+ * ========
+ */
+
+/*
+ * Gets the current isolate's currently set UserTag instance.
+ *
+ * \return The currently set UserTag instance.
+ */
+DART_EXPORT Dart_Handle Dart_GetCurrentUserTag();
+
+/*
+ * Gets the current isolate's default UserTag instance.
+ *
+ * \return The default UserTag with label 'Default'
+ */
+DART_EXPORT Dart_Handle Dart_GetDefaultUserTag();
+
+/*
+ * Creates a new UserTag instance.
+ *
+ * \param label The name of the new UserTag.
+ *
+ * \return The newly created UserTag instance or an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewUserTag(const char* label);
+
+/*
+ * Updates the current isolate's UserTag to a new value.
+ *
+ * \param user_tag The UserTag to be set as the current UserTag.
+ *
+ * \return The previously set UserTag instance or an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_SetCurrentUserTag(Dart_Handle user_tag);
+
+/*
+ * Returns the label of a given UserTag instance.
+ *
+ * \param user_tag The UserTag from which the label will be retrieved.
+ *
+ * \return The UserTag's label. NULL if the user_tag is invalid. The caller is
+ *   responsible for freeing the returned label.
+ */
+DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_GetUserTagLabel(
+    Dart_Handle user_tag);
+
+/*
+ * =======
+ * Heap Snapshot
+ * =======
+ */
+
+/**
+ * Callback provided by the caller of `Dart_WriteHeapSnapshot` which is
+ * used to write out chunks of the requested heap snapshot.
+ *
+ * \param context An opaque context which was passed to `Dart_WriteHeapSnapshot`
+ *   together with this callback.
+ *
+ * \param buffer Pointer to the buffer containing a chunk of the snapshot.
+ *   The callback owns the buffer and needs to `free` it.
+ *
+ * \param size Number of bytes in the `buffer` to be written.
+ *
+ * \param is_last Set to `true` for the last chunk. The callback will not
+ *   be invoked again after it was invoked once with `is_last` set to `true`.
+ */
+typedef void (*Dart_HeapSnapshotWriteChunkCallback)(void* context,
+                                                    uint8_t* buffer,
+                                                    intptr_t size,
+                                                    bool is_last);
+
+/**
+ * Generate heap snapshot of the current isolate group and stream it into the
+ * given `callback`. VM would produce snapshot in chunks and send these chunks
+ * one by one back to the embedder by invoking the provided `callback`.
+ *
+ * This API enables embedder to stream snapshot into a file or socket without
+ * allocating a buffer to hold the whole snapshot in memory.
+ *
+ * The isolate group will be paused for the duration of this operation.
+ *
+ * \param write Callback used to write chunks of the heap snapshot.
+ *
+ * \param context Opaque context which would be passed on each invocation of
+ *   `write` callback.
+ *
+ * \returns `nullptr` if the operation is successful otherwise error message.
+ *   Caller owns error message string and needs to `free` it.
+ */
+DART_EXPORT char* Dart_WriteHeapSnapshot(
+    Dart_HeapSnapshotWriteChunkCallback write,
+    void* context);
+
+#endif  // RUNTIME_INCLUDE_DART_TOOLS_API_H_
diff --git a/pkgs/jni/src/include/dart_version.h b/pkgs/jni/src/include/dart_version.h
new file mode 100644
index 0000000..e2d3651
--- /dev/null
+++ b/pkgs/jni/src/include/dart_version.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ */
+
+#ifndef RUNTIME_INCLUDE_DART_VERSION_H_
+#define RUNTIME_INCLUDE_DART_VERSION_H_
+
+// On breaking changes the major version is increased.
+// On backwards compatible changes the minor version is increased.
+// The versioning covers the symbols exposed in dart_api_dl.h
+#define DART_API_DL_MAJOR_VERSION 2
+#define DART_API_DL_MINOR_VERSION 3
+
+#endif /* RUNTIME_INCLUDE_DART_VERSION_H_ */ /* NOLINT */
diff --git a/pkgs/jni/src/include/internal/dart_api_dl_impl.h b/pkgs/jni/src/include/internal/dart_api_dl_impl.h
new file mode 100644
index 0000000..e4a5689
--- /dev/null
+++ b/pkgs/jni/src/include/internal/dart_api_dl_impl.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ */
+
+#ifndef RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_
+#define RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_
+
+typedef struct {
+  const char* name;
+  void (*function)(void);
+} DartApiEntry;
+
+typedef struct {
+  const int major;
+  const int minor;
+  const DartApiEntry* const functions;
+} DartApi;
+
+#endif /* RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ */ /* NOLINT */
diff --git a/pkgs/jni/src/jni_constants.h b/pkgs/jni/src/jni_constants.h
new file mode 100644
index 0000000..8423ca1
--- /dev/null
+++ b/pkgs/jni/src/jni_constants.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This file re-exports some JNI constants as enum, because they are not
+// currently being included when they are in macro form.
+
+enum JniBooleanValues { JNI_FALSE = 0, JNI_TRUE = 1 };
+
+enum JniVersions {
+  JNI_VERSION_1_1 = 0x00010001,
+  JNI_VERSION_1_2 = 0x00010002,
+  JNI_VERSION_1_4 = 0x00010004,
+  JNI_VERSION_1_6 = 0x00010006,
+};
+
+enum JniErrorCode {
+  // Error codes from JNI
+  JNI_OK = 0,         /* no error */
+  JNI_ERR = -1,       /* generic error */
+  JNI_EDETACHED = -2, /* thread detached from the VM */
+  JNI_EVERSION = -3,  /* JNI version error */
+  JNI_ENOMEM = -4,    /* Out of memory */
+  JNI_EEXIST = -5,    /* VM already created */
+  JNI_EINVAL = -6,    /* Invalid argument */
+};
+
+enum JniBufferWriteBack {
+  JNI_COMMIT = 1, /* copy content, do not free buffer */
+  JNI_ABORT = 2,  /* free buffer w/o copying back */
+};
diff --git a/pkgs/jni/src/third_party/global_jni_env.c b/pkgs/jni/src/third_party/global_jni_env.c
new file mode 100644
index 0000000..7ddc164
--- /dev/null
+++ b/pkgs/jni/src/third_party/global_jni_env.c
@@ -0,0 +1,2509 @@
+// Auto generated file. Do not edit.
+
+// This is generated from JNI header in Android NDK. License for the same is
+// provided below.
+
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/*
+ * JNI specification, as defined by Sun:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
+ *
+ * Everything here is expected to be VM-neutral.
+ */
+
+#include "global_jni_env.h"
+
+JniResult globalEnv_GetVersion() {
+  attach_thread();
+  jint _result = (*jniEnv)->GetVersion(jniEnv);
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniClassLookupResult globalEnv_DefineClass(char* name,
+                                           jobject loader,
+                                           jbyte* buf,
+                                           jsize bufLen) {
+  attach_thread();
+  jclass _result = (*jniEnv)->DefineClass(jniEnv, name, loader, buf, bufLen);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniClassLookupResult){.value = NULL, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniClassLookupResult){.value = _result, .exception = NULL};
+}
+
+JniClassLookupResult globalEnv_FindClass(char* name) {
+  attach_thread();
+  jclass _result = (*jniEnv)->FindClass(jniEnv, name);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniClassLookupResult){.value = NULL, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniClassLookupResult){.value = _result, .exception = NULL};
+}
+
+JniPointerResult globalEnv_FromReflectedMethod(jobject method) {
+  attach_thread();
+  jmethodID _result = (*jniEnv)->FromReflectedMethod(jniEnv, method);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniPointerResult globalEnv_FromReflectedField(jobject field) {
+  attach_thread();
+  jfieldID _result = (*jniEnv)->FromReflectedField(jniEnv, field);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniResult globalEnv_ToReflectedMethod(jclass cls,
+                                      jmethodID methodId,
+                                      jboolean isStatic) {
+  attach_thread();
+  jobject _result =
+      (*jniEnv)->ToReflectedMethod(jniEnv, cls, methodId, isStatic);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniClassLookupResult globalEnv_GetSuperclass(jclass clazz) {
+  attach_thread();
+  jclass _result = (*jniEnv)->GetSuperclass(jniEnv, clazz);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniClassLookupResult){.value = NULL, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniClassLookupResult){.value = _result, .exception = NULL};
+}
+
+JniResult globalEnv_IsAssignableFrom(jclass clazz1, jclass clazz2) {
+  attach_thread();
+  jboolean _result = (*jniEnv)->IsAssignableFrom(jniEnv, clazz1, clazz2);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_ToReflectedField(jclass cls,
+                                     jfieldID fieldID,
+                                     jboolean isStatic) {
+  attach_thread();
+  jobject _result = (*jniEnv)->ToReflectedField(jniEnv, cls, fieldID, isStatic);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_Throw(jthrowable obj) {
+  attach_thread();
+  jint _result = (*jniEnv)->Throw(jniEnv, obj);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_ThrowNew(jclass clazz, char* message) {
+  attach_thread();
+  jint _result = (*jniEnv)->ThrowNew(jniEnv, clazz, message);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_ExceptionOccurred() {
+  attach_thread();
+  jthrowable _result = (*jniEnv)->ExceptionOccurred(jniEnv);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_ExceptionDescribe() {
+  attach_thread();
+  (*jniEnv)->ExceptionDescribe(jniEnv);
+  return NULL;
+}
+
+jthrowable globalEnv_ExceptionClear() {
+  attach_thread();
+  (*jniEnv)->ExceptionClear(jniEnv);
+  return NULL;
+}
+
+jthrowable globalEnv_FatalError(char* msg) {
+  attach_thread();
+  (*jniEnv)->FatalError(jniEnv, msg);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_PushLocalFrame(jint capacity) {
+  attach_thread();
+  jint _result = (*jniEnv)->PushLocalFrame(jniEnv, capacity);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_PopLocalFrame(jobject result) {
+  attach_thread();
+  jobject _result = (*jniEnv)->PopLocalFrame(jniEnv, result);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewGlobalRef(jobject obj) {
+  attach_thread();
+  jobject _result = (*jniEnv)->NewGlobalRef(jniEnv, obj);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_DeleteGlobalRef(jobject globalRef) {
+  attach_thread();
+  (*jniEnv)->DeleteGlobalRef(jniEnv, globalRef);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_DeleteLocalRef(jobject localRef) {
+  attach_thread();
+  (*jniEnv)->DeleteLocalRef(jniEnv, localRef);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_IsSameObject(jobject ref1, jobject ref2) {
+  attach_thread();
+  jboolean _result = (*jniEnv)->IsSameObject(jniEnv, ref1, ref2);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewLocalRef(jobject obj) {
+  attach_thread();
+  jobject _result = (*jniEnv)->NewLocalRef(jniEnv, obj);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_EnsureLocalCapacity(jint capacity) {
+  attach_thread();
+  jint _result = (*jniEnv)->EnsureLocalCapacity(jniEnv, capacity);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_AllocObject(jclass clazz) {
+  attach_thread();
+  jobject _result = (*jniEnv)->AllocObject(jniEnv, clazz);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewObject(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jobject _result = (*jniEnv)->NewObject(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewObjectA(jclass clazz, jmethodID methodID, jvalue* args) {
+  attach_thread();
+  jobject _result = (*jniEnv)->NewObjectA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniClassLookupResult globalEnv_GetObjectClass(jobject obj) {
+  attach_thread();
+  jclass _result = (*jniEnv)->GetObjectClass(jniEnv, obj);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniClassLookupResult){.value = NULL, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniClassLookupResult){.value = _result, .exception = NULL};
+}
+
+JniResult globalEnv_IsInstanceOf(jobject obj, jclass clazz) {
+  attach_thread();
+  jboolean _result = (*jniEnv)->IsInstanceOf(jniEnv, obj, clazz);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetMethodID(jclass clazz, char* name, char* sig) {
+  attach_thread();
+  jmethodID _result = (*jniEnv)->GetMethodID(jniEnv, clazz, name, sig);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniResult globalEnv_CallObjectMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallObjectMethodA(jobject obj,
+                                      jmethodID methodID,
+                                      jvalue* args) {
+  attach_thread();
+  jobject _result = (*jniEnv)->CallObjectMethodA(jniEnv, obj, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallBooleanMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  jboolean _result = (*jniEnv)->CallBooleanMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallBooleanMethodA(jobject obj,
+                                       jmethodID methodId,
+                                       jvalue* args) {
+  attach_thread();
+  jboolean _result = (*jniEnv)->CallBooleanMethodA(jniEnv, obj, methodId, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallByteMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  jbyte _result = (*jniEnv)->CallByteMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.b = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallByteMethodA(jobject obj,
+                                    jmethodID methodID,
+                                    jvalue* args) {
+  attach_thread();
+  jbyte _result = (*jniEnv)->CallByteMethodA(jniEnv, obj, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.b = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallCharMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  jchar _result = (*jniEnv)->CallCharMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.c = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallCharMethodA(jobject obj,
+                                    jmethodID methodID,
+                                    jvalue* args) {
+  attach_thread();
+  jchar _result = (*jniEnv)->CallCharMethodA(jniEnv, obj, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.c = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallShortMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  jshort _result = (*jniEnv)->CallShortMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.s = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallShortMethodA(jobject obj,
+                                     jmethodID methodID,
+                                     jvalue* args) {
+  attach_thread();
+  jshort _result = (*jniEnv)->CallShortMethodA(jniEnv, obj, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.s = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallIntMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  jint _result = (*jniEnv)->CallIntMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallIntMethodA(jobject obj,
+                                   jmethodID methodID,
+                                   jvalue* args) {
+  attach_thread();
+  jint _result = (*jniEnv)->CallIntMethodA(jniEnv, obj, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallLongMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  jlong _result = (*jniEnv)->CallLongMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.j = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallLongMethodA(jobject obj,
+                                    jmethodID methodID,
+                                    jvalue* args) {
+  attach_thread();
+  jlong _result = (*jniEnv)->CallLongMethodA(jniEnv, obj, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.j = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallFloatMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  jfloat _result = (*jniEnv)->CallFloatMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.f = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallFloatMethodA(jobject obj,
+                                     jmethodID methodID,
+                                     jvalue* args) {
+  attach_thread();
+  jfloat _result = (*jniEnv)->CallFloatMethodA(jniEnv, obj, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.f = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallDoubleMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  jdouble _result = (*jniEnv)->CallDoubleMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.d = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallDoubleMethodA(jobject obj,
+                                      jmethodID methodID,
+                                      jvalue* args) {
+  attach_thread();
+  jdouble _result = (*jniEnv)->CallDoubleMethodA(jniEnv, obj, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.d = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_CallVoidMethod(jobject obj, jmethodID methodID) {
+  attach_thread();
+  (*jniEnv)->CallVoidMethod(jniEnv, obj, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_CallVoidMethodA(jobject obj,
+                                     jmethodID methodID,
+                                     jvalue* args) {
+  attach_thread();
+  (*jniEnv)->CallVoidMethodA(jniEnv, obj, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_CallNonvirtualObjectMethod(jobject obj,
+                                               jclass clazz,
+                                               jmethodID methodID) {
+  attach_thread();
+  jobject _result =
+      (*jniEnv)->CallNonvirtualObjectMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualObjectMethodA(jobject obj,
+                                                jclass clazz,
+                                                jmethodID methodID,
+                                                jvalue* args) {
+  attach_thread();
+  jobject _result = (*jniEnv)->CallNonvirtualObjectMethodA(jniEnv, obj, clazz,
+                                                           methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualBooleanMethod(jobject obj,
+                                                jclass clazz,
+                                                jmethodID methodID) {
+  attach_thread();
+  jboolean _result =
+      (*jniEnv)->CallNonvirtualBooleanMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualBooleanMethodA(jobject obj,
+                                                 jclass clazz,
+                                                 jmethodID methodID,
+                                                 jvalue* args) {
+  attach_thread();
+  jboolean _result = (*jniEnv)->CallNonvirtualBooleanMethodA(jniEnv, obj, clazz,
+                                                             methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualByteMethod(jobject obj,
+                                             jclass clazz,
+                                             jmethodID methodID) {
+  attach_thread();
+  jbyte _result =
+      (*jniEnv)->CallNonvirtualByteMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.b = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualByteMethodA(jobject obj,
+                                              jclass clazz,
+                                              jmethodID methodID,
+                                              jvalue* args) {
+  attach_thread();
+  jbyte _result =
+      (*jniEnv)->CallNonvirtualByteMethodA(jniEnv, obj, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.b = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualCharMethod(jobject obj,
+                                             jclass clazz,
+                                             jmethodID methodID) {
+  attach_thread();
+  jchar _result =
+      (*jniEnv)->CallNonvirtualCharMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.c = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualCharMethodA(jobject obj,
+                                              jclass clazz,
+                                              jmethodID methodID,
+                                              jvalue* args) {
+  attach_thread();
+  jchar _result =
+      (*jniEnv)->CallNonvirtualCharMethodA(jniEnv, obj, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.c = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualShortMethod(jobject obj,
+                                              jclass clazz,
+                                              jmethodID methodID) {
+  attach_thread();
+  jshort _result =
+      (*jniEnv)->CallNonvirtualShortMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.s = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualShortMethodA(jobject obj,
+                                               jclass clazz,
+                                               jmethodID methodID,
+                                               jvalue* args) {
+  attach_thread();
+  jshort _result =
+      (*jniEnv)->CallNonvirtualShortMethodA(jniEnv, obj, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.s = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualIntMethod(jobject obj,
+                                            jclass clazz,
+                                            jmethodID methodID) {
+  attach_thread();
+  jint _result =
+      (*jniEnv)->CallNonvirtualIntMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualIntMethodA(jobject obj,
+                                             jclass clazz,
+                                             jmethodID methodID,
+                                             jvalue* args) {
+  attach_thread();
+  jint _result =
+      (*jniEnv)->CallNonvirtualIntMethodA(jniEnv, obj, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualLongMethod(jobject obj,
+                                             jclass clazz,
+                                             jmethodID methodID) {
+  attach_thread();
+  jlong _result =
+      (*jniEnv)->CallNonvirtualLongMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.j = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualLongMethodA(jobject obj,
+                                              jclass clazz,
+                                              jmethodID methodID,
+                                              jvalue* args) {
+  attach_thread();
+  jlong _result =
+      (*jniEnv)->CallNonvirtualLongMethodA(jniEnv, obj, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.j = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualFloatMethod(jobject obj,
+                                              jclass clazz,
+                                              jmethodID methodID) {
+  attach_thread();
+  jfloat _result =
+      (*jniEnv)->CallNonvirtualFloatMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.f = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualFloatMethodA(jobject obj,
+                                               jclass clazz,
+                                               jmethodID methodID,
+                                               jvalue* args) {
+  attach_thread();
+  jfloat _result =
+      (*jniEnv)->CallNonvirtualFloatMethodA(jniEnv, obj, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.f = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualDoubleMethod(jobject obj,
+                                               jclass clazz,
+                                               jmethodID methodID) {
+  attach_thread();
+  jdouble _result =
+      (*jniEnv)->CallNonvirtualDoubleMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.d = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallNonvirtualDoubleMethodA(jobject obj,
+                                                jclass clazz,
+                                                jmethodID methodID,
+                                                jvalue* args) {
+  attach_thread();
+  jdouble _result = (*jniEnv)->CallNonvirtualDoubleMethodA(jniEnv, obj, clazz,
+                                                           methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.d = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_CallNonvirtualVoidMethod(jobject obj,
+                                              jclass clazz,
+                                              jmethodID methodID) {
+  attach_thread();
+  (*jniEnv)->CallNonvirtualVoidMethod(jniEnv, obj, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_CallNonvirtualVoidMethodA(jobject obj,
+                                               jclass clazz,
+                                               jmethodID methodID,
+                                               jvalue* args) {
+  attach_thread();
+  (*jniEnv)->CallNonvirtualVoidMethodA(jniEnv, obj, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniPointerResult globalEnv_GetFieldID(jclass clazz, char* name, char* sig) {
+  attach_thread();
+  jfieldID _result = (*jniEnv)->GetFieldID(jniEnv, clazz, name, sig);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniResult globalEnv_GetObjectField(jobject obj, jfieldID fieldID) {
+  attach_thread();
+  jobject _result = (*jniEnv)->GetObjectField(jniEnv, obj, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetBooleanField(jobject obj, jfieldID fieldID) {
+  attach_thread();
+  jboolean _result = (*jniEnv)->GetBooleanField(jniEnv, obj, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetByteField(jobject obj, jfieldID fieldID) {
+  attach_thread();
+  jbyte _result = (*jniEnv)->GetByteField(jniEnv, obj, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.b = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetCharField(jobject obj, jfieldID fieldID) {
+  attach_thread();
+  jchar _result = (*jniEnv)->GetCharField(jniEnv, obj, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.c = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetShortField(jobject obj, jfieldID fieldID) {
+  attach_thread();
+  jshort _result = (*jniEnv)->GetShortField(jniEnv, obj, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.s = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetIntField(jobject obj, jfieldID fieldID) {
+  attach_thread();
+  jint _result = (*jniEnv)->GetIntField(jniEnv, obj, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetLongField(jobject obj, jfieldID fieldID) {
+  attach_thread();
+  jlong _result = (*jniEnv)->GetLongField(jniEnv, obj, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.j = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetFloatField(jobject obj, jfieldID fieldID) {
+  attach_thread();
+  jfloat _result = (*jniEnv)->GetFloatField(jniEnv, obj, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.f = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetDoubleField(jobject obj, jfieldID fieldID) {
+  attach_thread();
+  jdouble _result = (*jniEnv)->GetDoubleField(jniEnv, obj, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.d = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_SetObjectField(jobject obj,
+                                    jfieldID fieldID,
+                                    jobject val) {
+  attach_thread();
+  (*jniEnv)->SetObjectField(jniEnv, obj, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetBooleanField(jobject obj,
+                                     jfieldID fieldID,
+                                     jboolean val) {
+  attach_thread();
+  (*jniEnv)->SetBooleanField(jniEnv, obj, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetByteField(jobject obj, jfieldID fieldID, jbyte val) {
+  attach_thread();
+  (*jniEnv)->SetByteField(jniEnv, obj, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetCharField(jobject obj, jfieldID fieldID, jchar val) {
+  attach_thread();
+  (*jniEnv)->SetCharField(jniEnv, obj, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetShortField(jobject obj, jfieldID fieldID, jshort val) {
+  attach_thread();
+  (*jniEnv)->SetShortField(jniEnv, obj, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetIntField(jobject obj, jfieldID fieldID, jint val) {
+  attach_thread();
+  (*jniEnv)->SetIntField(jniEnv, obj, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetLongField(jobject obj, jfieldID fieldID, jlong val) {
+  attach_thread();
+  (*jniEnv)->SetLongField(jniEnv, obj, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetFloatField(jobject obj, jfieldID fieldID, jfloat val) {
+  attach_thread();
+  (*jniEnv)->SetFloatField(jniEnv, obj, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetDoubleField(jobject obj,
+                                    jfieldID fieldID,
+                                    jdouble val) {
+  attach_thread();
+  (*jniEnv)->SetDoubleField(jniEnv, obj, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniPointerResult globalEnv_GetStaticMethodID(jclass clazz,
+                                             char* name,
+                                             char* sig) {
+  attach_thread();
+  jmethodID _result = (*jniEnv)->GetStaticMethodID(jniEnv, clazz, name, sig);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticObjectMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticObjectMethodA(jclass clazz,
+                                            jmethodID methodID,
+                                            jvalue* args) {
+  attach_thread();
+  jobject _result =
+      (*jniEnv)->CallStaticObjectMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticBooleanMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jboolean _result =
+      (*jniEnv)->CallStaticBooleanMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticBooleanMethodA(jclass clazz,
+                                             jmethodID methodID,
+                                             jvalue* args) {
+  attach_thread();
+  jboolean _result =
+      (*jniEnv)->CallStaticBooleanMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticByteMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jbyte _result = (*jniEnv)->CallStaticByteMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.b = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticByteMethodA(jclass clazz,
+                                          jmethodID methodID,
+                                          jvalue* args) {
+  attach_thread();
+  jbyte _result =
+      (*jniEnv)->CallStaticByteMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.b = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticCharMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jchar _result = (*jniEnv)->CallStaticCharMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.c = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticCharMethodA(jclass clazz,
+                                          jmethodID methodID,
+                                          jvalue* args) {
+  attach_thread();
+  jchar _result =
+      (*jniEnv)->CallStaticCharMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.c = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticShortMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jshort _result = (*jniEnv)->CallStaticShortMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.s = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticShortMethodA(jclass clazz,
+                                           jmethodID methodID,
+                                           jvalue* args) {
+  attach_thread();
+  jshort _result =
+      (*jniEnv)->CallStaticShortMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.s = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticIntMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jint _result = (*jniEnv)->CallStaticIntMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticIntMethodA(jclass clazz,
+                                         jmethodID methodID,
+                                         jvalue* args) {
+  attach_thread();
+  jint _result = (*jniEnv)->CallStaticIntMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticLongMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jlong _result = (*jniEnv)->CallStaticLongMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.j = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticLongMethodA(jclass clazz,
+                                          jmethodID methodID,
+                                          jvalue* args) {
+  attach_thread();
+  jlong _result =
+      (*jniEnv)->CallStaticLongMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.j = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticFloatMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jfloat _result = (*jniEnv)->CallStaticFloatMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.f = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticFloatMethodA(jclass clazz,
+                                           jmethodID methodID,
+                                           jvalue* args) {
+  attach_thread();
+  jfloat _result =
+      (*jniEnv)->CallStaticFloatMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.f = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticDoubleMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  jdouble _result = (*jniEnv)->CallStaticDoubleMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.d = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_CallStaticDoubleMethodA(jclass clazz,
+                                            jmethodID methodID,
+                                            jvalue* args) {
+  attach_thread();
+  jdouble _result =
+      (*jniEnv)->CallStaticDoubleMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.d = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_CallStaticVoidMethod(jclass clazz, jmethodID methodID) {
+  attach_thread();
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, clazz, methodID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_CallStaticVoidMethodA(jclass clazz,
+                                           jmethodID methodID,
+                                           jvalue* args) {
+  attach_thread();
+  (*jniEnv)->CallStaticVoidMethodA(jniEnv, clazz, methodID, args);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniPointerResult globalEnv_GetStaticFieldID(jclass clazz,
+                                            char* name,
+                                            char* sig) {
+  attach_thread();
+  jfieldID _result = (*jniEnv)->GetStaticFieldID(jniEnv, clazz, name, sig);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniResult globalEnv_GetStaticObjectField(jclass clazz, jfieldID fieldID) {
+  attach_thread();
+  jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, clazz, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStaticBooleanField(jclass clazz, jfieldID fieldID) {
+  attach_thread();
+  jboolean _result = (*jniEnv)->GetStaticBooleanField(jniEnv, clazz, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStaticByteField(jclass clazz, jfieldID fieldID) {
+  attach_thread();
+  jbyte _result = (*jniEnv)->GetStaticByteField(jniEnv, clazz, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.b = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStaticCharField(jclass clazz, jfieldID fieldID) {
+  attach_thread();
+  jchar _result = (*jniEnv)->GetStaticCharField(jniEnv, clazz, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.c = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStaticShortField(jclass clazz, jfieldID fieldID) {
+  attach_thread();
+  jshort _result = (*jniEnv)->GetStaticShortField(jniEnv, clazz, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.s = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStaticIntField(jclass clazz, jfieldID fieldID) {
+  attach_thread();
+  jint _result = (*jniEnv)->GetStaticIntField(jniEnv, clazz, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStaticLongField(jclass clazz, jfieldID fieldID) {
+  attach_thread();
+  jlong _result = (*jniEnv)->GetStaticLongField(jniEnv, clazz, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.j = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStaticFloatField(jclass clazz, jfieldID fieldID) {
+  attach_thread();
+  jfloat _result = (*jniEnv)->GetStaticFloatField(jniEnv, clazz, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.f = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStaticDoubleField(jclass clazz, jfieldID fieldID) {
+  attach_thread();
+  jdouble _result = (*jniEnv)->GetStaticDoubleField(jniEnv, clazz, fieldID);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.d = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_SetStaticObjectField(jclass clazz,
+                                          jfieldID fieldID,
+                                          jobject val) {
+  attach_thread();
+  (*jniEnv)->SetStaticObjectField(jniEnv, clazz, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetStaticBooleanField(jclass clazz,
+                                           jfieldID fieldID,
+                                           jboolean val) {
+  attach_thread();
+  (*jniEnv)->SetStaticBooleanField(jniEnv, clazz, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetStaticByteField(jclass clazz,
+                                        jfieldID fieldID,
+                                        jbyte val) {
+  attach_thread();
+  (*jniEnv)->SetStaticByteField(jniEnv, clazz, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetStaticCharField(jclass clazz,
+                                        jfieldID fieldID,
+                                        jchar val) {
+  attach_thread();
+  (*jniEnv)->SetStaticCharField(jniEnv, clazz, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetStaticShortField(jclass clazz,
+                                         jfieldID fieldID,
+                                         jshort val) {
+  attach_thread();
+  (*jniEnv)->SetStaticShortField(jniEnv, clazz, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetStaticIntField(jclass clazz,
+                                       jfieldID fieldID,
+                                       jint val) {
+  attach_thread();
+  (*jniEnv)->SetStaticIntField(jniEnv, clazz, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetStaticLongField(jclass clazz,
+                                        jfieldID fieldID,
+                                        jlong val) {
+  attach_thread();
+  (*jniEnv)->SetStaticLongField(jniEnv, clazz, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetStaticFloatField(jclass clazz,
+                                         jfieldID fieldID,
+                                         jfloat val) {
+  attach_thread();
+  (*jniEnv)->SetStaticFloatField(jniEnv, clazz, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetStaticDoubleField(jclass clazz,
+                                          jfieldID fieldID,
+                                          jdouble val) {
+  attach_thread();
+  (*jniEnv)->SetStaticDoubleField(jniEnv, clazz, fieldID, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_NewString(jchar* unicodeChars, jsize len) {
+  attach_thread();
+  jstring _result = (*jniEnv)->NewString(jniEnv, unicodeChars, len);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStringLength(jstring string) {
+  attach_thread();
+  jsize _result = (*jniEnv)->GetStringLength(jniEnv, string);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetStringChars(jstring string, jboolean* isCopy) {
+  attach_thread();
+  const jchar* _result = (*jniEnv)->GetStringChars(jniEnv, string, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+jthrowable globalEnv_ReleaseStringChars(jstring string, jchar* isCopy) {
+  attach_thread();
+  (*jniEnv)->ReleaseStringChars(jniEnv, string, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_NewStringUTF(char* bytes) {
+  attach_thread();
+  jstring _result = (*jniEnv)->NewStringUTF(jniEnv, bytes);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetStringUTFLength(jstring string) {
+  attach_thread();
+  jsize _result = (*jniEnv)->GetStringUTFLength(jniEnv, string);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetStringUTFChars(jstring string, jboolean* isCopy) {
+  attach_thread();
+  const char* _result = (*jniEnv)->GetStringUTFChars(jniEnv, string, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+jthrowable globalEnv_ReleaseStringUTFChars(jstring string, char* utf) {
+  attach_thread();
+  (*jniEnv)->ReleaseStringUTFChars(jniEnv, string, utf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_GetArrayLength(jarray array) {
+  attach_thread();
+  jsize _result = (*jniEnv)->GetArrayLength(jniEnv, array);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewObjectArray(jsize length,
+                                   jclass elementClass,
+                                   jobject initialElement) {
+  attach_thread();
+  jobjectArray _result =
+      (*jniEnv)->NewObjectArray(jniEnv, length, elementClass, initialElement);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetObjectArrayElement(jobjectArray array, jsize index) {
+  attach_thread();
+  jobject _result = (*jniEnv)->GetObjectArrayElement(jniEnv, array, index);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_SetObjectArrayElement(jobjectArray array,
+                                           jsize index,
+                                           jobject val) {
+  attach_thread();
+  (*jniEnv)->SetObjectArrayElement(jniEnv, array, index, val);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_NewBooleanArray(jsize length) {
+  attach_thread();
+  jbooleanArray _result = (*jniEnv)->NewBooleanArray(jniEnv, length);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewByteArray(jsize length) {
+  attach_thread();
+  jbyteArray _result = (*jniEnv)->NewByteArray(jniEnv, length);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewCharArray(jsize length) {
+  attach_thread();
+  jcharArray _result = (*jniEnv)->NewCharArray(jniEnv, length);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewShortArray(jsize length) {
+  attach_thread();
+  jshortArray _result = (*jniEnv)->NewShortArray(jniEnv, length);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewIntArray(jsize length) {
+  attach_thread();
+  jintArray _result = (*jniEnv)->NewIntArray(jniEnv, length);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewLongArray(jsize length) {
+  attach_thread();
+  jlongArray _result = (*jniEnv)->NewLongArray(jniEnv, length);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewFloatArray(jsize length) {
+  attach_thread();
+  jfloatArray _result = (*jniEnv)->NewFloatArray(jniEnv, length);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewDoubleArray(jsize length) {
+  attach_thread();
+  jdoubleArray _result = (*jniEnv)->NewDoubleArray(jniEnv, length);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetBooleanArrayElements(jbooleanArray array,
+                                                   jboolean* isCopy) {
+  attach_thread();
+  jboolean* _result = (*jniEnv)->GetBooleanArrayElements(jniEnv, array, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetByteArrayElements(jbyteArray array,
+                                                jboolean* isCopy) {
+  attach_thread();
+  jbyte* _result = (*jniEnv)->GetByteArrayElements(jniEnv, array, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetCharArrayElements(jcharArray array,
+                                                jboolean* isCopy) {
+  attach_thread();
+  jchar* _result = (*jniEnv)->GetCharArrayElements(jniEnv, array, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetShortArrayElements(jshortArray array,
+                                                 jboolean* isCopy) {
+  attach_thread();
+  jshort* _result = (*jniEnv)->GetShortArrayElements(jniEnv, array, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetIntArrayElements(jintArray array,
+                                               jboolean* isCopy) {
+  attach_thread();
+  jint* _result = (*jniEnv)->GetIntArrayElements(jniEnv, array, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetLongArrayElements(jlongArray array,
+                                                jboolean* isCopy) {
+  attach_thread();
+  jlong* _result = (*jniEnv)->GetLongArrayElements(jniEnv, array, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetFloatArrayElements(jfloatArray array,
+                                                 jboolean* isCopy) {
+  attach_thread();
+  jfloat* _result = (*jniEnv)->GetFloatArrayElements(jniEnv, array, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetDoubleArrayElements(jdoubleArray array,
+                                                  jboolean* isCopy) {
+  attach_thread();
+  jdouble* _result = (*jniEnv)->GetDoubleArrayElements(jniEnv, array, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+jthrowable globalEnv_ReleaseBooleanArrayElements(jbooleanArray array,
+                                                 jboolean* elems,
+                                                 jint mode) {
+  attach_thread();
+  (*jniEnv)->ReleaseBooleanArrayElements(jniEnv, array, elems, mode);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_ReleaseByteArrayElements(jbyteArray array,
+                                              jbyte* elems,
+                                              jint mode) {
+  attach_thread();
+  (*jniEnv)->ReleaseByteArrayElements(jniEnv, array, elems, mode);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_ReleaseCharArrayElements(jcharArray array,
+                                              jchar* elems,
+                                              jint mode) {
+  attach_thread();
+  (*jniEnv)->ReleaseCharArrayElements(jniEnv, array, elems, mode);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_ReleaseShortArrayElements(jshortArray array,
+                                               jshort* elems,
+                                               jint mode) {
+  attach_thread();
+  (*jniEnv)->ReleaseShortArrayElements(jniEnv, array, elems, mode);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_ReleaseIntArrayElements(jintArray array,
+                                             jint* elems,
+                                             jint mode) {
+  attach_thread();
+  (*jniEnv)->ReleaseIntArrayElements(jniEnv, array, elems, mode);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_ReleaseLongArrayElements(jlongArray array,
+                                              jlong* elems,
+                                              jint mode) {
+  attach_thread();
+  (*jniEnv)->ReleaseLongArrayElements(jniEnv, array, elems, mode);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_ReleaseFloatArrayElements(jfloatArray array,
+                                               jfloat* elems,
+                                               jint mode) {
+  attach_thread();
+  (*jniEnv)->ReleaseFloatArrayElements(jniEnv, array, elems, mode);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_ReleaseDoubleArrayElements(jdoubleArray array,
+                                                jdouble* elems,
+                                                jint mode) {
+  attach_thread();
+  (*jniEnv)->ReleaseDoubleArrayElements(jniEnv, array, elems, mode);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_GetBooleanArrayRegion(jbooleanArray array,
+                                           jsize start,
+                                           jsize len,
+                                           jboolean* buf) {
+  attach_thread();
+  (*jniEnv)->GetBooleanArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_GetByteArrayRegion(jbyteArray array,
+                                        jsize start,
+                                        jsize len,
+                                        jbyte* buf) {
+  attach_thread();
+  (*jniEnv)->GetByteArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_GetCharArrayRegion(jcharArray array,
+                                        jsize start,
+                                        jsize len,
+                                        jchar* buf) {
+  attach_thread();
+  (*jniEnv)->GetCharArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_GetShortArrayRegion(jshortArray array,
+                                         jsize start,
+                                         jsize len,
+                                         jshort* buf) {
+  attach_thread();
+  (*jniEnv)->GetShortArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_GetIntArrayRegion(jintArray array,
+                                       jsize start,
+                                       jsize len,
+                                       jint* buf) {
+  attach_thread();
+  (*jniEnv)->GetIntArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_GetLongArrayRegion(jlongArray array,
+                                        jsize start,
+                                        jsize len,
+                                        jlong* buf) {
+  attach_thread();
+  (*jniEnv)->GetLongArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_GetFloatArrayRegion(jfloatArray array,
+                                         jsize start,
+                                         jsize len,
+                                         jfloat* buf) {
+  attach_thread();
+  (*jniEnv)->GetFloatArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_GetDoubleArrayRegion(jdoubleArray array,
+                                          jsize start,
+                                          jsize len,
+                                          jdouble* buf) {
+  attach_thread();
+  (*jniEnv)->GetDoubleArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetBooleanArrayRegion(jbooleanArray array,
+                                           jsize start,
+                                           jsize len,
+                                           jboolean* buf) {
+  attach_thread();
+  (*jniEnv)->SetBooleanArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetByteArrayRegion(jbyteArray array,
+                                        jsize start,
+                                        jsize len,
+                                        jbyte* buf) {
+  attach_thread();
+  (*jniEnv)->SetByteArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetCharArrayRegion(jcharArray array,
+                                        jsize start,
+                                        jsize len,
+                                        jchar* buf) {
+  attach_thread();
+  (*jniEnv)->SetCharArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetShortArrayRegion(jshortArray array,
+                                         jsize start,
+                                         jsize len,
+                                         jshort* buf) {
+  attach_thread();
+  (*jniEnv)->SetShortArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetIntArrayRegion(jintArray array,
+                                       jsize start,
+                                       jsize len,
+                                       jint* buf) {
+  attach_thread();
+  (*jniEnv)->SetIntArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetLongArrayRegion(jlongArray array,
+                                        jsize start,
+                                        jsize len,
+                                        jlong* buf) {
+  attach_thread();
+  (*jniEnv)->SetLongArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetFloatArrayRegion(jfloatArray array,
+                                         jsize start,
+                                         jsize len,
+                                         jfloat* buf) {
+  attach_thread();
+  (*jniEnv)->SetFloatArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_SetDoubleArrayRegion(jdoubleArray array,
+                                          jsize start,
+                                          jsize len,
+                                          jdouble* buf) {
+  attach_thread();
+  (*jniEnv)->SetDoubleArrayRegion(jniEnv, array, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_RegisterNatives(jclass clazz,
+                                    JNINativeMethod* methods,
+                                    jint nMethods) {
+  attach_thread();
+  jint _result = (*jniEnv)->RegisterNatives(jniEnv, clazz, methods, nMethods);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_UnregisterNatives(jclass clazz) {
+  attach_thread();
+  jint _result = (*jniEnv)->UnregisterNatives(jniEnv, clazz);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_MonitorEnter(jobject obj) {
+  attach_thread();
+  jint _result = (*jniEnv)->MonitorEnter(jniEnv, obj);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_MonitorExit(jobject obj) {
+  attach_thread();
+  jint _result = (*jniEnv)->MonitorExit(jniEnv, obj);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetJavaVM(JavaVM** vm) {
+  attach_thread();
+  jint _result = (*jniEnv)->GetJavaVM(jniEnv, vm);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_GetStringRegion(jstring str,
+                                     jsize start,
+                                     jsize len,
+                                     jchar* buf) {
+  attach_thread();
+  (*jniEnv)->GetStringRegion(jniEnv, str, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+jthrowable globalEnv_GetStringUTFRegion(jstring str,
+                                        jsize start,
+                                        jsize len,
+                                        char* buf) {
+  attach_thread();
+  (*jniEnv)->GetStringUTFRegion(jniEnv, str, start, len, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniPointerResult globalEnv_GetPrimitiveArrayCritical(jarray array,
+                                                     jboolean* isCopy) {
+  attach_thread();
+  void* _result = (*jniEnv)->GetPrimitiveArrayCritical(jniEnv, array, isCopy);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+jthrowable globalEnv_ReleasePrimitiveArrayCritical(jarray array,
+                                                   void* carray,
+                                                   jint mode) {
+  attach_thread();
+  (*jniEnv)->ReleasePrimitiveArrayCritical(jniEnv, array, carray, mode);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniPointerResult globalEnv_GetStringCritical(jstring str, jboolean* isCopy) {
+  attach_thread();
+  const jchar* _result = (*jniEnv)->GetStringCritical(jniEnv, str, isCopy);
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+jthrowable globalEnv_ReleaseStringCritical(jstring str, jchar* carray) {
+  attach_thread();
+  (*jniEnv)->ReleaseStringCritical(jniEnv, str, carray);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_NewWeakGlobalRef(jobject obj) {
+  attach_thread();
+  jweak _result = (*jniEnv)->NewWeakGlobalRef(jniEnv, obj);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+jthrowable globalEnv_DeleteWeakGlobalRef(jweak obj) {
+  attach_thread();
+  (*jniEnv)->DeleteWeakGlobalRef(jniEnv, obj);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return _exception;
+  }
+  return NULL;
+}
+
+JniResult globalEnv_ExceptionCheck() {
+  attach_thread();
+  jboolean _result = (*jniEnv)->ExceptionCheck(jniEnv);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.z = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_NewDirectByteBuffer(void* address, jlong capacity) {
+  attach_thread();
+  jobject _result = (*jniEnv)->NewDirectByteBuffer(jniEnv, address, capacity);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  _result = to_global_ref(_result);
+  return (JniResult){.value = {.l = _result}, .exception = NULL};
+}
+
+JniPointerResult globalEnv_GetDirectBufferAddress(jobject buf) {
+  attach_thread();
+  void* _result = (*jniEnv)->GetDirectBufferAddress(jniEnv, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniPointerResult){.value = NULL, .exception = _exception};
+  }
+  return (JniPointerResult){.value = _result, .exception = NULL};
+}
+
+JniResult globalEnv_GetDirectBufferCapacity(jobject buf) {
+  attach_thread();
+  jlong _result = (*jniEnv)->GetDirectBufferCapacity(jniEnv, buf);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.j = _result}, .exception = NULL};
+}
+
+JniResult globalEnv_GetObjectRefType(jobject obj) {
+  attach_thread();
+  int32_t _result = (*jniEnv)->GetObjectRefType(jniEnv, obj);
+  jthrowable _exception = check_exception();
+  if (_exception != NULL) {
+    return (JniResult){.value = {.j = 0}, .exception = _exception};
+  }
+  return (JniResult){.value = {.i = _result}, .exception = NULL};
+}
+
+GlobalJniEnvStruct globalJniEnv = {
+    .reserved0 = NULL,
+    .reserved1 = NULL,
+    .reserved2 = NULL,
+    .reserved3 = NULL,
+    .GetVersion = globalEnv_GetVersion,
+    .DefineClass = globalEnv_DefineClass,
+    .FindClass = globalEnv_FindClass,
+    .FromReflectedMethod = globalEnv_FromReflectedMethod,
+    .FromReflectedField = globalEnv_FromReflectedField,
+    .ToReflectedMethod = globalEnv_ToReflectedMethod,
+    .GetSuperclass = globalEnv_GetSuperclass,
+    .IsAssignableFrom = globalEnv_IsAssignableFrom,
+    .ToReflectedField = globalEnv_ToReflectedField,
+    .Throw = globalEnv_Throw,
+    .ThrowNew = globalEnv_ThrowNew,
+    .ExceptionOccurred = globalEnv_ExceptionOccurred,
+    .ExceptionDescribe = globalEnv_ExceptionDescribe,
+    .ExceptionClear = globalEnv_ExceptionClear,
+    .FatalError = globalEnv_FatalError,
+    .PushLocalFrame = globalEnv_PushLocalFrame,
+    .PopLocalFrame = globalEnv_PopLocalFrame,
+    .NewGlobalRef = globalEnv_NewGlobalRef,
+    .DeleteGlobalRef = globalEnv_DeleteGlobalRef,
+    .DeleteLocalRef = globalEnv_DeleteLocalRef,
+    .IsSameObject = globalEnv_IsSameObject,
+    .NewLocalRef = globalEnv_NewLocalRef,
+    .EnsureLocalCapacity = globalEnv_EnsureLocalCapacity,
+    .AllocObject = globalEnv_AllocObject,
+    .NewObject = globalEnv_NewObject,
+    .NewObjectV = NULL,
+    .NewObjectA = globalEnv_NewObjectA,
+    .GetObjectClass = globalEnv_GetObjectClass,
+    .IsInstanceOf = globalEnv_IsInstanceOf,
+    .GetMethodID = globalEnv_GetMethodID,
+    .CallObjectMethod = globalEnv_CallObjectMethod,
+    .CallObjectMethodV = NULL,
+    .CallObjectMethodA = globalEnv_CallObjectMethodA,
+    .CallBooleanMethod = globalEnv_CallBooleanMethod,
+    .CallBooleanMethodV = NULL,
+    .CallBooleanMethodA = globalEnv_CallBooleanMethodA,
+    .CallByteMethod = globalEnv_CallByteMethod,
+    .CallByteMethodV = NULL,
+    .CallByteMethodA = globalEnv_CallByteMethodA,
+    .CallCharMethod = globalEnv_CallCharMethod,
+    .CallCharMethodV = NULL,
+    .CallCharMethodA = globalEnv_CallCharMethodA,
+    .CallShortMethod = globalEnv_CallShortMethod,
+    .CallShortMethodV = NULL,
+    .CallShortMethodA = globalEnv_CallShortMethodA,
+    .CallIntMethod = globalEnv_CallIntMethod,
+    .CallIntMethodV = NULL,
+    .CallIntMethodA = globalEnv_CallIntMethodA,
+    .CallLongMethod = globalEnv_CallLongMethod,
+    .CallLongMethodV = NULL,
+    .CallLongMethodA = globalEnv_CallLongMethodA,
+    .CallFloatMethod = globalEnv_CallFloatMethod,
+    .CallFloatMethodV = NULL,
+    .CallFloatMethodA = globalEnv_CallFloatMethodA,
+    .CallDoubleMethod = globalEnv_CallDoubleMethod,
+    .CallDoubleMethodV = NULL,
+    .CallDoubleMethodA = globalEnv_CallDoubleMethodA,
+    .CallVoidMethod = globalEnv_CallVoidMethod,
+    .CallVoidMethodV = NULL,
+    .CallVoidMethodA = globalEnv_CallVoidMethodA,
+    .CallNonvirtualObjectMethod = globalEnv_CallNonvirtualObjectMethod,
+    .CallNonvirtualObjectMethodV = NULL,
+    .CallNonvirtualObjectMethodA = globalEnv_CallNonvirtualObjectMethodA,
+    .CallNonvirtualBooleanMethod = globalEnv_CallNonvirtualBooleanMethod,
+    .CallNonvirtualBooleanMethodV = NULL,
+    .CallNonvirtualBooleanMethodA = globalEnv_CallNonvirtualBooleanMethodA,
+    .CallNonvirtualByteMethod = globalEnv_CallNonvirtualByteMethod,
+    .CallNonvirtualByteMethodV = NULL,
+    .CallNonvirtualByteMethodA = globalEnv_CallNonvirtualByteMethodA,
+    .CallNonvirtualCharMethod = globalEnv_CallNonvirtualCharMethod,
+    .CallNonvirtualCharMethodV = NULL,
+    .CallNonvirtualCharMethodA = globalEnv_CallNonvirtualCharMethodA,
+    .CallNonvirtualShortMethod = globalEnv_CallNonvirtualShortMethod,
+    .CallNonvirtualShortMethodV = NULL,
+    .CallNonvirtualShortMethodA = globalEnv_CallNonvirtualShortMethodA,
+    .CallNonvirtualIntMethod = globalEnv_CallNonvirtualIntMethod,
+    .CallNonvirtualIntMethodV = NULL,
+    .CallNonvirtualIntMethodA = globalEnv_CallNonvirtualIntMethodA,
+    .CallNonvirtualLongMethod = globalEnv_CallNonvirtualLongMethod,
+    .CallNonvirtualLongMethodV = NULL,
+    .CallNonvirtualLongMethodA = globalEnv_CallNonvirtualLongMethodA,
+    .CallNonvirtualFloatMethod = globalEnv_CallNonvirtualFloatMethod,
+    .CallNonvirtualFloatMethodV = NULL,
+    .CallNonvirtualFloatMethodA = globalEnv_CallNonvirtualFloatMethodA,
+    .CallNonvirtualDoubleMethod = globalEnv_CallNonvirtualDoubleMethod,
+    .CallNonvirtualDoubleMethodV = NULL,
+    .CallNonvirtualDoubleMethodA = globalEnv_CallNonvirtualDoubleMethodA,
+    .CallNonvirtualVoidMethod = globalEnv_CallNonvirtualVoidMethod,
+    .CallNonvirtualVoidMethodV = NULL,
+    .CallNonvirtualVoidMethodA = globalEnv_CallNonvirtualVoidMethodA,
+    .GetFieldID = globalEnv_GetFieldID,
+    .GetObjectField = globalEnv_GetObjectField,
+    .GetBooleanField = globalEnv_GetBooleanField,
+    .GetByteField = globalEnv_GetByteField,
+    .GetCharField = globalEnv_GetCharField,
+    .GetShortField = globalEnv_GetShortField,
+    .GetIntField = globalEnv_GetIntField,
+    .GetLongField = globalEnv_GetLongField,
+    .GetFloatField = globalEnv_GetFloatField,
+    .GetDoubleField = globalEnv_GetDoubleField,
+    .SetObjectField = globalEnv_SetObjectField,
+    .SetBooleanField = globalEnv_SetBooleanField,
+    .SetByteField = globalEnv_SetByteField,
+    .SetCharField = globalEnv_SetCharField,
+    .SetShortField = globalEnv_SetShortField,
+    .SetIntField = globalEnv_SetIntField,
+    .SetLongField = globalEnv_SetLongField,
+    .SetFloatField = globalEnv_SetFloatField,
+    .SetDoubleField = globalEnv_SetDoubleField,
+    .GetStaticMethodID = globalEnv_GetStaticMethodID,
+    .CallStaticObjectMethod = globalEnv_CallStaticObjectMethod,
+    .CallStaticObjectMethodV = NULL,
+    .CallStaticObjectMethodA = globalEnv_CallStaticObjectMethodA,
+    .CallStaticBooleanMethod = globalEnv_CallStaticBooleanMethod,
+    .CallStaticBooleanMethodV = NULL,
+    .CallStaticBooleanMethodA = globalEnv_CallStaticBooleanMethodA,
+    .CallStaticByteMethod = globalEnv_CallStaticByteMethod,
+    .CallStaticByteMethodV = NULL,
+    .CallStaticByteMethodA = globalEnv_CallStaticByteMethodA,
+    .CallStaticCharMethod = globalEnv_CallStaticCharMethod,
+    .CallStaticCharMethodV = NULL,
+    .CallStaticCharMethodA = globalEnv_CallStaticCharMethodA,
+    .CallStaticShortMethod = globalEnv_CallStaticShortMethod,
+    .CallStaticShortMethodV = NULL,
+    .CallStaticShortMethodA = globalEnv_CallStaticShortMethodA,
+    .CallStaticIntMethod = globalEnv_CallStaticIntMethod,
+    .CallStaticIntMethodV = NULL,
+    .CallStaticIntMethodA = globalEnv_CallStaticIntMethodA,
+    .CallStaticLongMethod = globalEnv_CallStaticLongMethod,
+    .CallStaticLongMethodV = NULL,
+    .CallStaticLongMethodA = globalEnv_CallStaticLongMethodA,
+    .CallStaticFloatMethod = globalEnv_CallStaticFloatMethod,
+    .CallStaticFloatMethodV = NULL,
+    .CallStaticFloatMethodA = globalEnv_CallStaticFloatMethodA,
+    .CallStaticDoubleMethod = globalEnv_CallStaticDoubleMethod,
+    .CallStaticDoubleMethodV = NULL,
+    .CallStaticDoubleMethodA = globalEnv_CallStaticDoubleMethodA,
+    .CallStaticVoidMethod = globalEnv_CallStaticVoidMethod,
+    .CallStaticVoidMethodV = NULL,
+    .CallStaticVoidMethodA = globalEnv_CallStaticVoidMethodA,
+    .GetStaticFieldID = globalEnv_GetStaticFieldID,
+    .GetStaticObjectField = globalEnv_GetStaticObjectField,
+    .GetStaticBooleanField = globalEnv_GetStaticBooleanField,
+    .GetStaticByteField = globalEnv_GetStaticByteField,
+    .GetStaticCharField = globalEnv_GetStaticCharField,
+    .GetStaticShortField = globalEnv_GetStaticShortField,
+    .GetStaticIntField = globalEnv_GetStaticIntField,
+    .GetStaticLongField = globalEnv_GetStaticLongField,
+    .GetStaticFloatField = globalEnv_GetStaticFloatField,
+    .GetStaticDoubleField = globalEnv_GetStaticDoubleField,
+    .SetStaticObjectField = globalEnv_SetStaticObjectField,
+    .SetStaticBooleanField = globalEnv_SetStaticBooleanField,
+    .SetStaticByteField = globalEnv_SetStaticByteField,
+    .SetStaticCharField = globalEnv_SetStaticCharField,
+    .SetStaticShortField = globalEnv_SetStaticShortField,
+    .SetStaticIntField = globalEnv_SetStaticIntField,
+    .SetStaticLongField = globalEnv_SetStaticLongField,
+    .SetStaticFloatField = globalEnv_SetStaticFloatField,
+    .SetStaticDoubleField = globalEnv_SetStaticDoubleField,
+    .NewString = globalEnv_NewString,
+    .GetStringLength = globalEnv_GetStringLength,
+    .GetStringChars = globalEnv_GetStringChars,
+    .ReleaseStringChars = globalEnv_ReleaseStringChars,
+    .NewStringUTF = globalEnv_NewStringUTF,
+    .GetStringUTFLength = globalEnv_GetStringUTFLength,
+    .GetStringUTFChars = globalEnv_GetStringUTFChars,
+    .ReleaseStringUTFChars = globalEnv_ReleaseStringUTFChars,
+    .GetArrayLength = globalEnv_GetArrayLength,
+    .NewObjectArray = globalEnv_NewObjectArray,
+    .GetObjectArrayElement = globalEnv_GetObjectArrayElement,
+    .SetObjectArrayElement = globalEnv_SetObjectArrayElement,
+    .NewBooleanArray = globalEnv_NewBooleanArray,
+    .NewByteArray = globalEnv_NewByteArray,
+    .NewCharArray = globalEnv_NewCharArray,
+    .NewShortArray = globalEnv_NewShortArray,
+    .NewIntArray = globalEnv_NewIntArray,
+    .NewLongArray = globalEnv_NewLongArray,
+    .NewFloatArray = globalEnv_NewFloatArray,
+    .NewDoubleArray = globalEnv_NewDoubleArray,
+    .GetBooleanArrayElements = globalEnv_GetBooleanArrayElements,
+    .GetByteArrayElements = globalEnv_GetByteArrayElements,
+    .GetCharArrayElements = globalEnv_GetCharArrayElements,
+    .GetShortArrayElements = globalEnv_GetShortArrayElements,
+    .GetIntArrayElements = globalEnv_GetIntArrayElements,
+    .GetLongArrayElements = globalEnv_GetLongArrayElements,
+    .GetFloatArrayElements = globalEnv_GetFloatArrayElements,
+    .GetDoubleArrayElements = globalEnv_GetDoubleArrayElements,
+    .ReleaseBooleanArrayElements = globalEnv_ReleaseBooleanArrayElements,
+    .ReleaseByteArrayElements = globalEnv_ReleaseByteArrayElements,
+    .ReleaseCharArrayElements = globalEnv_ReleaseCharArrayElements,
+    .ReleaseShortArrayElements = globalEnv_ReleaseShortArrayElements,
+    .ReleaseIntArrayElements = globalEnv_ReleaseIntArrayElements,
+    .ReleaseLongArrayElements = globalEnv_ReleaseLongArrayElements,
+    .ReleaseFloatArrayElements = globalEnv_ReleaseFloatArrayElements,
+    .ReleaseDoubleArrayElements = globalEnv_ReleaseDoubleArrayElements,
+    .GetBooleanArrayRegion = globalEnv_GetBooleanArrayRegion,
+    .GetByteArrayRegion = globalEnv_GetByteArrayRegion,
+    .GetCharArrayRegion = globalEnv_GetCharArrayRegion,
+    .GetShortArrayRegion = globalEnv_GetShortArrayRegion,
+    .GetIntArrayRegion = globalEnv_GetIntArrayRegion,
+    .GetLongArrayRegion = globalEnv_GetLongArrayRegion,
+    .GetFloatArrayRegion = globalEnv_GetFloatArrayRegion,
+    .GetDoubleArrayRegion = globalEnv_GetDoubleArrayRegion,
+    .SetBooleanArrayRegion = globalEnv_SetBooleanArrayRegion,
+    .SetByteArrayRegion = globalEnv_SetByteArrayRegion,
+    .SetCharArrayRegion = globalEnv_SetCharArrayRegion,
+    .SetShortArrayRegion = globalEnv_SetShortArrayRegion,
+    .SetIntArrayRegion = globalEnv_SetIntArrayRegion,
+    .SetLongArrayRegion = globalEnv_SetLongArrayRegion,
+    .SetFloatArrayRegion = globalEnv_SetFloatArrayRegion,
+    .SetDoubleArrayRegion = globalEnv_SetDoubleArrayRegion,
+    .RegisterNatives = globalEnv_RegisterNatives,
+    .UnregisterNatives = globalEnv_UnregisterNatives,
+    .MonitorEnter = globalEnv_MonitorEnter,
+    .MonitorExit = globalEnv_MonitorExit,
+    .GetJavaVM = globalEnv_GetJavaVM,
+    .GetStringRegion = globalEnv_GetStringRegion,
+    .GetStringUTFRegion = globalEnv_GetStringUTFRegion,
+    .GetPrimitiveArrayCritical = globalEnv_GetPrimitiveArrayCritical,
+    .ReleasePrimitiveArrayCritical = globalEnv_ReleasePrimitiveArrayCritical,
+    .GetStringCritical = globalEnv_GetStringCritical,
+    .ReleaseStringCritical = globalEnv_ReleaseStringCritical,
+    .NewWeakGlobalRef = globalEnv_NewWeakGlobalRef,
+    .DeleteWeakGlobalRef = globalEnv_DeleteWeakGlobalRef,
+    .ExceptionCheck = globalEnv_ExceptionCheck,
+    .NewDirectByteBuffer = globalEnv_NewDirectByteBuffer,
+    .GetDirectBufferAddress = globalEnv_GetDirectBufferAddress,
+    .GetDirectBufferCapacity = globalEnv_GetDirectBufferCapacity,
+    .GetObjectRefType = globalEnv_GetObjectRefType,
+};
+FFI_PLUGIN_EXPORT
+GlobalJniEnvStruct* GetGlobalEnv() {
+  if (jni->jvm == NULL) {
+    return NULL;
+  }
+  return &globalJniEnv;
+}
diff --git a/pkgs/jni/src/third_party/global_jni_env.h b/pkgs/jni/src/third_party/global_jni_env.h
new file mode 100644
index 0000000..562b1bf
--- /dev/null
+++ b/pkgs/jni/src/third_party/global_jni_env.h
@@ -0,0 +1,434 @@
+// Auto generated file. Do not edit.
+
+// This is generated from JNI header in Android NDK. License for the same is
+// provided below.
+
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/*
+ * JNI specification, as defined by Sun:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
+ *
+ * Everything here is expected to be VM-neutral.
+ */
+
+#include <stdint.h>
+#include "../dartjni.h"
+
+typedef struct GlobalJniEnvStruct {
+  void* reserved0;
+  void* reserved1;
+  void* reserved2;
+  void* reserved3;
+  JniResult (*GetVersion)();
+  JniClassLookupResult (*DefineClass)(char* name,
+                                      jobject loader,
+                                      jbyte* buf,
+                                      jsize bufLen);
+  JniClassLookupResult (*FindClass)(char* name);
+  JniPointerResult (*FromReflectedMethod)(jobject method);
+  JniPointerResult (*FromReflectedField)(jobject field);
+  JniResult (*ToReflectedMethod)(jclass cls,
+                                 jmethodID methodId,
+                                 jboolean isStatic);
+  JniClassLookupResult (*GetSuperclass)(jclass clazz);
+  JniResult (*IsAssignableFrom)(jclass clazz1, jclass clazz2);
+  JniResult (*ToReflectedField)(jclass cls,
+                                jfieldID fieldID,
+                                jboolean isStatic);
+  JniResult (*Throw)(jthrowable obj);
+  JniResult (*ThrowNew)(jclass clazz, char* message);
+  JniResult (*ExceptionOccurred)();
+  jthrowable (*ExceptionDescribe)();
+  jthrowable (*ExceptionClear)();
+  jthrowable (*FatalError)(char* msg);
+  JniResult (*PushLocalFrame)(jint capacity);
+  JniResult (*PopLocalFrame)(jobject result);
+  JniResult (*NewGlobalRef)(jobject obj);
+  jthrowable (*DeleteGlobalRef)(jobject globalRef);
+  jthrowable (*DeleteLocalRef)(jobject localRef);
+  JniResult (*IsSameObject)(jobject ref1, jobject ref2);
+  JniResult (*NewLocalRef)(jobject obj);
+  JniResult (*EnsureLocalCapacity)(jint capacity);
+  JniResult (*AllocObject)(jclass clazz);
+  JniResult (*NewObject)(jclass clazz, jmethodID methodID);
+  JniResult (*NewObjectV)(jclass, jmethodID, void*);
+  JniResult (*NewObjectA)(jclass clazz, jmethodID methodID, jvalue* args);
+  JniClassLookupResult (*GetObjectClass)(jobject obj);
+  JniResult (*IsInstanceOf)(jobject obj, jclass clazz);
+  JniPointerResult (*GetMethodID)(jclass clazz, char* name, char* sig);
+  JniResult (*CallObjectMethod)(jobject obj, jmethodID methodID);
+  JniResult (*CallObjectMethodV)(jobject, jmethodID, void*);
+  JniResult (*CallObjectMethodA)(jobject obj, jmethodID methodID, jvalue* args);
+  JniResult (*CallBooleanMethod)(jobject obj, jmethodID methodID);
+  JniResult (*CallBooleanMethodV)(jobject, jmethodID, void*);
+  JniResult (*CallBooleanMethodA)(jobject obj,
+                                  jmethodID methodId,
+                                  jvalue* args);
+  JniResult (*CallByteMethod)(jobject obj, jmethodID methodID);
+  JniResult (*CallByteMethodV)(jobject, jmethodID, void*);
+  JniResult (*CallByteMethodA)(jobject obj, jmethodID methodID, jvalue* args);
+  JniResult (*CallCharMethod)(jobject obj, jmethodID methodID);
+  JniResult (*CallCharMethodV)(jobject, jmethodID, void*);
+  JniResult (*CallCharMethodA)(jobject obj, jmethodID methodID, jvalue* args);
+  JniResult (*CallShortMethod)(jobject obj, jmethodID methodID);
+  JniResult (*CallShortMethodV)(jobject, jmethodID, void*);
+  JniResult (*CallShortMethodA)(jobject obj, jmethodID methodID, jvalue* args);
+  JniResult (*CallIntMethod)(jobject obj, jmethodID methodID);
+  JniResult (*CallIntMethodV)(jobject, jmethodID, void*);
+  JniResult (*CallIntMethodA)(jobject obj, jmethodID methodID, jvalue* args);
+  JniResult (*CallLongMethod)(jobject obj, jmethodID methodID);
+  JniResult (*CallLongMethodV)(jobject, jmethodID, void*);
+  JniResult (*CallLongMethodA)(jobject obj, jmethodID methodID, jvalue* args);
+  JniResult (*CallFloatMethod)(jobject obj, jmethodID methodID);
+  JniResult (*CallFloatMethodV)(jobject, jmethodID, void*);
+  JniResult (*CallFloatMethodA)(jobject obj, jmethodID methodID, jvalue* args);
+  JniResult (*CallDoubleMethod)(jobject obj, jmethodID methodID);
+  JniResult (*CallDoubleMethodV)(jobject, jmethodID, void*);
+  JniResult (*CallDoubleMethodA)(jobject obj, jmethodID methodID, jvalue* args);
+  jthrowable (*CallVoidMethod)(jobject obj, jmethodID methodID);
+  jthrowable (*CallVoidMethodV)(jobject, jmethodID, void*);
+  jthrowable (*CallVoidMethodA)(jobject obj, jmethodID methodID, jvalue* args);
+  JniResult (*CallNonvirtualObjectMethod)(jobject obj,
+                                          jclass clazz,
+                                          jmethodID methodID);
+  JniResult (*CallNonvirtualObjectMethodV)(jobject, jclass, jmethodID, void*);
+  JniResult (*CallNonvirtualObjectMethodA)(jobject obj,
+                                           jclass clazz,
+                                           jmethodID methodID,
+                                           jvalue* args);
+  JniResult (*CallNonvirtualBooleanMethod)(jobject obj,
+                                           jclass clazz,
+                                           jmethodID methodID);
+  JniResult (*CallNonvirtualBooleanMethodV)(jobject, jclass, jmethodID, void*);
+  JniResult (*CallNonvirtualBooleanMethodA)(jobject obj,
+                                            jclass clazz,
+                                            jmethodID methodID,
+                                            jvalue* args);
+  JniResult (*CallNonvirtualByteMethod)(jobject obj,
+                                        jclass clazz,
+                                        jmethodID methodID);
+  JniResult (*CallNonvirtualByteMethodV)(jobject, jclass, jmethodID, void*);
+  JniResult (*CallNonvirtualByteMethodA)(jobject obj,
+                                         jclass clazz,
+                                         jmethodID methodID,
+                                         jvalue* args);
+  JniResult (*CallNonvirtualCharMethod)(jobject obj,
+                                        jclass clazz,
+                                        jmethodID methodID);
+  JniResult (*CallNonvirtualCharMethodV)(jobject, jclass, jmethodID, void*);
+  JniResult (*CallNonvirtualCharMethodA)(jobject obj,
+                                         jclass clazz,
+                                         jmethodID methodID,
+                                         jvalue* args);
+  JniResult (*CallNonvirtualShortMethod)(jobject obj,
+                                         jclass clazz,
+                                         jmethodID methodID);
+  JniResult (*CallNonvirtualShortMethodV)(jobject, jclass, jmethodID, void*);
+  JniResult (*CallNonvirtualShortMethodA)(jobject obj,
+                                          jclass clazz,
+                                          jmethodID methodID,
+                                          jvalue* args);
+  JniResult (*CallNonvirtualIntMethod)(jobject obj,
+                                       jclass clazz,
+                                       jmethodID methodID);
+  JniResult (*CallNonvirtualIntMethodV)(jobject, jclass, jmethodID, void*);
+  JniResult (*CallNonvirtualIntMethodA)(jobject obj,
+                                        jclass clazz,
+                                        jmethodID methodID,
+                                        jvalue* args);
+  JniResult (*CallNonvirtualLongMethod)(jobject obj,
+                                        jclass clazz,
+                                        jmethodID methodID);
+  JniResult (*CallNonvirtualLongMethodV)(jobject, jclass, jmethodID, void*);
+  JniResult (*CallNonvirtualLongMethodA)(jobject obj,
+                                         jclass clazz,
+                                         jmethodID methodID,
+                                         jvalue* args);
+  JniResult (*CallNonvirtualFloatMethod)(jobject obj,
+                                         jclass clazz,
+                                         jmethodID methodID);
+  JniResult (*CallNonvirtualFloatMethodV)(jobject, jclass, jmethodID, void*);
+  JniResult (*CallNonvirtualFloatMethodA)(jobject obj,
+                                          jclass clazz,
+                                          jmethodID methodID,
+                                          jvalue* args);
+  JniResult (*CallNonvirtualDoubleMethod)(jobject obj,
+                                          jclass clazz,
+                                          jmethodID methodID);
+  JniResult (*CallNonvirtualDoubleMethodV)(jobject, jclass, jmethodID, void*);
+  JniResult (*CallNonvirtualDoubleMethodA)(jobject obj,
+                                           jclass clazz,
+                                           jmethodID methodID,
+                                           jvalue* args);
+  jthrowable (*CallNonvirtualVoidMethod)(jobject obj,
+                                         jclass clazz,
+                                         jmethodID methodID);
+  jthrowable (*CallNonvirtualVoidMethodV)(jobject, jclass, jmethodID, void*);
+  jthrowable (*CallNonvirtualVoidMethodA)(jobject obj,
+                                          jclass clazz,
+                                          jmethodID methodID,
+                                          jvalue* args);
+  JniPointerResult (*GetFieldID)(jclass clazz, char* name, char* sig);
+  JniResult (*GetObjectField)(jobject obj, jfieldID fieldID);
+  JniResult (*GetBooleanField)(jobject obj, jfieldID fieldID);
+  JniResult (*GetByteField)(jobject obj, jfieldID fieldID);
+  JniResult (*GetCharField)(jobject obj, jfieldID fieldID);
+  JniResult (*GetShortField)(jobject obj, jfieldID fieldID);
+  JniResult (*GetIntField)(jobject obj, jfieldID fieldID);
+  JniResult (*GetLongField)(jobject obj, jfieldID fieldID);
+  JniResult (*GetFloatField)(jobject obj, jfieldID fieldID);
+  JniResult (*GetDoubleField)(jobject obj, jfieldID fieldID);
+  jthrowable (*SetObjectField)(jobject obj, jfieldID fieldID, jobject val);
+  jthrowable (*SetBooleanField)(jobject obj, jfieldID fieldID, jboolean val);
+  jthrowable (*SetByteField)(jobject obj, jfieldID fieldID, jbyte val);
+  jthrowable (*SetCharField)(jobject obj, jfieldID fieldID, jchar val);
+  jthrowable (*SetShortField)(jobject obj, jfieldID fieldID, jshort val);
+  jthrowable (*SetIntField)(jobject obj, jfieldID fieldID, jint val);
+  jthrowable (*SetLongField)(jobject obj, jfieldID fieldID, jlong val);
+  jthrowable (*SetFloatField)(jobject obj, jfieldID fieldID, jfloat val);
+  jthrowable (*SetDoubleField)(jobject obj, jfieldID fieldID, jdouble val);
+  JniPointerResult (*GetStaticMethodID)(jclass clazz, char* name, char* sig);
+  JniResult (*CallStaticObjectMethod)(jclass clazz, jmethodID methodID);
+  JniResult (*CallStaticObjectMethodV)(jclass, jmethodID, void*);
+  JniResult (*CallStaticObjectMethodA)(jclass clazz,
+                                       jmethodID methodID,
+                                       jvalue* args);
+  JniResult (*CallStaticBooleanMethod)(jclass clazz, jmethodID methodID);
+  JniResult (*CallStaticBooleanMethodV)(jclass, jmethodID, void*);
+  JniResult (*CallStaticBooleanMethodA)(jclass clazz,
+                                        jmethodID methodID,
+                                        jvalue* args);
+  JniResult (*CallStaticByteMethod)(jclass clazz, jmethodID methodID);
+  JniResult (*CallStaticByteMethodV)(jclass, jmethodID, void*);
+  JniResult (*CallStaticByteMethodA)(jclass clazz,
+                                     jmethodID methodID,
+                                     jvalue* args);
+  JniResult (*CallStaticCharMethod)(jclass clazz, jmethodID methodID);
+  JniResult (*CallStaticCharMethodV)(jclass, jmethodID, void*);
+  JniResult (*CallStaticCharMethodA)(jclass clazz,
+                                     jmethodID methodID,
+                                     jvalue* args);
+  JniResult (*CallStaticShortMethod)(jclass clazz, jmethodID methodID);
+  JniResult (*CallStaticShortMethodV)(jclass, jmethodID, void*);
+  JniResult (*CallStaticShortMethodA)(jclass clazz,
+                                      jmethodID methodID,
+                                      jvalue* args);
+  JniResult (*CallStaticIntMethod)(jclass clazz, jmethodID methodID);
+  JniResult (*CallStaticIntMethodV)(jclass, jmethodID, void*);
+  JniResult (*CallStaticIntMethodA)(jclass clazz,
+                                    jmethodID methodID,
+                                    jvalue* args);
+  JniResult (*CallStaticLongMethod)(jclass clazz, jmethodID methodID);
+  JniResult (*CallStaticLongMethodV)(jclass, jmethodID, void*);
+  JniResult (*CallStaticLongMethodA)(jclass clazz,
+                                     jmethodID methodID,
+                                     jvalue* args);
+  JniResult (*CallStaticFloatMethod)(jclass clazz, jmethodID methodID);
+  JniResult (*CallStaticFloatMethodV)(jclass, jmethodID, void*);
+  JniResult (*CallStaticFloatMethodA)(jclass clazz,
+                                      jmethodID methodID,
+                                      jvalue* args);
+  JniResult (*CallStaticDoubleMethod)(jclass clazz, jmethodID methodID);
+  JniResult (*CallStaticDoubleMethodV)(jclass, jmethodID, void*);
+  JniResult (*CallStaticDoubleMethodA)(jclass clazz,
+                                       jmethodID methodID,
+                                       jvalue* args);
+  jthrowable (*CallStaticVoidMethod)(jclass clazz, jmethodID methodID);
+  jthrowable (*CallStaticVoidMethodV)(jclass, jmethodID, void*);
+  jthrowable (*CallStaticVoidMethodA)(jclass clazz,
+                                      jmethodID methodID,
+                                      jvalue* args);
+  JniPointerResult (*GetStaticFieldID)(jclass clazz, char* name, char* sig);
+  JniResult (*GetStaticObjectField)(jclass clazz, jfieldID fieldID);
+  JniResult (*GetStaticBooleanField)(jclass clazz, jfieldID fieldID);
+  JniResult (*GetStaticByteField)(jclass clazz, jfieldID fieldID);
+  JniResult (*GetStaticCharField)(jclass clazz, jfieldID fieldID);
+  JniResult (*GetStaticShortField)(jclass clazz, jfieldID fieldID);
+  JniResult (*GetStaticIntField)(jclass clazz, jfieldID fieldID);
+  JniResult (*GetStaticLongField)(jclass clazz, jfieldID fieldID);
+  JniResult (*GetStaticFloatField)(jclass clazz, jfieldID fieldID);
+  JniResult (*GetStaticDoubleField)(jclass clazz, jfieldID fieldID);
+  jthrowable (*SetStaticObjectField)(jclass clazz,
+                                     jfieldID fieldID,
+                                     jobject val);
+  jthrowable (*SetStaticBooleanField)(jclass clazz,
+                                      jfieldID fieldID,
+                                      jboolean val);
+  jthrowable (*SetStaticByteField)(jclass clazz, jfieldID fieldID, jbyte val);
+  jthrowable (*SetStaticCharField)(jclass clazz, jfieldID fieldID, jchar val);
+  jthrowable (*SetStaticShortField)(jclass clazz, jfieldID fieldID, jshort val);
+  jthrowable (*SetStaticIntField)(jclass clazz, jfieldID fieldID, jint val);
+  jthrowable (*SetStaticLongField)(jclass clazz, jfieldID fieldID, jlong val);
+  jthrowable (*SetStaticFloatField)(jclass clazz, jfieldID fieldID, jfloat val);
+  jthrowable (*SetStaticDoubleField)(jclass clazz,
+                                     jfieldID fieldID,
+                                     jdouble val);
+  JniResult (*NewString)(jchar* unicodeChars, jsize len);
+  JniResult (*GetStringLength)(jstring string);
+  JniPointerResult (*GetStringChars)(jstring string, jboolean* isCopy);
+  jthrowable (*ReleaseStringChars)(jstring string, jchar* isCopy);
+  JniResult (*NewStringUTF)(char* bytes);
+  JniResult (*GetStringUTFLength)(jstring string);
+  JniPointerResult (*GetStringUTFChars)(jstring string, jboolean* isCopy);
+  jthrowable (*ReleaseStringUTFChars)(jstring string, char* utf);
+  JniResult (*GetArrayLength)(jarray array);
+  JniResult (*NewObjectArray)(jsize length,
+                              jclass elementClass,
+                              jobject initialElement);
+  JniResult (*GetObjectArrayElement)(jobjectArray array, jsize index);
+  jthrowable (*SetObjectArrayElement)(jobjectArray array,
+                                      jsize index,
+                                      jobject val);
+  JniResult (*NewBooleanArray)(jsize length);
+  JniResult (*NewByteArray)(jsize length);
+  JniResult (*NewCharArray)(jsize length);
+  JniResult (*NewShortArray)(jsize length);
+  JniResult (*NewIntArray)(jsize length);
+  JniResult (*NewLongArray)(jsize length);
+  JniResult (*NewFloatArray)(jsize length);
+  JniResult (*NewDoubleArray)(jsize length);
+  JniPointerResult (*GetBooleanArrayElements)(jbooleanArray array,
+                                              jboolean* isCopy);
+  JniPointerResult (*GetByteArrayElements)(jbyteArray array, jboolean* isCopy);
+  JniPointerResult (*GetCharArrayElements)(jcharArray array, jboolean* isCopy);
+  JniPointerResult (*GetShortArrayElements)(jshortArray array,
+                                            jboolean* isCopy);
+  JniPointerResult (*GetIntArrayElements)(jintArray array, jboolean* isCopy);
+  JniPointerResult (*GetLongArrayElements)(jlongArray array, jboolean* isCopy);
+  JniPointerResult (*GetFloatArrayElements)(jfloatArray array,
+                                            jboolean* isCopy);
+  JniPointerResult (*GetDoubleArrayElements)(jdoubleArray array,
+                                             jboolean* isCopy);
+  jthrowable (*ReleaseBooleanArrayElements)(jbooleanArray array,
+                                            jboolean* elems,
+                                            jint mode);
+  jthrowable (*ReleaseByteArrayElements)(jbyteArray array,
+                                         jbyte* elems,
+                                         jint mode);
+  jthrowable (*ReleaseCharArrayElements)(jcharArray array,
+                                         jchar* elems,
+                                         jint mode);
+  jthrowable (*ReleaseShortArrayElements)(jshortArray array,
+                                          jshort* elems,
+                                          jint mode);
+  jthrowable (*ReleaseIntArrayElements)(jintArray array,
+                                        jint* elems,
+                                        jint mode);
+  jthrowable (*ReleaseLongArrayElements)(jlongArray array,
+                                         jlong* elems,
+                                         jint mode);
+  jthrowable (*ReleaseFloatArrayElements)(jfloatArray array,
+                                          jfloat* elems,
+                                          jint mode);
+  jthrowable (*ReleaseDoubleArrayElements)(jdoubleArray array,
+                                           jdouble* elems,
+                                           jint mode);
+  jthrowable (*GetBooleanArrayRegion)(jbooleanArray array,
+                                      jsize start,
+                                      jsize len,
+                                      jboolean* buf);
+  jthrowable (*GetByteArrayRegion)(jbyteArray array,
+                                   jsize start,
+                                   jsize len,
+                                   jbyte* buf);
+  jthrowable (*GetCharArrayRegion)(jcharArray array,
+                                   jsize start,
+                                   jsize len,
+                                   jchar* buf);
+  jthrowable (*GetShortArrayRegion)(jshortArray array,
+                                    jsize start,
+                                    jsize len,
+                                    jshort* buf);
+  jthrowable (*GetIntArrayRegion)(jintArray array,
+                                  jsize start,
+                                  jsize len,
+                                  jint* buf);
+  jthrowable (*GetLongArrayRegion)(jlongArray array,
+                                   jsize start,
+                                   jsize len,
+                                   jlong* buf);
+  jthrowable (*GetFloatArrayRegion)(jfloatArray array,
+                                    jsize start,
+                                    jsize len,
+                                    jfloat* buf);
+  jthrowable (*GetDoubleArrayRegion)(jdoubleArray array,
+                                     jsize start,
+                                     jsize len,
+                                     jdouble* buf);
+  jthrowable (*SetBooleanArrayRegion)(jbooleanArray array,
+                                      jsize start,
+                                      jsize len,
+                                      jboolean* buf);
+  jthrowable (*SetByteArrayRegion)(jbyteArray array,
+                                   jsize start,
+                                   jsize len,
+                                   jbyte* buf);
+  jthrowable (*SetCharArrayRegion)(jcharArray array,
+                                   jsize start,
+                                   jsize len,
+                                   jchar* buf);
+  jthrowable (*SetShortArrayRegion)(jshortArray array,
+                                    jsize start,
+                                    jsize len,
+                                    jshort* buf);
+  jthrowable (*SetIntArrayRegion)(jintArray array,
+                                  jsize start,
+                                  jsize len,
+                                  jint* buf);
+  jthrowable (*SetLongArrayRegion)(jlongArray array,
+                                   jsize start,
+                                   jsize len,
+                                   jlong* buf);
+  jthrowable (*SetFloatArrayRegion)(jfloatArray array,
+                                    jsize start,
+                                    jsize len,
+                                    jfloat* buf);
+  jthrowable (*SetDoubleArrayRegion)(jdoubleArray array,
+                                     jsize start,
+                                     jsize len,
+                                     jdouble* buf);
+  JniResult (*RegisterNatives)(jclass clazz,
+                               JNINativeMethod* methods,
+                               jint nMethods);
+  JniResult (*UnregisterNatives)(jclass clazz);
+  JniResult (*MonitorEnter)(jobject obj);
+  JniResult (*MonitorExit)(jobject obj);
+  JniResult (*GetJavaVM)(JavaVM** vm);
+  jthrowable (*GetStringRegion)(jstring str,
+                                jsize start,
+                                jsize len,
+                                jchar* buf);
+  jthrowable (*GetStringUTFRegion)(jstring str,
+                                   jsize start,
+                                   jsize len,
+                                   char* buf);
+  JniPointerResult (*GetPrimitiveArrayCritical)(jarray array, jboolean* isCopy);
+  jthrowable (*ReleasePrimitiveArrayCritical)(jarray array,
+                                              void* carray,
+                                              jint mode);
+  JniPointerResult (*GetStringCritical)(jstring str, jboolean* isCopy);
+  jthrowable (*ReleaseStringCritical)(jstring str, jchar* carray);
+  JniResult (*NewWeakGlobalRef)(jobject obj);
+  jthrowable (*DeleteWeakGlobalRef)(jweak obj);
+  JniResult (*ExceptionCheck)();
+  JniResult (*NewDirectByteBuffer)(void* address, jlong capacity);
+  JniPointerResult (*GetDirectBufferAddress)(jobject buf);
+  JniResult (*GetDirectBufferCapacity)(jobject buf);
+  JniResult (*GetObjectRefType)(jobject obj);
+} GlobalJniEnvStruct;
+FFI_PLUGIN_EXPORT GlobalJniEnvStruct* GetGlobalEnv();
diff --git a/pkgs/jni/test/boxed_test.dart b/pkgs/jni/test/boxed_test.dart
new file mode 100644
index 0000000..261b1ac
--- /dev/null
+++ b/pkgs/jni/test/boxed_test.dart
@@ -0,0 +1,146 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  testRunner('JByte', () {
+    const val = 1 << 5;
+    using((arena) {
+      expect(JByte(val).byteValue(releaseOriginal: true), val);
+      expect((-val).toJByte().byteValue(releaseOriginal: true), -val);
+    });
+  });
+  testRunner('JCharacter', () {
+    const val = 1 << 5;
+    using((arena) {
+      expect(JCharacter(val).charValue(releaseOriginal: true), val);
+      expect(JCharacter(0).charValue(releaseOriginal: true), 0);
+    });
+  });
+  testRunner('JShort', () {
+    const val = 1 << 10;
+    using((arena) {
+      expect(JShort(val).shortValue(releaseOriginal: true), val);
+      expect((-val).toJShort().shortValue(releaseOriginal: true), -val);
+    });
+  });
+  testRunner('JInteger', () {
+    const val = 1 << 20;
+    using((arena) {
+      expect(JInteger(val).intValue(releaseOriginal: true), val);
+      expect((-val).toJInteger().intValue(releaseOriginal: true), -val);
+    });
+  });
+  testRunner('JLong', () {
+    const val = 1 << 40;
+    using((arena) {
+      expect(JLong(val).longValue(releaseOriginal: true), val);
+      expect((-val).toJLong().longValue(releaseOriginal: true), -val);
+    });
+  });
+  testRunner('JFloat', () {
+    const val = 3.14;
+    const eps = 1e-6;
+    using((arena) {
+      expect(JFloat(val).floatValue(releaseOriginal: true), closeTo(val, eps));
+      expect((-val).toJFloat().floatValue(releaseOriginal: true),
+          closeTo(-val, eps));
+    });
+  });
+  testRunner('JDouble', () {
+    const val = 3.14;
+    const eps = 1e-9;
+    using((arena) {
+      expect(
+          JDouble(val).doubleValue(releaseOriginal: true), closeTo(val, eps));
+      expect((-val).toJDouble().doubleValue(releaseOriginal: true),
+          closeTo(-val, eps));
+    });
+  });
+  testRunner('JBoolean', () {
+    using((arena) {
+      expect(JBoolean(false).booleanValue(releaseOriginal: true), false);
+      expect(JBoolean(true).booleanValue(releaseOriginal: true), true);
+    });
+  });
+  testRunner('JByte.\$type hashCode and ==', () {
+    using((arena) {
+      final a = JByte(1)..releasedBy(arena);
+      final b = JByte(2)..releasedBy(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+    });
+  });
+  testRunner('JCharacter.\$type hashCode and ==', () {
+    using((arena) {
+      final a = JCharacter(1)..releasedBy(arena);
+      final b = JCharacter(2)..releasedBy(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+    });
+  });
+  testRunner('JShort.\$type hashCode and ==', () {
+    using((arena) {
+      final a = JShort(1)..releasedBy(arena);
+      final b = JShort(2)..releasedBy(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+    });
+  });
+  testRunner('JInteger.\$type hashCode and ==', () {
+    using((arena) {
+      final a = JInteger(1)..releasedBy(arena);
+      final b = JInteger(2)..releasedBy(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+    });
+  });
+  testRunner('JLong.\$type hashCode and ==', () {
+    using((arena) {
+      final a = JLong(1)..releasedBy(arena);
+      final b = JLong(2)..releasedBy(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+    });
+  });
+  testRunner('JFloat.\$type hashCode and ==', () {
+    using((arena) {
+      final a = JFloat(1.0)..releasedBy(arena);
+      final b = JFloat(2.0)..releasedBy(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+    });
+  });
+  testRunner('JDouble.\$type hashCode and ==', () {
+    using((arena) {
+      final a = JDouble(1.0)..releasedBy(arena);
+      final b = JDouble(2.0)..releasedBy(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+    });
+  });
+  testRunner('JBoolean.\$type hashCode and ==', () {
+    using((arena) {
+      final a = JBoolean(true)..releasedBy(arena);
+      final b = JBoolean(false)..releasedBy(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+    });
+  });
+}
diff --git a/pkgs/jni/test/exception_test.dart b/pkgs/jni/test/exception_test.dart
new file mode 100644
index 0000000..42d2297
--- /dev/null
+++ b/pkgs/jni/test/exception_test.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+import 'package:jni/jni.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    bool caught = false;
+    try {
+      // If library does not exist, a helpful error should be thrown.
+      // we can't test this directly because `test` schedules functions
+      // asynchronously.
+      Jni.spawn(dylibDir: "wrong_dir");
+    } on HelperNotFoundError catch (_) {
+      // stderr.write("\n$_\n");
+      Jni.spawnIfNotExists(
+          dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+      caught = true;
+    } on JniVmExistsError {
+      stderr.writeln('cannot verify: HelperNotFoundError thrown');
+    }
+    if (!caught) {
+      throw "Expected HelperNotFoundException\n"
+          "Read exception_test.dart for details.";
+    }
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  testRunner("double free throws exception", () {
+    final r = Jni.newInstance("java/util/Random", "()V", []);
+    r.release();
+    expect(r.release, throwsA(isA<DoubleReleaseError>()));
+  });
+
+  testRunner("Use after free throws exception", () {
+    final r = Jni.newInstance("java/util/Random", "()V", []);
+    r.release();
+    expect(() => r.callMethodByName<int>("nextInt", "(I)I", [JValueInt(256)]),
+        throwsA(isA<UseAfterReleaseError>()));
+  });
+
+  testRunner("void fieldType throws exception", () {
+    final r = Jni.newInstance("java/util/Random", "()V", []);
+    expect(() => r.getField<void>(nullptr, JniCallType.voidType),
+        throwsArgumentError);
+    expect(() => r.getStaticField<void>(nullptr, JniCallType.voidType),
+        throwsArgumentError);
+  });
+
+  testRunner("Wrong callType throws error", () {
+    final r = Jni.newInstance("java/util/Random", "()V", []);
+    expect(
+        () => r.callMethodByName<int>(
+            "nextInt", "(I)I", [JValueInt(256)], JniCallType.doubleType),
+        throwsA(isA<InvalidCallTypeError>()));
+  });
+
+  testRunner("An exception in JNI throws JniException in Dart", () {
+    final r = Jni.newInstance("java/util/Random", "()V", []);
+    expect(() => r.callMethodByName<int>("nextInt", "(I)I", [JValueInt(-1)]),
+        throwsA(isA<JniException>()));
+  });
+}
diff --git a/pkgs/jni/test/global_env_test.dart b/pkgs/jni/test/global_env_test.dart
new file mode 100644
index 0000000..a5588cd
--- /dev/null
+++ b/pkgs/jni/test/global_env_test.dart
@@ -0,0 +1,215 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:ffi/ffi.dart';
+import 'package:jni/jni.dart';
+import 'package:jni/src/jvalues.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+const maxLongInJava = 9223372036854775807;
+
+void main() {
+  // Running on Android through flutter, this plugin
+  // will bind to Android runtime's JVM.
+  // On other platforms eg Flutter desktop or Dart standalone
+  // You need to manually create a JVM at beginning.
+  //
+  // On flutter desktop, the C wrappers are bundled, and helperPath param
+  // is not required.
+  //
+  // On dart standalone, however, there's no way to bundle the wrappers.
+  // You have to manually pass the path to the `dartjni` dynamic library.
+
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+// We are factoring these tests into a run method which allows us to run same
+// tests with different environments. Eg: standalone and integration tests.
+void run({required TestRunnerCallback testRunner}) {
+  // Tests in this file demonstrate how to use `GlobalJniEnv`, a thin
+  // abstraction over JNIEnv in JNI C API. This can be used from multiple
+  // threads, and converts all returned object references to global references,
+  // so that you don't need to worry about whether your Dart code will be
+  // scheduled on another thread.
+  //
+  // GlobalJniEnv wraps all methods of JNIEnv (UpperCamelCase, reflecting the
+  // original name of the method) and provides few more extension methods
+  // (lowerCamelCase).
+  //
+  // For examples of a higher level API, see `jni_object_tests.dart`.
+  final env = Jni.env;
+  testRunner('initDLApi', () {
+    Jni.initDLApi();
+  });
+
+  testRunner('get JNI Version', () {
+    expect(Jni.env.GetVersion(), isNot(equals(0)));
+  });
+
+  testRunner(
+      'Manually lookup & call Integer.toHexString',
+      () => using((arena) {
+            // Method names on JniEnv* from C JNI API are capitalized
+            // like in original, while other extension methods
+            // follow Dart naming conventions.
+            final integerClass =
+                env.FindClass("java/lang/Integer".toNativeChars(arena));
+            // Refer JNI spec on how to construct method signatures
+            // Passing wrong signature leads to a segfault
+            final hexMethod = env.GetStaticMethodID(
+                integerClass,
+                "toHexString".toNativeChars(arena),
+                "(I)Ljava/lang/String;".toNativeChars(arena));
+
+            for (var i in [1, 80, 13, 76, 11344]) {
+              // if your argument is int, bool, or JObject (`Pointer<Void>`)
+              // it can be directly placed in the list. To convert into different primitive
+              // types, use JValue<Type> wrappers.
+              final jres = env.CallStaticObjectMethodA(integerClass, hexMethod,
+                  Jni.jvalues([JValueInt(i)], allocator: arena));
+
+              // use asDartString extension method on Pointer<JniEnv>
+              // to convert a String jobject result to string
+              final res = env.toDartString(jres);
+              expect(res, equals(i.toRadixString(16)));
+
+              // Any object or class result from java is a local reference
+              // and needs to be deleted explicitly.
+              // Note that method and field IDs aren't local references.
+              // But they are valid only until a reference to corresponding
+              // java class exists.
+              env.DeleteGlobalRef(jres);
+            }
+            env.DeleteGlobalRef(integerClass);
+          }));
+
+  testRunner("asJString extension method", () {
+    const str = "QWERTY QWERTY";
+    // convenience method that wraps
+    // converting dart string to native string,
+    // instantiating java string, and freeing the native string
+    final jstr = env.toJStringPtr(str);
+    expect(str, equals(env.toDartString(jstr)));
+    env.DeleteGlobalRef(jstr);
+  });
+
+  testRunner(
+      'GlobalJniEnv should catch exceptions',
+      () => using((arena) {
+            final integerClass =
+                env.FindClass("java/lang/Integer".toNativeChars(arena));
+            final parseIntMethod = env.GetStaticMethodID(
+                integerClass,
+                "parseInt".toNativeChars(arena),
+                "(Ljava/lang/String;)I".toNativeChars(arena));
+            final args = JValueArgs(["hello"], arena);
+            expect(
+                () => env.CallStaticIntMethodA(
+                    integerClass, parseIntMethod, args.values),
+                throwsA(isA<JniException>()));
+          }));
+
+  testRunner(
+      "Convert back & forth between Dart & Java strings (UTF-8)",
+      () => using((arena) {
+            const str = "ABCD EFGH";
+            final jstr = env.NewStringUTF(str.toNativeChars(arena));
+            final jchars = env.GetStringUTFChars(jstr, nullptr);
+            final jlen = env.GetStringUTFLength(jstr);
+            final dstr = jchars.toDartString(length: jlen);
+            env.ReleaseStringUTFChars(jstr, jchars);
+            expect(str, equals(dstr));
+            env.DeleteGlobalRef(jstr);
+          }));
+
+  testRunner(
+      "Convert back & forth between Dart & Java strings (UTF-16)",
+      () => using((arena) {
+            const str = "ABCD EFGH";
+            final jstr = env.NewString(str.toNativeUtf16().cast(), str.length);
+            final jchars = env.GetStringChars(jstr, nullptr);
+            final jlen = env.GetStringLength(jstr);
+            final dstr = jchars.cast<Utf16>().toDartString(length: jlen);
+            env.ReleaseStringChars(jstr, jchars);
+            expect(str, equals(dstr));
+            env.DeleteGlobalRef(jstr);
+          }));
+
+  testRunner(
+      "Print something from Java",
+      () => using((arena) {
+            final system =
+                env.FindClass("java/lang/System".toNativeChars(arena));
+            final field = env.GetStaticFieldID(
+                system,
+                "out".toNativeChars(arena),
+                "Ljava/io/PrintStream;".toNativeChars(arena));
+            final out = env.GetStaticObjectField(system, field);
+            final printStream = env.GetObjectClass(out);
+            final println = env.GetMethodID(
+                printStream,
+                "println".toNativeChars(arena),
+                "(Ljava/lang/String;)V".toNativeChars(arena));
+            const str = "\nHello World from JNI!";
+            final jstr = env.toJStringPtr(str);
+            env.CallVoidMethodA(out, println, Jni.jvalues([jstr]));
+            env.deleteAllRefs([system, printStream, jstr]);
+          }));
+  testRunner(
+      'Env create reference methods should retain their default behavior', () {
+    final systemOut = Jni.retrieveStaticField<JObjectPtr>(
+        "java/lang/System", "out", "Ljava/io/PrintStream;");
+    var refType = env.GetObjectRefType(systemOut);
+    expect(refType, equals(JObjectRefType.JNIGlobalRefType));
+    final localRef = env.NewLocalRef(systemOut);
+    refType = env.GetObjectRefType(localRef);
+    expect(refType, equals(JObjectRefType.JNILocalRefType));
+    final weakRef = env.NewWeakGlobalRef(systemOut);
+    refType = env.GetObjectRefType(weakRef);
+    expect(refType, equals(JObjectRefType.JNIWeakGlobalRefType));
+    final globalRef = env.NewGlobalRef(localRef);
+    refType = env.GetObjectRefType(globalRef);
+    expect(refType, equals(JObjectRefType.JNIGlobalRefType));
+    env.DeleteGlobalRef(globalRef);
+    env.DeleteWeakGlobalRef(weakRef);
+    env.DeleteLocalRef(localRef);
+    env.DeleteGlobalRef(systemOut);
+  });
+  testRunner('long methods return long int without loss of precision', () {
+    using((arena) {
+      final longClass = env.FindClass("java/lang/Long".toNativeChars(arena));
+      final maxField = env.GetStaticFieldID(
+        longClass,
+        'MAX_VALUE'.toNativeChars(arena),
+        'J'.toNativeChars(arena),
+      );
+      final maxValue = env.GetStaticLongField(longClass, maxField);
+      expect(maxValue, equals(maxLongInJava));
+      env.DeleteGlobalRef(longClass);
+    });
+  });
+
+  testRunner('class <-> object methods', () {
+    using((arena) {
+      final systemOut = Jni.retrieveStaticField<JObjectPtr>(
+          "java/lang/System", "out", "Ljava/io/PrintStream;");
+      final systemErr = Jni.retrieveStaticField<JObjectPtr>(
+          "java/lang/System", "err", "Ljava/io/PrintStream;");
+      final outClass = env.GetObjectClass(systemOut);
+      expect(env.IsInstanceOf(systemOut, outClass), isTrue);
+      expect(env.IsInstanceOf(systemErr, outClass), isTrue);
+      final errClass = env.GetObjectClass(systemErr);
+      expect(env.IsSameObject(outClass, errClass), isTrue);
+      env.deleteAllRefs([systemOut, systemErr, outClass, errClass]);
+    });
+  });
+}
diff --git a/pkgs/jni/test/jarray_test.dart b/pkgs/jni/test/jarray_test.dart
new file mode 100644
index 0000000..064bc84
--- /dev/null
+++ b/pkgs/jni/test/jarray_test.dart
@@ -0,0 +1,374 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  testRunner("Java boolean array", () {
+    using((arena) {
+      final array = JArray(jboolean.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      array[0] = true;
+      array[1] = false;
+      array[2] = false;
+      expect(array[0], true);
+      expect(array[1], false);
+      expect(array[2], false);
+      array.setRange(0, 3, [false, true, true, true], 1);
+      expect(array[0], true);
+      expect(array[1], true);
+      expect(array[2], true);
+      expect(() {
+        final _ = array[-1];
+      }, throwsRangeError);
+      expect(() {
+        array[-1] = false;
+      }, throwsRangeError);
+      expect(() {
+        array[3] = false;
+      }, throwsRangeError);
+    });
+  });
+  testRunner("Java char array", () {
+    using((arena) {
+      final array = JArray(jchar.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      array[0] = 'ح';
+      array[1] = '2';
+      array[2] = '3';
+      expect(array[0], 'ح');
+      expect(array[1], '2');
+      expect(array[2], '3');
+      array.setRange(0, 3, ['4', '5', '6', '7'], 1);
+      expect(array[0], '5');
+      expect(array[1], '6');
+      expect(array[2], '7');
+      expect(() {
+        final _ = array[-1];
+      }, throwsRangeError);
+      expect(() {
+        array[-1] = '4';
+      }, throwsRangeError);
+      expect(() {
+        array[3] = '4';
+      }, throwsRangeError);
+    });
+  });
+  testRunner("Java byte array", () {
+    using((arena) {
+      final array = JArray(jbyte.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      array[0] = 1;
+      array[1] = 2;
+      array[2] = 3 + 256 * 5; // truncates the input;
+      expect(array[0], 1);
+      expect(array[1], 2);
+      expect(array[2], 3);
+      array.setRange(0, 3, [4, 5, 6, 7], 1);
+      expect(array[0], 5);
+      expect(array[1], 6);
+      expect(array[2], 7);
+      expect(() {
+        final _ = array[-1];
+      }, throwsRangeError);
+      expect(() {
+        array[-1] = 4;
+      }, throwsRangeError);
+      expect(() {
+        array[3] = 4;
+      }, throwsRangeError);
+    });
+  });
+  testRunner("Java short array", () {
+    using((arena) {
+      final array = JArray(jshort.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      array[0] = 1;
+      array[1] = 2;
+      array[2] = 3 + 256 * 256 * 5; // truncates the input
+      expect(array[0], 1);
+      expect(array[1], 2);
+      expect(array[2], 3);
+      array.setRange(0, 3, [4, 5, 6, 7], 1);
+      expect(array[0], 5);
+      expect(array[1], 6);
+      expect(array[2], 7);
+      expect(() {
+        final _ = array[-1];
+      }, throwsRangeError);
+      expect(() {
+        array[-1] = 4;
+      }, throwsRangeError);
+      expect(() {
+        array[3] = 4;
+      }, throwsRangeError);
+    });
+  });
+  testRunner("Java int array", () {
+    using((arena) {
+      final array = JArray(jint.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      array[0] = 1;
+      array[1] = 2;
+      array[2] = 3 + 256 * 256 * 256 * 256 * 5; // truncates the input
+      expect(array[0], 1);
+      expect(array[1], 2);
+      expect(array[2], 3);
+      array.setRange(0, 3, [4, 5, 6, 7], 1);
+      expect(array[0], 5);
+      expect(array[1], 6);
+      expect(array[2], 7);
+      expect(() {
+        final _ = array[-1];
+      }, throwsRangeError);
+      expect(() {
+        array[-1] = 4;
+      }, throwsRangeError);
+      expect(() {
+        array[3] = 4;
+      }, throwsRangeError);
+    });
+  });
+  testRunner("Java long array", () {
+    using((arena) {
+      final array = JArray(jlong.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      array[0] = 1;
+      array[1] = 2;
+      array[2] = 3 + 256 * 256 * 256 * 256 * 5;
+      expect(array[0], 1);
+      expect(array[1], 2);
+      expect(array[2], 3 + 256 * 256 * 256 * 256 * 5);
+      array.setRange(0, 3, [4, 5, 6, 7], 1);
+      expect(array[0], 5);
+      expect(array[1], 6);
+      expect(array[2], 7);
+      expect(() {
+        final _ = array[-1];
+      }, throwsRangeError);
+      expect(() {
+        array[-1] = 4;
+      }, throwsRangeError);
+      expect(() {
+        array[3] = 4;
+      }, throwsRangeError);
+    });
+  });
+  const epsilon = 1e-6;
+  testRunner("Java float array", () {
+    using((arena) {
+      final array = JArray(jfloat.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      array[0] = 0.5;
+      array[1] = 2;
+      array[2] = 3;
+      expect(array[0], closeTo(0.5, epsilon));
+      expect(array[1], closeTo(2, epsilon));
+      expect(array[2], closeTo(3, epsilon));
+      array.setRange(0, 3, [4, 5, 6, 7], 1);
+      expect(array[0], closeTo(5, epsilon));
+      expect(array[1], closeTo(6, epsilon));
+      expect(array[2], closeTo(7, epsilon));
+      expect(() {
+        final _ = array[-1];
+      }, throwsRangeError);
+      expect(() {
+        array[-1] = 4;
+      }, throwsRangeError);
+      expect(() {
+        array[3] = 4;
+      }, throwsRangeError);
+    });
+  });
+  testRunner("Java double array", () {
+    using((arena) {
+      final array = JArray(jdouble.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      array[0] = 0.5;
+      array[1] = 2;
+      array[2] = 3;
+      expect(array[0], closeTo(0.5, epsilon));
+      expect(array[1], closeTo(2, epsilon));
+      expect(array[2], closeTo(3, epsilon));
+      array.setRange(0, 3, [4, 5, 6, 7], 1);
+      expect(array[0], closeTo(5, epsilon));
+      expect(array[1], closeTo(6, epsilon));
+      expect(array[2], closeTo(7, epsilon));
+      expect(() {
+        final _ = array[-1];
+      }, throwsRangeError);
+      expect(() {
+        array[-1] = 4;
+      }, throwsRangeError);
+      expect(() {
+        array[3] = 4;
+      }, throwsRangeError);
+    });
+  });
+  testRunner("Java string array", () {
+    using((arena) {
+      final array = JArray(JString.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      array[0] = "حس".toJString()..releasedBy(arena);
+      array[1] = "\$".toJString()..releasedBy(arena);
+      array[2] = "33".toJString()..releasedBy(arena);
+      expect(array[0].toDartString(releaseOriginal: true), "حس");
+      expect(array[1].toDartString(releaseOriginal: true), "\$");
+      expect(array[2].toDartString(releaseOriginal: true), "33");
+      array.setRange(
+        0,
+        3,
+        [
+          "44".toJString()..releasedBy(arena),
+          "55".toJString()..releasedBy(arena),
+          "66".toJString()..releasedBy(arena),
+          "77".toJString()..releasedBy(arena),
+        ],
+        1,
+      );
+      expect(array[0].toDartString(releaseOriginal: true), "55");
+      expect(array[1].toDartString(releaseOriginal: true), "66");
+      expect(array[2].toDartString(releaseOriginal: true), "77");
+      expect(() {
+        final _ = array[-1];
+      }, throwsRangeError);
+      expect(() {
+        array[-1] = "44".toJString()..releasedBy(arena);
+      }, throwsRangeError);
+      expect(() {
+        array[3] = "44".toJString()..releasedBy(arena);
+      }, throwsRangeError);
+    });
+  });
+  testRunner("Java object array", () {
+    using((arena) {
+      final array = JArray(JObject.type, 3)..releasedBy(arena);
+      expect(array.length, 3);
+      expect(array[0].reference, nullptr);
+      expect(array[1].reference, nullptr);
+      expect(array[2].reference, nullptr);
+    });
+  });
+  testRunner("Java 2d array", () {
+    using((arena) {
+      final array = JArray(jint.type, 3)..releasedBy(arena);
+      array[0] = 1;
+      array[1] = 2;
+      array[2] = 3;
+      final twoDimArray = JArray(JArray.type(jint.type), 3)..releasedBy(arena);
+      expect(twoDimArray.length, 3);
+      twoDimArray[0] = array;
+      twoDimArray[1] = array;
+      twoDimArray[2] = array;
+      for (var i = 0; i < 3; ++i) {
+        expect(twoDimArray[i][0], 1);
+        expect(twoDimArray[i][1], 2);
+        expect(twoDimArray[i][2], 3);
+      }
+      twoDimArray[2][2] = 4;
+      expect(twoDimArray[2][2], 4);
+    });
+  });
+  testRunner("JArray.filled", () {
+    using((arena) {
+      final string = "abc".toJString()..releasedBy(arena);
+      final array = JArray.filled(3, string)..releasedBy(arena);
+      expect(
+        () => JArray.filled(-3, JString.fromRef(nullptr)),
+        throwsA(isA<RangeError>()),
+      );
+      expect(array.length, 3);
+      expect(array[0].toDartString(releaseOriginal: true), "abc");
+      expect(array[1].toDartString(releaseOriginal: true), "abc");
+      expect(array[2].toDartString(releaseOriginal: true), "abc");
+    });
+  });
+  testRunner('JArray of JByte', () {
+    using((arena) {
+      final arr = JArray(JByte.type, 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JShort', () {
+    using((arena) {
+      final arr = JArray(JShort.type, 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JInteger', () {
+    using((arena) {
+      final arr = JArray(JInteger.type, 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JCharacter', () {
+    using((arena) {
+      final arr = JArray(JCharacter.type, 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JLong', () {
+    using((arena) {
+      final arr = JArray(JLong.type, 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JFloat', () {
+    using((arena) {
+      final arr = JArray(JFloat.type, 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JDouble', () {
+    using((arena) {
+      final arr = JArray(JDouble.type, 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JBoolean', () {
+    using((arena) {
+      final arr = JArray(JBoolean.type, 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JSet', () {
+    using((arena) {
+      final arr = JArray(JSet.type(JString.type), 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JList', () {
+    using((arena) {
+      final arr = JArray(JList.type(JString.type), 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JMap', () {
+    using((arena) {
+      final arr = JArray(JMap.type(JString.type, JString.type), 1)
+        ..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+  testRunner('JArray of JIterator', () {
+    using((arena) {
+      final arr = JArray(JIterator.type(JString.type), 1)..releasedBy(arena);
+      expect((arr[0]..releasedBy(arena)).isNull, true);
+    });
+  });
+}
diff --git a/pkgs/jni/test/jbyte_buffer_test.dart b/pkgs/jni/test/jbyte_buffer_test.dart
new file mode 100644
index 0000000..48f9eaf
--- /dev/null
+++ b/pkgs/jni/test/jbyte_buffer_test.dart
@@ -0,0 +1,229 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  final throwsAJniException = throwsA(isA<JniException>());
+  JByteBuffer testDataBuffer(Arena arena) {
+    final buffer = JByteBuffer.allocate(3)..releasedBy(arena);
+    buffer.nextByte = 1;
+    buffer.nextByte = 2;
+    buffer.nextByte = 3;
+    buffer.position = 0;
+    return buffer;
+  }
+
+  testRunner('wrap whole array', () {
+    using((arena) {
+      final array = JArray(jbyte.type, 3)..releasedBy(arena);
+      array[0] = 1;
+      array[1] = 2;
+      array[2] = 3;
+      final buffer = JByteBuffer.wrap(array)..releasedBy(arena);
+      expect(buffer, testDataBuffer(arena));
+    });
+  });
+
+  testRunner('wrap partial array', () {
+    using((arena) {
+      final array = JArray(jbyte.type, 3)..releasedBy(arena);
+      array[0] = 1;
+      array[1] = 2;
+      array[2] = 3;
+      final buffer = JByteBuffer.wrap(array, 1, 1)..releasedBy(arena);
+      expect(buffer.nextByte, 2);
+      expect(() => buffer.nextByte, throwsAJniException);
+    });
+  });
+
+  testRunner('capacity', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.capacity, 3);
+    });
+  });
+
+  testRunner('position', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.position, 0);
+      buffer.position = 2;
+      expect(buffer.position, 2);
+    });
+  });
+
+  testRunner('limit', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.limit, 3);
+      buffer.limit = 2;
+      expect(buffer.limit, 2);
+    });
+  });
+
+  testRunner('mark and reset', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 1;
+      buffer.mark();
+      buffer.position = 2;
+      buffer.reset();
+      expect(buffer.position, 1);
+    });
+  });
+
+  testRunner('clear', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 2;
+      buffer.limit = 2;
+      buffer.clear();
+      expect(buffer.limit, 3);
+      expect(buffer.position, 0);
+    });
+  });
+
+  testRunner('flip', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 2;
+      buffer.flip();
+      expect(buffer.limit, 2);
+      expect(buffer.position, 0);
+    });
+  });
+
+  testRunner('rewind', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.mark();
+      buffer.position = 2;
+      buffer.rewind();
+      expect(buffer.position, 0);
+      expect(buffer.reset, throwsAJniException);
+    });
+  });
+
+  testRunner('remaining and hasRemaining', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 2;
+      expect(buffer.remaining, 1);
+      expect(buffer.hasRemaining, true);
+      buffer.position = 3;
+      expect(buffer.remaining, 0);
+      expect(buffer.hasRemaining, false);
+    });
+  });
+
+  testRunner('isReadOnly and asReadOnlyBuffer', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.isReadOnly, false);
+      final readOnly = buffer.asReadOnlyBuffer()..releasedBy(arena);
+      expect(readOnly.isReadOnly, true);
+    });
+  });
+
+  testRunner('hasArray, array and arrayOffset', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.hasArray, true);
+      expect(buffer.arrayOffset, 0);
+      expect(buffer.array.length, 3);
+      final directBuffer = JByteBuffer.allocateDirect(3)..releasedBy(arena);
+      expect(directBuffer.hasArray, false);
+    });
+  });
+
+  testRunner('isDirect', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.isDirect, false);
+      final directBuffer = JByteBuffer.allocateDirect(3)..releasedBy(arena);
+      expect(directBuffer.isDirect, true);
+    });
+  });
+
+  testRunner('slice', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 1;
+      buffer.limit = 2;
+      final sliced = buffer.slice()..releasedBy(arena);
+      expect(sliced.capacity, 1);
+      expect(sliced.nextByte, 2);
+    });
+  });
+
+  testRunner('duplicate', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      buffer.position = 1;
+      buffer.limit = 2;
+      final duplicate = buffer.duplicate()..releasedBy(arena);
+      expect(duplicate.capacity, 3);
+      expect(duplicate.position, 1);
+      expect(duplicate.limit, 2);
+    });
+  });
+
+  testRunner('asUint8List', () {
+    using((arena) {
+      final buffer = testDataBuffer(arena);
+      expect(buffer.asUint8List, throwsA(isA<StateError>()));
+      final list = Uint8List.fromList([1, 2, 3]);
+      final directBuffer = list.toJByteBuffer()..releasedBy(arena);
+      expect(directBuffer.asUint8List(), list);
+    });
+  });
+
+  testRunner('type hashCode, ==', () {
+    using((arena) {
+      final a = testDataBuffer(arena);
+      final b = testDataBuffer(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+      final c = JBuffer.fromRef(nullptr);
+      final d = JBuffer.fromRef(nullptr);
+      expect(c.$type, d.$type);
+      expect(c.$type.hashCode, d.$type.hashCode);
+
+      expect(a.$type, isNot(c.$type));
+      expect(a.$type.hashCode, isNot(c.$type.hashCode));
+    });
+  });
+
+  testRunner('asUint8List releasing original', () {
+    using((arena) {
+      // Used as an example in [JByteBuffer].
+      final directBuffer = JByteBuffer.allocateDirect(3);
+      final data1 = directBuffer.asUint8List();
+      directBuffer.nextByte = 42; // No problem!
+      expect(data1[0], 42);
+      final data2 = directBuffer.asUint8List(releaseOriginal: true);
+      expect(
+        () => directBuffer.nextByte = 42,
+        throwsA(isA<UseAfterReleaseError>()),
+      );
+      expect(data2[0], 42);
+    });
+  });
+}
diff --git a/pkgs/jni/test/jfinal_string_test.dart b/pkgs/jni/test/jfinal_string_test.dart
new file mode 100644
index 0000000..c8a9980
--- /dev/null
+++ b/pkgs/jni/test/jfinal_string_test.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:jni/src/jfinal_string.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  testRunner('JFinalString', () {
+    const string = 'abc';
+    var referenceFetchedCount = 0;
+    final finalString = JFinalString(
+      () {
+        ++referenceFetchedCount;
+        return string.toJString().reference;
+      },
+      string,
+    );
+    expect(finalString.toDartString(), string);
+    expect(referenceFetchedCount, 0);
+    expect(finalString.reference, isNot(nullptr));
+    expect(referenceFetchedCount, 1);
+    finalString.reference;
+    expect(referenceFetchedCount, 1);
+    expect(finalString.toDartString(releaseOriginal: true), string);
+    expect(() => finalString.reference, throwsA(isA<UseAfterReleaseError>()));
+    expect(finalString.release, throwsA(isA<DoubleReleaseError>()));
+  });
+}
diff --git a/pkgs/jni/test/jlist_test.dart b/pkgs/jni/test/jlist_test.dart
new file mode 100644
index 0000000..b410525
--- /dev/null
+++ b/pkgs/jni/test/jlist_test.dart
@@ -0,0 +1,216 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  JList<JString> testDataList(Arena arena) {
+    return [
+      "1".toJString()..releasedBy(arena),
+      "2".toJString()..releasedBy(arena),
+      "3".toJString()..releasedBy(arena),
+    ].toJList(JString.type)
+      ..releasedBy(arena);
+  }
+
+  testRunner('length get', () {
+    using((arena) {
+      final list = testDataList(arena);
+      expect(list.length, 3);
+    });
+  });
+  testRunner('length set', () {
+    using((arena) {
+      final list = testDataList(arena);
+      list.length = 2;
+      expect(list.length, 2);
+      list.length = 3;
+      expect(list.length, 3);
+      expect(list.last.isNull, true);
+    });
+  });
+  testRunner('[]', () {
+    using((arena) {
+      final list = testDataList(arena);
+      expect(list[0].toDartString(releaseOriginal: true), "1");
+      expect(list[1].toDartString(releaseOriginal: true), "2");
+      expect(list[2].toDartString(releaseOriginal: true), "3");
+    });
+  });
+  testRunner('[]=', () {
+    using((arena) {
+      final list = testDataList(arena);
+      expect(list[0].toDartString(releaseOriginal: true), "1");
+      list[0] = "2".toJString()..releasedBy(arena);
+      expect(list[0].toDartString(releaseOriginal: true), "2");
+    });
+  });
+  testRunner('add', () {
+    using((arena) {
+      final list = testDataList(arena);
+      list.add("4".toJString()..releasedBy(arena));
+      expect(list.length, 4);
+      expect(list[3].toDartString(releaseOriginal: true), "4");
+    });
+  });
+  testRunner('addAll', () {
+    using((arena) {
+      final list = testDataList(arena);
+      final toAppend = testDataList(arena);
+      list.addAll(toAppend);
+      expect(list.length, 6);
+      list.addAll(["4".toJString()..releasedBy(arena)]);
+      expect(list.length, 7);
+    });
+  });
+  testRunner('clear, isEmpty, isNotEmpty', () {
+    using((arena) {
+      final list = testDataList(arena);
+      expect(list.isNotEmpty, true);
+      expect(list.isEmpty, false);
+      list.clear();
+      expect(list.isNotEmpty, false);
+      expect(list.isEmpty, true);
+    });
+  });
+  testRunner('contains', () {
+    using((arena) {
+      final list = testDataList(arena);
+      // ignore: iterable_contains_unrelated_type
+      expect(list.contains("1"), false);
+      expect(list.contains("1".toJString()..releasedBy(arena)), true);
+      expect(list.contains("4".toJString()..releasedBy(arena)), false);
+    });
+  });
+  testRunner('getRange', () {
+    using((arena) {
+      final list = testDataList(arena);
+      // ignore: iterable_contains_unrelated_type
+      final range = list.getRange(1, 2)..releasedBy(arena);
+      expect(range.length, 1);
+      expect(range.first.toDartString(releaseOriginal: true), "2");
+    });
+  });
+  testRunner('indexOf', () {
+    using((arena) {
+      final list = testDataList(arena);
+      expect(list.indexOf(1), -1);
+      expect(list.indexOf("1".toJString()..toDartString()), 0);
+      expect(list.indexOf("2".toJString()..toDartString()), 1);
+      expect(list.indexOf("1".toJString()..toDartString(), 1), -1);
+      expect(list.indexOf("1".toJString()..toDartString(), -1), 0);
+    });
+  });
+  testRunner('insert', () {
+    using((arena) {
+      final list = testDataList(arena);
+      list.insert(1, "0".toJString()..releasedBy(arena));
+      expect(list.length, 4);
+      expect(list[1].toDartString(releaseOriginal: true), "0");
+    });
+  });
+  testRunner('insertAll', () {
+    using((arena) {
+      final list = testDataList(arena);
+      final toInsert = testDataList(arena);
+      list.insertAll(1, toInsert);
+      expect(list[1].toDartString(releaseOriginal: true), "1");
+      expect(list.length, 6);
+      list.insertAll(1, ["4".toJString()..releasedBy(arena)]);
+      expect(list.length, 7);
+      expect(list[1].toDartString(releaseOriginal: true), "4");
+    });
+  });
+  testRunner('iterator', () {
+    using((arena) {
+      final list = testDataList(arena);
+      final it = list.iterator;
+      expect(it.moveNext(), true);
+      expect(it.current.toDartString(releaseOriginal: true), "1");
+      expect(it.moveNext(), true);
+      expect(it.current.toDartString(releaseOriginal: true), "2");
+      expect(it.moveNext(), true);
+      expect(it.current.toDartString(releaseOriginal: true), "3");
+      expect(it.moveNext(), false);
+    });
+  });
+  testRunner('remove', () {
+    using((arena) {
+      final list = testDataList(arena);
+      expect(list.remove("3".toJString()..releasedBy(arena)), true);
+      expect(list.length, 2);
+      expect(list.remove("4".toJString()..releasedBy(arena)), false);
+      // ignore: list_remove_unrelated_type
+      expect(list.remove(1), false);
+    });
+  });
+  testRunner('removeAt', () {
+    using((arena) {
+      final list = testDataList(arena);
+      expect(list.removeAt(0).toDartString(releaseOriginal: true), "1");
+      expect(list.removeAt(1).toDartString(releaseOriginal: true), "3");
+    });
+  });
+  testRunner('removeRange', () {
+    using((arena) {
+      final list = testDataList(arena);
+      list.removeRange(0, 2);
+      expect(list.single.toDartString(releaseOriginal: true), "3");
+    });
+  });
+  testRunner('==, hashCode', () {
+    using((arena) {
+      final a = testDataList(arena);
+      final b = testDataList(arena);
+      expect(a.hashCode, b.hashCode);
+      expect(a, b);
+      b.add("4".toJString()..releasedBy(arena));
+      expect(a.hashCode, isNot(b.hashCode));
+      expect(a, isNot(b));
+    });
+  });
+  testRunner('toSet', () {
+    using((arena) {
+      final list = testDataList(arena);
+      final set = list.toSet()..releasedBy(arena);
+      expect(set.length, 3);
+    });
+  });
+  testRunner('type hashCode, ==', () {
+    using((arena) {
+      final a = testDataList(arena);
+      final b = testDataList(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+      final c = JList.array(JObject.type)..releasedBy(arena);
+      expect(a.$type, isNot(c.$type));
+      expect(a.$type.hashCode, isNot(c.$type.hashCode));
+    });
+  });
+  testRunner('JIterator type hashCode, ==', () {
+    using((arena) {
+      final a = testDataList(arena);
+      final b = testDataList(arena);
+      expect(a.iterator.$type, b.iterator.$type);
+      expect(a.iterator.$type.hashCode, b.iterator.$type.hashCode);
+      final c = JList.array(JObject.type)..releasedBy(arena);
+      expect(a.iterator.$type, isNot(c.iterator.$type));
+      expect(a.iterator.$type.hashCode, isNot(c.iterator.$type.hashCode));
+    });
+  });
+}
diff --git a/pkgs/jni/test/jmap_test.dart b/pkgs/jni/test/jmap_test.dart
new file mode 100644
index 0000000..4ece751
--- /dev/null
+++ b/pkgs/jni/test/jmap_test.dart
@@ -0,0 +1,170 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  JMap<JString, JString> testDataMap(Arena arena) {
+    return {
+      "1".toJString()..releasedBy(arena): "One".toJString()..releasedBy(arena),
+      "2".toJString()..releasedBy(arena): "Two".toJString()..releasedBy(arena),
+      "3".toJString()..releasedBy(arena): "Three".toJString()
+        ..releasedBy(arena),
+    }.toJMap(JString.type, JString.type)
+      ..releasedBy(arena);
+  }
+
+  testRunner('length', () {
+    using((arena) {
+      final map = testDataMap(arena);
+      expect(map.length, 3);
+    });
+  });
+  testRunner('[]', () {
+    using((arena) {
+      final map = testDataMap(arena);
+      // ignore: collection_methods_unrelated_type
+      expect(map[1], null);
+      expect(
+        map["1".toJString()..releasedBy(arena)]
+            ?.toDartString(releaseOriginal: true),
+        "One",
+      );
+      expect(
+        map["4".toJString()..releasedBy(arena)],
+        null,
+      );
+    });
+  });
+  testRunner('[]=', () {
+    using((arena) {
+      final map = testDataMap(arena);
+      map["0".toJString()..releasedBy(arena)] = "Zero".toJString()
+        ..releasedBy(arena);
+      expect(
+        map["0".toJString()..releasedBy(arena)]
+            ?.toDartString(releaseOriginal: true),
+        "Zero",
+      );
+      expect(map.length, 4);
+      map["1".toJString()..releasedBy(arena)] = "one!".toJString()
+        ..releasedBy(arena);
+      expect(
+        map["1".toJString()..releasedBy(arena)]
+            ?.toDartString(releaseOriginal: true),
+        "one!",
+      );
+      expect(map.length, 4);
+    });
+  });
+  testRunner('addAll', () {
+    using((arena) {
+      final map = testDataMap(arena);
+      final toAdd = {
+        "0".toJString()..releasedBy(arena): "Zero".toJString()
+          ..releasedBy(arena),
+        "1".toJString()..releasedBy(arena): "one!".toJString()
+          ..releasedBy(arena),
+      }.toJMap(JString.type, JString.type);
+      map.addAll(toAdd);
+      expect(map.length, 4);
+      expect(
+        map["0".toJString()..releasedBy(arena)]
+            ?.toDartString(releaseOriginal: true),
+        "Zero",
+      );
+      expect(
+        map["1".toJString()..releasedBy(arena)]
+            ?.toDartString(releaseOriginal: true),
+        "one!",
+      );
+      map.addAll({
+        "4".toJString()..releasedBy(arena): "Four".toJString()
+          ..releasedBy(arena)
+      });
+      expect(map.length, 5);
+    });
+  });
+  testRunner('clear, isEmpty, isNotEmpty', () {
+    using((arena) {
+      final map = testDataMap(arena);
+      expect(map.isEmpty, false);
+      expect(map.isNotEmpty, true);
+      map.clear();
+      expect(map.isEmpty, true);
+      expect(map.isNotEmpty, false);
+    });
+  });
+  testRunner('containsKey', () {
+    using((arena) {
+      final map = testDataMap(arena);
+      // ignore: iterable_contains_unrelated_type
+      expect(map.containsKey(1), false);
+      expect(map.containsKey("1".toJString()..releasedBy(arena)), true);
+      expect(map.containsKey("4".toJString()..releasedBy(arena)), false);
+    });
+  });
+  testRunner('containsValue', () {
+    using((arena) {
+      final map = testDataMap(arena);
+      // ignore: iterable_contains_unrelated_type
+      expect(map.containsValue(1), false);
+      expect(map.containsValue("One".toJString()..releasedBy(arena)), true);
+      expect(map.containsValue("Four".toJString()..releasedBy(arena)), false);
+    });
+  });
+  testRunner('keys', () {
+    using((arena) {
+      final map = testDataMap(arena);
+      final keys = map.keys;
+      expect(
+        keys
+            .map((element) => element.toDartString(releaseOriginal: true))
+            .toSet(),
+        {"1", "2", "3"},
+      );
+    });
+  });
+  testRunner('remove', () {
+    using((arena) {
+      final map = testDataMap(arena);
+      // ignore: collection_methods_unrelated_type
+      expect(map.remove(1), null);
+      expect(map.remove("4".toJString()..releasedBy(arena)), null);
+      expect(map.length, 3);
+      expect(
+        map
+            .remove("3".toJString()..releasedBy(arena))
+            ?.toDartString(releaseOriginal: true),
+        "Three",
+      );
+      expect(map.length, 2);
+    });
+  });
+  testRunner('type hashCode, ==', () {
+    using((arena) {
+      final a = testDataMap(arena);
+      final b = testDataMap(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+      final c = JMap.hash(JObject.type, JObject.type)..releasedBy(arena);
+      expect(a.$type, isNot(c.$type));
+      expect(a.$type.hashCode, isNot(c.$type.hashCode));
+    });
+  });
+}
diff --git a/pkgs/jni/test/jobject_test.dart b/pkgs/jni/test/jobject_test.dart
new file mode 100644
index 0000000..a63828c
--- /dev/null
+++ b/pkgs/jni/test/jobject_test.dart
@@ -0,0 +1,293 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'dart:ffi';
+import 'dart:isolate';
+
+import 'package:test/test.dart';
+
+import 'package:jni/jni.dart';
+
+import 'test_util/test_util.dart';
+
+const maxLongInJava = 9223372036854775807;
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  // The API based on JniEnv is intended to closely mimic C API of JNI,
+  // And thus can be too verbose for simple experimenting and one-off uses
+  // JObject API provides an easier way to perform some common operations.
+  //
+  // However, if binding generation using jnigen is possible, that should be
+  // the first choice.
+  testRunner("Long.intValue() using JObject", () {
+    // JClass wraps a local class reference, and
+    // provides convenience functions.
+    final longClass = Jni.findJClass("java/lang/Long");
+
+    // looks for a constructor with given signature.
+    // equivalently you can lookup a method with name <init>
+    final longCtor = longClass.getCtorID("(J)V");
+
+    // note that the arguments are just passed as a list.
+    // allowed argument types are primitive types, JObject and its subclasses,
+    // and raw JNI references (JObject). Strings will be automatically converted
+    // to JNI strings.
+    final long = longClass.newInstance(longCtor, [176]);
+
+    final intValue = long.callMethodByName<int>(
+      "intValue",
+      "()I",
+      [],
+      JniCallType.intType,
+    );
+    expect(intValue, equals(176));
+
+    // Release any JObject and JClass instances using `.release()` after use.
+    // This is not strictly required since JNI objects / classes have
+    // a [NativeFinalizer]. But deleting them after use is a good practice.
+    long.release();
+    longClass.release();
+  });
+
+  testRunner("call a static method using JClass APIs", () {
+    final integerClass = Jni.findJClass("java/lang/Integer");
+    final result = integerClass.callStaticMethodByName<JString>(
+        "toHexString", "(I)Ljava/lang/String;", [JValueInt(31)]);
+
+    // If the object is supposed to be a Java string you can call
+    // [toDartString] on it.
+    final resultString = result.toDartString();
+
+    // Dart string is a copy, original object can be released.
+    result.release();
+    expect(resultString, equals("1f"));
+
+    // Also don't forget to release the class.
+    integerClass.release();
+  });
+
+  testRunner("Call method with null argument, expect exception", () {
+    final integerClass = Jni.findJClass("java/lang/Integer");
+    expect(
+        () => integerClass.callStaticMethodByName<int>(
+            "parseInt", "(Ljava/lang/String;)I", [nullptr]),
+        throwsException);
+    integerClass.release();
+  });
+
+  // Skip this test on Android integration test because it fails there, possibly
+  // due to a CheckJNI precondition check.
+  if (!Platform.isAndroid) {
+    testRunner("Try to find a non-exisiting class, expect exception", () {
+      expect(() => Jni.findJClass("java/lang/NotExists"), throwsException);
+    });
+  }
+
+  // [callMethodByName] will be expensive if making same call many times.
+  // Use [getMethodID] to get a method ID and use it in subsequent calls.
+  testRunner("Example for using getMethodID", () {
+    final longClass = Jni.findJClass("java/lang/Long");
+    final bitCountMethod = longClass.getStaticMethodID("bitCount", "(J)I");
+
+    // Use newInstance if you want only one instance.
+    // It finds the class, gets constructor ID and constructs an instance.
+    final random = Jni.newInstance("java/util/Random", "()V", []);
+
+    // You don't need a [JClass] reference to get instance method IDs.
+    final nextIntMethod = random.getMethodID("nextInt", "(I)I");
+
+    for (int i = 0; i < 100; i++) {
+      int r = random.callMethod<int>(
+        nextIntMethod,
+        [JValueInt(256 * 256)],
+        JniCallType.intType,
+      );
+      int bits = 0;
+      final jbc = longClass.callStaticMethod<int>(
+        bitCountMethod,
+        [r],
+        JniCallType.intType,
+      );
+      while (r != 0) {
+        bits += r % 2;
+        r = (r / 2).floor();
+      }
+      expect(jbc, equals(bits));
+    }
+    random.release();
+    longClass.release();
+  });
+
+  // One-off invocation of static method in single call.
+  testRunner("invoke_", () {
+    final m = Jni.invokeStaticMethod<int>(
+      "java/lang/Short",
+      "compare",
+      "(SS)I",
+      [JValueShort(1234), JValueShort(1324)],
+      JniCallType.intType,
+    );
+    expect(m, equals(1234 - 1324));
+  });
+
+  testRunner("Java char from string", () {
+    final m = Jni.invokeStaticMethod<bool>("java/lang/Character", "isLowerCase",
+        "(C)Z", [JValueChar.fromString('X')]);
+    expect(m, isFalse);
+  });
+
+  // One-off access of static field in single call.
+  testRunner("Get static field directly", () {
+    final maxLong = Jni.retrieveStaticField<int>(
+        "java/lang/Short", "MAX_VALUE", "S", JniCallType.shortType);
+    expect(maxLong, equals(32767));
+  });
+
+  // Use [callStringMethod] if all you care about is a string result
+  testRunner("callStaticStringMethod", () {
+    final longClass = Jni.findJClass("java/lang/Long");
+    const n = 1223334444;
+    final strFromJava = longClass.callStaticMethodByName<String>(
+        "toOctalString", "(J)Ljava/lang/String;", [n]);
+    expect(strFromJava, equals(n.toRadixString(8)));
+    longClass.release();
+  });
+
+  // In [JObject], [JClass], and retrieve_/invoke_ methods
+  // you can also pass Dart strings, apart from range of types
+  // allowed by [Jni.jvalues].
+  // They will be converted automatically.
+  testRunner(
+    "Passing strings in arguments",
+    () {
+      final out = Jni.retrieveStaticField<JObject>(
+          "java/lang/System", "out", "Ljava/io/PrintStream;");
+      // uncomment next line to see output
+      // (\n because test runner prints first char at end of the line)
+      //out.callMethodByName<Null>(
+      //    "println", "(Ljava/lang/Object;)V", ["\nWorks (Apparently)"]);
+      out.release();
+    },
+  );
+
+  testRunner("Passing strings in arguments 2", () {
+    final twelve = Jni.invokeStaticMethod<int>("java/lang/Byte", "parseByte",
+        "(Ljava/lang/String;)B", ["12"], JniCallType.byteType);
+    expect(twelve, equals(12));
+  });
+
+  // You can use() method on JObject for using once and deleting.
+  testRunner("use() method", () {
+    final randomInt = Jni.newInstance("java/util/Random", "()V", []).use(
+      (random) => random.callMethodByName<int>(
+        "nextInt",
+        "(I)I",
+        [JValueInt(15)],
+        JniCallType.intType,
+      ),
+    );
+    expect(randomInt, lessThan(15));
+  });
+
+  // The JObject and JClass have NativeFinalizer. However, it's possible to
+  // explicitly use `Arena`.
+  testRunner('Using arena', () {
+    final objects = <JObject>[];
+    using((arena) {
+      final r = Jni.findJClass('java/util/Random')..releasedBy(arena);
+      final ctor = r.getCtorID("()V");
+      for (int i = 0; i < 10; i++) {
+        objects.add(r.newInstance(ctor, [])..releasedBy(arena));
+      }
+    });
+    for (var object in objects) {
+      expect(object.isReleased, isTrue);
+    }
+  });
+
+  testRunner("enums", () {
+    // Don't forget to escape $ in nested type names
+    final ordinal = Jni.retrieveStaticField<JObject>(
+            "java/net/Proxy\$Type", "HTTP", "Ljava/net/Proxy\$Type;")
+        .use(
+      (f) => f.callMethodByName<int>(
+        "ordinal",
+        "()I",
+        [],
+        JniCallType.intType,
+      ),
+    );
+    expect(ordinal, equals(1));
+  });
+
+  testRunner("casting", () {
+    using((arena) {
+      final str = "hello".toJString()..releasedBy(arena);
+      final obj = str.castTo(JObject.type)..releasedBy(arena);
+      final backToStr = obj.castTo(JString.type);
+      expect(backToStr.toDartString(), str.toDartString());
+      final _ = backToStr.castTo(JObject.type, releaseOriginal: true)
+        ..releasedBy(arena);
+      expect(backToStr.toDartString, throwsA(isA<UseAfterReleaseError>()));
+      expect(backToStr.release, throwsA(isA<DoubleReleaseError>()));
+    });
+  });
+
+  testRunner("Isolate", () {
+    Isolate.spawn(doSomeWorkInIsolate, null);
+  });
+
+  testRunner("Methods rethrow exceptions in Java as JniException", () {
+    expect(
+      () => Jni.invokeStaticMethod<int>(
+          "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", ["X"]),
+      throwsA(isA<JniException>()),
+    );
+  });
+
+  testRunner("Passing long integer values to JNI", () {
+    final maxLongStr = Jni.invokeStaticMethod<String>(
+      "java/lang/Long",
+      "toString",
+      "(J)Ljava/lang/String;",
+      [maxLongInJava],
+    );
+    expect(maxLongStr, equals('$maxLongInJava'));
+  });
+
+  testRunner('Returning long integers from JNI', () {
+    final maxLong = Jni.retrieveStaticField<int>(
+      "java/lang/Long",
+      "MAX_VALUE",
+      "J",
+      JniCallType.longType,
+    );
+    expect(maxLong, equals(maxLongInJava));
+  });
+}
+
+void doSomeWorkInIsolate(Void? _) {
+  // On standalone target, make sure to call [setDylibDir] before accessing
+  // any JNI function in a new isolate.
+  //
+  // otherwise subsequent JNI calls will throw a "library not found" exception.
+  Jni.setDylibDir(dylibDir: "build/jni_libs");
+  final random = Jni.newInstance("java/util/Random", "()V", []);
+  // final r = random.callMethodByName<int>("nextInt", "(I)I", [256]);
+  // expect(r, lessThan(256));
+  // Expect throws an [OutsideTestException]
+  // but you can uncomment below print and see it works
+  // print("\n$r");
+  random.release();
+}
diff --git a/pkgs/jni/test/jset_test.dart b/pkgs/jni/test/jset_test.dart
new file mode 100644
index 0000000..2d93e63
--- /dev/null
+++ b/pkgs/jni/test/jset_test.dart
@@ -0,0 +1,202 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  JSet<JString> testDataSet(Arena arena) {
+    return {
+      "1".toJString()..releasedBy(arena),
+      "2".toJString()..releasedBy(arena),
+      "3".toJString()..releasedBy(arena),
+    }.toJSet(JString.type)
+      ..releasedBy(arena);
+  }
+
+  testRunner('length', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      expect(set.length, 3);
+    });
+  });
+  testRunner('add', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      set.add("1".toJString()..releasedBy(arena));
+      expect(set.length, 3);
+      set.add("4".toJString()..releasedBy(arena));
+      expect(set.length, 4);
+    });
+  });
+  testRunner('addAll', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      final toAdd = testDataSet(arena);
+      toAdd.add("4".toJString()..releasedBy(arena));
+      set.addAll(toAdd);
+      expect(set.length, 4);
+      set.addAll([
+        "1".toJString()..releasedBy(arena),
+        "5".toJString()..releasedBy(arena),
+      ]);
+      expect(set.length, 5);
+    });
+  });
+  testRunner('clear, isEmpty, isNotEmpty', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      set.clear();
+      expect(set.isEmpty, true);
+      expect(set.isNotEmpty, false);
+    });
+  });
+  testRunner('contains', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      // ignore: iterable_contains_unrelated_type
+      expect(set.contains(1), false);
+      expect(set.contains("1".toJString()..releasedBy(arena)), true);
+      expect(set.contains("4".toJString()..releasedBy(arena)), false);
+    });
+  });
+  testRunner('containsAll', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      expect(set.containsAll(set), true);
+      expect(
+        set.containsAll([
+          "1".toJString()..releasedBy(arena),
+          "2".toJString()..releasedBy(arena),
+        ]),
+        true,
+      );
+      final testSet = testDataSet(arena);
+      testSet.add("4".toJString()..releasedBy(arena));
+      expect(set.containsAll(testSet), false);
+      expect(set.containsAll(["4".toJString()..releasedBy(arena)]), false);
+    });
+  });
+  testRunner('iterator', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      final it = set.iterator;
+      // There are no order guarantees in a hashset.
+      final dartSet = <String>{};
+      expect(it.moveNext(), true);
+      dartSet.add(it.current.toDartString(releaseOriginal: true));
+      expect(it.moveNext(), true);
+      dartSet.add(it.current.toDartString(releaseOriginal: true));
+      expect(it.moveNext(), true);
+      dartSet.add(it.current.toDartString(releaseOriginal: true));
+      expect(it.moveNext(), false);
+      // So we just check if the elements have appeared in some order.
+      expect(dartSet, {"1", "2", "3"});
+    });
+  });
+  testRunner('remove', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      // ignore: collection_methods_unrelated_type
+      expect(set.remove(1), false);
+      expect(set.remove("4".toJString()..releasedBy(arena)), false);
+      expect(set.length, 3);
+      expect(set.remove("3".toJString()..releasedBy(arena)), true);
+      expect(set.length, 2);
+    });
+  });
+  testRunner('removeAll', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      final toRemoveExclusive = {"4".toJString()..releasedBy(arena)}
+          .toJSet(JString.type)
+        ..releasedBy(arena);
+      set.removeAll(toRemoveExclusive);
+      expect(set.length, 3);
+      final toRemoveInclusive = {
+        "1".toJString()..releasedBy(arena),
+        "4".toJString()..releasedBy(arena),
+      }.toJSet(JString.type)
+        ..releasedBy(arena);
+      set.removeAll(toRemoveInclusive);
+      expect(set.length, 2);
+      set.removeAll(["2".toJString()..releasedBy(arena)]);
+      expect(set.length, 1);
+    });
+  });
+  testRunner('retainAll', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      final toRetain = {
+        "1".toJString()..releasedBy(arena),
+        "3".toJString()..releasedBy(arena),
+        "4".toJString()..releasedBy(arena),
+      };
+      set.retainAll(set);
+      expect(set.length, 3);
+      set.retainAll(toRetain);
+      expect(set.length, 2);
+      final toRetainJSet = toRetain.toJSet(JString.type)..releasedBy(arena);
+      set.retainAll(toRetainJSet);
+      expect(set.length, 2);
+    });
+  });
+  testRunner('==, hashCode', () {
+    using((arena) {
+      final a = testDataSet(arena);
+      final b = testDataSet(arena);
+      expect(a.hashCode, b.hashCode);
+      expect(a, b);
+      b.add("4".toJString()..releasedBy(arena));
+      expect(a.hashCode, isNot(b.hashCode));
+      expect(a, isNot(b));
+    });
+  });
+  testRunner('lookup', () {
+    using((arena) {
+      final set = testDataSet(arena);
+      // ignore: iterable_contains_unrelated_type
+      expect(set.lookup(1), null);
+      expect(
+        set.lookup("1".toJString())?.toDartString(releaseOriginal: true),
+        "1",
+      );
+      expect(set.lookup("4".toJString()..releasedBy(arena)), null);
+    });
+  });
+  testRunner('toSet', () {
+    using((arena) {
+      // Test if the set gets copied.
+      final set = testDataSet(arena);
+      final setCopy = set.toSet()..releasedBy(arena);
+      expect(set, setCopy);
+      set.add("4".toJString()..releasedBy(arena));
+      expect(set, isNot(setCopy));
+    });
+  });
+  testRunner('type hashCode, ==', () {
+    using((arena) {
+      final a = testDataSet(arena);
+      final b = testDataSet(arena);
+      expect(a.$type, b.$type);
+      expect(a.$type.hashCode, b.$type.hashCode);
+      final c = JSet.hash(JObject.type)..releasedBy(arena);
+      expect(a.$type, isNot(c.$type));
+      expect(a.$type.hashCode, isNot(c.$type.hashCode));
+    });
+  });
+}
diff --git a/pkgs/jni/test/jstring_test.dart b/pkgs/jni/test/jstring_test.dart
new file mode 100644
index 0000000..c1a968c
--- /dev/null
+++ b/pkgs/jni/test/jstring_test.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void testStringBackAndForth(String str) {
+  final jstring = str.toJString();
+  final dartString = jstring.toDartString(releaseOriginal: true);
+  expect(dartString, str);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  group("String encoding tests", () {
+    testRunner('Long string back-and-forth', () {
+      testStringBackAndForth('1' * 8096);
+    });
+
+    testRunner('#278 UTF-8 bug', () {
+      testStringBackAndForth('🐬');
+    });
+
+    testRunner('String containing null character', () {
+      final str = 'A${String.fromCharCode(0)}B';
+      testStringBackAndForth(str);
+    });
+
+    testRunner('Zero length string', () {
+      testStringBackAndForth('');
+    });
+  });
+
+  testRunner('Inherited toString', () {
+    final s = 'hello'.toJString();
+    expect(s.toString(), 'hello');
+  });
+}
diff --git a/pkgs/jni/test/load_test.dart b/pkgs/jni/test/load_test.dart
new file mode 100644
index 0000000..d74a5fc
--- /dev/null
+++ b/pkgs/jni/test/load_test.dart
@@ -0,0 +1,135 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@Tags(['load_test'])
+
+import 'dart:io';
+import 'dart:ffi';
+import 'dart:math';
+
+import 'package:ffi/ffi.dart';
+import 'package:test/test.dart';
+
+import 'package:jni/jni.dart';
+
+import 'test_util/test_util.dart';
+
+const maxLongInJava = 9223372036854775807;
+
+/// Taken from
+/// https://github.com/dart-lang/ffigen/blob/master/test/native_objc_test/automated_ref_count_test.dart
+final executeInternalCommand = DynamicLibrary.process().lookupFunction<
+    Void Function(Pointer<Char>, Pointer<Void>),
+    void Function(Pointer<Char>, Pointer<Void>)>('Dart_ExecuteInternalCommand');
+
+void doGC() {
+  final gcNow = "gc-now".toNativeUtf8();
+  executeInternalCommand(gcNow.cast(), nullptr);
+  calloc.free(gcNow);
+}
+
+void main() {
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+const k4 = 4 * 1024;
+const k64 = 64 * 1024;
+const k256 = 256 * 1024;
+
+const secureRandomSeedBound = 4294967296;
+
+JObject getSystemOut() => Jni.retrieveStaticField<JObject>(
+      'System',
+      'out',
+      'Ljava/io/PrintStream;',
+    );
+
+final random = Random.secure();
+
+JObject newRandom() => Jni.newInstance(
+      "java/util/Random",
+      "(J)V",
+      [random.nextInt(secureRandomSeedBound)],
+    );
+
+void run({required TestRunnerCallback testRunner}) {
+  testRunner('Test 4K refs can be created in a row', () {
+    final list = <JObject>[];
+    for (int i = 0; i < k4; i++) {
+      list.add(newRandom());
+    }
+    for (final jobject in list) {
+      jobject.release();
+    }
+  });
+
+  testRunner('Create and release 256K references in a loop using arena', () {
+    for (int i = 0; i < k256; i++) {
+      using((arena) {
+        final random = newRandom()..releasedBy(arena);
+        // The actual expect here does not matter. I am just being paranoid
+        // against assigning to `_` because compiler may optimize it. (It has
+        // side effect of calling FFI but still.)
+        expect(random.reference, isNot(nullptr));
+      });
+    }
+  });
+
+  testRunner('Create and release 256K references in a loop (explicit release)',
+      () {
+    for (int i = 0; i < k256; i++) {
+      final random = newRandom();
+      expect(random.reference, isNot(nullptr));
+      random.release();
+    }
+  });
+
+  testRunner('Create and release 64K references, in batches of 256', () {
+    for (int i = 0; i < 64 * 4; i++) {
+      using((arena) {
+        for (int i = 0; i < 256; i++) {
+          final r = newRandom()..releasedBy(arena);
+          expect(r.reference, isNot(nullptr));
+        }
+      });
+    }
+  });
+
+  // We don't have a direct way to check if something creates JNI references.
+  // So we are checking if we can run this for large number of times.
+  testRunner('Verify a call returning primitive can be run any times', () {
+    final random = newRandom();
+    final nextInt = random.getMethodID("nextInt", "()I");
+    for (int i = 0; i < k256; i++) {
+      final rInt = random.callMethod<int>(nextInt, []);
+      expect(rInt, isA<int>());
+    }
+  });
+
+  void testRefValidityAfterGC(int delayInSeconds) {
+    testRunner('Validate reference after GC & ${delayInSeconds}s sleep', () {
+      final random = newRandom();
+      doGC();
+      sleep(Duration(seconds: delayInSeconds));
+      expect(
+        random.callMethodByName<int>("nextInt", "()I", []),
+        isA<int>(),
+      );
+      expect(
+        Jni.env.GetObjectRefType(random.reference),
+        equals(JObjectRefType.JNIGlobalRefType),
+      );
+    });
+  }
+
+  // Dart_ExecuteInternalCommand doesn't exist in Android.
+  if (!Platform.isAndroid) {
+    testRefValidityAfterGC(1);
+    testRefValidityAfterGC(10);
+  }
+}
diff --git a/pkgs/jni/test/test_util/test_util.dart b/pkgs/jni/test/test_util/test_util.dart
new file mode 100644
index 0000000..07e711a
--- /dev/null
+++ b/pkgs/jni/test/test_util/test_util.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/src/build_util/build_util.dart';
+
+typedef TestCaseCallback = void Function();
+typedef TestRunnerCallback = void Function(
+  String description,
+  TestCaseCallback test,
+);
+
+final currentDir = Directory.current.uri;
+final dllSuffix =
+    Platform.isWindows ? "dll" : (Platform.isMacOS ? "dylib" : "so");
+final dllPrefix = Platform.isWindows ? '' : 'lib';
+final dllPath =
+    currentDir.resolve("build/jni_libs/${dllPrefix}dartjni.$dllSuffix");
+final srcPath = currentDir.resolve("src/");
+
+/// Fail if dartjni dll is stale.
+void checkDylibIsUpToDate() {
+  final dllFile = File.fromUri(dllPath);
+  if (needsBuild(File.fromUri(dllPath), Directory.fromUri(srcPath))) {
+    final cause = dllFile.existsSync()
+        ? 'not up-to-date with source modifications'
+        : 'not built';
+    var message = '\nFatal: dartjni.$dllSuffix is $cause. Please run '
+        '`dart run jni:setup` and try again.';
+    if (stderr.supportsAnsiEscapes) {
+      message = ansiRed + message + ansiDefault;
+    }
+    stderr.writeln(message);
+    exit(1);
+  }
+}
diff --git a/pkgs/jni/test/type_test.dart b/pkgs/jni/test/type_test.dart
new file mode 100644
index 0000000..0ad8490
--- /dev/null
+++ b/pkgs/jni/test/type_test.dart
@@ -0,0 +1,327 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+// Mocking this type tree:
+//   JObject
+//      |  \
+//      A   B
+//     / \   \
+//    C   D   E
+//   /
+//  F
+
+class A extends JObject {
+  A.fromRef(super.reference) : super.fromRef();
+  @override
+  JObjType<JObject> get $type => $AType();
+}
+
+final class $AType extends JObjType<A> {
+  @override
+  A fromRef(Pointer<Void> ref) {
+    return A.fromRef(ref);
+  }
+
+  @override
+  String get signature => 'A';
+
+  @override
+  int get superCount => superType.superCount + 1;
+
+  @override
+  JObjType<JObject> get superType => JObject.type;
+
+  @override
+  int get hashCode => ($AType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == $AType && other is $AType;
+  }
+}
+
+class B extends JObject {
+  B.fromRef(super.reference) : super.fromRef();
+  @override
+  JObjType<JObject> get $type => $BType();
+}
+
+final class $BType extends JObjType<B> {
+  @override
+  B fromRef(Pointer<Void> ref) {
+    return B.fromRef(ref);
+  }
+
+  @override
+  String get signature => 'B';
+
+  @override
+  int get superCount => superType.superCount + 1;
+
+  @override
+  JObjType<JObject> get superType => JObject.type;
+
+  @override
+  int get hashCode => ($BType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == $BType && other is $BType;
+  }
+}
+
+class C extends A {
+  C.fromRef(super.reference) : super.fromRef();
+
+  @override
+  JObjType<JObject> get $type => $CType();
+}
+
+final class $CType extends JObjType<C> {
+  @override
+  C fromRef(Pointer<Void> ref) {
+    return C.fromRef(ref);
+  }
+
+  @override
+  String get signature => 'C';
+
+  @override
+  int get superCount => superType.superCount + 1;
+
+  @override
+  JObjType<JObject> get superType => $AType();
+
+  @override
+  int get hashCode => ($CType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == $CType && other is $CType;
+  }
+}
+
+class D extends A {
+  D.fromRef(super.reference) : super.fromRef();
+
+  @override
+  JObjType<JObject> get $type => $DType();
+}
+
+final class $DType extends JObjType<D> {
+  @override
+  D fromRef(Pointer<Void> ref) {
+    return D.fromRef(ref);
+  }
+
+  @override
+  String get signature => 'D';
+
+  @override
+  int get superCount => superType.superCount + 1;
+
+  @override
+  JObjType<JObject> get superType => $AType();
+
+  @override
+  int get hashCode => ($DType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == $DType && other is $DType;
+  }
+}
+
+class E extends B {
+  E.fromRef(super.reference) : super.fromRef();
+
+  @override
+  JObjType<JObject> get $type => $EType();
+}
+
+final class $EType extends JObjType<E> {
+  @override
+  E fromRef(Pointer<Void> ref) {
+    return E.fromRef(ref);
+  }
+
+  @override
+  String get signature => 'E';
+
+  @override
+  int get superCount => superType.superCount + 1;
+
+  @override
+  JObjType<JObject> get superType => $BType();
+
+  @override
+  int get hashCode => ($EType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == $EType && other is $EType;
+  }
+}
+
+class F extends C {
+  F.fromRef(super.reference) : super.fromRef();
+
+  @override
+  JObjType<JObject> get $type => $FType();
+}
+
+final class $FType extends JObjType<F> {
+  @override
+  F fromRef(Pointer<Void> ref) {
+    return F.fromRef(ref);
+  }
+
+  @override
+  String get signature => 'F';
+
+  @override
+  int get superCount => superType.superCount + 1;
+
+  @override
+  JObjType<JObject> get superType => $CType();
+
+  @override
+  int get hashCode => ($FType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == $FType && other is $FType;
+  }
+}
+
+void main() {
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  testRunner('lowestCommonSuperType', () {
+    expect(lowestCommonSuperType([JObject.type]), JObject.type);
+    expect(lowestCommonSuperType([JString.type]), JString.type);
+    expect(lowestCommonSuperType([JObject.type, JObject.type]), JObject.type);
+    expect(lowestCommonSuperType([JString.type, JString.type]), JString.type);
+    expect(lowestCommonSuperType([JString.type, JArray.type(jlong.type)]),
+        JObject.type);
+  });
+
+  testRunner('Boxed types', () {
+    expect(
+      lowestCommonSuperType([
+        JByte.type,
+        JInteger.type,
+        JLong.type,
+        JShort.type,
+        JDouble.type,
+        JFloat.type,
+      ]),
+      JNumber.type,
+    );
+    expect(lowestCommonSuperType([JByte.type, JBoolean.type]), JObject.type);
+  });
+
+  testRunner('array types', () {
+    using((arena) {
+      expect(
+        lowestCommonSuperType([
+          JArray.type(jint.type),
+          JArray.type(jint.type),
+        ]),
+        JArray.type(jint.type),
+      );
+      expect(
+        lowestCommonSuperType([
+          JArray.type(JObject.type),
+          JArray.type(JObject.type),
+        ]),
+        JArray.type(JObject.type),
+      );
+      expect(
+        lowestCommonSuperType([
+          JArray.type(JObject.type),
+          JArray.type(jint.type),
+        ]),
+        JObject.type,
+      );
+    });
+  });
+
+  testRunner('util types', () {
+    using((arena) {
+      expect(
+        lowestCommonSuperType([
+          JList.type(JObject.type),
+          JList.type(JObject.type),
+        ]),
+        JList.type(JObject.type),
+      );
+      expect(
+        lowestCommonSuperType([
+          JList.type(JObject.type),
+          JList.type(JString.type),
+        ]),
+        JObject.type,
+      );
+      expect(
+        lowestCommonSuperType([
+          JList.type(JObject.type),
+          JMap.type(JObject.type, JObject.type),
+        ]),
+        JObject.type,
+      );
+      expect(
+        lowestCommonSuperType([
+          JSet.type(JObject.type),
+          JIterator.type(JObject.type),
+        ]),
+        JObject.type,
+      );
+      expect(
+        lowestCommonSuperType([
+          JByteBuffer.type,
+          JBuffer.type,
+        ]),
+        JBuffer.type,
+      );
+    });
+  });
+
+  testRunner('Mocked type tree', () {
+    // As a reminder, this is how the type tree looks like:
+    //   JObject
+    //      |  \
+    //      A   B
+    //     / \   \
+    //    C   D   E
+    //   /
+    //  F
+    expect(lowestCommonSuperType([$AType(), $BType()]), const JObjectType());
+    expect(lowestCommonSuperType([$CType(), $BType()]), const JObjectType());
+    expect(lowestCommonSuperType([$FType(), $BType()]), const JObjectType());
+    expect(lowestCommonSuperType([$EType(), $CType(), $FType()]),
+        const JObjectType());
+
+    expect(lowestCommonSuperType([$CType(), $DType()]), $AType());
+    expect(lowestCommonSuperType([$FType(), $DType()]), $AType());
+    expect(lowestCommonSuperType([$FType(), $CType(), $DType()]), $AType());
+
+    expect(lowestCommonSuperType([$EType(), $BType()]), $BType());
+    expect(lowestCommonSuperType([$BType(), $BType()]), $BType());
+  });
+}
diff --git a/pkgs/jni/third_party/jni.h b/pkgs/jni/third_party/jni.h
new file mode 100644
index 0000000..b71105e
--- /dev/null
+++ b/pkgs/jni/third_party/jni.h
@@ -0,0 +1,1490 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/*
+ * JNI specification, as defined by Sun:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
+ *
+ * Everything here is expected to be VM-neutral.
+ */
+
+/* ANNOTATED COPY FOR DART JNI LIBRARY */
+
+#pragma once
+
+#include <stdarg.h>
+#include <stdint.h>
+
+/* Primitive types that match up with Java equivalents. */
+typedef uint8_t jboolean; /* unsigned 8 bits */
+typedef int8_t jbyte;     /* signed 8 bits */
+typedef uint16_t jchar;   /* unsigned 16 bits */
+typedef int16_t jshort;   /* signed 16 bits */
+typedef int32_t jint;     /* signed 32 bits */
+typedef int64_t jlong;    /* signed 64 bits */
+typedef float jfloat;     /* 32-bit IEEE 754 */
+typedef double jdouble;   /* 64-bit IEEE 754 */
+
+/* "cardinal indices and sizes" */
+typedef jint jsize;
+
+#ifdef __cplusplus
+/*
+ * Reference types, in C++
+ */
+class _jobject
+{
+};
+class _jclass : public _jobject
+{
+};
+class _jstring : public _jobject
+{
+};
+class _jarray : public _jobject
+{
+};
+class _jobjectArray : public _jarray
+{
+};
+class _jbooleanArray : public _jarray
+{
+};
+class _jbyteArray : public _jarray
+{
+};
+class _jcharArray : public _jarray
+{
+};
+class _jshortArray : public _jarray
+{
+};
+class _jintArray : public _jarray
+{
+};
+class _jlongArray : public _jarray
+{
+};
+class _jfloatArray : public _jarray
+{
+};
+class _jdoubleArray : public _jarray
+{
+};
+class _jthrowable : public _jobject
+{
+};
+
+typedef _jobject *jobject;
+typedef _jclass *jclass;
+typedef _jstring *jstring;
+typedef _jarray *jarray;
+typedef _jobjectArray *jobjectArray;
+typedef _jbooleanArray *jbooleanArray;
+typedef _jbyteArray *jbyteArray;
+typedef _jcharArray *jcharArray;
+typedef _jshortArray *jshortArray;
+typedef _jintArray *jintArray;
+typedef _jlongArray *jlongArray;
+typedef _jfloatArray *jfloatArray;
+typedef _jdoubleArray *jdoubleArray;
+typedef _jthrowable *jthrowable;
+typedef _jobject *jweak;
+
+#else /* not __cplusplus */
+
+/*
+ * Reference types, in C.
+ */
+typedef void *jobject;
+typedef jobject jclass;
+typedef jobject jstring;
+typedef jobject jarray;
+typedef jarray jobjectArray;
+typedef jarray jbooleanArray;
+typedef jarray jbyteArray;
+typedef jarray jcharArray;
+typedef jarray jshortArray;
+typedef jarray jintArray;
+typedef jarray jlongArray;
+typedef jarray jfloatArray;
+typedef jarray jdoubleArray;
+typedef jobject jthrowable;
+typedef jobject jweak;
+
+#endif /* not __cplusplus */
+
+struct _jfieldID;                   /* opaque structure */
+typedef struct _jfieldID *jfieldID; /* field IDs */
+
+struct _jmethodID;                    /* opaque structure */
+typedef struct _jmethodID *jmethodID; /* method IDs */
+
+struct JNIInvokeInterface;
+
+typedef union jvalue
+{
+    jboolean z;
+    jbyte b;
+    jchar c;
+    jshort s;
+    jint i;
+    jlong j;
+    jfloat f;
+    jdouble d;
+    jobject l;
+} jvalue;
+
+typedef enum jobjectRefType
+{
+    JNIInvalidRefType = 0,
+    JNILocalRefType = 1,
+    JNIGlobalRefType = 2,
+    JNIWeakGlobalRefType = 3
+} jobjectRefType;
+
+typedef struct
+{
+    const char *name;
+    const char *signature;
+    void *fnPtr;
+} JNINativeMethod;
+
+struct _JNIEnv;
+struct _JavaVM;
+typedef const struct JNINativeInterface *C_JNIEnv;
+
+#if defined(__cplusplus)
+typedef _JNIEnv JNIEnv;
+typedef _JavaVM JavaVM;
+#else
+typedef const struct JNINativeInterface *JNIEnv;
+typedef const struct JNIInvokeInterface *JavaVM;
+#endif
+
+/*
+ * Table of interface function pointers.
+ */
+struct JNINativeInterface
+{
+    void *reserved0;
+    void *reserved1;
+    void *reserved2;
+    void *reserved3;
+
+    jint (*GetVersion)(JNIEnv *env);
+    jclass (*DefineClass)(JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
+                          jsize bufLen);
+    jclass (*FindClass)(JNIEnv *env, const char *name);
+
+    jmethodID (*FromReflectedMethod)(JNIEnv *env, jobject method);
+    jfieldID (*FromReflectedField)(JNIEnv *env, jobject field);
+
+    /* spec doesn't show jboolean parameter */
+
+    jobject (*ToReflectedMethod)(JNIEnv *env, jclass cls, jmethodID methodId, jboolean isStatic);
+
+    jclass (*GetSuperclass)(JNIEnv *env, jclass clazz);
+    jboolean (*IsAssignableFrom)(JNIEnv *env, jclass clazz1, jclass clazz2);
+
+    /* spec doesn't show jboolean parameter */
+
+    jobject (*ToReflectedField)(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);
+
+    jint (*Throw)(JNIEnv *env, jthrowable obj);
+    jint (*ThrowNew)(JNIEnv *env, jclass clazz, const char *message);
+    jthrowable (*ExceptionOccurred)(JNIEnv *env);
+    void (*ExceptionDescribe)(JNIEnv *env);
+    void (*ExceptionClear)(JNIEnv *env);
+    void (*FatalError)(JNIEnv *env, const char *msg);
+
+    jint (*PushLocalFrame)(JNIEnv *env, jint capacity);
+    jobject (*PopLocalFrame)(JNIEnv *env, jobject result);
+
+    jobject (*NewGlobalRef)(JNIEnv *env, jobject obj);
+    void (*DeleteGlobalRef)(JNIEnv *env, jobject globalRef);
+    void (*DeleteLocalRef)(JNIEnv *env, jobject localRef);
+    jboolean (*IsSameObject)(JNIEnv *env, jobject ref1, jobject ref2);
+
+    jobject (*NewLocalRef)(JNIEnv *env, jobject obj);
+    jint (*EnsureLocalCapacity)(JNIEnv *env, jint capacity);
+
+    jobject (*AllocObject)(JNIEnv *env, jclass clazz);
+    jobject (*NewObject)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jobject (*NewObjectV)(JNIEnv *, jclass, jmethodID, void *);
+    jobject (*NewObjectA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jclass (*GetObjectClass)(JNIEnv *env, jobject obj);
+    jboolean (*IsInstanceOf)(JNIEnv *env, jobject obj, jclass clazz);
+    jmethodID (*GetMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);
+
+    jobject (*CallObjectMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jobject (*CallObjectMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    jobject (*CallObjectMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+    jboolean (*CallBooleanMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jboolean (*CallBooleanMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    jboolean (*CallBooleanMethodA)(JNIEnv *env, jobject obj, jmethodID methodId, const jvalue *args);
+    jbyte (*CallByteMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jbyte (*CallByteMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    jbyte (*CallByteMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+    jchar (*CallCharMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jchar (*CallCharMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    jchar (*CallCharMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+    jshort (*CallShortMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jshort (*CallShortMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    jshort (*CallShortMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+    jint (*CallIntMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jint (*CallIntMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    jint (*CallIntMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+    jlong (*CallLongMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jlong (*CallLongMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    jlong (*CallLongMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+    jfloat (*CallFloatMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jfloat (*CallFloatMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    jfloat (*CallFloatMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+    jdouble (*CallDoubleMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    jdouble (*CallDoubleMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    jdouble (*CallDoubleMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+    void (*CallVoidMethod)(JNIEnv *env, jobject obj, jmethodID methodID, ...);
+    void (*CallVoidMethodV)(JNIEnv *, jobject, jmethodID, void *);
+    void (*CallVoidMethodA)(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
+
+    jobject (*CallNonvirtualObjectMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                          jmethodID methodID, ...);
+    jobject (*CallNonvirtualObjectMethodV)(JNIEnv *, jobject, jclass,
+                                           jmethodID, void *);
+    jobject (*CallNonvirtualObjectMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                           jmethodID methodID, const jvalue *args);
+    jboolean (*CallNonvirtualBooleanMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                            jmethodID methodID, ...);
+    jboolean (*CallNonvirtualBooleanMethodV)(JNIEnv *, jobject, jclass,
+                                             jmethodID, void *);
+    jboolean (*CallNonvirtualBooleanMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                             jmethodID methodID, const jvalue *args);
+    jbyte (*CallNonvirtualByteMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                      jmethodID methodID, ...);
+    jbyte (*CallNonvirtualByteMethodV)(JNIEnv *, jobject, jclass,
+                                       jmethodID, void *);
+    jbyte (*CallNonvirtualByteMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                       jmethodID methodID, const jvalue *args);
+    jchar (*CallNonvirtualCharMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                      jmethodID methodID, ...);
+    jchar (*CallNonvirtualCharMethodV)(JNIEnv *, jobject, jclass,
+                                       jmethodID, void *);
+    jchar (*CallNonvirtualCharMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                       jmethodID methodID, const jvalue *args);
+    jshort (*CallNonvirtualShortMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                        jmethodID methodID, ...);
+    jshort (*CallNonvirtualShortMethodV)(JNIEnv *, jobject, jclass,
+                                         jmethodID, void *);
+    jshort (*CallNonvirtualShortMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                         jmethodID methodID, const jvalue *args);
+    jint (*CallNonvirtualIntMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                    jmethodID methodID, ...);
+    jint (*CallNonvirtualIntMethodV)(JNIEnv *, jobject, jclass,
+                                     jmethodID, void *);
+    jint (*CallNonvirtualIntMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                     jmethodID methodID, const jvalue *args);
+    jlong (*CallNonvirtualLongMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                      jmethodID methodID, ...);
+    jlong (*CallNonvirtualLongMethodV)(JNIEnv *, jobject, jclass,
+                                       jmethodID, void *);
+    jlong (*CallNonvirtualLongMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                       jmethodID methodID, const jvalue *args);
+    jfloat (*CallNonvirtualFloatMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                      jmethodID methodID, ...);
+    jfloat (*CallNonvirtualFloatMethodV)(JNIEnv *, jobject, jclass,
+                                         jmethodID, void *);
+    jfloat (*CallNonvirtualFloatMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                         jmethodID methodID, const jvalue *args);
+    jdouble (*CallNonvirtualDoubleMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                      jmethodID methodID, ...);
+    jdouble (*CallNonvirtualDoubleMethodV)(JNIEnv *, jobject, jclass,
+                                           jmethodID, void *);
+    jdouble (*CallNonvirtualDoubleMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                           jmethodID methodID, const jvalue *args);
+    void (*CallNonvirtualVoidMethod)(JNIEnv *env, jobject obj, jclass clazz,
+                                      jmethodID methodID, ...);
+    void (*CallNonvirtualVoidMethodV)(JNIEnv *, jobject, jclass,
+                                      jmethodID, void *);
+    void (*CallNonvirtualVoidMethodA)(JNIEnv *env, jobject obj, jclass clazz,
+                                      jmethodID methodID, const jvalue *args);
+
+    jfieldID (*GetFieldID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);
+
+    jobject (*GetObjectField)(JNIEnv *env, jobject obj, jfieldID fieldID);
+    jboolean (*GetBooleanField)(JNIEnv *env, jobject obj, jfieldID fieldID);
+    jbyte (*GetByteField)(JNIEnv *env, jobject obj, jfieldID fieldID);
+    jchar (*GetCharField)(JNIEnv *env, jobject obj, jfieldID fieldID);
+    jshort (*GetShortField)(JNIEnv *env, jobject obj, jfieldID fieldID);
+    jint (*GetIntField)(JNIEnv *env, jobject obj, jfieldID fieldID);
+    jlong (*GetLongField)(JNIEnv *env, jobject obj, jfieldID fieldID);
+    jfloat (*GetFloatField)(JNIEnv *env, jobject obj, jfieldID fieldID);
+    jdouble (*GetDoubleField)(JNIEnv *env, jobject obj, jfieldID fieldID);
+
+    void (*SetObjectField)(JNIEnv *env, jobject obj, jfieldID fieldID, jobject val);
+    void (*SetBooleanField)(JNIEnv *env, jobject obj, jfieldID fieldID, jboolean val);
+    void (*SetByteField)(JNIEnv *env, jobject obj, jfieldID fieldID, jbyte val);
+    void (*SetCharField)(JNIEnv *env, jobject obj, jfieldID fieldID, jchar val);
+    void (*SetShortField)(JNIEnv *env, jobject obj, jfieldID fieldID, jshort val);
+    void (*SetIntField)(JNIEnv *env, jobject obj, jfieldID fieldID, jint val);
+    void (*SetLongField)(JNIEnv *env, jobject obj, jfieldID fieldID, jlong val);
+    void (*SetFloatField)(JNIEnv *env, jobject obj, jfieldID fieldID, jfloat val);
+    void (*SetDoubleField)(JNIEnv *env, jobject obj, jfieldID fieldID, jdouble val);
+
+    jmethodID (*GetStaticMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);
+
+    jobject (*CallStaticObjectMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jobject (*CallStaticObjectMethodV)(JNIEnv *, jclass, jmethodID, void *);
+    jobject (*CallStaticObjectMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+    jboolean (*CallStaticBooleanMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jboolean (*CallStaticBooleanMethodV)(JNIEnv *, jclass, jmethodID,
+                                         void *);
+    jboolean (*CallStaticBooleanMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+    jbyte (*CallStaticByteMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jbyte (*CallStaticByteMethodV)(JNIEnv *, jclass, jmethodID, void *);
+    jbyte (*CallStaticByteMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+    jchar (*CallStaticCharMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jchar (*CallStaticCharMethodV)(JNIEnv *, jclass, jmethodID, void *);
+    jchar (*CallStaticCharMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+    jshort (*CallStaticShortMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jshort (*CallStaticShortMethodV)(JNIEnv *, jclass, jmethodID, void *);
+    jshort (*CallStaticShortMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+    jint (*CallStaticIntMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jint (*CallStaticIntMethodV)(JNIEnv *, jclass, jmethodID, void *);
+    jint (*CallStaticIntMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+    jlong (*CallStaticLongMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jlong (*CallStaticLongMethodV)(JNIEnv *, jclass, jmethodID, void *);
+    jlong (*CallStaticLongMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+    jfloat (*CallStaticFloatMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jfloat (*CallStaticFloatMethodV)(JNIEnv *, jclass, jmethodID, void *);
+    jfloat (*CallStaticFloatMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+    jdouble (*CallStaticDoubleMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    jdouble (*CallStaticDoubleMethodV)(JNIEnv *, jclass, jmethodID, void *);
+    jdouble (*CallStaticDoubleMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+    void (*CallStaticVoidMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
+    void (*CallStaticVoidMethodV)(JNIEnv *, jclass, jmethodID, void *);
+    void (*CallStaticVoidMethodA)(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
+
+    jfieldID (*GetStaticFieldID)(JNIEnv *env, jclass clazz, const char *name,
+                                 const char *sig);
+
+    jobject (*GetStaticObjectField)(JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jboolean (*GetStaticBooleanField)(JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jbyte (*GetStaticByteField)(JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jchar (*GetStaticCharField)(JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jshort (*GetStaticShortField)(JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jint (*GetStaticIntField)(JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jlong (*GetStaticLongField)(JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jfloat (*GetStaticFloatField)(JNIEnv *env, jclass clazz, jfieldID fieldID);
+    jdouble (*GetStaticDoubleField)(JNIEnv *env, jclass clazz, jfieldID fieldID);
+
+    void (*SetStaticObjectField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jobject val);
+    void (*SetStaticBooleanField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jboolean val);
+    void (*SetStaticByteField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jbyte val);
+    void (*SetStaticCharField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jchar val);
+    void (*SetStaticShortField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jshort val);
+    void (*SetStaticIntField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jint val);
+    void (*SetStaticLongField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jlong val);
+    void (*SetStaticFloatField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jfloat val);
+    void (*SetStaticDoubleField)(JNIEnv *env, jclass clazz, jfieldID fieldID, jdouble val);
+
+    jstring (*NewString)(JNIEnv *env, const jchar *unicodeChars, jsize len);
+    jsize (*GetStringLength)(JNIEnv *env, jstring string);
+    const jchar *(*GetStringChars)(JNIEnv *env, jstring string, jboolean *isCopy);
+    void (*ReleaseStringChars)(JNIEnv *env, jstring string, const jchar *isCopy);
+    jstring (*NewStringUTF)(JNIEnv *env, const char *bytes);
+    jsize (*GetStringUTFLength)(JNIEnv *env, jstring string);
+    const char *(*GetStringUTFChars)(JNIEnv *env, jstring string, jboolean *isCopy);
+    void (*ReleaseStringUTFChars)(JNIEnv *env, jstring string, const char *utf);
+    jsize (*GetArrayLength)(JNIEnv *env, jarray array);
+    jobjectArray (*NewObjectArray)(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
+    jobject (*GetObjectArrayElement)(JNIEnv *env, jobjectArray array, jsize index);
+    void (*SetObjectArrayElement)(JNIEnv *env, jobjectArray array, jsize index, jobject val);
+
+    jbooleanArray (*NewBooleanArray)(JNIEnv *env, jsize length);
+    jbyteArray (*NewByteArray)(JNIEnv *env, jsize length);
+    jcharArray (*NewCharArray)(JNIEnv *env, jsize length);
+    jshortArray (*NewShortArray)(JNIEnv *env, jsize length);
+    jintArray (*NewIntArray)(JNIEnv *env, jsize length);
+    jlongArray (*NewLongArray)(JNIEnv *env, jsize length);
+    jfloatArray (*NewFloatArray)(JNIEnv *env, jsize length);
+    jdoubleArray (*NewDoubleArray)(JNIEnv *env, jsize length);
+
+    jboolean *(*GetBooleanArrayElements)(JNIEnv *env, jbooleanArray array, jboolean *isCopy);
+    jbyte *(*GetByteArrayElements)(JNIEnv *env, jbyteArray array, jboolean *isCopy);
+    jchar *(*GetCharArrayElements)(JNIEnv *env, jcharArray array, jboolean *isCopy);
+    jshort *(*GetShortArrayElements)(JNIEnv *env, jshortArray array, jboolean *isCopy);
+    jint *(*GetIntArrayElements)(JNIEnv *env, jintArray array, jboolean *isCopy);
+    jlong *(*GetLongArrayElements)(JNIEnv *env, jlongArray array, jboolean *isCopy);
+    jfloat *(*GetFloatArrayElements)(JNIEnv *env, jfloatArray array, jboolean *isCopy);
+    jdouble *(*GetDoubleArrayElements)(JNIEnv *env, jdoubleArray array, jboolean *isCopy);
+
+    void (*ReleaseBooleanArrayElements)(JNIEnv *env, jbooleanArray array,
+                                        jboolean *elems, jint mode);
+    void (*ReleaseByteArrayElements)(JNIEnv *env, jbyteArray array,
+                                     jbyte *elems, jint mode);
+    void (*ReleaseCharArrayElements)(JNIEnv *env, jcharArray array,
+                                     jchar *elems, jint mode);
+    void (*ReleaseShortArrayElements)(JNIEnv *env, jshortArray array,
+                                      jshort *elems, jint mode);
+    void (*ReleaseIntArrayElements)(JNIEnv *env, jintArray array,
+                                    jint *elems, jint mode);
+    void (*ReleaseLongArrayElements)(JNIEnv *env, jlongArray array,
+                                     jlong *elems, jint mode);
+    void (*ReleaseFloatArrayElements)(JNIEnv *env, jfloatArray array,
+                                      jfloat *elems, jint mode);
+    void (*ReleaseDoubleArrayElements)(JNIEnv *env, jdoubleArray array,
+                                       jdouble *elems, jint mode);
+
+    void (*GetBooleanArrayRegion)(JNIEnv *env, jbooleanArray array,
+                                  jsize start, jsize len, jboolean *buf);
+    void (*GetByteArrayRegion)(JNIEnv *env, jbyteArray array,
+                               jsize start, jsize len, jbyte *buf);
+    void (*GetCharArrayRegion)(JNIEnv *env, jcharArray array,
+                               jsize start, jsize len, jchar *buf);
+    void (*GetShortArrayRegion)(JNIEnv *env, jshortArray array,
+                                jsize start, jsize len, jshort *buf);
+    void (*GetIntArrayRegion)(JNIEnv *env, jintArray array,
+                              jsize start, jsize len, jint *buf);
+    void (*GetLongArrayRegion)(JNIEnv *env, jlongArray array,
+                               jsize start, jsize len, jlong *buf);
+    void (*GetFloatArrayRegion)(JNIEnv *env, jfloatArray array,
+                                jsize start, jsize len, jfloat *buf);
+    void (*GetDoubleArrayRegion)(JNIEnv *env, jdoubleArray array,
+                                 jsize start, jsize len, jdouble *buf);
+
+    /* spec shows these without const; some jni.h do, some don't */
+    void (*SetBooleanArrayRegion)(JNIEnv *env, jbooleanArray array,
+                                  jsize start, jsize len, const jboolean *buf);
+    void (*SetByteArrayRegion)(JNIEnv *env, jbyteArray array,
+                               jsize start, jsize len, const jbyte *buf);
+    void (*SetCharArrayRegion)(JNIEnv *env, jcharArray array,
+                               jsize start, jsize len, const jchar *buf);
+    void (*SetShortArrayRegion)(JNIEnv *env, jshortArray array,
+                                jsize start, jsize len, const jshort *buf);
+    void (*SetIntArrayRegion)(JNIEnv *env, jintArray array,
+                              jsize start, jsize len, const jint *buf);
+    void (*SetLongArrayRegion)(JNIEnv *env, jlongArray array,
+                               jsize start, jsize len, const jlong *buf);
+    void (*SetFloatArrayRegion)(JNIEnv *env, jfloatArray array,
+                                jsize start, jsize len, const jfloat *buf);
+    void (*SetDoubleArrayRegion)(JNIEnv *env, jdoubleArray array,
+                                 jsize start, jsize len, const jdouble *buf);
+
+    jint (*RegisterNatives)(JNIEnv *env, jclass clazz, const JNINativeMethod *methods,
+                            jint nMethods);
+    jint (*UnregisterNatives)(JNIEnv *env, jclass clazz);
+    jint (*MonitorEnter)(JNIEnv *env, jobject obj);
+    jint (*MonitorExit)(JNIEnv *env, jobject obj);
+    jint (*GetJavaVM)(JNIEnv *env, JavaVM **vm);
+
+    void (*GetStringRegion)(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);
+    void (*GetStringUTFRegion)(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);
+
+    void *(*GetPrimitiveArrayCritical)(JNIEnv *env, jarray array, jboolean *isCopy);
+    void (*ReleasePrimitiveArrayCritical)(JNIEnv *env, jarray array, void *carray, jint mode);
+
+    const jchar *(*GetStringCritical)(JNIEnv *env, jstring str, jboolean *isCopy);
+    void (*ReleaseStringCritical)(JNIEnv *env, jstring str, const jchar *carray);
+
+    jweak (*NewWeakGlobalRef)(JNIEnv *env, jobject obj);
+    void (*DeleteWeakGlobalRef)(JNIEnv *env, jweak obj);
+
+    jboolean (*ExceptionCheck)(JNIEnv *env);
+
+    jobject (*NewDirectByteBuffer)(JNIEnv *env, void *address, jlong capacity);
+    void *(*GetDirectBufferAddress)(JNIEnv *env, jobject buf);
+    jlong (*GetDirectBufferCapacity)(JNIEnv *env, jobject buf);
+
+    /* added in JNI 1.6 */
+    jobjectRefType (*GetObjectRefType)(JNIEnv *env, jobject obj);
+};
+
+/*
+ * C++ object wrapper.
+ *
+ * This is usually overlaid on a C struct whose first element is a
+ * JNINativeInterface*.  We rely somewhat on compiler behavior.
+ */
+struct _JNIEnv
+{
+    /* do not rename this; it does not seem to be entirely opaque */
+    const struct JNINativeInterface *functions;
+
+#if defined(__cplusplus)
+
+    jint GetVersion()
+    {
+        return functions->GetVersion(this);
+    }
+
+    jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
+                       jsize bufLen)
+    {
+        return functions->DefineClass(this, name, loader, buf, bufLen);
+    }
+
+    jclass FindClass(const char *name)
+    {
+        return functions->FindClass(this, name);
+    }
+
+    jmethodID FromReflectedMethod(jobject method)
+    {
+        return functions->FromReflectedMethod(this, method);
+    }
+
+    jfieldID FromReflectedField(jobject field)
+    {
+        return functions->FromReflectedField(this, field);
+    }
+
+    jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic)
+    {
+        return functions->ToReflectedMethod(this, cls, methodID, isStatic);
+    }
+
+    jclass GetSuperclass(jclass clazz)
+    {
+        return functions->GetSuperclass(this, clazz);
+    }
+
+    jboolean IsAssignableFrom(jclass clazz1, jclass clazz2)
+    {
+        return functions->IsAssignableFrom(this, clazz1, clazz2);
+    }
+
+    jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic)
+    {
+        return functions->ToReflectedField(this, cls, fieldID, isStatic);
+    }
+
+    jint Throw(jthrowable obj)
+    {
+        return functions->Throw(this, obj);
+    }
+
+    jint ThrowNew(jclass clazz, const char *message)
+    {
+        return functions->ThrowNew(this, clazz, message);
+    }
+
+    jthrowable ExceptionOccurred()
+    {
+        return functions->ExceptionOccurred(this);
+    }
+
+    void ExceptionDescribe()
+    {
+        functions->ExceptionDescribe(this);
+    }
+
+    void ExceptionClear()
+    {
+        functions->ExceptionClear(this);
+    }
+
+    void FatalError(const char *msg)
+    {
+        functions->FatalError(this, msg);
+    }
+
+    jint PushLocalFrame(jint capacity)
+    {
+        return functions->PushLocalFrame(this, capacity);
+    }
+
+    jobject PopLocalFrame(jobject result)
+    {
+        return functions->PopLocalFrame(this, result);
+    }
+
+    jobject NewGlobalRef(jobject obj)
+    {
+        return functions->NewGlobalRef(this, obj);
+    }
+
+    void DeleteGlobalRef(jobject globalRef)
+    {
+        functions->DeleteGlobalRef(this, globalRef);
+    }
+
+    void DeleteLocalRef(jobject localRef)
+    {
+        functions->DeleteLocalRef(this, localRef);
+    }
+
+    jboolean IsSameObject(jobject ref1, jobject ref2)
+    {
+        return functions->IsSameObject(this, ref1, ref2);
+    }
+
+    jobject NewLocalRef(jobject obj)
+    {
+        return functions->NewLocalRef(this, ref);
+    }
+
+    jint EnsureLocalCapacity(jint capacity)
+    {
+        return functions->EnsureLocalCapacity(this, capacity);
+    }
+
+    jobject AllocObject(jclass clazz)
+    {
+        return functions->AllocObject(this, clazz);
+    }
+
+    jobject NewObject(jclass clazz, jmethodID methodID, ...)
+    {
+        va_list args;
+        va_start(args, methodID);
+        jobject result = functions->NewObjectV(this, clazz, methodID, args);
+        va_end(args);
+        return result;
+    }
+
+    jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args)
+    {
+        return functions->NewObjectV(this, clazz, methodID, args);
+    }
+
+    jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue *args)
+    {
+        return functions->NewObjectA(this, clazz, methodID, args);
+    }
+
+    jclass GetObjectClass(jobject obj)
+    {
+        return functions->GetObjectClass(this, obj);
+    }
+
+    jboolean IsInstanceOf(jobject obj, jclass clazz)
+    {
+        return functions->IsInstanceOf(this, obj, clazz);
+    }
+
+    jmethodID GetMethodID(jclass clazz, const char *name, const char *sig)
+    {
+        return functions->GetMethodID(this, clazz, name, sig);
+    }
+
+#define CALL_TYPE_METHOD(_jtype, _jname)                               \
+    _jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...)  \
+    {                                                                  \
+        _jtype result;                                                 \
+        va_list args;                                                  \
+        va_start(args, methodID);                                      \
+        result = functions->Call##_jname##MethodV(this, obj, methodID, \
+                                                  args);               \
+        va_end(args);                                                  \
+        return result;                                                 \
+    }
+#define CALL_TYPE_METHODV(_jtype, _jname)                                   \
+    _jtype Call##_jname##MethodV(jobject obj, jmethodID methodID,           \
+                                 va_list args)                              \
+    {                                                                       \
+        return functions->Call##_jname##MethodV(this, obj, methodID, args); \
+    }
+#define CALL_TYPE_METHODA(_jtype, _jname)                                   \
+    _jtype Call##_jname##MethodA(jobject obj, jmethodID methodID,           \
+                                 const jvalue *args)                        \
+    {                                                                       \
+        return functions->Call##_jname##MethodA(this, obj, methodID, args); \
+    }
+
+#define CALL_TYPE(_jtype, _jname)     \
+    CALL_TYPE_METHOD(_jtype, _jname)  \
+    CALL_TYPE_METHODV(_jtype, _jname) \
+    CALL_TYPE_METHODA(_jtype, _jname)
+
+    CALL_TYPE(jobject, Object)
+    CALL_TYPE(jboolean, Boolean)
+    CALL_TYPE(jbyte, Byte)
+    CALL_TYPE(jchar, Char)
+    CALL_TYPE(jshort, Short)
+    CALL_TYPE(jint, Int)
+    CALL_TYPE(jlong, Long)
+    CALL_TYPE(jfloat, Float)
+    CALL_TYPE(jdouble, Double)
+
+    void CallVoidMethod(jobject obj, jmethodID methodID, ...)
+    {
+        va_list args;
+        va_start(args, methodID);
+        functions->CallVoidMethodV(this, obj, methodID, args);
+        va_end(args);
+    }
+    void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args)
+    {
+        functions->CallVoidMethodV(this, obj, methodID, args);
+    }
+    void CallVoidMethodA(jobject obj, jmethodID methodID, const jvalue *args)
+    {
+        functions->CallVoidMethodA(this, obj, methodID, args);
+    }
+
+#define CALL_NONVIRT_TYPE_METHOD(_jtype, _jname)                                    \
+    _jtype CallNonvirtual##_jname##Method(jobject obj, jclass clazz,                \
+                                          jmethodID methodID, ...)                  \
+    {                                                                               \
+        _jtype result;                                                              \
+        va_list args;                                                               \
+        va_start(args, methodID);                                                   \
+        result = functions->CallNonvirtual##_jname##MethodV(this, obj,              \
+                                                            clazz, methodID, args); \
+        va_end(args);                                                               \
+        return result;                                                              \
+    }
+#define CALL_NONVIRT_TYPE_METHODV(_jtype, _jname)                            \
+    _jtype CallNonvirtual##_jname##MethodV(jobject obj, jclass clazz,        \
+                                           jmethodID methodID, va_list args) \
+    {                                                                        \
+        return functions->CallNonvirtual##_jname##MethodV(this, obj, clazz,  \
+                                                          methodID, args);   \
+    }
+#define CALL_NONVIRT_TYPE_METHODA(_jtype, _jname)                                  \
+    _jtype CallNonvirtual##_jname##MethodA(jobject obj, jclass clazz,              \
+                                           jmethodID methodID, const jvalue *args) \
+    {                                                                              \
+        return functions->CallNonvirtual##_jname##MethodA(this, obj, clazz,        \
+                                                          methodID, args);         \
+    }
+
+#define CALL_NONVIRT_TYPE(_jtype, _jname)     \
+    CALL_NONVIRT_TYPE_METHOD(_jtype, _jname)  \
+    CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \
+    CALL_NONVIRT_TYPE_METHODA(_jtype, _jname)
+
+    CALL_NONVIRT_TYPE(jobject, Object)
+    CALL_NONVIRT_TYPE(jboolean, Boolean)
+    CALL_NONVIRT_TYPE(jbyte, Byte)
+    CALL_NONVIRT_TYPE(jchar, Char)
+    CALL_NONVIRT_TYPE(jshort, Short)
+    CALL_NONVIRT_TYPE(jint, Int)
+    CALL_NONVIRT_TYPE(jlong, Long)
+    CALL_NONVIRT_TYPE(jfloat, Float)
+    CALL_NONVIRT_TYPE(jdouble, Double)
+
+    void CallNonvirtualVoidMethod(jobject obj, jclass clazz,
+                                  jmethodID methodID, ...)
+    {
+        va_list args;
+        va_start(args, methodID);
+        functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args);
+        va_end(args);
+    }
+    void CallNonvirtualVoidMethodV(jobject obj, jclass clazz,
+                                   jmethodID methodID, va_list args)
+    {
+        functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args);
+    }
+    void CallNonvirtualVoidMethodA(jobject obj, jclass clazz,
+                                   jmethodID methodID, const jvalue *args)
+    {
+        functions->CallNonvirtualVoidMethodA(this, obj, clazz, methodID, args);
+    }
+
+    jfieldID GetFieldID(jclass clazz, const char *name, const char *sig)
+    {
+        return functions->GetFieldID(this, clazz, name, sig);
+    }
+
+    jobject GetObjectField(jobject obj, jfieldID fieldID)
+    {
+        return functions->GetObjectField(this, obj, fieldID);
+    }
+    jboolean GetBooleanField(jobject obj, jfieldID fieldID)
+    {
+        return functions->GetBooleanField(this, obj, fieldID);
+    }
+    jbyte GetByteField(jobject obj, jfieldID fieldID)
+    {
+        return functions->GetByteField(this, obj, fieldID);
+    }
+    jchar GetCharField(jobject obj, jfieldID fieldID)
+    {
+        return functions->GetCharField(this, obj, fieldID);
+    }
+    jshort GetShortField(jobject obj, jfieldID fieldID)
+    {
+        return functions->GetShortField(this, obj, fieldID);
+    }
+    jint GetIntField(jobject obj, jfieldID fieldID)
+    {
+        return functions->GetIntField(this, obj, fieldID);
+    }
+    jlong GetLongField(jobject obj, jfieldID fieldID)
+    {
+        return functions->GetLongField(this, obj, fieldID);
+    }
+    jfloat GetFloatField(jobject obj, jfieldID fieldID)
+    {
+        return functions->GetFloatField(this, obj, fieldID);
+    }
+    jdouble GetDoubleField(jobject obj, jfieldID fieldID)
+    {
+        return functions->GetDoubleField(this, obj, fieldID);
+    }
+
+    void SetObjectField(jobject obj, jfieldID fieldID, jobject val)
+    {
+        functions->SetObjectField(this, obj, fieldID, val);
+    }
+    void SetBooleanField(jobject obj, jfieldID fieldID, jboolean val)
+    {
+        functions->SetBooleanField(this, obj, fieldID, val);
+    }
+    void SetByteField(jobject obj, jfieldID fieldID, jbyte val)
+    {
+        functions->SetByteField(this, obj, fieldID, val);
+    }
+    void SetCharField(jobject obj, jfieldID fieldID, jchar val)
+    {
+        functions->SetCharField(this, obj, fieldID, val);
+    }
+    void SetShortField(jobject obj, jfieldID fieldID, jshort val)
+    {
+        functions->SetShortField(this, obj, fieldID, val);
+    }
+    void SetIntField(jobject obj, jfieldID fieldID, jint val)
+    {
+        functions->SetIntField(this, obj, fieldID, val);
+    }
+    void SetLongField(jobject obj, jfieldID fieldID, jlong val)
+    {
+        functions->SetLongField(this, obj, fieldID, val);
+    }
+    void SetFloatField(jobject obj, jfieldID fieldID, jfloat val)
+    {
+        functions->SetFloatField(this, obj, fieldID, val);
+    }
+    void SetDoubleField(jobject obj, jfieldID fieldID, jdouble val)
+    {
+        functions->SetDoubleField(this, obj, fieldID, val);
+    }
+
+    jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig)
+    {
+        return functions->GetStaticMethodID(this, clazz, name, sig);
+    }
+
+#define CALL_STATIC_TYPE_METHOD(_jtype, _jname)                          \
+    _jtype CallStatic##_jname##Method(jclass clazz, jmethodID methodID,  \
+                                      ...)                               \
+    {                                                                    \
+        _jtype result;                                                   \
+        va_list args;                                                    \
+        va_start(args, methodID);                                        \
+        result = functions->CallStatic##_jname##MethodV(this, clazz,     \
+                                                        methodID, args); \
+        va_end(args);                                                    \
+        return result;                                                   \
+    }
+#define CALL_STATIC_TYPE_METHODV(_jtype, _jname)                             \
+    _jtype CallStatic##_jname##MethodV(jclass clazz, jmethodID methodID,     \
+                                       va_list args)                         \
+    {                                                                        \
+        return functions->CallStatic##_jname##MethodV(this, clazz, methodID, \
+                                                      args);                 \
+    }
+#define CALL_STATIC_TYPE_METHODA(_jtype, _jname)                             \
+    _jtype CallStatic##_jname##MethodA(jclass clazz, jmethodID methodID,     \
+                                       const jvalue *args)                   \
+    {                                                                        \
+        return functions->CallStatic##_jname##MethodA(this, clazz, methodID, \
+                                                      args);                 \
+    }
+
+#define CALL_STATIC_TYPE(_jtype, _jname)     \
+    CALL_STATIC_TYPE_METHOD(_jtype, _jname)  \
+    CALL_STATIC_TYPE_METHODV(_jtype, _jname) \
+    CALL_STATIC_TYPE_METHODA(_jtype, _jname)
+
+    CALL_STATIC_TYPE(jobject, Object)
+    CALL_STATIC_TYPE(jboolean, Boolean)
+    CALL_STATIC_TYPE(jbyte, Byte)
+    CALL_STATIC_TYPE(jchar, Char)
+    CALL_STATIC_TYPE(jshort, Short)
+    CALL_STATIC_TYPE(jint, Int)
+    CALL_STATIC_TYPE(jlong, Long)
+    CALL_STATIC_TYPE(jfloat, Float)
+    CALL_STATIC_TYPE(jdouble, Double)
+
+    void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
+    {
+        va_list args;
+        va_start(args, methodID);
+        functions->CallStaticVoidMethodV(this, clazz, methodID, args);
+        va_end(args);
+    }
+    void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args)
+    {
+        functions->CallStaticVoidMethodV(this, clazz, methodID, args);
+    }
+    void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, const jvalue *args)
+    {
+        functions->CallStaticVoidMethodA(this, clazz, methodID, args);
+    }
+
+    jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig)
+    {
+        return functions->GetStaticFieldID(this, clazz, name, sig);
+    }
+
+    jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
+    {
+        return functions->GetStaticObjectField(this, clazz, fieldID);
+    }
+    jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)
+    {
+        return functions->GetStaticBooleanField(this, clazz, fieldID);
+    }
+    jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)
+    {
+        return functions->GetStaticByteField(this, clazz, fieldID);
+    }
+    jchar GetStaticCharField(jclass clazz, jfieldID fieldID)
+    {
+        return functions->GetStaticCharField(this, clazz, fieldID);
+    }
+    jshort GetStaticShortField(jclass clazz, jfieldID fieldID)
+    {
+        return functions->GetStaticShortField(this, clazz, fieldID);
+    }
+    jint GetStaticIntField(jclass clazz, jfieldID fieldID)
+    {
+        return functions->GetStaticIntField(this, clazz, fieldID);
+    }
+    jlong GetStaticLongField(jclass clazz, jfieldID fieldID)
+    {
+        return functions->GetStaticLongField(this, clazz, fieldID);
+    }
+    jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)
+    {
+        return functions->GetStaticFloatField(this, clazz, fieldID);
+    }
+    jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID)
+    {
+        return functions->GetStaticDoubleField(this, clazz, fieldID);
+    }
+
+    void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject val)
+    {
+        functions->SetStaticObjectField(this, clazz, fieldID, val);
+    }
+    void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean val)
+    {
+        functions->SetStaticBooleanField(this, clazz, fieldID, val);
+    }
+    void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte val)
+    {
+        functions->SetStaticByteField(this, clazz, fieldID, val);
+    }
+    void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar val)
+    {
+        functions->SetStaticCharField(this, clazz, fieldID, val);
+    }
+    void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort val)
+    {
+        functions->SetStaticShortField(this, clazz, fieldID, val);
+    }
+    void SetStaticIntField(jclass clazz, jfieldID fieldID, jint val)
+    {
+        functions->SetStaticIntField(this, clazz, fieldID, val);
+    }
+    void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong val)
+    {
+        functions->SetStaticLongField(this, clazz, fieldID, val);
+    }
+    void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat val)
+    {
+        functions->SetStaticFloatField(this, clazz, fieldID, val);
+    }
+    void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble val)
+    {
+        functions->SetStaticDoubleField(this, clazz, fieldID, val);
+    }
+
+    jstring NewString(const jchar *unicodeChars, jsize len)
+    {
+        return functions->NewString(this, unicodeChars, len);
+    }
+
+    jsize GetStringLength(jstring string)
+    {
+        return functions->GetStringLength(this, string);
+    }
+
+    const jchar *GetStringChars(jstring string, jboolean *isCopy)
+    {
+        return functions->GetStringChars(this, string, isCopy);
+    }
+
+    void ReleaseStringChars(jstring string, const jchar *chars)
+    {
+        functions->ReleaseStringChars(this, string, chars);
+    }
+
+    jstring NewStringUTF(const char *bytes)
+    {
+        return functions->NewStringUTF(this, bytes);
+    }
+
+    jsize GetStringUTFLength(jstring string)
+    {
+        return functions->GetStringUTFLength(this, string);
+    }
+
+    const char *GetStringUTFChars(jstring string, jboolean *isCopy)
+    {
+        return functions->GetStringUTFChars(this, string, isCopy);
+    }
+
+    void ReleaseStringUTFChars(jstring string, const char *utf)
+    {
+        functions->ReleaseStringUTFChars(this, string, utf);
+    }
+
+    jsize GetArrayLength(jarray array)
+    {
+        return functions->GetArrayLength(this, array);
+    }
+
+    jobjectArray NewObjectArray(jsize length, jclass elementClass,
+                                jobject initialElement)
+    {
+        return functions->NewObjectArray(this, length, elementClass,
+                                         initialElement);
+    }
+
+    jobject GetObjectArrayElement(jobjectArray array, jsize index)
+    {
+        return functions->GetObjectArrayElement(this, array, index);
+    }
+
+    void SetObjectArrayElement(jobjectArray array, jsize index, jobject val)
+    {
+        functions->SetObjectArrayElement(this, array, index, val);
+    }
+
+    jbooleanArray NewBooleanArray(jsize length)
+    {
+        return functions->NewBooleanArray(this, length);
+    }
+    jbyteArray NewByteArray(jsize length)
+    {
+        return functions->NewByteArray(this, length);
+    }
+    jcharArray NewCharArray(jsize length)
+    {
+        return functions->NewCharArray(this, length);
+    }
+    jshortArray NewShortArray(jsize length)
+    {
+        return functions->NewShortArray(this, length);
+    }
+    jintArray NewIntArray(jsize length)
+    {
+        return functions->NewIntArray(this, length);
+    }
+    jlongArray NewLongArray(jsize length)
+    {
+        return functions->NewLongArray(this, length);
+    }
+    jfloatArray NewFloatArray(jsize length)
+    {
+        return functions->NewFloatArray(this, length);
+    }
+    jdoubleArray NewDoubleArray(jsize length)
+    {
+        return functions->NewDoubleArray(this, length);
+    }
+
+    jboolean *GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy)
+    {
+        return functions->GetBooleanArrayElements(this, array, isCopy);
+    }
+    jbyte *GetByteArrayElements(jbyteArray array, jboolean *isCopy)
+    {
+        return functions->GetByteArrayElements(this, array, isCopy);
+    }
+    jchar *GetCharArrayElements(jcharArray array, jboolean *isCopy)
+    {
+        return functions->GetCharArrayElements(this, array, isCopy);
+    }
+    jshort *GetShortArrayElements(jshortArray array, jboolean *isCopy)
+    {
+        return functions->GetShortArrayElements(this, array, isCopy);
+    }
+    jint *GetIntArrayElements(jintArray array, jboolean *isCopy)
+    {
+        return functions->GetIntArrayElements(this, array, isCopy);
+    }
+    jlong *GetLongArrayElements(jlongArray array, jboolean *isCopy)
+    {
+        return functions->GetLongArrayElements(this, array, isCopy);
+    }
+    jfloat *GetFloatArrayElements(jfloatArray array, jboolean *isCopy)
+    {
+        return functions->GetFloatArrayElements(this, array, isCopy);
+    }
+    jdouble *GetDoubleArrayElements(jdoubleArray array, jboolean *isCopy)
+    {
+        return functions->GetDoubleArrayElements(this, array, isCopy);
+    }
+
+    void ReleaseBooleanArrayElements(jbooleanArray array, jboolean *elems,
+                                     jint mode)
+    {
+        functions->ReleaseBooleanArrayElements(this, array, elems, mode);
+    }
+    void ReleaseByteArrayElements(jbyteArray array, jbyte *elems,
+                                  jint mode)
+    {
+        functions->ReleaseByteArrayElements(this, array, elems, mode);
+    }
+    void ReleaseCharArrayElements(jcharArray array, jchar *elems,
+                                  jint mode)
+    {
+        functions->ReleaseCharArrayElements(this, array, elems, mode);
+    }
+    void ReleaseShortArrayElements(jshortArray array, jshort *elems,
+                                   jint mode)
+    {
+        functions->ReleaseShortArrayElements(this, array, elems, mode);
+    }
+    void ReleaseIntArrayElements(jintArray array, jint *elems,
+                                 jint mode)
+    {
+        functions->ReleaseIntArrayElements(this, array, elems, mode);
+    }
+    void ReleaseLongArrayElements(jlongArray array, jlong *elems,
+                                  jint mode)
+    {
+        functions->ReleaseLongArrayElements(this, array, elems, mode);
+    }
+    void ReleaseFloatArrayElements(jfloatArray array, jfloat *elems,
+                                   jint mode)
+    {
+        functions->ReleaseFloatArrayElements(this, array, elems, mode);
+    }
+    void ReleaseDoubleArrayElements(jdoubleArray array, jdouble *elems,
+                                    jint mode)
+    {
+        functions->ReleaseDoubleArrayElements(this, array, elems, mode);
+    }
+
+    void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len,
+                               jboolean *buf)
+    {
+        functions->GetBooleanArrayRegion(this, array, start, len, buf);
+    }
+    void GetByteArrayRegion(jbyteArray array, jsize start, jsize len,
+                            jbyte *buf)
+    {
+        functions->GetByteArrayRegion(this, array, start, len, buf);
+    }
+    void GetCharArrayRegion(jcharArray array, jsize start, jsize len,
+                            jchar *buf)
+    {
+        functions->GetCharArrayRegion(this, array, start, len, buf);
+    }
+    void GetShortArrayRegion(jshortArray array, jsize start, jsize len,
+                             jshort *buf)
+    {
+        functions->GetShortArrayRegion(this, array, start, len, buf);
+    }
+    void GetIntArrayRegion(jintArray array, jsize start, jsize len,
+                           jint *buf)
+    {
+        functions->GetIntArrayRegion(this, array, start, len, buf);
+    }
+    void GetLongArrayRegion(jlongArray array, jsize start, jsize len,
+                            jlong *buf)
+    {
+        functions->GetLongArrayRegion(this, array, start, len, buf);
+    }
+    void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len,
+                             jfloat *buf)
+    {
+        functions->GetFloatArrayRegion(this, array, start, len, buf);
+    }
+    void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len,
+                              jdouble *buf)
+    {
+        functions->GetDoubleArrayRegion(this, array, start, len, buf);
+    }
+
+    void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len,
+                               const jboolean *buf)
+    {
+        functions->SetBooleanArrayRegion(this, array, start, len, buf);
+    }
+    void SetByteArrayRegion(jbyteArray array, jsize start, jsize len,
+                            const jbyte *buf)
+    {
+        functions->SetByteArrayRegion(this, array, start, len, buf);
+    }
+    void SetCharArrayRegion(jcharArray array, jsize start, jsize len,
+                            const jchar *buf)
+    {
+        functions->SetCharArrayRegion(this, array, start, len, buf);
+    }
+    void SetShortArrayRegion(jshortArray array, jsize start, jsize len,
+                             const jshort *buf)
+    {
+        functions->SetShortArrayRegion(this, array, start, len, buf);
+    }
+    void SetIntArrayRegion(jintArray array, jsize start, jsize len,
+                           const jint *buf)
+    {
+        functions->SetIntArrayRegion(this, array, start, len, buf);
+    }
+    void SetLongArrayRegion(jlongArray array, jsize start, jsize len,
+                            const jlong *buf)
+    {
+        functions->SetLongArrayRegion(this, array, start, len, buf);
+    }
+    void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len,
+                             const jfloat *buf)
+    {
+        functions->SetFloatArrayRegion(this, array, start, len, buf);
+    }
+    void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len,
+                              const jdouble *buf)
+    {
+        functions->SetDoubleArrayRegion(this, array, start, len, buf);
+    }
+
+    jint RegisterNatives(jclass clazz, const JNINativeMethod *methods,
+                         jint nMethods)
+    {
+        return functions->RegisterNatives(this, clazz, methods, nMethods);
+    }
+
+    jint UnregisterNatives(jclass clazz)
+    {
+        return functions->UnregisterNatives(this, clazz);
+    }
+
+    jint MonitorEnter(jobject obj)
+    {
+        return functions->MonitorEnter(this, obj);
+    }
+
+    jint MonitorExit(jobject obj)
+    {
+        return functions->MonitorExit(this, obj);
+    }
+
+    jint GetJavaVM(JavaVM **vm)
+    {
+        return functions->GetJavaVM(this, vm);
+    }
+
+    void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf)
+    {
+        functions->GetStringRegion(this, str, start, len, buf);
+    }
+
+    void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf)
+    {
+        return functions->GetStringUTFRegion(this, str, start, len, buf);
+    }
+
+    void *GetPrimitiveArrayCritical(jarray array, jboolean *isCopy)
+    {
+        return functions->GetPrimitiveArrayCritical(this, array, isCopy);
+    }
+
+    void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode)
+    {
+        functions->ReleasePrimitiveArrayCritical(this, array, carray, mode);
+    }
+
+    const jchar *GetStringCritical(jstring string, jboolean *isCopy)
+    {
+        return functions->GetStringCritical(this, string, isCopy);
+    }
+
+    void ReleaseStringCritical(jstring string, const jchar *carray)
+    {
+        functions->ReleaseStringCritical(this, string, carray);
+    }
+
+    jweak NewWeakGlobalRef(jobject obj)
+    {
+        return functions->NewWeakGlobalRef(this, obj);
+    }
+
+    void DeleteWeakGlobalRef(jweak obj)
+    {
+        functions->DeleteWeakGlobalRef(this, obj);
+    }
+
+    jboolean ExceptionCheck()
+    {
+        return functions->ExceptionCheck(this);
+    }
+
+    jobject NewDirectByteBuffer(void *address, jlong capacity)
+    {
+        return functions->NewDirectByteBuffer(this, address, capacity);
+    }
+
+    void *GetDirectBufferAddress(jobject buf)
+    {
+        return functions->GetDirectBufferAddress(this, buf);
+    }
+
+    jlong GetDirectBufferCapacity(jobject buf)
+    {
+        return functions->GetDirectBufferCapacity(this, buf);
+    }
+
+    /* added in JNI 1.6 */
+    jobjectRefType GetObjectRefType(jobject obj)
+    {
+        return functions->GetObjectRefType(this, obj);
+    }
+#endif /*__cplusplus*/
+};
+
+/*
+ * JNI invocation interface.
+ */
+struct JNIInvokeInterface
+{
+    void *reserved0;
+    void *reserved1;
+    void *reserved2;
+
+    jint (*DestroyJavaVM)(JavaVM *vm);
+    jint (*AttachCurrentThread)(JavaVM *vm, JNIEnv **p_env, void *thr_args);
+    jint (*DetachCurrentThread)(JavaVM *vm);
+    jint (*GetEnv)(JavaVM *vm, void **p_env, jint version);
+    jint (*AttachCurrentThreadAsDaemon)(JavaVM *vm, JNIEnv **p_env, void *thr_args);
+};
+
+/*
+ * C++ version.
+ */
+struct _JavaVM
+{
+    const struct JNIInvokeInterface *functions;
+
+#if defined(__cplusplus)
+    jint DestroyJavaVM()
+    {
+        return functions->DestroyJavaVM(this);
+    }
+    jint AttachCurrentThread(JNIEnv **p_env, void *thr_args)
+    {
+        return functions->AttachCurrentThread(this, p_env, thr_args);
+    }
+    jint DetachCurrentThread()
+    {
+        return functions->DetachCurrentThread(this);
+    }
+    jint GetEnv(void **env, jint version)
+    {
+        return functions->GetEnv(this, env, version);
+    }
+    jint AttachCurrentThreadAsDaemon(JNIEnv **p_env, void *thr_args)
+    {
+        return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args);
+    }
+#endif /*__cplusplus*/
+};
+
+struct JavaVMAttachArgs
+{
+    jint version;     /* must be >= JNI_VERSION_1_2 */
+    const char *name; /* NULL or name of thread as modified UTF-8 str */
+    jobject group;    /* global ref of a ThreadGroup object, or NULL */
+};
+typedef struct JavaVMAttachArgs JavaVMAttachArgs;
+
+/*
+ * JNI 1.2+ initialization.  (As of 1.6, the pre-1.2 structures are no
+ * longer supported.)
+ */
+typedef struct JavaVMOption
+{
+    const char *optionString;
+    void *extraInfo;
+} JavaVMOption;
+
+typedef struct JavaVMInitArgs
+{
+    jint version; /* use JNI_VERSION_1_2 or later */
+
+    jint nOptions;
+    JavaVMOption *options;
+    jboolean ignoreUnrecognized;
+} JavaVMInitArgs;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+    /*
+     * VM initialization functions.
+     *
+     * Note these are the only symbols exported for JNI by the VM.
+     */
+
+    jint JNI_GetDefaultJavaVMInitArgs(void *);
+    jint JNI_CreateJavaVM(JavaVM **, JNIEnv **, void *);
+    jint JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *);
+
+#define JNIIMPORT
+#define JNIEXPORT __attribute__((visibility("default")))
+#define JNICALL
+
+    /*
+     * Prototypes for functions exported by loadable shared libs.  These are
+     * called by JNI, not provided by JNI.
+     */
+    JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved);
+    JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved);
+
+#ifdef __cplusplus
+}
+#endif
+
+/*
+ * Manifest constants.
+ */
+#define JNI_FALSE 0
+#define JNI_TRUE 1
+
+#define JNI_VERSION_1_1 0x00010001
+#define JNI_VERSION_1_2 0x00010002
+#define JNI_VERSION_1_4 0x00010004
+#define JNI_VERSION_1_6 0x00010006
+
+#define JNI_OK (0)         /* no error */
+#define JNI_ERR (-1)       /* generic error */
+#define JNI_EDETACHED (-2) /* thread detached from the VM */
+#define JNI_EVERSION (-3)  /* JNI version error */
+#define JNI_ENOMEM (-4)    /* Out of memory */
+#define JNI_EEXIST (-5)    /* VM already created */
+#define JNI_EINVAL (-6)    /* Invalid argument */
+
+#define JNI_COMMIT 1 /* copy content, do not free buffer */
+#define JNI_ABORT 2  /* free buffer w/o copying back */
diff --git a/pkgs/jni/tool/generate_ffi_bindings.dart b/pkgs/jni/tool/generate_ffi_bindings.dart
new file mode 100644
index 0000000..35b9db2
--- /dev/null
+++ b/pkgs/jni/tool/generate_ffi_bindings.dart
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This script generates all FFIGEN-based bindings we require to use JNI, which
+// includes some C wrappers over `JNIEnv` type and some Dart extension methods.
+
+import 'dart:io';
+
+import 'package:logging/logging.dart';
+import 'package:args/args.dart';
+
+import 'wrapper_generators/logging.dart';
+import 'wrapper_generators/generate_c_extensions.dart';
+import 'wrapper_generators/generate_dart_extensions.dart';
+
+import 'package:ffigen/ffigen.dart' as ffigen;
+
+void main(List<String> args) {
+  final levels = Map.fromEntries(
+    Level.LEVELS.map((l) => MapEntry(l.name.toLowerCase(), l)),
+  );
+  final argParser = ArgParser()
+    ..addOption(
+      'verbose',
+      defaultsTo: 'severe',
+      help: 'set ffigen log verbosity',
+      allowed: levels.keys,
+    )
+    ..addFlag(
+      'help',
+      negatable: false,
+      abbr: 'h',
+      defaultsTo: false,
+      help: 'display this help message',
+    );
+
+  final argResults = argParser.parse(args);
+
+  if (argResults['help']) {
+    stderr.writeln('Generates FFI bindings required for package:jni');
+    stderr.writeln(argParser.usage);
+    exitCode = 1;
+    return;
+  }
+
+  hierarchicalLoggingEnabled = true;
+  Logger.root.level = levels[argResults['verbose']]!;
+  logger.level = Level.INFO;
+  Logger.root.onRecord.listen((record) {
+    stderr.writeln('${record.level.name}: ${record.message}');
+  });
+
+  logger.info("Generating C wrappers");
+  final minimalConfig = ffigen.Config.fromFile(File('ffigen_exts.yaml'));
+  final minimalLibrary = ffigen.parse(minimalConfig);
+  generateCWrappers(minimalLibrary);
+
+  logger.info("Generating FFI bindings for package:jni");
+
+  final config = ffigen.Config.fromFile(File('ffigen.yaml'));
+  final library = ffigen.parse(config);
+  final outputFile = File(config.output);
+  library.generateFile(outputFile);
+
+  logger.info("Generating Dart extensions");
+  generateDartExtensions(library);
+}
diff --git a/pkgs/jni/tool/generate_ide_files.dart b/pkgs/jni/tool/generate_ide_files.dart
new file mode 100644
index 0000000..ab44ad8
--- /dev/null
+++ b/pkgs/jni/tool/generate_ide_files.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:args/args.dart';
+
+const makefilesGenerator = 'make';
+const ninjaGenerator = 'ninja';
+
+const cmakeGeneratorNames = {
+  makefilesGenerator: 'Unix Makefiles',
+  ninjaGenerator: 'Ninja',
+};
+
+void runCommand(String exec, List<String> args, String workingDirectory) {
+  stderr.writeln('+ $exec ${args.join(" ")}');
+  final process =
+      Process.runSync(exec, args, workingDirectory: workingDirectory);
+  if (process.exitCode != 0) {
+    stdout.writeln(process.stdout);
+    stderr.writeln(process.stderr);
+    throw "command failed with exit code ${process.exitCode}";
+  }
+}
+
+void main(List<String> arguments) {
+  final argParser = ArgParser()
+    ..addOption(
+      "generator",
+      abbr: 'G',
+      help: 'Generator to pass to CMake. (Either "ninja" or "make").',
+      allowed: [ninjaGenerator, makefilesGenerator],
+      defaultsTo: Platform.isWindows ? ninjaGenerator : makefilesGenerator,
+    )
+    ..addFlag('help', abbr: 'h', help: 'Show this usage information.');
+  final argResults = argParser.parse(arguments);
+  if (argResults.rest.isNotEmpty || argResults['help']) {
+    stderr.writeln('This script generates compile_commands.json for '
+        'C source files in src/');
+    stderr.writeln(argParser.usage);
+    exitCode = 1;
+    return;
+  }
+  final generator = cmakeGeneratorNames[argResults['generator']];
+  final tempDir = Directory.current.createTempSync("clangd_setup_temp_");
+  final src = Directory.current.uri.resolve("src/");
+  try {
+    runCommand(
+        "cmake",
+        [
+          "-DCMAKE_EXPORT_COMPILE_COMMANDS=1",
+          src.toFilePath(),
+          "-G",
+          generator!,
+        ],
+        tempDir.path);
+    final createdFile = tempDir.uri.resolve("compile_commands.json");
+    final target = src.resolve("compile_commands.json");
+    File.fromUri(createdFile).renameSync(target.toFilePath());
+  } finally {
+    tempDir.deleteSync(recursive: true);
+  }
+}
diff --git a/pkgs/jni/tool/wrapper_generators/ffigen_util.dart b/pkgs/jni/tool/wrapper_generators/ffigen_util.dart
new file mode 100644
index 0000000..2b234f6
--- /dev/null
+++ b/pkgs/jni/tool/wrapper_generators/ffigen_util.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:ffigen/src/code_generator/writer.dart';
+import 'package:ffigen/src/code_generator.dart';
+
+final dummyWriter = Writer(
+  lookUpBindings: [],
+  ffiNativeBindings: [],
+  noLookUpBindings: [],
+  className: 'unused',
+);
+
+/// Find compound having [name] in [library].
+Compound findCompound(library, String name) {
+  return library.bindings.firstWhere((element) => element.name == name)
+      as Compound;
+}
+
+const preamble = '''
+// Auto generated file. Do not edit.
+
+// This is generated from JNI header in Android NDK. License for the same is
+// provided below.
+
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.
+ */
+
+/*
+ * JNI specification, as defined by Sun:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
+ *
+ * Everything here is expected to be VM-neutral.
+ */
+
+''';
diff --git a/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart b/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart
new file mode 100644
index 0000000..f1e15b2
--- /dev/null
+++ b/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart
@@ -0,0 +1,297 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:ffigen/src/code_generator.dart';
+
+import 'ffigen_util.dart';
+import 'logging.dart';
+
+class Paths {
+  static final currentDir = Directory.current.uri;
+  static final src = currentDir.resolve("src/");
+  static final thirdParty = src.resolve("third_party/");
+  static final globalJniEnvH = thirdParty.resolve("global_jni_env.h");
+  static final globalJniEnvC = thirdParty.resolve("global_jni_env.c");
+  static final bindingsDir = currentDir.resolve("lib/src/third_party/");
+  static final envExtensions = bindingsDir.resolve("env_extensions.dart");
+}
+
+/// Name of variable used in wrappers to hold the result.
+const resultVar = '_result';
+
+/// Name of variable used in wrappers to hold the exception.
+const errorVar = '_exception';
+
+/// Name of JNIEnv struct definition in JNI headers.
+const envType = 'JNINativeInterface';
+
+/// Name of wrapper to JNIEnv
+const wrapperName = 'GlobalJniEnvStruct';
+
+const wrapperIncludes = '''
+#include "global_jni_env.h"
+
+''';
+
+const wrapperDeclIncludes = '''
+#include <stdint.h>
+#include "../dartjni.h"
+
+''';
+
+const wrapperGetter = '''
+FFI_PLUGIN_EXPORT
+$wrapperName* GetGlobalEnv() {
+  if (jni->jvm == NULL) {
+    return NULL;
+  }
+  return &globalJniEnv;
+}
+''';
+
+const wrapperGetterDecl = '''
+FFI_PLUGIN_EXPORT $wrapperName* GetGlobalEnv();
+''';
+
+/// Get C name of a type from its ffigen representation.
+String getCType(Type type) {
+  if (type is PointerType) {
+    return '${getCType(type.child)}*';
+  }
+  final cType = type.getCType(dummyWriter);
+  const specialCaseMappings = {
+    'JNIEnv1': 'JNIEnv',
+    'ffi.Char': 'char',
+    'ffi.Void': 'void',
+    'ffi.Int': 'int',
+    'ffi.Int32': 'int32_t',
+  };
+  return specialCaseMappings[cType] ?? cType;
+}
+
+/// Get type of wrapping function for a JNIEnv function.
+FunctionType getGlobalJniEnvFunctionType(FunctionType ft) {
+  return FunctionType(
+    returnType: ft.returnType,
+    parameters: ft.parameters.sublist(1),
+  );
+}
+
+// Returns declaration of function field in GlobalJniEnv struct
+String getFunctionFieldDecl(
+  Member field,
+) {
+  final fieldType = field.type;
+  if (fieldType is PointerType && fieldType.child is NativeFunc) {
+    final nativeFunc = fieldType.child as NativeFunc;
+    final functionType = getGlobalJniEnvFunctionType(nativeFunc.type);
+    final resultWrapper = getResultWrapper(getCType(functionType.returnType));
+    final name = field.name;
+    final params = functionType.parameters
+        .map((param) => '${getCType(param.type)} ${param.name}')
+        .join(', ');
+    return ('${resultWrapper.returnType} (*$name)($params);');
+  } else {
+    return 'void* ${field.name};';
+  }
+}
+
+String getWrapperFuncName(Member field) {
+  return 'globalEnv_${field.name}';
+}
+
+class ResultWrapper {
+  String returnType, onResult, onError;
+  ResultWrapper.withResultAndError(
+      this.returnType, this.onResult, this.onError);
+  ResultWrapper.unionType(
+    String returnType,
+    String defaultValue,
+  ) : this.withResultAndError(
+          returnType,
+          '($returnType){.value = $resultVar, .exception = NULL}',
+          '($returnType){.value = $defaultValue, .exception = $errorVar}',
+        );
+  ResultWrapper.forJValueField(String fieldChar)
+      : this.withResultAndError(
+          'JniResult',
+          '(JniResult){.value = {.$fieldChar = $resultVar}, .exception = NULL}',
+          '(JniResult){.value = {.j = 0}, .exception = $errorVar}',
+        );
+}
+
+ResultWrapper getResultWrapper(String returnType) {
+  if (returnType.endsWith("*")) {
+    return ResultWrapper.unionType('JniPointerResult', 'NULL');
+  }
+
+  final jobjectWrapper = ResultWrapper.forJValueField('l');
+  if (returnType.endsWith('Array')) {
+    return jobjectWrapper;
+  }
+
+  const jfields = {
+    'jboolean': 'z',
+    'jbyte': 'b',
+    'jshort': 's',
+    'jchar': 'c',
+    'jint': 'i',
+    'jsize': 'i', // jsize is an alias to jint
+    'jfloat': 'f',
+    'jlong': 'j',
+    'jdouble': 'd',
+    'jobject': 'l',
+    'jweak': 'l',
+    'jarray': 'l',
+    'jstring': 'l',
+    'jthrowable': 'l',
+  };
+
+  switch (returnType) {
+    case 'void':
+      return ResultWrapper.withResultAndError(
+        'jthrowable',
+        'NULL',
+        errorVar,
+      );
+    case 'jmethodID':
+    case 'jfieldID':
+      return ResultWrapper.unionType('JniPointerResult', 'NULL');
+    case 'jclass':
+      return ResultWrapper.unionType('JniClassLookupResult', 'NULL');
+    case 'int32_t':
+      return ResultWrapper.forJValueField('i');
+    default:
+      if (jfields.containsKey(returnType)) {
+        return ResultWrapper.forJValueField(jfields[returnType]!);
+      }
+      throw 'Unknown type $returnType for return type';
+  }
+}
+
+bool isJRefType(String type) {
+  // No need to include jweak here, its only returned by ref-related functions.
+  const refTypes = {
+    'jclass',
+    'jobject',
+    'jstring',
+    'jthrowable',
+    'jarray',
+    'jweak'
+  };
+  return (type.startsWith('j') && type.endsWith('Array')) ||
+      refTypes.contains(type);
+}
+
+const refFunctions = {
+  'NewGlobalRef',
+  'DeleteGlobalRef',
+  'NewLocalRef',
+  'DeleteLocalRef',
+  'NewWeakGlobalRef',
+  'DeleteWeakGlobalRef',
+};
+
+/// These return const ptrs so the assignment statement needs to be
+/// adjusted in the wrapper.
+const constBufferReturningFunctions = {
+  'GetStringChars',
+  'GetStringUTFChars',
+  'GetStringCritical',
+};
+
+/// Methods which do not throw exceptions, and thus not need to be checked
+const _noCheckException = {
+  'GetVersion',
+  'GetStringCritical',
+  'ExceptionClear',
+  'ExceptionDescribe',
+};
+
+String? getWrapperFunc(Member field) {
+  final fieldType = field.type;
+  if (fieldType is PointerType && fieldType.child is NativeFunc) {
+    final functionType = (fieldType.child as NativeFunc).type;
+    if (functionType.parameters.first.name.isEmpty) {
+      return null;
+    }
+
+    final outerFunctionType = getGlobalJniEnvFunctionType(functionType);
+    final wrapperName = getWrapperFuncName(field);
+    final returnType = getCType(outerFunctionType.returnType);
+    final params = outerFunctionType.parameters
+        .map((param) => '${getCType(param.type)} ${param.name}')
+        .join(', ');
+    var returnCapture = returnType == 'void' ? '' : '$returnType $resultVar =';
+    if (constBufferReturningFunctions.contains(field.name)) {
+      returnCapture = 'const $returnCapture';
+    }
+    final callParams = [
+      'jniEnv',
+      ...(outerFunctionType.parameters.map((param) => param.name).toList())
+    ].join(', ');
+    final resultWrapper = getResultWrapper(returnType);
+
+    var convertRef = '';
+    if (isJRefType(returnType) && !refFunctions.contains(field.name)) {
+      convertRef = '  $resultVar = to_global_ref($resultVar);\n';
+    }
+    final exceptionCheck = _noCheckException.contains(field.name)
+        ? ''
+        : '  jthrowable $errorVar = check_exception();\n'
+            '  if ($errorVar != NULL) {\n'
+            '    return ${resultWrapper.onError};\n'
+            '  }\n';
+    return '${resultWrapper.returnType} $wrapperName($params) {\n'
+        '  attach_thread();\n'
+        '  $returnCapture (*jniEnv)->${field.name}($callParams);\n'
+        '$exceptionCheck'
+        '$convertRef'
+        '  return ${resultWrapper.onResult};\n'
+        '}\n';
+  }
+  return null;
+}
+
+void writeGlobalJniEnvWrapper(Library library) {
+  final jniEnvType = findCompound(library, envType);
+
+  final fieldDecls = jniEnvType.members.map(getFunctionFieldDecl).join('\n');
+  final structDecl =
+      'typedef struct $wrapperName {\n$fieldDecls\n} $wrapperName;\n';
+  File.fromUri(Paths.globalJniEnvH).writeAsStringSync(
+      '$preamble$wrapperDeclIncludes$structDecl$wrapperGetterDecl');
+
+  final functionWrappers = StringBuffer();
+  final structInst = StringBuffer('$wrapperName globalJniEnv = {\n');
+  for (final member in jniEnvType.members) {
+    final wrapper = getWrapperFunc(member);
+    if (wrapper == null) {
+      structInst.write('.${member.name} = NULL,\n');
+    } else {
+      structInst.write('.${member.name} = ${getWrapperFuncName(member)},\n');
+      functionWrappers.write('$wrapper\n');
+    }
+  }
+  structInst.write('};');
+  File.fromUri(Paths.globalJniEnvC).writeAsStringSync(
+      '$preamble$wrapperIncludes$functionWrappers$structInst$wrapperGetter');
+}
+
+void executeClangFormat(List<Uri> files) {
+  final paths = files.map((u) => u.toFilePath()).toList();
+  logger.info('execute clang-format -i ${paths.join(" ")}');
+  final format = Process.runSync('clang-format', ['-i', ...paths]);
+  if (format.exitCode != 0) {
+    stderr.writeln('clang-format exited with ${format.exitCode}');
+    stderr.writeln(format.stderr);
+  }
+}
+
+void generateCWrappers(Library minimalLibrary) {
+  writeGlobalJniEnvWrapper(minimalLibrary);
+  executeClangFormat([Paths.globalJniEnvC, Paths.globalJniEnvH]);
+}
diff --git a/pkgs/jni/tool/wrapper_generators/generate_dart_extensions.dart b/pkgs/jni/tool/wrapper_generators/generate_dart_extensions.dart
new file mode 100644
index 0000000..c78b883
--- /dev/null
+++ b/pkgs/jni/tool/wrapper_generators/generate_dart_extensions.dart
@@ -0,0 +1,253 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:collection/collection.dart';
+import 'package:ffigen/src/code_generator.dart';
+
+import 'ffigen_util.dart';
+import 'logging.dart';
+
+class Paths {
+  static final currentDir = Directory.current.uri;
+  static final bindingsDir = currentDir.resolve("lib/src/third_party/");
+  // Contains extensions for our C wrapper types.
+  static final globalEnvExts =
+      bindingsDir.resolve("global_env_extensions.dart");
+  // Contains extensions for JNI's struct types.
+  static final localEnvExts =
+      bindingsDir.resolve("jnienv_javavm_extensions.dart");
+}
+
+const writeLocalEnvExtensions = false;
+
+void executeDartFormat(List<Uri> files) {
+  final paths = files.map((u) => u.toFilePath()).toList();
+  logger.info('execute dart format ${paths.join(" ")}');
+  final format = Process.runSync('dart', ['format', ...paths]);
+  if (format.exitCode != 0) {
+    logger.severe('dart format exited with ${format.exitCode}');
+    stderr.writeln(format.stderr);
+  }
+}
+
+const globalEnvType = 'GlobalJniEnvStruct';
+const localEnvType = 'JNINativeInterface';
+const jvmType = 'JNIInvokeInterface';
+
+String getCheckedGetter(Type returnType) {
+  if (returnType is PointerType) {
+    final child = returnType.child.getCType(dummyWriter);
+    return 'getPointer<$child>()';
+  }
+  final cType = returnType.getCType(dummyWriter);
+  if (cType.endsWith("ArrayPtr")) {
+    return 'object';
+  }
+  const mappings = {
+    'JBooleanMarker': 'boolean',
+    'JByteMarker': 'byte',
+    'JShortMarker': 'short',
+    'JCharMarker': 'char',
+    'JIntMarker': 'integer',
+    'JSizeMarker': 'integer', // jsize is aliased to jint
+    'JLongMarker': 'long',
+    'JFloatMarker': 'float',
+    'JDoubleMarker': 'doubleFloat',
+    'JObjectPtr': 'object',
+    'JThrowablePtr': 'object',
+    'JStringPtr': 'object',
+    'JClassPtr': 'value',
+    'JFieldIDPtr': 'fieldID',
+    'JMethodIDPtr': 'methodID',
+    'ffi.Int32': 'integer',
+    'ffi.Void': 'check()',
+    'JWeakPtr': 'object',
+  };
+  if (mappings.containsKey(cType)) {
+    return mappings[cType]!;
+  }
+  throw 'Unknown return type: $cType';
+}
+
+String? getGlobalEnvExtensionFunction(Member field, Type? checkedReturnType) {
+  final fieldType = field.type;
+  if (fieldType is PointerType && fieldType.child is NativeFunc) {
+    final nativeFunc = fieldType.child as NativeFunc;
+    final functionType = nativeFunc.type;
+    final returnType = functionType.returnType;
+    var params = functionType.parameters;
+    if (params.first.name.isEmpty) {
+      return null;
+    }
+
+    // Remove env parameter
+    params = params.sublist(1);
+
+    final signature = params
+        .map((p) => '${p.type.getDartType(dummyWriter)} ${p.name}')
+        .join(', ');
+
+    final dartType =
+        FunctionType(returnType: checkedReturnType!, parameters: params)
+            .getDartType(dummyWriter);
+    final callArgs = params.map((p) => p.name).join(', ');
+    final checkedGetter = getCheckedGetter(returnType);
+    var returns = returnType.getDartType(dummyWriter);
+    if (checkedGetter == 'boolean') {
+      returns = 'bool';
+    }
+    return '''
+late final _${field.name} =
+  ptr.ref.${field.name}.asFunction<$dartType>();\n
+$returns ${field.name}($signature) =>
+  _${field.name}($callArgs).$checkedGetter;
+
+''';
+  }
+  return null;
+}
+
+void writeDartExtensions(Library library) {
+  const header = '''
+// ignore_for_file: non_constant_identifier_names
+// coverage:ignore-file
+
+import "dart:ffi" as ffi;\n
+import "jni_bindings_generated.dart";
+
+''';
+  const importAccessors = '''
+import "../accessors.dart";
+
+''';
+
+  final globalEnvExtension = getGlobalEnvExtension(library);
+  final accessorExtension = getFunctionPointerExtension(
+    library,
+    'JniAccessorsStruct',
+    'JniAccessors',
+  );
+  File.fromUri(Paths.globalEnvExts).writeAsStringSync(preamble +
+      header +
+      importAccessors +
+      globalEnvExtension +
+      accessorExtension);
+  final localEnvExtsFile = File.fromUri(Paths.localEnvExts);
+  if (localEnvExtsFile.existsSync()) {
+    localEnvExtsFile.deleteSync();
+  }
+  if (!writeLocalEnvExtensions) {
+    return;
+  }
+  final envExtension = getFunctionPointerExtension(
+    library,
+    'JniEnv',
+    'LocalJniEnv',
+    indirect: true,
+    implicitThis: true,
+  );
+  final jvmExtension = getFunctionPointerExtension(
+    library,
+    'JavaVM',
+    'JniJavaVM',
+    indirect: true,
+    implicitThis: true,
+  );
+  localEnvExtsFile
+      .writeAsStringSync(preamble + header + envExtension + jvmExtension);
+}
+
+String getGlobalEnvExtension(
+  Library library,
+) {
+  final env = findCompound(library, localEnvType);
+  final globalEnv = findCompound(library, globalEnvType);
+  final checkedReturnTypes = {};
+  for (var field in globalEnv.members) {
+    final fieldType = field.type;
+    if (fieldType is PointerType && fieldType.child is NativeFunc) {
+      checkedReturnTypes[field.name] =
+          (fieldType.child as NativeFunc).type.returnType;
+    }
+  }
+  final extensionFunctions = env.members
+      .map((m) => getGlobalEnvExtensionFunction(m, checkedReturnTypes[m.name]))
+      .whereNotNull()
+      .join('\n');
+  return '''
+/// Wraps over Pointer<GlobalJniEnvStruct> and exposes function pointer fields
+/// as methods.
+class GlobalJniEnv {
+  final ffi.Pointer<GlobalJniEnvStruct> ptr;
+  GlobalJniEnv(this.ptr);
+  $extensionFunctions
+}
+''';
+}
+
+String? getFunctionPointerExtensionFunction(Member field,
+    {bool indirect = false, bool implicitThis = false}) {
+  final fieldType = field.type;
+  if (fieldType is PointerType && fieldType.child is NativeFunc) {
+    final nativeFunc = fieldType.child as NativeFunc;
+    final functionType = nativeFunc.type;
+    final returnType = functionType.returnType;
+    final params = functionType.parameters;
+    if (params.first.name.isEmpty) {
+      return null;
+    }
+
+    final visibleParams = implicitThis ? params.sublist(1) : params;
+
+    final signature = visibleParams
+        .map((p) => '${p.type.getDartType(dummyWriter)} ${p.name}')
+        .join(', ');
+
+    final dartType = FunctionType(returnType: returnType, parameters: params)
+        .getDartType(dummyWriter);
+    final callArgs = [
+      if (implicitThis) 'ptr',
+      ...visibleParams.map((p) => p.name)
+    ].join(', ');
+    final returns = returnType.getDartType(dummyWriter);
+    final dereference = indirect ? 'value.ref' : 'ref';
+    return '''
+late final _${field.name} =
+  ptr.$dereference.${field.name}.asFunction<$dartType>();
+$returns ${field.name}($signature) => _${field.name}($callArgs);
+
+''';
+  }
+  return null;
+}
+
+String getFunctionPointerExtension(
+    Library library, String type, String wrapperClassName,
+    {bool indirect = false, bool implicitThis = false}) {
+  final typeBinding =
+      library.bindings.firstWhere((b) => b.name == type) as Type;
+  final compound = typeBinding.typealiasType.baseType as Compound;
+  final extensionFunctions = compound.members
+      .map((f) => getFunctionPointerExtensionFunction(f,
+          indirect: indirect, implicitThis: implicitThis))
+      .whereNotNull()
+      .join('\n');
+  return '''
+/// Wraps over the function pointers in $type and exposes them as methods.
+class $wrapperClassName {
+  final ffi.Pointer<$type> ptr;
+  $wrapperClassName(this.ptr);
+
+  $extensionFunctions
+}
+
+''';
+}
+
+void generateDartExtensions(Library library) {
+  writeDartExtensions(library);
+  executeDartFormat([Paths.globalEnvExts, Paths.localEnvExts]);
+}
diff --git a/pkgs/jni/tool/wrapper_generators/logging.dart b/pkgs/jni/tool/wrapper_generators/logging.dart
new file mode 100644
index 0000000..386f5c6
--- /dev/null
+++ b/pkgs/jni/tool/wrapper_generators/logging.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:logging/logging.dart';
+
+final logger = Logger('ffi_wrappers_generator');
diff --git a/pkgs/jni/windows/.gitignore b/pkgs/jni/windows/.gitignore
new file mode 100644
index 0000000..b3eb2be
--- /dev/null
+++ b/pkgs/jni/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/pkgs/jni/windows/CMakeLists.txt b/pkgs/jni/windows/CMakeLists.txt
new file mode 100644
index 0000000..67bbdc6
--- /dev/null
+++ b/pkgs/jni/windows/CMakeLists.txt
@@ -0,0 +1,23 @@
+# The Flutter tooling requires that developers have a version of Visual Studio
+# installed that includes CMake 3.14 or later. You should not increase this
+# version, as doing so will cause the plugin to fail to compile for some
+# customers of the plugin.
+cmake_minimum_required(VERSION 3.14)
+
+# Project-level configuration.
+set(PROJECT_NAME "jni")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+# Invoke the build for native code shared with the other target platforms.
+# This can be changed to accomodate different builds.
+add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
+
+# List of absolute paths to libraries that should be bundled with the plugin.
+# This list could contain prebuilt libraries, or libraries created by an
+# external build triggered from this build file.
+set(jni_bundled_libraries
+  # Defined in ../src/CMakeLists.txt.
+  # This can be changed to accomodate different builds.
+  $<TARGET_FILE:jni>
+  PARENT_SCOPE
+)
diff --git a/pkgs/jnigen/.gitignore b/pkgs/jnigen/.gitignore
new file mode 100644
index 0000000..a63fd72
--- /dev/null
+++ b/pkgs/jnigen/.gitignore
@@ -0,0 +1,48 @@
+# See https://www.dartlang.org/guides/libraries/private-files
+
+# Files and directories created by pub.
+.dart_tool/
+.packages
+build/
+pubspec.lock
+.flutter-plugins
+.flutter-plugins-dependencies
+
+# created by maven
+/target/
+
+# Directory created by dartdoc.
+doc/api/
+
+# Avoid committing generated Javascript files:
+*.dart.js
+*.info.json      # Produced by the --dump-info flag.
+*.js             # When generated by dart2js. Don't specify *.js if your
+                 # project includes source files written in JavaScript.
+*.js_
+*.js.deps
+*.js.map
+
+# IDE and debugger files.
+.clangd
+.gdb_history
+.history
+.vscode
+compile_commands.json
+
+# IntelliJ
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# Mac
+.DS_Store
+
+# Vim
+.*.swp
+
+## Downloaded files and build artifacts
+*.jar
+*.class
+*.exe
diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md
new file mode 100644
index 0000000..0056f41
--- /dev/null
+++ b/pkgs/jnigen/CHANGELOG.md
@@ -0,0 +1,79 @@
+## 0.8.0-wip
+
+- **Breaking Change**: The generated impl class for interfaces is now an
+  `interface`.
+
+## 0.7.0
+
+- **Breaking Change** ([#387](https://github.com/dart-lang/jnigen/issues/387)):
+  Added `JBuffer` and `JByteBuffer` classes as default classes for
+  `java.nio.Buffer` and `java.nio.ByteBuffer` respectively.
+- **Breaking Change**: Made the type classes `final`.
+- Added `ignore_for_file: lines_longer_than_80_chars` to the generated file
+  preamble.
+- Added an explicit cast in generated `<Interface>.implement` code to allow
+  `dart analyze` to pass when `strict-casts` is set.
+
+## 0.6.0
+
+- **Breaking Change** ([#131](https://github.com/dart-lang/jnigen/issues/131)):
+  Renamed `delete*` to `release*`.
+- **Breaking Change** ([#354](https://github.com/dart-lang/jnigen/issues/354)):
+  Renamed constructors from `ctor1`, `ctor2`, ... to `new1`, `new2`, ...
+- **Breaking Change**: Specifying a class always pulls in nested classes by
+  default. If a nested class is specified in config, it will be an error.
+- **Breaking Change**: Removed `suspend_fun_to_async` flag from the config. It's
+  now happening by default since we read the Kotlin's metadata and reliably
+  identify the `suspend fun`s.
+- Fixed a bug where the nested classes would be generated incorrectly depending
+  on the backend used for generation.
+- Fixed a bug where ASM backend would produce the incorrect parent for
+  multi-level nested classes.
+- Fixed a bug where the backends would produce different descriptors for the
+  same method.
+- Added `enable_experiment` option to config.
+- Created an experiment called `interface_implementation` which creates a
+  `.implement` method for interfaces, so you can implement them using Dart.
+- Save all `jnigen` logs to a file in `.dart_tool/jnigen/logs/`. This is useful
+  for debugging.
+
+## 0.5.0
+
+- **Breaking Change** ([#72](https://github.com/dart-lang/jnigen/issues/72)):
+  Removed support for `importMap` in favor of the newly added interop mechanism
+  with importing yaml files.
+- **Breaking Change** ([#72](https://github.com/dart-lang/jnigen/issues/72)):
+  `java.util.Set`, `java.util.Map`, `java.util.List`, `java.util.Iterator` and
+  the boxed types like `java.lang.Integer`, `java.lang.Double`, ... will be
+  generated as their corresponding classes in `package:jni`.
+- Strings now use UTF16.
+
+## 0.4.0
+
+- **Breaking Change** ([#145](https://github.com/dart-lang/jnigen/issues/145)):
+  Type arguments are now named instead of positional.
+- Type parameters can now be inferred when possible.
+- Fixed a bug where passing a `long` argument truncated it to `int` in pure dart
+  bindings.
+- Removed array extensions from the generated code.
+- Added the ability to use source dependencies from Gradle.
+- Fixed an issue with the field setter.
+- Fixed an issue where exceptions were not properly thrown in pure Dart
+  bindings.
+
+## 0.3.0
+
+- Added the option to convert Kotlin `suspend fun` to Dart async methods. Add
+  `suspend_fun_to_async: true` to `jnigen.yaml`.
+
+## 0.2.0
+
+- Support generating bindings for generics.
+
+## 0.1.1
+
+- Windows support for running tests and examples on development machines.
+
+## 0.1.0
+
+- Initial version: Basic bindings generation, maven and android utilities
diff --git a/pkgs/jnigen/LICENSE b/pkgs/jnigen/LICENSE
new file mode 100644
index 0000000..f0604ef
--- /dev/null
+++ b/pkgs/jnigen/LICENSE
@@ -0,0 +1,32 @@
+Copyright 2022, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google LLC nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Code generated by JNIgen is owned by the owner of the input file used
+when generating it. This code is not standalone and requires a
+support library to be linked with it. This support library is itself
+covered by the above license.
diff --git a/pkgs/jnigen/README.md b/pkgs/jnigen/README.md
new file mode 100644
index 0000000..eaffeaa
--- /dev/null
+++ b/pkgs/jnigen/README.md
@@ -0,0 +1,285 @@
+[![Build Status](https://github.com/dart-lang/jnigen/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/jnigen/actions?query=workflow%3A%22Dart+CI%22+branch%3Amain)
+[![Coverage Status](https://coveralls.io/repos/github/dart-lang/jnigen/badge.svg?branch=main)](https://coveralls.io/github/dart-lang/jnigen?branch=main)
+
+## Introduction
+Experimental bindings generator for Java bindings through dart:ffi and JNI.
+
+`jnigen` scans compiled JAR files or Java source code to generate a description of the API, then uses it to generate Dart annd C bindings. The Dart bindings call the C bindings, which in-turn call the Java functions through JNI. Shared functionality and base classes are provided through the support library, `package:jni`.
+
+The configuration for binding generation is usually provided through YAML.
+
+Three configuration details are needed to generate the bindings. Everything else is optional:
+
+* _Inputs_: input can be Java source files (`source_path`), or compiled classes / JARs (`class_path`). Some maven / gradle based tooling is also provided to simplify obtaining dependencies.
+
+* _Outputs_: Output can be generated in package-structured (one file per class) or single file bindings. Target path to write C and Dart bindings needs to be specified.
+
+* _Classes_: Specify which classes or packages you need bindings for. Specifying a package includes all classes inside it recursively.
+
+Check out the [examples](jnigen/example/) to see some sample configurations.
+
+C code is always generated into a directory with it's own build configuration. It's built as a separate dynamic library.
+
+Lastly, [dart_only bindings](#pure-dart-bindings) mode is also available as a proof-of-concept. It does not need intermediate C bindings, only a dependency on the support library `package:jni`.
+
+## Example
+It's possible to generate bindings for JAR libraries, or Java source files.
+
+Here's a simple example Java file, in a Flutter Android app.
+
+```java
+package com.example.in_app_java;
+
+import android.app.Activity;
+import android.widget.Toast;
+import androidx.annotation.Keep;
+
+@Keep
+public abstract class AndroidUtils {
+  // Hide constructor
+  private AndroidUtils() {}
+
+  public static void showToast(Activity mainActivity, CharSequence text, int duration) {
+    mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, text, duration).show());
+  }
+}
+```
+
+This produces the following boilerplate:
+
+#### Dart Bindings:
+
+```dart
+/// Some boilerplate is omitted for clarity.
+final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
+    ProtectedJniExtensions.initGeneratedLibrary("android_utils");
+
+/// from: com.example.in_app_java.AndroidUtils
+class AndroidUtils extends jni.JObject {
+  AndroidUtils.fromRef(ffi.Pointer<ffi.Void> ref) : super.fromRef(ref);
+
+  static final _showToast = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>, ffi.Int32)>>("AndroidUtils__showToast")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: static public void showToast(android.app.Activity mainActivity, java.lang.CharSequence text, int duration)
+  static void showToast(
+          jni.JObject mainActivity, jni.JObject text, int duration) =>
+      _showToast(mainActivity.reference, text.reference, duration).check();
+}
+```
+
+#### C Bindings:
+
+```c
+// Some boilerplate is omitted for clarity.
+
+// com.example.in_app_java.AndroidUtils
+jclass _c_AndroidUtils = NULL;
+
+jmethodID _m_AndroidUtils__showToast = NULL;
+FFI_PLUGIN_EXPORT
+JniResult AndroidUtils__showToast(jobject mainActivity,
+                                  jobject text,
+                                  int32_t duration) {
+  load_env();
+  load_class_gr(&_c_AndroidUtils, "com/example/in_app_java/AndroidUtils");
+  if (_c_AndroidUtils == NULL)
+    return (JniResult){.result = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_AndroidUtils, &_m_AndroidUtils__showToast, "showToast",
+                     "(Landroid/app/Activity;Ljava/lang/CharSequence;I)V");
+  if (_m_AndroidUtils__showToast == NULL)
+    return (JniResult){.result = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_AndroidUtils,
+                                  _m_AndroidUtils__showToast, mainActivity,
+                                  text, duration);
+  return (JniResult){.result = {.j = 0}, .exception = check_exception()};
+}
+```
+
+The YAML configuration used to generate the above code looks like this:
+
+```yaml
+android_sdk_config:
+  add_gradle_deps: true
+
+output:
+  c:
+    library_name: android_utils
+    path: src/android_utils/
+  dart:
+    path: lib/android_utils.dart
+    structure: single_file
+
+source_path:
+  - 'android/app/src/main/java'
+classes:
+  - 'com.example.in_app_java.AndroidUtils'
+```
+
+The complete example can be found in [jnigen/example/in_app_java](jnigen/example/in_app_java), which adds few more classes to demonstrate using classes from gradle JAR and source dependencies.
+
+## Supported platforms
+| Platform | Dart Standalone | Flutter       |
+| -------- | --------------- | ------------- |
+| Android  | n/a             | Supported     |
+| Linux    | Supported       | Supported     |
+| Windows  | Supported       | Supported     |
+| MacOS    | Supported       | Not Yet       |
+
+On Android, the flutter application runs embedded in Android JVM. On other platforms, a JVM needs to be explicitly spawned using `Jni.spawn`. `package:jni` provides the infrastructure for initializing and managing the JNI on both Android and Non-Android platforms.
+
+## Java features support
+Currently basic features of the Java language are supported in the bindings. Each Java class is mapped to a Dart class. Bindings are generated for methods, constructors and fields. Exceptions thrown in Java are rethrown in Dart with stack trace from Java.
+
+More advanced features such as callbacks are not supported yet. Support for these features is tracked in the [issue tracker](https://github.com/dart-lang/jnigen/issues).
+
+## Note on Dart (standalone) target
+`package:jni` is an FFI plugin containing native code, and any bindings generated from jnigen contains native code too.
+
+On Flutter targets, native libraries are built automatically and bundled. On standalone platforms, no such infrastructure exists yet. As a stopgap solution, running `dart run jni:setup` in a target directory builds all JNI native dependencies of the package into `build/jni_libs`. 
+
+By default `jni:setup` goes through pubspec configuration and builds all JNI dependencies of the project. It can be overridden to build a custom directory using `-s` switch, which can be useful when output configuration for C bindings does not follow standard FFI plugin layout.
+
+The build directory has to be passed to `Jni.spawn` call. It's assumed that all dependencies are built into the same target directory, so that once JNI is initialized, generated bindings can load their respective C libraries automatically.
+
+## Requirements
+### SDK
+Flutter SDK is required.
+
+Dart standalone target is supported, but due to some problems with pubspec format, the `dart` command must be from Flutter SDK and not Dart SDK. See [dart-lang/pub#3563](https://github.com/dart-lang/pub/issues/3563).
+
+### Java tooling
+
+**Use JDK versions 11 to 17. The newer versions will not work because of their lack of [compatibility](https://docs.gradle.org/current/userguide/compatibility.html) with Gradle.**
+
+Along with JDK, maven (`mvn` command) is required. On windows, it can be installed using a package manager such as [chocolatey](https://community.chocolatey.org/packages/maven) or [scoop](https://scoop.sh/#/apps?q=maven).
+
+__On windows, append the path of `jvm.dll` in your JDK installation to PATH.__
+
+For example, on Powershell:
+
+```powershell
+$env:Path += ";${env:JAVA_HOME}\bin\server".
+```
+
+If JAVA_HOME not set, find the `java.exe` executable and set the environment variable in Control Panel. If java is installed through a package manager, there may be a more automatic way to do this. (Eg: `scoop reset`).
+
+### C/C++ tooling
+CMake and a standard C toolchain are required to build `package:jni` and C bindings generated by `jnigen`.
+
+It's recommended to have `clang-format` installed for formatting the generated C bindings. On Windows, it's part of LLVM installation. On most Linux distributions it is available as a separate package. On MacOS, it can be installed using Homebrew.
+
+## FAQs
+
+#### I am getting ClassNotFoundError at runtime.
+`jnigen` does not handle getting the classes into application. It has to be done by target-specific mechanism. Such as adding a gradle dependency on Android, or manually providing classpath to `Jni.spawn` on desktop / standalone targets.
+
+On Android, `proguard` prunes classes which it deems inaccessible. Since JNI class lookup happens in runtime, this leads to ClassNotFound errors in release mode even if the dependency is included in gradle. [in_app_java example](jnigen/example/in_app_java/) discusses two mechanisms to prevent this: using `Keep` annotation (`androidx.annotation.Keep`) for the code written in the application itself, and [proguard-rules file](jnigen/example/in_app_java/android/app/proguard-rules.pro) for external libraries.
+
+Lastly, some libraries such as `java.awt` do not exist in android. Attempting to use libraries which depend on them can also lead to ClassNotFound errors.
+
+#### `jnigen` is not finding classes.
+Ensure you are providing correct source and class paths, and they follow standard directory structure. If your class name is `com.abc.MyClass`, `MyClass` must be in `com/abc/MyClass.java` relative to one of the source paths, or `com/abc/MyClass.class` relative to one of the class paths specified in YAML.
+
+If the classes are in JAR file, make sure to provide path to JAR file itself, and not the containing directory.
+
+#### `jnigen` is unable to parse sources.
+If the errors are similar to `symbol not found`, ensure all dependencies of the source are available. If such dependency is compiled, it can be included in `class_path`.
+
+#### How are classes mapped into bindings?
+Each Java class generates a subclass of `JObject` class, which wraps a `jobject` reference in JNI. Nested classes use `_` as separator, `Example.NestedClass` will be mapped to `Example_NestedClass`.
+
+#### Does `JObject` hold a local or global reference? Does it need to be manually released?
+Each Java object returned into Dart creates a JNI global reference. Reference deletion is taken care of by `NativeFinalizer` and that's usually sufficient.
+
+It's a good practice to keep the interface between languages sparse. However, if there's a need to create several references (Eg: in a loop), you can use FFI Arena mechanism (`using` function) and `releasedBy` method, or manually release the object using `release` method.
+
+#### Should I use `jnigen` over Method channels?
+This is currently an experimental package. Many features are missing, and it's rough around the edges. You're welcome to try it and give feedback.
+
+## YAML Configuration Reference
+Keys ending with a colon (`:`) denote subsections.
+
+The typical invocation with YAML configuration is
+
+```
+dart run jnigen --config jnigen.yaml
+```
+
+Any configuration can be overridden through command line using `-D` or `--override` switch. For example `-Dlog_level=warning` or `-Dsummarizer.backend=asm`. (Use `.` to separate subsection and property name).
+
+A `*` denotes required configuration.
+
+| Configuration property | Type / Values | Description                                                             |
+| ---------------------- | ------------- | ----------------------------------------------------------------------- |
+| `preamble`             | Text          | Text to be pasted in the start of each generated file.                  |
+| `source_path`          | List of directory paths | Directories to search for source files. Note: source_path for dependencies downloaded using `maven_downloads` configuration is added automatically without the need to specify here. |
+| `class_path`           | List of directory / JAR paths | Classpath for API summary generation. This should include any JAR dependencies of the source files in `source_path`. |
+| `classes` *            | List of qualified class / package names | List of qualified class / package names. `source_path` will be scanned assuming the sources follow standard java-ish hierarchy. That is a.b.c either maps to a directory `a/b/c` or a class file `a/b/c.java`.  |
+| `enable_experiment`    | List of experiment names:<br><ul><li>`interface_implementation`</li></ul> | List of enabled experiments. These features are still in development and their API might break.  |
+| `output:`              | (Subsection) | This subsection will contain configuration related to output files. |
+| `output:` >> `bindings_type` | `c_based` (default) or `dart_only` | Binding generation strategy. [Trade-offs](#pure-dart-bindings) are explained at the end of this document. |
+| `output:` >> `c:`      | (Subsection) | This subsection specified C output configuration. Required if `bindings_type` is `c_based`. |
+| `output:` >> `c:` >> path *       | Directory path | Directory to write C bindings. Usually `src/` in case of an FFI plugin template. |
+| `output:` >> `c:` >> subdir       | Directory path | If specified, C bindings will be written to `subdir` resolved relative to `path`. This is useful when bindings are supposed to be under source's license, and written to a subdirectory such as `third_party`. |
+| `output:` >> `c:` >> `library_name` *| Identifier (snake_case) | Name for generated C library.
+| `output:` >> `dart:` | (Subsection) | This subsection specifies Dart output configuration. |
+| `output:` >> `dart:` >> `structure` | `package_structure` / `single_file` | Whether to map resulting dart bindings to file-per-class source layout, or write all bindings to single file.
+| `output:` >> `dart:` >> `path` * | Directory path or File path | Path to write Dart bindings. Should end in `.dart` for `single_file` configurations, and end in `/` for `package_structure` (default) configuration. |
+| `maven_downloads:`     | (Subsection) | This subsection will contain configuration for automatically downloading Java dependencies (source and JAR) through maven. |
+| `maven_downloads:` >> `source_deps` | List of maven package coordinates | Source packages to download and unpack using maven. The names should be valid maven artifact coordinates. (Eg: `org.apache.pdfbox:pdfbox:2.0.26`). The downloads do not include transitive dependencies. |
+| `maven_downloads:` >> `source_dir` | Path | Directory in which maven sources are extracted. Defaults to `mvn_java`. It's not required to list this explicitly in source_path. |
+| `maven_downloads:` >> `jar_only_deps` | List of maven package coordinates | JAR dependencies to download which are not mandatory transitive dependencies of `source_deps`. Often, it's required to find and include optional dependencies so that entire source is valid for further processing. |
+| `maven_downloads:` >> `jar_dir` | Path | Directory to store downloaded JARs. Defaults to `mvn_jar`. |
+| `log_level`            | Logging level | Configure logging level. Defaults to `info`.                            |
+| `android_sdk_config:`  | (Subsection)  | Configuration for autodetection of Android dependencies and SDK. Note that this is more experimental than others, and very likely subject to change. |
+| `android_sdk_config:` >> `add_gradle_deps` | Boolean | If true, run a gradle stub during `jnigen` invocation, and add Android compile classpath to the classpath of jnigen. This requires a release build to have happened before, so that all dependencies are cached appropriately. |
+| `android_sdk_config:` >> `android_example` | Directory path | In case of an Android plugin project, the plugin itself cannot be built and `add_gradle_deps` is not directly feasible. This property can be set to relative path of package example app (usually `example/` so that gradle dependencies can be collected by running a stub in this directory. See [notification_plugin example](jnigen/example/notification_plugin/jnigen.yaml) for an example. |
+| `summarizer:`          | (Subsection)  | Configuration specific to summarizer component, which builds API descriptions from Java sources or JAR files. |
+| `summarizer:` >> `backend` | `auto`, `doclet` or `asm` | Specifies the backend to use in API summary generation. `doclet` uses OpenJDK Doclet API to build summary from sources. `asm` uses ASM library to build summary from classes in `class_path` JARs. `auto` attempts to find the class in sources, and falls back to using ASM. |
+| `summarizer:` >> `extra_args` (DEV) | List of CLI arguments | Extra arguments to pass to summarizer JAR. |
+| `exclude:`             | (Subsection)  | Exclude methods or fields using regex filters. It's generally useful to exclude problematic fields or methods which, with current binding generation, can lead to syntax errors |
+| `exclude:` >> `methods`| List of methods in `classBinaryName#methodName` format where classBinaryName is same as qualified name, but `$` preceding a nested class instead of `.`. Example: `com.example.MyClass` or `com.example.MyClass$NestedClass` | Methods to exclude.
+| `exclude:` >> `fields` | List of fields in `classBinaryName#fieldName` format | Fields to exclude.
+
+It's possible to use the programmatic API instead of YAML.
+
+* Create a tool script. (Eg: `tool/generate_jni_bindings.dart`)
+* import `package:jnigen/jnigen.dart`
+* construct a `Config` object and pass it to `generateJniBindings` function. The parameters are similar to the ones described above.
+
+## Pure dart Bindings
+It's possible to generate bindings that do not rely on an intermediate layer of C code. Bindings will still depend on `package:jni` and its support library written in C. But this approach avoids large C bindings.
+
+To enable pure dart bindings, specify
+```
+output:
+	bindings_type: dart_only
+```
+
+Any C output configuration will be ignored.
+
+However, pure dart bindings will require additional allocations and check runtimeType of the arguments. This will be the case until Variadic arguments land in Dart FFI.
+
+## Android core libraries
+These days, Android projects depend heavily on AndroidX and other libraries downloaded via gradle. We have a tracking issue to improve detection of android SDK and dependencies. (#31). Currently we can fetch the JAR dependencies of an android project, by running a gradle stub, if `android_sdk_config` >> `add_gradle_deps` is specified. 
+
+But core libraries (the `android.**` namespace) are not downloaded through gradle. The core libraries are shipped as stub JARs with the Android SDK. (`$SDK_ROOT/platforms/android-$VERSION/android-stubs-src.jar`).
+
+Currently we don't have an automatic mechanism for using these. You can unpack this JAR manually into some directory and provide it as a source path.
+
+However there are 2 caveats to this caveat.
+
+* SDK stubs after version 28 are incomplete. OpenJDK Doclet API we use to generate API summaries will error on incomplete sources.
+* The API can't process the `java.**` namespaces in the Android SDK stubs, because it expects a module layout. So if you want to generate bindings for, say, `java.lang.Math`, you cannot use the Android SDK stubs. OpenJDK sources can be used instead.
+
+The JAR files (`$SDK_ROOT/platforms/android-$VERSION/android.jar`) can be used instead. But compiled JARs do not include JavaDoc and method parameter names. This JAR is automatically included by Gradle when `android_sdk_config` >> `add_gradle_deps` is specified.
+
+## Contributing
+See the wiki for architecture-related documents.
+
diff --git a/pkgs/jnigen/analysis_options.yaml b/pkgs/jnigen/analysis_options.yaml
new file mode 100644
index 0000000..4bebf0e
--- /dev/null
+++ b/pkgs/jnigen/analysis_options.yaml
@@ -0,0 +1,19 @@
+# Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+include: package:lints/recommended.yaml
+
+analyzer:
+  exclude: [build/**, example/**]
+  language:
+    strict-raw-types: true
+    strict-inference: true
+
+linter:
+  rules:
+    - prefer_final_locals
+    - prefer_const_declarations
+    - unawaited_futures
+    - prefer_const_constructors
+    - prefer_relative_imports
diff --git a/pkgs/jnigen/android_test_runner/.gitignore b/pkgs/jnigen/android_test_runner/.gitignore
new file mode 100644
index 0000000..24476c5
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/.gitignore
@@ -0,0 +1,44 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/pkgs/jnigen/android_test_runner/.metadata b/pkgs/jnigen/android_test_runner/.metadata
new file mode 100644
index 0000000..957ea81
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+  revision: f72efea43c3013323d1b95cff571f3c1caa37583
+  channel: stable
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
+      base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
+    - platform: android
+      create_revision: f72efea43c3013323d1b95cff571f3c1caa37583
+      base_revision: f72efea43c3013323d1b95cff571f3c1caa37583
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/pkgs/jnigen/android_test_runner/README.md b/pkgs/jnigen/android_test_runner/README.md
new file mode 100644
index 0000000..cd85e19
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/README.md
@@ -0,0 +1,17 @@
+## What's this?
+
+This is Flutter app project which serves as a skeleton for running binding runtime tests on android, using Flutter's integration testing mechanism.
+
+## How to run tests?
+
+Generate runtime test files, by running the following in parent (jnigen) directory.
+
+```bash
+dart run tool/generate_runtime_tests.dart
+```
+
+This will generate integration_test/runtime_test.dart in this directory, along with other runtime tests for `jnigen`. This can be run with regular integration test mechanism.
+
+```bash
+flutter test integration_test/
+```
\ No newline at end of file
diff --git a/pkgs/jnigen/android_test_runner/analysis_options.yaml b/pkgs/jnigen/android_test_runner/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jnigen/android_test_runner/android/.gitignore b/pkgs/jnigen/android_test_runner/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/pkgs/jnigen/android_test_runner/android/app/.gitignore b/pkgs/jnigen/android_test_runner/android/app/.gitignore
new file mode 100644
index 0000000..df2fa17
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/.gitignore
@@ -0,0 +1 @@
+.cxx
\ No newline at end of file
diff --git a/pkgs/jnigen/android_test_runner/android/app/CMakeLists.txt b/pkgs/jnigen/android_test_runner/android/app/CMakeLists.txt
new file mode 100644
index 0000000..7bc2474
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/CMakeLists.txt
@@ -0,0 +1,10 @@
+## Parent CMake for Android native build target. This will build
+## all C bindings from tests.
+
+cmake_minimum_required(VERSION 3.10)
+
+project(simple_package VERSION 0.0.1 LANGUAGES C)
+
+add_subdirectory(../../../test/jackson_core_test/third_party/c_based/c_bindings jackson_core_test_build)
+add_subdirectory(../../../test/simple_package_test/c_based/c_bindings simple_package_test_build)
+add_subdirectory(../../../test/kotlin_test/c_based/c_bindings kotlin_test_build)
diff --git a/pkgs/jnigen/android_test_runner/android/app/build.gradle b/pkgs/jnigen/android_test_runner/android/app/build.gradle
new file mode 100644
index 0000000..58cd27a
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/build.gradle
@@ -0,0 +1,79 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion flutter.compileSdkVersion
+    ndkVersion flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_11
+        targetCompatibility JavaVersion.VERSION_11
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+        main.java.srcDirs += '../../../test/simple_package_test/java'
+        main.java.srcDirs += '../../../test/kotlin_test/kotlin/src/main'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "com.github.dart_lang.jnigen.android_integration_test"
+        // You can update the following values to match your application needs.
+        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path 'CMakeLists.txt'
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    implementation "com.fasterxml.jackson.core:jackson-core:2.13.4"
+}
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/debug/AndroidManifest.xml b/pkgs/jnigen/android_test_runner/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..fc7efb6
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.github.dart_lang.jnigen.android_integration_test">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/AndroidManifest.xml b/pkgs/jnigen/android_test_runner/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..84209fb
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.github.dart_lang.jnigen.android_integration_test">
+   <application
+        android:label="android_integration_test"
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/kotlin/com/github/dart_lang/jnigen/android_integration_test/MainActivity.kt b/pkgs/jnigen/android_test_runner/android/app/src/main/kotlin/com/github/dart_lang/jnigen/android_integration_test/MainActivity.kt
new file mode 100644
index 0000000..5197ccc
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/kotlin/com/github/dart_lang/jnigen/android_integration_test/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.github.dart_lang.jnigen.android_integration_test
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/res/drawable-v21/launch_background.xml b/pkgs/jnigen/android_test_runner/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/res/drawable/launch_background.xml b/pkgs/jnigen/android_test_runner/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/res/values-night/styles.xml b/pkgs/jnigen/android_test_runner/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/main/res/values/styles.xml b/pkgs/jnigen/android_test_runner/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/android_test_runner/android/app/src/profile/AndroidManifest.xml b/pkgs/jnigen/android_test_runner/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..fc7efb6
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.github.dart_lang.jnigen.android_integration_test">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/android_test_runner/android/build.gradle b/pkgs/jnigen/android_test_runner/android/build.gradle
new file mode 100644
index 0000000..58a8c74
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+    ext.kotlin_version = '1.7.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.2.0'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/pkgs/jnigen/android_test_runner/android/gradle.properties b/pkgs/jnigen/android_test_runner/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/pkgs/jnigen/android_test_runner/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/jnigen/android_test_runner/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..3c472b9
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
diff --git a/pkgs/jnigen/android_test_runner/android/settings.gradle b/pkgs/jnigen/android_test_runner/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/pkgs/jnigen/android_test_runner/integration_test/.gitignore b/pkgs/jnigen/android_test_runner/integration_test/.gitignore
new file mode 100644
index 0000000..af7c365
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/integration_test/.gitignore
@@ -0,0 +1 @@
+runtime_test.dart
\ No newline at end of file
diff --git a/pkgs/jnigen/android_test_runner/lib/main.dart b/pkgs/jnigen/android_test_runner/lib/main.dart
new file mode 100644
index 0000000..da07cdd
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/lib/main.dart
@@ -0,0 +1,52 @@
+import 'package:flutter/material.dart';
+
+void main() {
+  runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  const MyApp({super.key});
+
+  // This widget is the root of your application.
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Flutter Demo',
+      theme: ThemeData(
+        // This is the theme of your application.
+        //
+        // Try running your application with "flutter run". You'll see the
+        // application has a blue toolbar. Then, without quitting the app, try
+        // changing the primarySwatch below to Colors.green and then invoke
+        // "hot reload" (press "r" in the console where you ran "flutter run",
+        // or simply save your changes to "hot reload" in a Flutter IDE).
+        // Notice that the counter didn't reset back to zero; the application
+        // is not restarted.
+        primarySwatch: Colors.blue,
+      ),
+      home: const MyHomePage(),
+    );
+  }
+}
+
+class MyHomePage extends StatelessWidget {
+  const MyHomePage({super.key});
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text("Integration test runner"),
+      ),
+      body: const Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            Text(
+              'This app should be run as flutter integration test',
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/pkgs/jnigen/android_test_runner/pubspec.yaml b/pkgs/jnigen/android_test_runner/pubspec.yaml
new file mode 100644
index 0000000..fccc86e
--- /dev/null
+++ b/pkgs/jnigen/android_test_runner/pubspec.yaml
@@ -0,0 +1,94 @@
+name: android_integration_test
+description: jnigen integration test runner for android
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+# In Windows, build-name is used as the major, minor, and patch parts
+# of the product and file versions while build-number is used as the build suffix.
+version: 1.0.0+1
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+  jni:
+    path: ../../jni/
+  ffi: any
+  flutter:
+    sdk: flutter
+
+  # The following adds the Cupertino Icons font to your application.
+  # Use with the CupertinoIcons class for iOS style icons.
+  cupertino_icons: ^1.0.2
+
+dev_dependencies:
+  test: any
+  flutter_test:
+    sdk: flutter
+  integration_test:
+    sdk: flutter
+
+  # The "flutter_lints" package below contains a set of recommended lints to
+  # encourage good coding practices. The lint set provided by the package is
+  # activated in the `analysis_options.yaml` file located at the root of your
+  # package. See that file for information about deactivating specific lint
+  # rules and activating additional ones.
+  flutter_lints: ^2.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+  # The following line ensures that the Material Icons font is
+  # included with your application, so that you can use the icons in
+  # the material Icons class.
+  uses-material-design: true
+
+  # To add assets to your application, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware
+
+  # For details regarding adding assets from package dependencies, see
+  # https://flutter.dev/assets-and-images/#from-packages
+
+  # To add custom fonts to your application, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts from package dependencies,
+  # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jnigen/bin/download_maven_jars.dart b/pkgs/jnigen/bin/download_maven_jars.dart
new file mode 100644
index 0000000..6e5b4b0
--- /dev/null
+++ b/pkgs/jnigen/bin/download_maven_jars.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jnigen/jnigen.dart';
+import 'package:jnigen/tools.dart';
+
+/// Downloads maven dependencies downloaded by equivalent jnigen invocation.
+///
+/// Useful for running standalone examples on already generated sources.
+void main(List<String> args) async {
+  final config = Config.parseArgs(args);
+  final mavenDownloads = config.mavenDownloads;
+  if (mavenDownloads != null) {
+    await MavenTools.downloadMavenJars(
+        MavenTools.deps(mavenDownloads.jarOnlyDeps + mavenDownloads.sourceDeps),
+        mavenDownloads.jarDir);
+    await Directory(mavenDownloads.jarDir)
+        .list()
+        .map((entry) => entry.path)
+        .where((path) => path.endsWith('.jar'))
+        .forEach(stdout.writeln);
+  } else {
+    stderr.writeln('No maven dependencies found in config.');
+    exitCode = 2;
+  }
+}
diff --git a/pkgs/jnigen/bin/jnigen.dart b/pkgs/jnigen/bin/jnigen.dart
new file mode 100644
index 0000000..8dc69e3
--- /dev/null
+++ b/pkgs/jnigen/bin/jnigen.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:jnigen/jnigen.dart';
+import 'package:jnigen/src/logging/logging.dart';
+
+void main(List<String> args) async {
+  enableLoggingToFile();
+  Config config;
+  try {
+    config = Config.parseArgs(args);
+  } on ConfigException catch (e) {
+    log.fatal(e);
+  } on FormatException catch (e) {
+    log.fatal(e);
+  }
+  await generateJniBindings(config);
+}
diff --git a/pkgs/jnigen/bin/setup.dart b/pkgs/jnigen/bin/setup.dart
new file mode 100644
index 0000000..54df277
--- /dev/null
+++ b/pkgs/jnigen/bin/setup.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jnigen/src/logging/logging.dart';
+import 'package:jnigen/src/tools/tools.dart';
+import 'package:logging/logging.dart';
+
+void main(List<String> args) async {
+  bool force = false;
+  setLoggingLevel(Level.INFO);
+  if (args.isNotEmpty) {
+    if (args.length != 1 || args[0] != '-f') {
+      stderr.writeln('usage: dart run jnigen:setup [-f]');
+      stderr.writeln('* -f\trebuild ApiSummarizer jar even if it already '
+          'exists.');
+    } else {
+      force = true;
+    }
+  }
+  await buildSummarizerIfNotExists(force: force);
+}
diff --git a/pkgs/jnigen/cmake/CMakeLists.txt.tmpl b/pkgs/jnigen/cmake/CMakeLists.txt.tmpl
new file mode 100644
index 0000000..d0b8a73
--- /dev/null
+++ b/pkgs/jnigen/cmake/CMakeLists.txt.tmpl
@@ -0,0 +1,32 @@
+# jni_native_build (Build with jni:setup. Do not delete this line.)
+
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project({{LIBRARY_NAME}} VERSION 0.0.1 LANGUAGES C)
+
+add_library({{LIBRARY_NAME}} SHARED
+  "{{SUBDIR}}/{{LIBRARY_NAME}}.c"
+)
+
+set_target_properties({{LIBRARY_NAME}} PROPERTIES
+  OUTPUT_NAME "{{LIBRARY_NAME}}"
+)
+
+target_compile_definitions({{LIBRARY_NAME}} PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries({{LIBRARY_NAME}} log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries({{LIBRARY_NAME}} ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jnigen/cmake/README.md b/pkgs/jnigen/cmake/README.md
new file mode 100644
index 0000000..65f9951
--- /dev/null
+++ b/pkgs/jnigen/cmake/README.md
@@ -0,0 +1,3 @@
+This directory contains a CMakeLists.txt.tmpl file which will be used to generate build configuration for generated C code.
+
+`{{LIBRARY_NAME}}` in this template will be replaced by `library_name` configuration parameter.
\ No newline at end of file
diff --git a/pkgs/jnigen/dart_test.yaml b/pkgs/jnigen/dart_test.yaml
new file mode 100644
index 0000000..fdd1587
--- /dev/null
+++ b/pkgs/jnigen/dart_test.yaml
@@ -0,0 +1,14 @@
+## Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+## for details. All rights reserved. Use of this source code is governed by a
+## BSD-style license that can be found in the LICENSE file.
+
+## This file defines test groups for jnigen. This will be helpful to exclude
+## lengthy tests by using -x flag.
+
+tags:
+  large_test:
+    timeout: 2x
+  summarizer_test:
+    timeout: 1x
+  runtime_test:
+    timeout: 1x
diff --git a/pkgs/jnigen/example/README.md b/pkgs/jnigen/example/README.md
new file mode 100644
index 0000000..dce00fb
--- /dev/null
+++ b/pkgs/jnigen/example/README.md
@@ -0,0 +1,51 @@
+## jnigen examples
+
+This directory contains examples on how to use jnigen.
+
+| Directory                                   | Description                                                                                                                                    |
+| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
+| [in_app_java](https://github.com/dart-lang/jnigen/tree/main/jnigen/example/in_app_java/)                 | Demonstrates how to include custom Java code in Flutter application and call that using jnigen                                                 |
+| [pdfbox_plugin](https://github.com/dart-lang/jnigen/tree/main/jnigen/example/pdfbox_plugin/)             | Example of a flutter plugin which provides bindings to Apache PDFBox library. Currently works on Flutter desktop and Dart standalone on linux. |
+| [notification_plugin](https://github.com/dart-lang/jnigen/tree/main/jnigen/example/notification_plugin/) | Example of a reusable Flutter plugin with custom Java code which uses Android libraries.                                                       |
+| [kotlin_plugin](https://github.com/dart-lang/jnigen/tree/main/jnigen/example/kotlin_plugin)             | Example of using jnigen to generate bindings for Kotlin.                                                                                       |
+
+We intend to cover few more use cases in future.
+
+## Creating a jnigen-based plugin from scratch
+
+### Dart package (Standalone only)
+
+- Create dart package, add `jni` as dependency and `jnigen` as dev dependency.
+- Write the jnigen config similar to [the one in pdfbox_plugin](https://github.com/dart-lang/jnigen/tree/main/jnigen/example/pdfbox_plugin/jnigen.yaml).
+- Generate JNI bindings by running `dart run jnigen --config jnigen.yaml`.
+
+- In the CLI project which uses this package, add this package, and `jni` as a dependency.
+- Run `dart run jni:setup` to build native libraries for JNI base library and jnigen generated package.
+- Import the package. See [pdf_info.dart](https://github.com/dart-lang/jnigen/tree/main/jnigen/example/pdfbox_plugin/dart_example/bin/pdf_info.dart) for an example of using JNI from dart standalone.
+
+### Flutter FFI plugin
+
+Flutter FFI plugin has the advantage of bundling the required native libraries along with Android / Linux Desktop app.
+
+To create an FFI plugin with JNI bindings:
+
+- Create a plugin using `plugin_ffi` template.
+- Remove ffigen-specific files and stubs.
+- Follow the above steps to generate JNI bindings. The plugin can be used from a flutter project.
+
+- It may be desirable to generate the bindings into a private directory (Eg: `lib/src/third_party`) and re-export the classes from the top level dart file.
+
+- To use the plugin from Dart projects as well, comment-out or remove flutter SDK requirements from the pubspec. This is however problematic if you want to publish the package.
+
+### Android plugin with custom Java code
+
+- Create an FFI plugin with Android as the only platform.
+- Build the example/ Android project using command `flutter build apk`. After a release build is done, jnigen can use a gradle stub to collect compile classpaths.
+- Write your custom Java code in `android/src/main/java` hierarchy of the plugin.
+- Generate JNI bindings as described above. See [notification_plugin/jnigen.yaml](https://github.com/dart-lang/jnigen/tree/main/jnigen/example/notification_plugin/jnigen.yaml) for example configuration.
+
+### Pure dart bindings
+
+With Pure dart bindings PoC, most of the FFI setup steps are not required. For example, a simple flutter package can be created instead of an FFI plugin, since there are no native artifacts to bundle.
+
+The generated bindings still depend on `package:jni`, therefore running `dart run jni:setup` is still a requirement on standalone target.
diff --git a/pkgs/jnigen/example/in_app_java/.gitignore b/pkgs/jnigen/example/in_app_java/.gitignore
new file mode 100644
index 0000000..8c64048
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/.gitignore
@@ -0,0 +1,50 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
+
+## Generated by build process
+android/app/.cxx
diff --git a/pkgs/jnigen/example/in_app_java/.metadata b/pkgs/jnigen/example/in_app_java/.metadata
new file mode 100644
index 0000000..2544bab
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+  revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+  channel: stable
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: android
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/pkgs/jnigen/example/in_app_java/README.md b/pkgs/jnigen/example/in_app_java/README.md
new file mode 100644
index 0000000..13813a8
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/README.md
@@ -0,0 +1,24 @@
+# In-App Java Example
+
+This example shows how to write custom java code in `android/app/src` and call it using `jnigen` generated bindings.
+
+#### How to run this example:
+* Run `flutter run` to run the app.
+
+* To regenerate bindings after changing Java code, run `flutter pub run jnigen --config jnigen.yaml`. This requires at least one APK build to have been run before, so that it's possible for `jnigen` to obtain classpaths of Android Gradle libraries. Therefore, once run `flutter build apk` before generating bindings for the first time, or after a `flutter clean`.
+
+#### General steps
+These are general steps to integrate Java code into a flutter project using `jnigen`.
+
+* Write Java code in suitable package folder, under `android/` subproject of the flutter app.
+
+* Create A jnigen config like `jnigen.yaml` in this example.
+
+* Generate bindings using jnigen config.
+
+* Add an `externalNativeBuild` to gradle script (see `android/app/build.gradle` in this example).
+
+* Add proguard rules to exclude your custom classes from tree shaking, since they are always accessed reflectively in JNI.
+
+* Build and run the app.
+
diff --git a/pkgs/jnigen/example/in_app_java/analysis_options.yaml b/pkgs/jnigen/example/in_app_java/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jnigen/example/in_app_java/android/.gitignore b/pkgs/jnigen/example/in_app_java/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/pkgs/jnigen/example/in_app_java/android/app/build.gradle b/pkgs/jnigen/example/in_app_java/android/app/build.gradle
new file mode 100644
index 0000000..59ebf16
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/build.gradle
@@ -0,0 +1,82 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion flutter.compileSdkVersion
+    ndkVersion flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "com.example.in_app_java"
+        // You can update the following values to match your application needs.
+        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+
+	externalNativeBuild {
+		cmake {
+			path "../../src/android_utils/CMakeLists.txt"
+		}
+	}
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+    /// Add AndroidX emoji2 library as dependency.
+    /// We will be using EmojiCompat's GlyphChecker in our flutter example.
+    def emoji2_version = "1.3.0"
+    implementation "androidx.emoji2:emoji2:$emoji2_version"
+}
diff --git a/pkgs/jnigen/example/in_app_java/android/app/proguard-rules.pro b/pkgs/jnigen/example/in_app_java/android/app/proguard-rules.pro
new file mode 100644
index 0000000..8eecc4c
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# This is the rules file which prevents `proguard` from aggressively
+# removing the classes which appear to be unused, in release mode.
+#
+# Since JNI is based on runtime lookup for classes, proguard will not have
+# a way to know EmojiCompat class in our example is actually used. Therefore
+# we have to explicitly list it in this file to prevent the application from
+# crashing with ClassNotFoundException.
+#
+# This doesn't apply to builtin classes, eg: those in `android.` namespace Since
+# they are already present on the device.
+# For the Java code which is written by yourself, you can add `androidx.annotation.Keep`
+# instead. (As done in `AndroidUtils.java`).
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+-keep class androidx.emoji2.text.EmojiCompat { public *; }
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/debug/AndroidManifest.xml b/pkgs/jnigen/example/in_app_java/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..a096030
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.in_app_java">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/AndroidManifest.xml b/pkgs/jnigen/example/in_app_java/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e674e57
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.in_app_java">
+   <application
+        android:label="in_app_java"
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java b/pkgs/jnigen/example/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java
new file mode 100644
index 0000000..84be6f1
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/java/com/example/in_app_java/AndroidUtils.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.example.in_app_java;
+
+import android.app.Activity;
+import android.widget.Toast;
+import androidx.annotation.Keep;
+
+@Keep
+public abstract class AndroidUtils {
+  // Hide constructor
+  private AndroidUtils() {}
+
+  public static void showToast(Activity mainActivity, CharSequence text, int duration) {
+    mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, text, duration).show());
+  }
+}
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/kotlin/com/example/in_app_java/MainActivity.kt b/pkgs/jnigen/example/in_app_java/android/app/src/main/kotlin/com/example/in_app_java/MainActivity.kt
new file mode 100644
index 0000000..c8fde0c
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/kotlin/com/example/in_app_java/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.in_app_java
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/res/drawable-v21/launch_background.xml b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/res/drawable/launch_background.xml b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/res/values-night/styles.xml b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/main/res/values/styles.xml b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/example/in_app_java/android/app/src/profile/AndroidManifest.xml b/pkgs/jnigen/example/in_app_java/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..a096030
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.in_app_java">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/example/in_app_java/android/build.gradle b/pkgs/jnigen/example/in_app_java/android/build.gradle
new file mode 100644
index 0000000..2fbab80
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/build.gradle
@@ -0,0 +1,32 @@
+buildscript {
+    ext.kotlin_version = '1.6.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.1.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+tasks.register("clean", Delete) {
+    delete rootProject.buildDir
+}
diff --git a/pkgs/jnigen/example/in_app_java/android/gradle.properties b/pkgs/jnigen/example/in_app_java/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/pkgs/jnigen/example/in_app_java/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/jnigen/example/in_app_java/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..cc5527d
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
diff --git a/pkgs/jnigen/example/in_app_java/android/settings.gradle b/pkgs/jnigen/example/in_app_java/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/pkgs/jnigen/example/in_app_java/jnigen.yaml b/pkgs/jnigen/example/in_app_java/jnigen.yaml
new file mode 100644
index 0000000..e588f4a
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/jnigen.yaml
@@ -0,0 +1,23 @@
+android_sdk_config:
+  add_gradle_deps: true
+  add_gradle_sources: true
+
+output:
+  c:
+    library_name: android_utils
+    path: src/android_utils/
+  dart:
+    path: lib/android_utils.dart
+    structure: single_file
+
+source_path:
+  - 'android/app/src/main/java'
+classes:
+  - 'com.example.in_app_java.AndroidUtils' # from source_path
+  - 'androidx.emoji2.text.EmojiCompat' # From gradle's source path
+  - 'androidx.emoji2.text.DefaultEmojiCompatConfig' # From gradle's source path
+  - 'android.os.Build' # from gradle's compile classpath
+  - 'java.util.HashMap' # from gradle's compile classpath
+exclude:
+  fields:
+    - 'android.os.Build\$VERSION_CODES#UPSIDE_DOWN_CAKE'
diff --git a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart
new file mode 100644
index 0000000..6e677a9
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart
@@ -0,0 +1,3656 @@
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+// Auto-generated initialization code.
+
+final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
+    ProtectedJniExtensions.initGeneratedLibrary("android_utils");
+
+/// from: com.example.in_app_java.AndroidUtils
+class AndroidUtils extends jni.JObject {
+  @override
+  late final jni.JObjType<AndroidUtils> $type = type;
+
+  AndroidUtils.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $AndroidUtilsType();
+  static final _showToast = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>, ffi.Int32)>>("AndroidUtils__showToast")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: static public void showToast(android.app.Activity mainActivity, java.lang.CharSequence text, int duration)
+  static void showToast(
+    jni.JObject mainActivity,
+    jni.JObject text,
+    int duration,
+  ) {
+    return _showToast(mainActivity.reference, text.reference, duration).check();
+  }
+}
+
+final class $AndroidUtilsType extends jni.JObjType<AndroidUtils> {
+  const $AndroidUtilsType();
+
+  @override
+  String get signature => r"Lcom/example/in_app_java/AndroidUtils;";
+
+  @override
+  AndroidUtils fromRef(jni.JObjectPtr ref) => AndroidUtils.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($AndroidUtilsType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($AndroidUtilsType) &&
+        other is $AndroidUtilsType;
+  }
+}
+
+/// from: androidx.emoji2.text.EmojiCompat
+///
+/// Main class to keep Android devices up to date with the newest emojis by adding EmojiSpans
+/// to a given CharSequence.
+/// <p/>
+/// By default, EmojiCompat is initialized by EmojiCompatInitializer, which performs
+/// deferred font loading to avoid potential app startup delays. The default behavior is to load
+/// the font shortly after the first Activity resumes. EmojiCompatInitializer will configure
+/// EmojiCompat to use the system emoji font provider via DefaultEmojiCompatConfig and
+/// always creates a new background thread for font loading.
+/// <p/>
+/// EmojiCompat will only allow one instance to be initialized and any calls to
+/// \#init(Config) after the first one will have no effect. As a result, configuration options
+/// may not be provided when using EmojiCompatInitializer. To provide a custom configuration,
+/// disable EmojiCompatInitializer in the manifest with:
+///
+/// <pre>
+///     &lt;provider
+///         android:name="androidx.startup.InitializationProvider"
+///         android:authorities="${applicationId}.androidx-startup"
+///         android:exported="false"
+///         tools:node="merge"&gt;
+///         &lt;meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
+///                   tools:node="remove" /&gt;
+///     &lt;/provider&gt;
+/// </pre>
+///
+/// When not using EmojiCompatInitializer, EmojiCompat must to be initialized manually using
+/// \#init(EmojiCompat.Config). It is recommended to make the initialization as early as
+/// possible in your app, such as from Application\#onCreate().
+/// <p/>
+/// \#init(Config) is fast and may be called from the main thread on the path to
+/// displaying the first activity. However, loading the emoji font takes significant resources on a
+/// background thread, so it is suggested to use \#LOAD_STRATEGY_MANUAL in all manual
+/// configurations to defer font loading until after the first screen displays. Font loading may
+/// be started by calling \#load()}. See the implementation EmojiCompatInitializer
+/// for ideas when building a manual configuration.
+/// <p/>
+/// After initialization the \#get() function can be used to get the configured instance and
+/// the \#process(CharSequence) function can be used to update a CharSequence with emoji
+/// EmojiSpans.
+/// <p/>
+/// <pre><code>CharSequence processedSequence = EmojiCompat.get().process("some string")</pre>
+/// <p/>
+/// During loading information about emojis is not available. Before the
+/// EmojiCompat instance has finished loading, calls to functions such as EmojiCompat\#process(CharSequence) will throw an exception. It is safe to call process when
+/// \#getLoadState() returns \#LOAD_STATE_SUCCEEDED. To register a callback when
+/// loading completes use InitCallback.
+/// <p/>
+class EmojiCompat extends jni.JObject {
+  @override
+  late final jni.JObjType<EmojiCompat> $type = type;
+
+  EmojiCompat.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $EmojiCompatType();
+
+  /// from: static public final java.lang.String EDITOR_INFO_METAVERSION_KEY
+  ///
+  /// Key in EditorInfo\#extras that represents the emoji metadata version used by the
+  /// widget. The existence of the value means that the widget is using EmojiCompat.
+  /// <p/>
+  /// If exists, the value for the key is an {@code int} and can be used to query EmojiCompat to
+  /// see whether the widget has the ability to display a certain emoji using
+  /// \#hasEmojiGlyph(CharSequence, int).
+  static const EDITOR_INFO_METAVERSION_KEY =
+      r"""android.support.text.emoji.emojiCompat_metadataVersion""";
+
+  /// from: static public final java.lang.String EDITOR_INFO_REPLACE_ALL_KEY
+  ///
+  /// Key in EditorInfo\#extras that represents EmojiCompat.Config\#setReplaceAll(boolean) configuration parameter. The key is added only if
+  /// EmojiCompat is used by the widget. If exists, the value is a boolean.
+  static const EDITOR_INFO_REPLACE_ALL_KEY =
+      r"""android.support.text.emoji.emojiCompat_replaceAll""";
+
+  /// from: static public final int LOAD_STATE_DEFAULT
+  ///
+  /// EmojiCompat instance is constructed, however the initialization did not start yet.
+  ///@see \#getLoadState()
+  static const LOAD_STATE_DEFAULT = 3;
+
+  /// from: static public final int LOAD_STATE_LOADING
+  ///
+  /// EmojiCompat is initializing.
+  ///@see \#getLoadState()
+  static const LOAD_STATE_LOADING = 0;
+
+  /// from: static public final int LOAD_STATE_SUCCEEDED
+  ///
+  /// EmojiCompat successfully initialized.
+  ///@see \#getLoadState()
+  static const LOAD_STATE_SUCCEEDED = 1;
+
+  /// from: static public final int LOAD_STATE_FAILED
+  ///
+  /// An unrecoverable error occurred during initialization of EmojiCompat. Calls to functions
+  /// such as \#process(CharSequence) will fail.
+  ///@see \#getLoadState()
+  static const LOAD_STATE_FAILED = 2;
+
+  /// from: static public final int REPLACE_STRATEGY_DEFAULT
+  ///
+  /// Replace strategy that uses the value given in EmojiCompat.Config.
+  ///@see \#process(CharSequence, int, int, int, int)
+  static const REPLACE_STRATEGY_DEFAULT = 0;
+
+  /// from: static public final int REPLACE_STRATEGY_ALL
+  ///
+  /// Replace strategy to add EmojiSpans for all emoji that were found.
+  ///@see \#process(CharSequence, int, int, int, int)
+  static const REPLACE_STRATEGY_ALL = 1;
+
+  /// from: static public final int REPLACE_STRATEGY_NON_EXISTENT
+  ///
+  /// Replace strategy to add EmojiSpans only for emoji that do not exist in the system.
+  static const REPLACE_STRATEGY_NON_EXISTENT = 2;
+
+  /// from: static public final int LOAD_STRATEGY_DEFAULT
+  ///
+  /// EmojiCompat will start loading metadata when \#init(Config) is called.
+  ///@see Config\#setMetadataLoadStrategy(int)
+  static const LOAD_STRATEGY_DEFAULT = 0;
+
+  /// from: static public final int LOAD_STRATEGY_MANUAL
+  ///
+  /// EmojiCompat will wait for \#load() to be called by developer in order to
+  /// start loading metadata.
+  ///@see Config\#setMetadataLoadStrategy(int)
+  static const LOAD_STRATEGY_MANUAL = 1;
+
+  /// from: static public final int EMOJI_UNSUPPORTED
+  ///
+  /// Result of \#getEmojiMatch(CharSequence, int) that means no part of this codepoint
+  /// sequence will ever generate an EmojiSpan at the requested metadata level.
+  ///
+  /// This return value implies:
+  /// - EmojiCompat will always defer to system emoji font
+  /// - System emoji font may or may not support this emoji
+  /// - This application MAY render this emoji
+  ///
+  /// This can be used by keyboards to learn that EmojiCompat does not support this codepoint
+  /// sequence at this metadata version. The system emoji font is not checked by this method,
+  /// and this result will be returned even if the system emoji font supports the emoji. This may
+  /// happen if the application is using an older version of the emoji compat font than the
+  /// system emoji font.
+  ///
+  /// Keyboards may optionally determine that the system emoji font will support the emoji, for
+  /// example by building a internal lookup table or calling
+  /// androidx.core.graphics.PaintCompat\#hasGlyph(Paint, String) to query the system
+  /// emoji font. Keyboards may use a lookup table to optimize this check, however they should be
+  /// aware that OEMs may add or remove emoji from the system emoji font.
+  ///
+  /// Keyboards may finally decide:
+  /// - If the system emoji font DOES NOT support the emoji, then the emoji IS NOT supported by
+  /// this application.
+  /// - If the system emoji font DOES support the emoji, then the emoji IS supported by this
+  /// application.
+  /// - If system emoji font is support is UNKNOWN, then assume the emoji IS NOT supported by
+  /// this application.
+  static const EMOJI_UNSUPPORTED = 0;
+
+  /// from: static public final int EMOJI_SUPPORTED
+  ///
+  /// Result of \#getEmojiMatch(CharSequence, int) that means this codepoint can be drawn
+  /// by an EmojiSpan at this metadata level.
+  ///
+  /// No further checks are required by keyboards for this result. The emoji is always supported
+  /// by this application.
+  ///
+  /// This return value implies:
+  /// - EmojiCompat can draw this emoji
+  /// - System emoji font may or may not support this emoji
+  /// - This application WILL render this emoji
+  ///
+  /// This result implies that EmojiCompat can successfully display this emoji. The system emoji
+  /// font is not checked by this method, and this result may be returned even if the platform
+  /// also supports the emoji sequence.
+  ///
+  /// If the application passes EmojiCompat\#REPLACE_STRATEGY_ALL of true, then an
+  /// EmojiSpan will always be generated for this emoji.
+  ///
+  /// If the application passes EmojiCompat\#REPLACE_STRATEGY_ALL of false, then an
+  /// EmojiSpan will only be generated if
+  /// androidx.core.graphics.PaintCompat\#hasGlyph(Paint, String)
+  /// returns false for this emoji.
+  static const EMOJI_SUPPORTED = 1;
+
+  /// from: static public final int EMOJI_FALLBACK
+  ///
+  /// Result of \#getEmojiMatch(CharSequence, int) that means the full codepoint sequence
+  /// is not known to emojicompat, but at least one subsequence is an emoji that is known at
+  /// this metadata level.
+  ///
+  /// Keyboards may decide that this emoji is not supported by the application when this result is
+  /// returned, with no further processing.
+  ///
+  /// This return value implies:
+  /// - EmojiCompat will decompose this ZWJ sequence into multiple glyphs when replaceAll=true
+  /// - EmojiCompat MAY defer to platform when replaceAll=false
+  /// - System emoji font may or may not support this emoji
+  /// - This application MAY render this emoji
+  ///
+  /// This return value is only ever returned for ZWJ sequences. To understand this result
+  /// consider when it may be returned for the multi-skin-tone handshake introduced in emoji 14.
+  ///
+  /// <pre>
+  ///     U+1FAF1 // unknown @ requested metadata level
+  ///     U+1F3FB // metadata level 1
+  ///     U+200D  // not displayed (ZWJ)
+  ///     U+1FAF2 // unknown @ requested metadata level
+  ///     U+1F3FD // metadata level 1
+  /// </pre>
+  ///
+  /// In this codepoint sequence, U+1F3FB and U+1F3FD are known from metadata level 1. When an
+  /// application is using a metadata level that doesn't understand this ZWJ and provides
+  /// EmojiCompat\#REPLACE_STRATEGY_ALL true, the color emoji are matched and replaced
+  /// with EmojiSpan. The system emoji font, even if it supports this ZWJ sequence, is
+  /// never queried and the added EmojiSpans force fallback rendering for the ZWJ sequence.
+  ///
+  /// The glyph will only display correctly for this application if ALL of the following
+  /// requirements are met:
+  /// - EmojiCompat\#REPLACE_STRATEGY_ALL is false
+  /// - androidx.core.graphics.PaintCompat\#hasGlyph(Paint, String) returns true for each
+  /// emoji subsequence known at this metadata level
+  /// - androidx.core.graphics.PaintCompat\#hasGlyph(Paint, String) returns true for the
+  /// full sequence
+  ///
+  /// Given this return value for the multi-skin-tone handshake above, if
+  /// EmojiCompat\#REPLACE_STRATEGY_ALL is false then the emoji will display if the
+  /// entire emoji sequence is matched by
+  /// androidx.core.graphics.PaintCompat\#hasGlyph(Paint, String) because U+1F3FB and
+  /// U+1F3FD are both in the system emoji font.
+  ///
+  /// Keyboards that wish to determine if the glyph will display correctly by the application in
+  /// response to this return value should consider building an internal lookup for new ZWJ
+  /// sequences instead of repeatedly calling
+  /// androidx.core.graphics.PaintCompat\#hasGlyph(Paint, String) for each emoji
+  /// subsequence.
+  static const EMOJI_FALLBACK = 2;
+
+  static final _init = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__init")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public androidx.emoji2.text.EmojiCompat init(android.content.Context context)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Initialize the singleton instance with the default system-provided configuration.
+  ///
+  /// This is the recommended configuration for most applications. For more details see
+  /// DefaultEmojiCompatConfig.
+  ///
+  ///
+  /// This call will use DefaultEmojiCompatConfig to lookup the default emoji font
+  /// provider installed on the system and use that, if present. If there is no default font
+  /// provider onthe system, this call will have no effect.
+  ///
+  ///
+  /// Note: EmojiCompat may only be initialized once, and will return the same instance
+  /// afterwords.
+  ///
+  ///@return Default EmojiCompat for this device, or null if there is no provider on the system.
+  static EmojiCompat init(
+    jni.JObject context,
+  ) {
+    return const $EmojiCompatType().fromRef(_init(context.reference).object);
+  }
+
+  static final _init1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("EmojiCompat__init1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public androidx.emoji2.text.EmojiCompat init(android.content.Context context, androidx.emoji2.text.DefaultEmojiCompatConfig.DefaultEmojiCompatConfigFactory defaultFactory)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// @hide
+  static EmojiCompat init1(
+    jni.JObject context,
+    DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory defaultFactory,
+  ) {
+    return const $EmojiCompatType()
+        .fromRef(_init1(context.reference, defaultFactory.reference).object);
+  }
+
+  static final _init2 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__init2")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public androidx.emoji2.text.EmojiCompat init(androidx.emoji2.text.EmojiCompat.Config config)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Initialize the singleton instance with a configuration. When used on devices running API 18
+  /// or below, the singleton instance is immediately moved into \#LOAD_STATE_SUCCEEDED
+  /// state without loading any metadata. When called for the first time, the library will create
+  /// the singleton instance and any call after that will not create a new instance and return
+  /// immediately.
+  ///@see EmojiCompat.Config
+  static EmojiCompat init2(
+    EmojiCompat_Config config,
+  ) {
+    return const $EmojiCompatType().fromRef(_init2(config.reference).object);
+  }
+
+  static final _isConfigured =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "EmojiCompat__isConfigured")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public boolean isConfigured()
+  ///
+  /// Return true if EmojiCompat has been configured by a successful call to
+  /// EmojiCompat\#init.
+  ///
+  /// You can use this to check if EmojiCompat\#get() will return a valid EmojiCompat
+  /// instance.
+  ///
+  /// This function does not check the \#getLoadState() and will return true even if the
+  /// font is still loading, or has failed to load.
+  ///@return true if EmojiCompat has been successfully initialized.
+  static bool isConfigured() {
+    return _isConfigured().boolean;
+  }
+
+  static final _reset = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__reset")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public androidx.emoji2.text.EmojiCompat reset(androidx.emoji2.text.EmojiCompat.Config config)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Used by the tests to reset EmojiCompat with a new configuration. Every time it is called a
+  /// new instance is created with the new configuration.
+  ///@hide
+  static EmojiCompat reset(
+    EmojiCompat_Config config,
+  ) {
+    return const $EmojiCompatType().fromRef(_reset(config.reference).object);
+  }
+
+  static final _reset1 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__reset1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public androidx.emoji2.text.EmojiCompat reset(androidx.emoji2.text.EmojiCompat emojiCompat)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Used by the tests to reset EmojiCompat with a new singleton instance.
+  ///@hide
+  static EmojiCompat reset1(
+    EmojiCompat emojiCompat,
+  ) {
+    return const $EmojiCompatType()
+        .fromRef(_reset1(emojiCompat.reference).object);
+  }
+
+  static final _skipDefaultConfigurationLookup =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Uint8)>>(
+              "EmojiCompat__skipDefaultConfigurationLookup")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: static public void skipDefaultConfigurationLookup(boolean shouldSkip)
+  ///
+  /// Reset default configuration lookup flag, for tests.
+  ///@hide
+  static void skipDefaultConfigurationLookup(
+    bool shouldSkip,
+  ) {
+    return _skipDefaultConfigurationLookup(shouldSkip ? 1 : 0).check();
+  }
+
+  static final _get0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "EmojiCompat__get0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: static public androidx.emoji2.text.EmojiCompat get()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Return singleton EmojiCompat instance. Should be called after
+  /// \#init(EmojiCompat.Config) is called to initialize the singleton instance.
+  ///@return EmojiCompat instance
+  ///@throws IllegalStateException if called before \#init(EmojiCompat.Config)
+  static EmojiCompat get0() {
+    return const $EmojiCompatType().fromRef(_get0().object);
+  }
+
+  static final _load = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__load")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void load()
+  ///
+  /// When Config\#setMetadataLoadStrategy(int) is set to \#LOAD_STRATEGY_MANUAL,
+  /// this function starts loading the metadata. Calling the function when
+  /// Config\#setMetadataLoadStrategy(int) is {@code not} set to
+  /// \#LOAD_STRATEGY_MANUAL will throw an exception. The load will {@code not} start if:
+  /// <ul>
+  ///     <li>the metadata is already loaded successfully and \#getLoadState() is
+  ///     \#LOAD_STATE_SUCCEEDED.
+  ///     </li>
+  ///      <li>a previous load attempt is not finished yet and \#getLoadState() is
+  ///     \#LOAD_STATE_LOADING.</li>
+  /// </ul>
+  ///@throws IllegalStateException when Config\#setMetadataLoadStrategy(int) is not set
+  /// to \#LOAD_STRATEGY_MANUAL
+  void load() {
+    return _load(reference).check();
+  }
+
+  static final _registerInitCallback = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("EmojiCompat__registerInitCallback")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void registerInitCallback(androidx.emoji2.text.EmojiCompat.InitCallback initCallback)
+  ///
+  /// Registers an initialization callback. If the initialization is already completed by the time
+  /// the listener is added, the callback functions are called immediately. Callbacks are called on
+  /// the main looper.
+  /// <p/>
+  /// When used on devices running API 18 or below, InitCallback\#onInitialized() is called
+  /// without loading any metadata. In such cases InitCallback\#onFailed(Throwable) is never
+  /// called.
+  ///@param initCallback the initialization callback to register, cannot be {@code null}
+  ///@see \#unregisterInitCallback(InitCallback)
+  void registerInitCallback(
+    EmojiCompat_InitCallback initCallback,
+  ) {
+    return _registerInitCallback(reference, initCallback.reference).check();
+  }
+
+  static final _unregisterInitCallback = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__unregisterInitCallback")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void unregisterInitCallback(androidx.emoji2.text.EmojiCompat.InitCallback initCallback)
+  ///
+  /// Unregisters a callback that was added before.
+  ///@param initCallback the callback to be removed, cannot be {@code null}
+  void unregisterInitCallback(
+    EmojiCompat_InitCallback initCallback,
+  ) {
+    return _unregisterInitCallback(reference, initCallback.reference).check();
+  }
+
+  static final _getLoadState = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__getLoadState")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getLoadState()
+  ///
+  /// Returns loading state of the EmojiCompat instance. When used on devices running API 18 or
+  /// below always returns \#LOAD_STATE_SUCCEEDED.
+  ///@return one of \#LOAD_STATE_DEFAULT, \#LOAD_STATE_LOADING,
+  /// \#LOAD_STATE_SUCCEEDED, \#LOAD_STATE_FAILED
+  int getLoadState() {
+    return _getLoadState(reference).integer;
+  }
+
+  static final _isEmojiSpanIndicatorEnabled = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__isEmojiSpanIndicatorEnabled")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isEmojiSpanIndicatorEnabled()
+  ///
+  /// @return whether a background should be drawn for the emoji for debugging
+  ///@hide
+  bool isEmojiSpanIndicatorEnabled() {
+    return _isEmojiSpanIndicatorEnabled(reference).boolean;
+  }
+
+  static final _getEmojiSpanIndicatorColor = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__getEmojiSpanIndicatorColor")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getEmojiSpanIndicatorColor()
+  ///
+  /// @return color of background drawn if EmojiCompat\#isEmojiSpanIndicatorEnabled is true
+  ///@hide
+  int getEmojiSpanIndicatorColor() {
+    return _getEmojiSpanIndicatorColor(reference).integer;
+  }
+
+  static final _getEmojiStart = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("EmojiCompat__getEmojiStart")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public int getEmojiStart(java.lang.CharSequence charSequence, int offset)
+  ///
+  /// Together with \#getEmojiEnd(CharSequence, int), if the character at {@code offset} is
+  /// part of an emoji, returns the index range of that emoji, start index inclusively/end index
+  /// exclusively so that {@code charSequence.subSequence(start, end)} will return that emoji.
+  /// E.g., getEmojiStart/End("AB\ud83d\ude00", 1) will return (-1,-1) since 'B' is not part an emoji;
+  ///       getEmojiStart/End("AB\ud83d\ude00", 3) will return [2,4), note that "\ud83d\ude00" contains 2 Chars.
+  /// Returns -1 otherwise.
+  ///@param charSequence the whole sequence
+  ///@param offset index of the emoji to look up
+  ///@return the start index inclusively/end index exclusively
+  int getEmojiStart(
+    jni.JObject charSequence,
+    int offset,
+  ) {
+    return _getEmojiStart(reference, charSequence.reference, offset).integer;
+  }
+
+  static final _getEmojiEnd = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("EmojiCompat__getEmojiEnd")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public int getEmojiEnd(java.lang.CharSequence charSequence, int offset)
+  ///
+  /// see \#getEmojiStart(CharSequence, int).
+  int getEmojiEnd(
+    jni.JObject charSequence,
+    int offset,
+  ) {
+    return _getEmojiEnd(reference, charSequence.reference, offset).integer;
+  }
+
+  static final _handleOnKeyDown = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Int32,
+                  ffi.Pointer<ffi.Void>)>>("EmojiCompat__handleOnKeyDown")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, int, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public boolean handleOnKeyDown(android.text.Editable editable, int keyCode, android.view.KeyEvent event)
+  ///
+  /// Handles onKeyDown commands from a KeyListener and if {@code keyCode} is one of
+  /// KeyEvent\#KEYCODE_DEL or KeyEvent\#KEYCODE_FORWARD_DEL it tries to delete an
+  /// EmojiSpan from an Editable. Returns {@code true} if an EmojiSpan is
+  /// deleted with the characters it covers.
+  /// <p/>
+  /// If there is a selection where selection start is not equal to selection end, does not
+  /// delete.
+  /// <p/>
+  /// When used on devices running API 18 or below, always returns {@code false}.
+  ///@param editable Editable instance passed to KeyListener\#onKeyDown(android.view.View,
+  ///                 Editable, int, KeyEvent)
+  ///@param keyCode keyCode passed to KeyListener\#onKeyDown(android.view.View, Editable,
+  ///                int, KeyEvent)
+  ///@param event KeyEvent passed to KeyListener\#onKeyDown(android.view.View, Editable,
+  ///              int, KeyEvent)
+  ///@return {@code true} if an EmojiSpan is deleted
+  static bool handleOnKeyDown(
+    jni.JObject editable,
+    int keyCode,
+    jni.JObject event,
+  ) {
+    return _handleOnKeyDown(editable.reference, keyCode, event.reference)
+        .boolean;
+  }
+
+  static final _handleDeleteSurroundingText = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Int32,
+                  ffi.Uint8)>>("EmojiCompat__handleDeleteSurroundingText")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int, int, int)>();
+
+  /// from: static public boolean handleDeleteSurroundingText(android.view.inputmethod.InputConnection inputConnection, android.text.Editable editable, int beforeLength, int afterLength, boolean inCodePoints)
+  ///
+  /// Handles deleteSurroundingText commands from InputConnection and tries to delete an
+  /// EmojiSpan from an Editable. Returns {@code true} if an EmojiSpan is
+  /// deleted.
+  /// <p/>
+  /// If there is a selection where selection start is not equal to selection end, does not
+  /// delete.
+  /// <p/>
+  /// When used on devices running API 18 or below, always returns {@code false}.
+  ///@param inputConnection InputConnection instance
+  ///@param editable TextView.Editable instance
+  ///@param beforeLength the number of characters before the cursor to be deleted
+  ///@param afterLength the number of characters after the cursor to be deleted
+  ///@param inCodePoints {@code true} if length parameters are in codepoints
+  ///@return {@code true} if an EmojiSpan is deleted
+  static bool handleDeleteSurroundingText(
+    jni.JObject inputConnection,
+    jni.JObject editable,
+    int beforeLength,
+    int afterLength,
+    bool inCodePoints,
+  ) {
+    return _handleDeleteSurroundingText(inputConnection.reference,
+            editable.reference, beforeLength, afterLength, inCodePoints ? 1 : 0)
+        .boolean;
+  }
+
+  static final _hasEmojiGlyph = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("EmojiCompat__hasEmojiGlyph")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean hasEmojiGlyph(java.lang.CharSequence sequence)
+  ///
+  /// Returns {@code true} if EmojiCompat is capable of rendering an emoji. When used on devices
+  /// running API 18 or below, always returns {@code false}.
+  ///@deprecated use getEmojiMatch which returns more accurate lookup information.
+  ///@param sequence CharSequence representing the emoji
+  ///@return {@code true} if EmojiCompat can render given emoji, cannot be {@code null}
+  ///@throws IllegalStateException if not initialized yet
+  bool hasEmojiGlyph(
+    jni.JObject sequence,
+  ) {
+    return _hasEmojiGlyph(reference, sequence.reference).boolean;
+  }
+
+  static final _hasEmojiGlyph1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("EmojiCompat__hasEmojiGlyph1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public boolean hasEmojiGlyph(java.lang.CharSequence sequence, int metadataVersion)
+  ///
+  /// Returns {@code true} if EmojiCompat is capable of rendering an emoji at the given metadata
+  /// version. When used on devices running API 18 or below, always returns {@code false}.
+  ///@deprecated use getEmojiMatch which returns more accurate lookup information.
+  ///@param sequence CharSequence representing the emoji
+  ///@param metadataVersion the metadata version to check against, should be greater than or
+  ///                        equal to {@code 0},
+  ///@return {@code true} if EmojiCompat can render given emoji, cannot be {@code null}
+  ///@throws IllegalStateException if not initialized yet
+  bool hasEmojiGlyph1(
+    jni.JObject sequence,
+    int metadataVersion,
+  ) {
+    return _hasEmojiGlyph1(reference, sequence.reference, metadataVersion)
+        .boolean;
+  }
+
+  static final _getEmojiMatch = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("EmojiCompat__getEmojiMatch")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public int getEmojiMatch(java.lang.CharSequence sequence, int metadataVersion)
+  ///
+  /// Attempts to lookup the entire sequence at the specified metadata version and returns what
+  /// the runtime match behavior would be.
+  ///
+  /// To be used by keyboards to show or hide emoji in response to specific metadata support.
+  ///@see \#EMOJI_SUPPORTED
+  ///@see \#EMOJI_UNSUPPORTED
+  ///@see \#EMOJI_FALLBACK
+  ///@param sequence CharSequence representing an emoji
+  ///@param metadataVersion the metada version to check against, should be greater than or
+  ///                        equal to {@code 0},
+  ///@return A match result, or decomposes if replaceAll would cause partial subsequence matches.
+  int getEmojiMatch(
+    jni.JObject sequence,
+    int metadataVersion,
+  ) {
+    return _getEmojiMatch(reference, sequence.reference, metadataVersion)
+        .integer;
+  }
+
+  static final _process = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("EmojiCompat__process")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.CharSequence process(java.lang.CharSequence charSequence)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Checks a given CharSequence for emojis, and adds EmojiSpans if any emojis are found. When
+  /// used on devices running API 18 or below, returns the given {@code charSequence} without
+  /// processing it.
+  ///@param charSequence CharSequence to add the EmojiSpans
+  ///@throws IllegalStateException if not initialized yet
+  ///@see \#process(CharSequence, int, int)
+  jni.JObject process(
+    jni.JObject charSequence,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_process(reference, charSequence.reference).object);
+  }
+
+  static final _process1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Int32)>>("EmojiCompat__process1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int, int)>();
+
+  /// from: public java.lang.CharSequence process(java.lang.CharSequence charSequence, int start, int end)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Checks a given CharSequence for emojis, and adds EmojiSpans if any emojis are found.
+  ///
+  /// <ul>
+  /// <li>If no emojis are found, {@code charSequence} given as the input is returned without
+  /// any changes. i.e. charSequence is a String, and no emojis are found, the same String is
+  /// returned.</li>
+  /// <li>If the given input is not a Spannable (such as String), and at least one emoji is found
+  /// a new android.text.Spannable instance is returned. </li>
+  /// <li>If the given input is a Spannable, the same instance is returned. </li>
+  /// </ul>
+  /// When used on devices running API 18 or below, returns the given {@code charSequence} without
+  /// processing it.
+  ///@param charSequence CharSequence to add the EmojiSpans, cannot be {@code null}
+  ///@param start start index in the charSequence to look for emojis, should be greater than or
+  ///              equal to {@code 0}, also less than or equal to {@code charSequence.length()}
+  ///@param end end index in the charSequence to look for emojis, should be greater than or equal
+  ///              to {@code start} parameter, also less than or equal to
+  ///              {@code charSequence.length()}
+  ///@throws IllegalStateException if not initialized yet
+  ///@throws IllegalArgumentException in the following cases:
+  ///                                  {@code start < 0}, {@code end < 0}, {@code end < start},
+  ///                                  {@code start > charSequence.length()},
+  ///                                  {@code end > charSequence.length()}
+  jni.JObject process1(
+    jni.JObject charSequence,
+    int start,
+    int end,
+  ) {
+    return const jni.JObjectType().fromRef(
+        _process1(reference, charSequence.reference, start, end).object);
+  }
+
+  static final _process2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Int32,
+                  ffi.Int32)>>("EmojiCompat__process2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int, int, int)>();
+
+  /// from: public java.lang.CharSequence process(java.lang.CharSequence charSequence, int start, int end, int maxEmojiCount)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Checks a given CharSequence for emojis, and adds EmojiSpans if any emojis are found.
+  ///
+  /// <ul>
+  /// <li>If no emojis are found, {@code charSequence} given as the input is returned without
+  /// any changes. i.e. charSequence is a String, and no emojis are found, the same String is
+  /// returned.</li>
+  /// <li>If the given input is not a Spannable (such as String), and at least one emoji is found
+  /// a new android.text.Spannable instance is returned. </li>
+  /// <li>If the given input is a Spannable, the same instance is returned. </li>
+  /// </ul>
+  /// When used on devices running API 18 or below, returns the given {@code charSequence} without
+  /// processing it.
+  ///@param charSequence CharSequence to add the EmojiSpans, cannot be {@code null}
+  ///@param start start index in the charSequence to look for emojis, should be greater than or
+  ///              equal to {@code 0}, also less than or equal to {@code charSequence.length()}
+  ///@param end end index in the charSequence to look for emojis, should be greater than or
+  ///            equal to {@code start} parameter, also less than or equal to
+  ///            {@code charSequence.length()}
+  ///@param maxEmojiCount maximum number of emojis in the {@code charSequence}, should be greater
+  ///                      than or equal to {@code 0}
+  ///@throws IllegalStateException if not initialized yet
+  ///@throws IllegalArgumentException in the following cases:
+  ///                                  {@code start < 0}, {@code end < 0}, {@code end < start},
+  ///                                  {@code start > charSequence.length()},
+  ///                                  {@code end > charSequence.length()}
+  ///                                  {@code maxEmojiCount < 0}
+  jni.JObject process2(
+    jni.JObject charSequence,
+    int start,
+    int end,
+    int maxEmojiCount,
+  ) {
+    return const jni.JObjectType().fromRef(
+        _process2(reference, charSequence.reference, start, end, maxEmojiCount)
+            .object);
+  }
+
+  static final _process3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Int32,
+                  ffi.Int32,
+                  ffi.Int32)>>("EmojiCompat__process3")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              int, int, int, int)>();
+
+  /// from: public java.lang.CharSequence process(java.lang.CharSequence charSequence, int start, int end, int maxEmojiCount, int replaceStrategy)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Checks a given CharSequence for emojis, and adds EmojiSpans if any emojis are found.
+  ///
+  /// <ul>
+  /// <li>If no emojis are found, {@code charSequence} given as the input is returned without
+  /// any changes. i.e. charSequence is a String, and no emojis are found, the same String is
+  /// returned.</li>
+  /// <li>If the given input is not a Spannable (such as String), and at least one emoji is found
+  /// a new android.text.Spannable instance is returned. </li>
+  /// <li>If the given input is a Spannable, the same instance is returned. </li>
+  /// </ul>
+  /// When used on devices running API 18 or below, returns the given {@code charSequence} without
+  /// processing it.
+  ///@param charSequence CharSequence to add the EmojiSpans, cannot be {@code null}
+  ///@param start start index in the charSequence to look for emojis, should be greater than or
+  ///              equal to {@code 0}, also less than or equal to {@code charSequence.length()}
+  ///@param end end index in the charSequence to look for emojis, should be greater than or
+  ///            equal to {@code start} parameter, also less than or equal to
+  ///            {@code charSequence.length()}
+  ///@param maxEmojiCount maximum number of emojis in the {@code charSequence}, should be greater
+  ///                      than or equal to {@code 0}
+  ///@param replaceStrategy whether to replace all emoji with EmojiSpans, should be one of
+  ///                        \#REPLACE_STRATEGY_DEFAULT,
+  ///                        \#REPLACE_STRATEGY_NON_EXISTENT,
+  ///                        \#REPLACE_STRATEGY_ALL
+  ///@throws IllegalStateException if not initialized yet
+  ///@throws IllegalArgumentException in the following cases:
+  ///                                  {@code start < 0}, {@code end < 0}, {@code end < start},
+  ///                                  {@code start > charSequence.length()},
+  ///                                  {@code end > charSequence.length()}
+  ///                                  {@code maxEmojiCount < 0}
+  jni.JObject process3(
+    jni.JObject charSequence,
+    int start,
+    int end,
+    int maxEmojiCount,
+    int replaceStrategy,
+  ) {
+    return const jni.JObjectType().fromRef(_process3(reference,
+            charSequence.reference, start, end, maxEmojiCount, replaceStrategy)
+        .object);
+  }
+
+  static final _getAssetSignature = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat__getAssetSignature")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getAssetSignature()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns signature for the currently loaded emoji assets. The signature is a SHA that is
+  /// constructed using emoji assets. Can be used to detect if currently loaded asset is different
+  /// then previous executions. When used on devices running API 18 or below, returns empty string.
+  ///@throws IllegalStateException if not initialized yet
+  jni.JString getAssetSignature() {
+    return const jni.JStringType()
+        .fromRef(_getAssetSignature(reference).object);
+  }
+
+  static final _updateEditorInfo = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("EmojiCompat__updateEditorInfo")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void updateEditorInfo(android.view.inputmethod.EditorInfo outAttrs)
+  ///
+  /// Updates the EditorInfo attributes in order to communicate information to Keyboards. When
+  /// used on devices running API 18 or below, does not update EditorInfo attributes.
+  ///
+  /// This is called from EditText integrations that use EmojiEditTextHelper. Custom
+  /// widgets that allow IME not subclassing EditText should call this method when creating an
+  /// input connection.
+  ///
+  /// When EmojiCompat is not in \#LOAD_STATE_SUCCEEDED, this method has no effect.
+  ///
+  /// Calling this method on API levels below API 19 will have no effect, as EmojiCompat may
+  /// never be configured. However, it is always safe to call, even on older API levels.
+  ///@param outAttrs EditorInfo instance passed to
+  ///                 android.widget.TextView\#onCreateInputConnection(EditorInfo)
+  ///@see \#EDITOR_INFO_METAVERSION_KEY
+  ///@see \#EDITOR_INFO_REPLACE_ALL_KEY
+  void updateEditorInfo(
+    jni.JObject outAttrs,
+  ) {
+    return _updateEditorInfo(reference, outAttrs.reference).check();
+  }
+}
+
+final class $EmojiCompatType extends jni.JObjType<EmojiCompat> {
+  const $EmojiCompatType();
+
+  @override
+  String get signature => r"Landroidx/emoji2/text/EmojiCompat;";
+
+  @override
+  EmojiCompat fromRef(jni.JObjectPtr ref) => EmojiCompat.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($EmojiCompatType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($EmojiCompatType) && other is $EmojiCompatType;
+  }
+}
+
+/// from: androidx.emoji2.text.EmojiCompat$Config
+///
+/// Configuration class for EmojiCompat. Changes to the values will be ignored after
+/// \#init(Config) is called.
+///@see \#init(EmojiCompat.Config)
+class EmojiCompat_Config extends jni.JObject {
+  @override
+  late final jni.JObjType<EmojiCompat_Config> $type = type;
+
+  EmojiCompat_Config.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $EmojiCompat_ConfigType();
+  static final _new0 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_Config__new0")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void <init>(androidx.emoji2.text.EmojiCompat.MetadataRepoLoader metadataLoader)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Default constructor.
+  ///@param metadataLoader MetadataRepoLoader instance, cannot be {@code null}
+  factory EmojiCompat_Config(
+    EmojiCompat_MetadataRepoLoader metadataLoader,
+  ) {
+    return EmojiCompat_Config.fromRef(_new0(metadataLoader.reference).object);
+  }
+
+  static final _registerInitCallback = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_Config__registerInitCallback")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config registerInitCallback(androidx.emoji2.text.EmojiCompat.InitCallback initCallback)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Registers an initialization callback.
+  ///@param initCallback the initialization callback to register, cannot be {@code null}
+  ///@return EmojiCompat.Config instance
+  EmojiCompat_Config registerInitCallback(
+    EmojiCompat_InitCallback initCallback,
+  ) {
+    return const $EmojiCompat_ConfigType().fromRef(
+        _registerInitCallback(reference, initCallback.reference).object);
+  }
+
+  static final _unregisterInitCallback = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_Config__unregisterInitCallback")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config unregisterInitCallback(androidx.emoji2.text.EmojiCompat.InitCallback initCallback)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Unregisters a callback that was added before.
+  ///@param initCallback the initialization callback to be removed, cannot be {@code null}
+  ///@return EmojiCompat.Config instance
+  EmojiCompat_Config unregisterInitCallback(
+    EmojiCompat_InitCallback initCallback,
+  ) {
+    return const $EmojiCompat_ConfigType().fromRef(
+        _unregisterInitCallback(reference, initCallback.reference).object);
+  }
+
+  static final _setReplaceAll = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Uint8)>>("EmojiCompat_Config__setReplaceAll")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config setReplaceAll(boolean replaceAll)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Determines whether EmojiCompat should replace all the emojis it finds with the
+  /// EmojiSpans. By default EmojiCompat tries its best to understand if the system already
+  /// can render an emoji and do not replace those emojis.
+  ///@param replaceAll replace all emojis found with EmojiSpans
+  ///@return EmojiCompat.Config instance
+  EmojiCompat_Config setReplaceAll(
+    bool replaceAll,
+  ) {
+    return const $EmojiCompat_ConfigType()
+        .fromRef(_setReplaceAll(reference, replaceAll ? 1 : 0).object);
+  }
+
+  static final _setUseEmojiAsDefaultStyle = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Uint8)>>("EmojiCompat_Config__setUseEmojiAsDefaultStyle")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config setUseEmojiAsDefaultStyle(boolean useEmojiAsDefaultStyle)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Determines whether EmojiCompat should use the emoji presentation style for emojis
+  /// that have text style as default. By default, the text style would be used, unless these
+  /// are followed by the U+FE0F variation selector.
+  /// Details about emoji presentation and text presentation styles can be found here:
+  /// http://unicode.org/reports/tr51/\#Presentation_Style
+  /// If useEmojiAsDefaultStyle is true, the emoji presentation style will be used for all
+  /// emojis, including potentially unexpected ones (such as digits or other keycap emojis). If
+  /// this is not the expected behaviour, method
+  /// \#setUseEmojiAsDefaultStyle(boolean, List) can be used to specify the
+  /// exception emojis that should be still presented as text style.
+  ///@param useEmojiAsDefaultStyle whether to use the emoji style presentation for all emojis
+  ///                               that would be presented as text style by default
+  EmojiCompat_Config setUseEmojiAsDefaultStyle(
+    bool useEmojiAsDefaultStyle,
+  ) {
+    return const $EmojiCompat_ConfigType().fromRef(
+        _setUseEmojiAsDefaultStyle(reference, useEmojiAsDefaultStyle ? 1 : 0)
+            .object);
+  }
+
+  static final _setUseEmojiAsDefaultStyle1 = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Uint8,
+                      ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_Config__setUseEmojiAsDefaultStyle1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, int, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config setUseEmojiAsDefaultStyle(boolean useEmojiAsDefaultStyle, java.util.List<java.lang.Integer> emojiAsDefaultStyleExceptions)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// @see \#setUseEmojiAsDefaultStyle(boolean)
+  ///@param emojiAsDefaultStyleExceptions Contains the exception emojis which will be still
+  ///                                      presented as text style even if the
+  ///                                      useEmojiAsDefaultStyle flag is set to {@code true}.
+  ///                                      This list will be ignored if useEmojiAsDefaultStyle
+  ///                                      is {@code false}. Note that emojis with default
+  ///                                      emoji style presentation will remain emoji style
+  ///                                      regardless the value of useEmojiAsDefaultStyle or
+  ///                                      whether they are included in the exceptions list or
+  ///                                      not. When no exception is wanted, the method
+  ///                                      \#setUseEmojiAsDefaultStyle(boolean) should
+  ///                                      be used instead.
+  EmojiCompat_Config setUseEmojiAsDefaultStyle1(
+    bool useEmojiAsDefaultStyle,
+    jni.JList<jni.JInteger> emojiAsDefaultStyleExceptions,
+  ) {
+    return const $EmojiCompat_ConfigType().fromRef(_setUseEmojiAsDefaultStyle1(
+            reference,
+            useEmojiAsDefaultStyle ? 1 : 0,
+            emojiAsDefaultStyleExceptions.reference)
+        .object);
+  }
+
+  static final _setEmojiSpanIndicatorEnabled = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Uint8)>>(
+          "EmojiCompat_Config__setEmojiSpanIndicatorEnabled")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config setEmojiSpanIndicatorEnabled(boolean emojiSpanIndicatorEnabled)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Determines whether a background will be drawn for the emojis that are found and
+  /// replaced by EmojiCompat. Should be used only for debugging purposes. The indicator color
+  /// can be set using \#setEmojiSpanIndicatorColor(int).
+  ///@param emojiSpanIndicatorEnabled when {@code true} a background is drawn for each emoji
+  ///                                  that is replaced
+  EmojiCompat_Config setEmojiSpanIndicatorEnabled(
+    bool emojiSpanIndicatorEnabled,
+  ) {
+    return const $EmojiCompat_ConfigType().fromRef(
+        _setEmojiSpanIndicatorEnabled(
+                reference, emojiSpanIndicatorEnabled ? 1 : 0)
+            .object);
+  }
+
+  static final _setEmojiSpanIndicatorColor = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("EmojiCompat_Config__setEmojiSpanIndicatorColor")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config setEmojiSpanIndicatorColor(int color)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Sets the color used as emoji span indicator. The default value is
+  /// Color\#GREEN Color.GREEN.
+  ///@see \#setEmojiSpanIndicatorEnabled(boolean)
+  EmojiCompat_Config setEmojiSpanIndicatorColor(
+    int color,
+  ) {
+    return const $EmojiCompat_ConfigType()
+        .fromRef(_setEmojiSpanIndicatorColor(reference, color).object);
+  }
+
+  static final _setMetadataLoadStrategy = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("EmojiCompat_Config__setMetadataLoadStrategy")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config setMetadataLoadStrategy(int strategy)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Determines the strategy to start loading the metadata. By default EmojiCompat
+  /// will start loading the metadata during EmojiCompat\#init(Config). When set to
+  /// EmojiCompat\#LOAD_STRATEGY_MANUAL, you should call EmojiCompat\#load() to
+  /// initiate metadata loading.
+  /// <p/>
+  /// Default implementations of EmojiCompat.MetadataRepoLoader start a thread
+  /// during their EmojiCompat.MetadataRepoLoader\#load functions. Just instantiating
+  /// and starting a thread might take time especially in older devices. Since
+  /// EmojiCompat\#init(Config) has to be called before any EmojiCompat widgets are
+  /// inflated, this results in time spent either on your Application.onCreate or Activity
+  /// .onCreate. If you'd like to gain more control on when to start loading the metadata
+  /// and be able to call EmojiCompat\#init(Config) with absolute minimum time cost you
+  /// can use EmojiCompat\#LOAD_STRATEGY_MANUAL.
+  /// <p/>
+  /// When set to EmojiCompat\#LOAD_STRATEGY_MANUAL, EmojiCompat will wait
+  /// for \#load() to be called by the developer in order to start loading metadata,
+  /// therefore you should call EmojiCompat\#load() to initiate metadata loading.
+  /// \#load() can be called from any thread.
+  /// <pre>
+  /// EmojiCompat.Config config = new FontRequestEmojiCompatConfig(context, fontRequest)
+  ///         .setMetadataLoadStrategy(EmojiCompat.LOAD_STRATEGY_MANUAL);
+  ///
+  /// // EmojiCompat will not start loading metadata and MetadataRepoLoader\#load(...)
+  /// // will not be called
+  /// EmojiCompat.init(config);
+  ///
+  /// // At any time (i.e. idle time or executorService is ready)
+  /// // call EmojiCompat\#load() to start loading metadata.
+  /// executorService.execute(() -> EmojiCompat.get().load());
+  /// </pre>
+  ///@param strategy one of EmojiCompat\#LOAD_STRATEGY_DEFAULT,
+  ///                  EmojiCompat\#LOAD_STRATEGY_MANUAL
+  EmojiCompat_Config setMetadataLoadStrategy(
+    int strategy,
+  ) {
+    return const $EmojiCompat_ConfigType()
+        .fromRef(_setMetadataLoadStrategy(reference, strategy).object);
+  }
+
+  static final _setSpanFactory = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("EmojiCompat_Config__setSpanFactory")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config setSpanFactory(androidx.emoji2.text.EmojiCompat.SpanFactory factory)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Set the span factory used to actually draw emoji replacements.
+  ///@param factory custum span factory that can draw the emoji replacements
+  ///@return this
+  EmojiCompat_Config setSpanFactory(
+    EmojiCompat_SpanFactory factory0,
+  ) {
+    return const $EmojiCompat_ConfigType()
+        .fromRef(_setSpanFactory(reference, factory0.reference).object);
+  }
+
+  static final _setGlyphChecker = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_Config__setGlyphChecker")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config setGlyphChecker(androidx.emoji2.text.EmojiCompat.GlyphChecker glyphChecker)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// The interface that is used by EmojiCompat in order to check if a given emoji can be
+  /// rendered by the system.
+  ///@param glyphChecker GlyphChecker instance to be used.
+  EmojiCompat_Config setGlyphChecker(
+    EmojiCompat_GlyphChecker glyphChecker,
+  ) {
+    return const $EmojiCompat_ConfigType()
+        .fromRef(_setGlyphChecker(reference, glyphChecker.reference).object);
+  }
+
+  static final _getMetadataRepoLoader = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_Config__getMetadataRepoLoader")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected final androidx.emoji2.text.EmojiCompat.MetadataRepoLoader getMetadataRepoLoader()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the MetadataRepoLoader.
+  EmojiCompat_MetadataRepoLoader getMetadataRepoLoader() {
+    return const $EmojiCompat_MetadataRepoLoaderType()
+        .fromRef(_getMetadataRepoLoader(reference).object);
+  }
+}
+
+final class $EmojiCompat_ConfigType extends jni.JObjType<EmojiCompat_Config> {
+  const $EmojiCompat_ConfigType();
+
+  @override
+  String get signature => r"Landroidx/emoji2/text/EmojiCompat$Config;";
+
+  @override
+  EmojiCompat_Config fromRef(jni.JObjectPtr ref) =>
+      EmojiCompat_Config.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($EmojiCompat_ConfigType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($EmojiCompat_ConfigType) &&
+        other is $EmojiCompat_ConfigType;
+  }
+}
+
+/// from: androidx.emoji2.text.EmojiCompat$MetadataRepoLoaderCallback
+///
+/// Callback to inform EmojiCompat about the state of the metadata load. Passed to
+/// MetadataRepoLoader during MetadataRepoLoader\#load(MetadataRepoLoaderCallback) call.
+class EmojiCompat_MetadataRepoLoaderCallback extends jni.JObject {
+  @override
+  late final jni.JObjType<EmojiCompat_MetadataRepoLoaderCallback> $type = type;
+
+  EmojiCompat_MetadataRepoLoaderCallback.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $EmojiCompat_MetadataRepoLoaderCallbackType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "EmojiCompat_MetadataRepoLoaderCallback__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory EmojiCompat_MetadataRepoLoaderCallback() {
+    return EmojiCompat_MetadataRepoLoaderCallback.fromRef(_new0().object);
+  }
+
+  static final _onLoaded = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_MetadataRepoLoaderCallback__onLoaded")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract void onLoaded(androidx.emoji2.text.MetadataRepo metadataRepo)
+  ///
+  /// Called by MetadataRepoLoader when metadata is loaded successfully.
+  ///@param metadataRepo MetadataRepo instance, cannot be {@code null}
+  void onLoaded(
+    jni.JObject metadataRepo,
+  ) {
+    return _onLoaded(reference, metadataRepo.reference).check();
+  }
+
+  static final _onFailed = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_MetadataRepoLoaderCallback__onFailed")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract void onFailed(java.lang.Throwable throwable)
+  ///
+  /// Called by MetadataRepoLoader if an error occurs while loading the metadata.
+  ///@param throwable the exception that caused the failure, {@code nullable}
+  void onFailed(
+    jni.JObject throwable,
+  ) {
+    return _onFailed(reference, throwable.reference).check();
+  }
+}
+
+final class $EmojiCompat_MetadataRepoLoaderCallbackType
+    extends jni.JObjType<EmojiCompat_MetadataRepoLoaderCallback> {
+  const $EmojiCompat_MetadataRepoLoaderCallbackType();
+
+  @override
+  String get signature =>
+      r"Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback;";
+
+  @override
+  EmojiCompat_MetadataRepoLoaderCallback fromRef(jni.JObjectPtr ref) =>
+      EmojiCompat_MetadataRepoLoaderCallback.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($EmojiCompat_MetadataRepoLoaderCallbackType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($EmojiCompat_MetadataRepoLoaderCallbackType) &&
+        other is $EmojiCompat_MetadataRepoLoaderCallbackType;
+  }
+}
+
+/// from: androidx.emoji2.text.EmojiCompat$GlyphChecker
+///
+/// Interface to check if a given emoji exists on the system.
+class EmojiCompat_GlyphChecker extends jni.JObject {
+  @override
+  late final jni.JObjType<EmojiCompat_GlyphChecker> $type = type;
+
+  EmojiCompat_GlyphChecker.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $EmojiCompat_GlyphCheckerType();
+  static final _hasGlyph = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Int32,
+                  ffi.Int32)>>("EmojiCompat_GlyphChecker__hasGlyph")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int, int, int)>();
+
+  /// from: public abstract boolean hasGlyph(java.lang.CharSequence charSequence, int start, int end, int sdkAdded)
+  ///
+  /// Return {@code true} if the emoji that is in {@code charSequence} between
+  /// {@code start}(inclusive) and {@code end}(exclusive) can be rendered on the system
+  /// using the default Typeface.
+  ///
+  /// This function is called after an emoji is identified in the given {@code charSequence}
+  /// and EmojiCompat wants to know if that emoji can be rendered on the system. The result
+  /// of this call will be cached and the same emoji sequence won't be asked for the same
+  /// EmojiCompat instance.
+  ///
+  /// When the function returns {@code true}, it will mean that the system can render the
+  /// emoji. In that case if Config\#setReplaceAll is set to {@code false}, then no
+  /// EmojiSpan will be added in the final emoji processing result.
+  ///
+  /// When the function returns {@code false}, it will mean that the system cannot render
+  /// the given emoji, therefore an EmojiSpan will be added to the final emoji
+  /// processing result.
+  ///
+  /// The default implementation of this class uses
+  /// androidx.core.graphics.PaintCompat\#hasGlyph(Paint, String) function to check
+  /// if the emoji can be rendered on the system. This is required even if EmojiCompat
+  /// knows about the SDK Version that the emoji was added on AOSP. Just the {@code sdkAdded}
+  /// information is not enough to reliably decide if emoji can be rendered since this
+  /// information may not be consistent across all the OEMs and all the Android versions.
+  ///
+  /// With this interface you can apply your own heuristics to check if the emoji can be
+  /// rendered on the system. For example, if you'd like to rely on the {@code sdkAdded}
+  /// information, and some predefined OEMs, it is possible to write the following code
+  /// snippet.
+  ///
+  /// {@sample frameworks/support/samples/SupportEmojiDemos/src/main/java/com/example/android/support/text/emoji/sample/GlyphCheckerSample.java glyphchecker}
+  ///@param charSequence the CharSequence that is being processed
+  ///@param start the inclusive starting offset for the emoji in the {@code charSequence}
+  ///@param end the exclusive end offset for the emoji in the {@code charSequence}
+  ///@param sdkAdded the API version that the emoji was added in AOSP
+  ///@return true if the given sequence can be rendered as a single glyph, otherwise false.
+  bool hasGlyph(
+    jni.JObject charSequence,
+    int start,
+    int end,
+    int sdkAdded,
+  ) {
+    return _hasGlyph(reference, charSequence.reference, start, end, sdkAdded)
+        .boolean;
+  }
+}
+
+final class $EmojiCompat_GlyphCheckerType
+    extends jni.JObjType<EmojiCompat_GlyphChecker> {
+  const $EmojiCompat_GlyphCheckerType();
+
+  @override
+  String get signature => r"Landroidx/emoji2/text/EmojiCompat$GlyphChecker;";
+
+  @override
+  EmojiCompat_GlyphChecker fromRef(jni.JObjectPtr ref) =>
+      EmojiCompat_GlyphChecker.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($EmojiCompat_GlyphCheckerType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($EmojiCompat_GlyphCheckerType) &&
+        other is $EmojiCompat_GlyphCheckerType;
+  }
+}
+
+/// from: androidx.emoji2.text.EmojiCompat$MetadataRepoLoader
+///
+/// Interface to load emoji metadata.
+class EmojiCompat_MetadataRepoLoader extends jni.JObject {
+  @override
+  late final jni.JObjType<EmojiCompat_MetadataRepoLoader> $type = type;
+
+  EmojiCompat_MetadataRepoLoader.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $EmojiCompat_MetadataRepoLoaderType();
+  static final _load = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_MetadataRepoLoader__load")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract void load(androidx.emoji2.text.EmojiCompat.MetadataRepoLoaderCallback loaderCallback)
+  ///
+  /// Start loading the metadata. When the loading operation is finished MetadataRepoLoaderCallback\#onLoaded(MetadataRepo) or
+  /// MetadataRepoLoaderCallback\#onFailed(Throwable) should be called. When used on
+  /// devices running API 18 or below, this function is never called.
+  ///@param loaderCallback callback to signal the loading state
+  void load(
+    EmojiCompat_MetadataRepoLoaderCallback loaderCallback,
+  ) {
+    return _load(reference, loaderCallback.reference).check();
+  }
+}
+
+final class $EmojiCompat_MetadataRepoLoaderType
+    extends jni.JObjType<EmojiCompat_MetadataRepoLoader> {
+  const $EmojiCompat_MetadataRepoLoaderType();
+
+  @override
+  String get signature =>
+      r"Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoader;";
+
+  @override
+  EmojiCompat_MetadataRepoLoader fromRef(jni.JObjectPtr ref) =>
+      EmojiCompat_MetadataRepoLoader.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($EmojiCompat_MetadataRepoLoaderType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($EmojiCompat_MetadataRepoLoaderType) &&
+        other is $EmojiCompat_MetadataRepoLoaderType;
+  }
+}
+
+/// from: androidx.emoji2.text.EmojiCompat$InitCallback
+///
+/// Listener class for the initialization of the EmojiCompat.
+class EmojiCompat_InitCallback extends jni.JObject {
+  @override
+  late final jni.JObjType<EmojiCompat_InitCallback> $type = type;
+
+  EmojiCompat_InitCallback.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $EmojiCompat_InitCallbackType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "EmojiCompat_InitCallback__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory EmojiCompat_InitCallback() {
+    return EmojiCompat_InitCallback.fromRef(_new0().object);
+  }
+
+  static final _onInitialized = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_InitCallback__onInitialized")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void onInitialized()
+  ///
+  /// Called when EmojiCompat is initialized and the emoji data is loaded. When used on devices
+  /// running API 18 or below, this function is always called.
+  void onInitialized() {
+    return _onInitialized(reference).check();
+  }
+
+  static final _onFailed = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("EmojiCompat_InitCallback__onFailed")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void onFailed(java.lang.Throwable throwable)
+  ///
+  /// Called when an unrecoverable error occurs during EmojiCompat initialization. When used on
+  /// devices running API 18 or below, this function is never called.
+  void onFailed(
+    jni.JObject throwable,
+  ) {
+    return _onFailed(reference, throwable.reference).check();
+  }
+}
+
+final class $EmojiCompat_InitCallbackType
+    extends jni.JObjType<EmojiCompat_InitCallback> {
+  const $EmojiCompat_InitCallbackType();
+
+  @override
+  String get signature => r"Landroidx/emoji2/text/EmojiCompat$InitCallback;";
+
+  @override
+  EmojiCompat_InitCallback fromRef(jni.JObjectPtr ref) =>
+      EmojiCompat_InitCallback.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($EmojiCompat_InitCallbackType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($EmojiCompat_InitCallbackType) &&
+        other is $EmojiCompat_InitCallbackType;
+  }
+}
+
+/// from: androidx.emoji2.text.EmojiCompat$DefaultSpanFactory
+///
+/// @hide
+class EmojiCompat_DefaultSpanFactory extends jni.JObject {
+  @override
+  late final jni.JObjType<EmojiCompat_DefaultSpanFactory> $type = type;
+
+  EmojiCompat_DefaultSpanFactory.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $EmojiCompat_DefaultSpanFactoryType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "EmojiCompat_DefaultSpanFactory__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory EmojiCompat_DefaultSpanFactory() {
+    return EmojiCompat_DefaultSpanFactory.fromRef(_new0().object);
+  }
+
+  static final _createSpan = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_DefaultSpanFactory__createSpan")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public androidx.emoji2.text.EmojiSpan createSpan(androidx.emoji2.text.TypefaceEmojiRasterizer rasterizer)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns a TypefaceEmojiSpan.
+  ///@param rasterizer TypefaceEmojiRasterizer instance, which can draw the emoji onto a
+  ///                   Canvas.
+  ///@return TypefaceEmojiSpan
+  jni.JObject createSpan(
+    jni.JObject rasterizer,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_createSpan(reference, rasterizer.reference).object);
+  }
+}
+
+final class $EmojiCompat_DefaultSpanFactoryType
+    extends jni.JObjType<EmojiCompat_DefaultSpanFactory> {
+  const $EmojiCompat_DefaultSpanFactoryType();
+
+  @override
+  String get signature =>
+      r"Landroidx/emoji2/text/EmojiCompat$DefaultSpanFactory;";
+
+  @override
+  EmojiCompat_DefaultSpanFactory fromRef(jni.JObjectPtr ref) =>
+      EmojiCompat_DefaultSpanFactory.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($EmojiCompat_DefaultSpanFactoryType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($EmojiCompat_DefaultSpanFactoryType) &&
+        other is $EmojiCompat_DefaultSpanFactoryType;
+  }
+}
+
+/// from: androidx.emoji2.text.EmojiCompat$SpanFactory
+///
+/// Factory class that creates the EmojiSpans.
+///
+/// By default it creates TypefaceEmojiSpan.
+///
+/// Apps should use this only if they want to control the drawing of EmojiSpans for non-standard
+/// emoji display (for example, resizing or repositioning emoji).
+class EmojiCompat_SpanFactory extends jni.JObject {
+  @override
+  late final jni.JObjType<EmojiCompat_SpanFactory> $type = type;
+
+  EmojiCompat_SpanFactory.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $EmojiCompat_SpanFactoryType();
+  static final _createSpan = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "EmojiCompat_SpanFactory__createSpan")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract androidx.emoji2.text.EmojiSpan createSpan(androidx.emoji2.text.TypefaceEmojiRasterizer rasterizer)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Create EmojiSpan instance.
+  ///@param rasterizer TypefaceEmojiRasterizer instance, which can draw the emoji onto a
+  ///                   Canvas.
+  ///@return EmojiSpan instance that can use TypefaceEmojiRasterizer to draw emoji.
+  jni.JObject createSpan(
+    jni.JObject rasterizer,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_createSpan(reference, rasterizer.reference).object);
+  }
+}
+
+final class $EmojiCompat_SpanFactoryType
+    extends jni.JObjType<EmojiCompat_SpanFactory> {
+  const $EmojiCompat_SpanFactoryType();
+
+  @override
+  String get signature => r"Landroidx/emoji2/text/EmojiCompat$SpanFactory;";
+
+  @override
+  EmojiCompat_SpanFactory fromRef(jni.JObjectPtr ref) =>
+      EmojiCompat_SpanFactory.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($EmojiCompat_SpanFactoryType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($EmojiCompat_SpanFactoryType) &&
+        other is $EmojiCompat_SpanFactoryType;
+  }
+}
+
+/// from: androidx.emoji2.text.DefaultEmojiCompatConfig
+///
+/// The default config will use downloadable fonts to fetch the emoji compat font file.
+///
+/// It will automatically fetch the emoji compat font from a {@code ContentProvider} that is
+/// installed on the devices system image, if present.
+///
+///
+/// You should use this if you want the default emoji font from a system installed
+/// downloadable fonts provider. This is the recommended behavior for all applications unless
+/// they install a custom emoji font.
+///
+///
+/// You may need to specialize the configuration beyond this default config in some
+/// situations:
+///
+/// <ul>
+///  <li>If you are trying to install a custom emoji font through downloadable fonts use
+///  FontRequestEmojiCompatConfig instead of this method.</li>
+///  <li>If you're trying to bundle an emoji font with your APK use {@code
+///  BundledEmojiCompatConfig} in the {@code emoji2-bundled} artifact.</li>
+///  <li>If you are building an APK that will be installed on devices that won't have a
+///  downloadable fonts provider, use {@code BundledEmojiCompatConfig}.</li>
+/// </ul>
+///
+/// The downloadable font provider used by {@code DefaultEmojiCompatConfig} always satisfies
+/// the following contract:
+///
+/// <ol>
+///  <li>It <i>MUST</i> provide an intent filter for {@code androidx.content.action.LOAD_EMOJI_FONT}.
+///  </li>
+///  <li>It <i>MUST</i> respond to the query {@code emojicompat-emoji-font} with a valid emoji compat
+///  font file including metadata.</li>
+///  <li>It <i>MUST</i> provide fonts via the same contract as downloadable fonts.</li>
+///  <li>It <i>MUST</i> be installed in the system image.</li>
+/// </ol>
+class DefaultEmojiCompatConfig extends jni.JObject {
+  @override
+  late final jni.JObjType<DefaultEmojiCompatConfig> $type = type;
+
+  DefaultEmojiCompatConfig.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $DefaultEmojiCompatConfigType();
+  static final _create = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "DefaultEmojiCompatConfig__create")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public androidx.emoji2.text.FontRequestEmojiCompatConfig create(android.content.Context context)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Get the default emoji compat config for this device.
+  ///
+  /// You may further configure the returned config before passing it to EmojiCompat\#init.
+  ///
+  /// Each call to this method will return a new EmojiCompat.Config, so changes to the returned
+  /// object will not modify future return values.
+  ///@param context context for lookup
+  ///@return A valid config for downloading the emoji compat font, or null if no font provider
+  /// could be found.
+  static jni.JObject create(
+    jni.JObject context,
+  ) {
+    return const jni.JObjectType().fromRef(_create(context.reference).object);
+  }
+}
+
+final class $DefaultEmojiCompatConfigType
+    extends jni.JObjType<DefaultEmojiCompatConfig> {
+  const $DefaultEmojiCompatConfigType();
+
+  @override
+  String get signature => r"Landroidx/emoji2/text/DefaultEmojiCompatConfig;";
+
+  @override
+  DefaultEmojiCompatConfig fromRef(jni.JObjectPtr ref) =>
+      DefaultEmojiCompatConfig.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($DefaultEmojiCompatConfigType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($DefaultEmojiCompatConfigType) &&
+        other is $DefaultEmojiCompatConfigType;
+  }
+}
+
+/// from: androidx.emoji2.text.DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API28
+///
+/// Helper to lookup signatures in package manager > API 28
+///@hide
+class DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28
+    extends DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19 {
+  @override
+  late final jni
+      .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28>
+      $type = type;
+
+  DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type =
+      $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28Type();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28() {
+    return DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28
+        .fromRef(_new0().object);
+  }
+
+  static final _getSigningSignatures1 = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__getSigningSignatures1")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public android.content.pm.Signature[] getSigningSignatures(android.content.pm.PackageManager packageManager, java.lang.String providerPackage)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JArray<jni.JObject> getSigningSignatures1(
+    jni.JObject packageManager,
+    jni.JString providerPackage,
+  ) {
+    return const jni.JArrayType(jni.JObjectType()).fromRef(
+        _getSigningSignatures1(
+                reference, packageManager.reference, providerPackage.reference)
+            .object);
+  }
+}
+
+final class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28Type
+    extends jni
+    .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28> {
+  const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28Type();
+
+  @override
+  String get signature =>
+      r"Landroidx/emoji2/text/DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API28;";
+
+  @override
+  DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28 fromRef(
+          jni.JObjectPtr ref) =>
+      DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28.fromRef(
+          ref);
+
+  @override
+  jni.JObjType get superType =>
+      const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type();
+
+  @override
+  final superCount = 3;
+
+  @override
+  int get hashCode =>
+      ($DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28Type)
+          .hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType ==
+            ($DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28Type) &&
+        other
+            is $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28Type;
+  }
+}
+
+/// from: androidx.emoji2.text.DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API19
+///
+/// Actually do lookups > API 19
+///@hide
+class DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19
+    extends DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper {
+  @override
+  late final jni
+      .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19>
+      $type = type;
+
+  DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type =
+      $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19() {
+    return DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19
+        .fromRef(_new0().object);
+  }
+
+  static final _queryIntentContentProviders = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Int32)>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__queryIntentContentProviders")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.pm.PackageManager packageManager, android.content.Intent intent, int flags)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JList<jni.JObject> queryIntentContentProviders(
+    jni.JObject packageManager,
+    jni.JObject intent,
+    int flags,
+  ) {
+    return const jni.JListType(jni.JObjectType()).fromRef(
+        _queryIntentContentProviders(
+                reference, packageManager.reference, intent.reference, flags)
+            .object);
+  }
+
+  static final _getProviderInfo = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__getProviderInfo")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public android.content.pm.ProviderInfo getProviderInfo(android.content.pm.ResolveInfo resolveInfo)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject getProviderInfo(
+    jni.JObject resolveInfo,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_getProviderInfo(reference, resolveInfo.reference).object);
+  }
+}
+
+final class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type
+    extends jni
+    .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19> {
+  const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type();
+
+  @override
+  String get signature =>
+      r"Landroidx/emoji2/text/DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API19;";
+
+  @override
+  DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19 fromRef(
+          jni.JObjectPtr ref) =>
+      DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19.fromRef(
+          ref);
+
+  @override
+  jni.JObjType get superType =>
+      const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType();
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode =>
+      ($DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type)
+          .hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType ==
+            ($DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type) &&
+        other
+            is $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19Type;
+  }
+}
+
+/// from: androidx.emoji2.text.DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper
+///
+/// Helper to lookup signatures in package manager.
+///@hide
+class DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper
+    extends jni.JObject {
+  @override
+  late final jni
+      .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper> $type =
+      type;
+
+  DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type =
+      $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper() {
+    return DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper.fromRef(
+        _new0().object);
+  }
+
+  static final _getSigningSignatures = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getSigningSignatures")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public android.content.pm.Signature[] getSigningSignatures(android.content.pm.PackageManager packageManager, java.lang.String providerPackage)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Get the signing signatures for a package in package manager.
+  jni.JArray<jni.JObject> getSigningSignatures(
+    jni.JObject packageManager,
+    jni.JString providerPackage,
+  ) {
+    return const jni.JArrayType(jni.JObjectType()).fromRef(
+        _getSigningSignatures(
+                reference, packageManager.reference, providerPackage.reference)
+            .object);
+  }
+
+  static final _queryIntentContentProviders = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Int32)>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__queryIntentContentProviders")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.pm.PackageManager packageManager, android.content.Intent intent, int flags)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Get the content provider by intent.
+  jni.JList<jni.JObject> queryIntentContentProviders(
+    jni.JObject packageManager,
+    jni.JObject intent,
+    int flags,
+  ) {
+    return const jni.JListType(jni.JObjectType()).fromRef(
+        _queryIntentContentProviders(
+                reference, packageManager.reference, intent.reference, flags)
+            .object);
+  }
+
+  static final _getProviderInfo = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getProviderInfo")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public android.content.pm.ProviderInfo getProviderInfo(android.content.pm.ResolveInfo resolveInfo)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Get a ProviderInfo, if present, from a ResolveInfo
+  ///@param resolveInfo the subject
+  ///@return resolveInfo.providerInfo above API 19
+  jni.JObject getProviderInfo(
+    jni.JObject resolveInfo,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_getProviderInfo(reference, resolveInfo.reference).object);
+  }
+}
+
+final class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType
+    extends jni
+    .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper> {
+  const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType();
+
+  @override
+  String get signature =>
+      r"Landroidx/emoji2/text/DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper;";
+
+  @override
+  DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper fromRef(
+          jni.JObjectPtr ref) =>
+      DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode =>
+      ($DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType ==
+            ($DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType) &&
+        other is $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelperType;
+  }
+}
+
+/// from: androidx.emoji2.text.DefaultEmojiCompatConfig$DefaultEmojiCompatConfigFactory
+///
+/// Actual factory for generating default emoji configs, does service locator lookup internally.
+///@see DefaultEmojiCompatConfig\#create
+///@hide
+class DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory
+    extends jni.JObject {
+  @override
+  late final jni
+      .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory>
+      $type = type;
+
+  DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type =
+      $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType();
+  static final _new0 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__new0")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(androidx.emoji2.text.DefaultEmojiCompatConfig.DefaultEmojiCompatConfigHelper helper)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// @hide
+  factory DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory(
+    DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper helper,
+  ) {
+    return DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory.fromRef(
+        _new0(helper.reference).object);
+  }
+
+  static final _create = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__create")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public androidx.emoji2.text.EmojiCompat.Config create(android.content.Context context)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// @see DefaultEmojiCompatConfig\#create
+  ///@hide
+  EmojiCompat_Config create(
+    jni.JObject context,
+  ) {
+    return const $EmojiCompat_ConfigType()
+        .fromRef(_create(reference, context.reference).object);
+  }
+}
+
+final class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType
+    extends jni
+    .JObjType<DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory> {
+  const $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType();
+
+  @override
+  String get signature =>
+      r"Landroidx/emoji2/text/DefaultEmojiCompatConfig$DefaultEmojiCompatConfigFactory;";
+
+  @override
+  DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory fromRef(
+          jni.JObjectPtr ref) =>
+      DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode =>
+      ($DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType ==
+            ($DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType) &&
+        other is $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType;
+  }
+}
+
+/// from: android.os.Build$Partition
+class Build_Partition extends jni.JObject {
+  @override
+  late final jni.JObjType<Build_Partition> $type = type;
+
+  Build_Partition.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Build_PartitionType();
+
+  /// from: static public final java.lang.String PARTITION_NAME_SYSTEM
+  static const PARTITION_NAME_SYSTEM = r"""system""";
+
+  static final _getName = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Build_Partition__getName")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getName()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString getName() {
+    return const jni.JStringType().fromRef(_getName(reference).object);
+  }
+
+  static final _getFingerprint = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Build_Partition__getFingerprint")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getFingerprint()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString getFingerprint() {
+    return const jni.JStringType().fromRef(_getFingerprint(reference).object);
+  }
+
+  static final _getBuildTimeMillis = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Build_Partition__getBuildTimeMillis")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public long getBuildTimeMillis()
+  int getBuildTimeMillis() {
+    return _getBuildTimeMillis(reference).long;
+  }
+
+  static final _equals1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Build_Partition__equals1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean equals(java.lang.Object object)
+  bool equals1(
+    jni.JObject object,
+  ) {
+    return _equals1(reference, object.reference).boolean;
+  }
+
+  static final _hashCode1 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Build_Partition__hashCode1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int hashCode()
+  int hashCode1() {
+    return _hashCode1(reference).integer;
+  }
+}
+
+final class $Build_PartitionType extends jni.JObjType<Build_Partition> {
+  const $Build_PartitionType();
+
+  @override
+  String get signature => r"Landroid/os/Build$Partition;";
+
+  @override
+  Build_Partition fromRef(jni.JObjectPtr ref) => Build_Partition.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Build_PartitionType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Build_PartitionType) &&
+        other is $Build_PartitionType;
+  }
+}
+
+/// from: android.os.Build$VERSION
+class Build_VERSION extends jni.JObject {
+  @override
+  late final jni.JObjType<Build_VERSION> $type = type;
+
+  Build_VERSION.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Build_VERSIONType();
+  static final _get_BASE_OS =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__BASE_OS")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String BASE_OS
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get BASE_OS =>
+      const jni.JStringType().fromRef(_get_BASE_OS().object);
+
+  static final _get_CODENAME =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__CODENAME")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String CODENAME
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get CODENAME =>
+      const jni.JStringType().fromRef(_get_CODENAME().object);
+
+  static final _get_INCREMENTAL =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__INCREMENTAL")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String INCREMENTAL
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get INCREMENTAL =>
+      const jni.JStringType().fromRef(_get_INCREMENTAL().object);
+
+  static final _get_MEDIA_PERFORMANCE_CLASS =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__MEDIA_PERFORMANCE_CLASS")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final int MEDIA_PERFORMANCE_CLASS
+  static int get MEDIA_PERFORMANCE_CLASS =>
+      _get_MEDIA_PERFORMANCE_CLASS().integer;
+
+  static final _get_PREVIEW_SDK_INT =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__PREVIEW_SDK_INT")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final int PREVIEW_SDK_INT
+  static int get PREVIEW_SDK_INT => _get_PREVIEW_SDK_INT().integer;
+
+  static final _get_RELEASE =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__RELEASE")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String RELEASE
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get RELEASE =>
+      const jni.JStringType().fromRef(_get_RELEASE().object);
+
+  static final _get_RELEASE_OR_CODENAME =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__RELEASE_OR_CODENAME")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String RELEASE_OR_CODENAME
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get RELEASE_OR_CODENAME =>
+      const jni.JStringType().fromRef(_get_RELEASE_OR_CODENAME().object);
+
+  static final _get_RELEASE_OR_PREVIEW_DISPLAY =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String RELEASE_OR_PREVIEW_DISPLAY
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get RELEASE_OR_PREVIEW_DISPLAY =>
+      const jni.JStringType().fromRef(_get_RELEASE_OR_PREVIEW_DISPLAY().object);
+
+  static final _get_SDK =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__SDK")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String SDK
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get SDK =>
+      const jni.JStringType().fromRef(_get_SDK().object);
+
+  static final _get_SDK_INT =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__SDK_INT")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final int SDK_INT
+  static int get SDK_INT => _get_SDK_INT().integer;
+
+  static final _get_SECURITY_PATCH =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build_VERSION__SECURITY_PATCH")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String SECURITY_PATCH
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get SECURITY_PATCH =>
+      const jni.JStringType().fromRef(_get_SECURITY_PATCH().object);
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "Build_VERSION__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Build_VERSION() {
+    return Build_VERSION.fromRef(_new0().object);
+  }
+}
+
+final class $Build_VERSIONType extends jni.JObjType<Build_VERSION> {
+  const $Build_VERSIONType();
+
+  @override
+  String get signature => r"Landroid/os/Build$VERSION;";
+
+  @override
+  Build_VERSION fromRef(jni.JObjectPtr ref) => Build_VERSION.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Build_VERSIONType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Build_VERSIONType) &&
+        other is $Build_VERSIONType;
+  }
+}
+
+/// from: android.os.Build$VERSION_CODES
+class Build_VERSION_CODES extends jni.JObject {
+  @override
+  late final jni.JObjType<Build_VERSION_CODES> $type = type;
+
+  Build_VERSION_CODES.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Build_VERSION_CODESType();
+
+  /// from: static public final int BASE
+  static const BASE = 1;
+
+  /// from: static public final int BASE_1_1
+  static const BASE_1_1 = 2;
+
+  /// from: static public final int CUPCAKE
+  static const CUPCAKE = 3;
+
+  /// from: static public final int CUR_DEVELOPMENT
+  static const CUR_DEVELOPMENT = 10000;
+
+  /// from: static public final int DONUT
+  static const DONUT = 4;
+
+  /// from: static public final int ECLAIR
+  static const ECLAIR = 5;
+
+  /// from: static public final int ECLAIR_0_1
+  static const ECLAIR_0_1 = 6;
+
+  /// from: static public final int ECLAIR_MR1
+  static const ECLAIR_MR1 = 7;
+
+  /// from: static public final int FROYO
+  static const FROYO = 8;
+
+  /// from: static public final int GINGERBREAD
+  static const GINGERBREAD = 9;
+
+  /// from: static public final int GINGERBREAD_MR1
+  static const GINGERBREAD_MR1 = 10;
+
+  /// from: static public final int HONEYCOMB
+  static const HONEYCOMB = 11;
+
+  /// from: static public final int HONEYCOMB_MR1
+  static const HONEYCOMB_MR1 = 12;
+
+  /// from: static public final int HONEYCOMB_MR2
+  static const HONEYCOMB_MR2 = 13;
+
+  /// from: static public final int ICE_CREAM_SANDWICH
+  static const ICE_CREAM_SANDWICH = 14;
+
+  /// from: static public final int ICE_CREAM_SANDWICH_MR1
+  static const ICE_CREAM_SANDWICH_MR1 = 15;
+
+  /// from: static public final int JELLY_BEAN
+  static const JELLY_BEAN = 16;
+
+  /// from: static public final int JELLY_BEAN_MR1
+  static const JELLY_BEAN_MR1 = 17;
+
+  /// from: static public final int JELLY_BEAN_MR2
+  static const JELLY_BEAN_MR2 = 18;
+
+  /// from: static public final int KITKAT
+  static const KITKAT = 19;
+
+  /// from: static public final int KITKAT_WATCH
+  static const KITKAT_WATCH = 20;
+
+  /// from: static public final int LOLLIPOP
+  static const LOLLIPOP = 21;
+
+  /// from: static public final int LOLLIPOP_MR1
+  static const LOLLIPOP_MR1 = 22;
+
+  /// from: static public final int M
+  static const M = 23;
+
+  /// from: static public final int N
+  static const N = 24;
+
+  /// from: static public final int N_MR1
+  static const N_MR1 = 25;
+
+  /// from: static public final int O
+  static const O = 26;
+
+  /// from: static public final int O_MR1
+  static const O_MR1 = 27;
+
+  /// from: static public final int P
+  static const P = 28;
+
+  /// from: static public final int Q
+  static const Q = 29;
+
+  /// from: static public final int R
+  static const R = 30;
+
+  /// from: static public final int S
+  static const S = 31;
+
+  /// from: static public final int S_V2
+  static const S_V2 = 32;
+
+  /// from: static public final int TIRAMISU
+  static const TIRAMISU = 33;
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "Build_VERSION_CODES__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Build_VERSION_CODES() {
+    return Build_VERSION_CODES.fromRef(_new0().object);
+  }
+}
+
+final class $Build_VERSION_CODESType extends jni.JObjType<Build_VERSION_CODES> {
+  const $Build_VERSION_CODESType();
+
+  @override
+  String get signature => r"Landroid/os/Build$VERSION_CODES;";
+
+  @override
+  Build_VERSION_CODES fromRef(jni.JObjectPtr ref) =>
+      Build_VERSION_CODES.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Build_VERSION_CODESType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Build_VERSION_CODESType) &&
+        other is $Build_VERSION_CODESType;
+  }
+}
+
+/// from: android.os.Build
+class Build extends jni.JObject {
+  @override
+  late final jni.JObjType<Build> $type = type;
+
+  Build.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $BuildType();
+  static final _get_BOARD =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__BOARD")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String BOARD
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get BOARD =>
+      const jni.JStringType().fromRef(_get_BOARD().object);
+
+  static final _get_BOOTLOADER =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__BOOTLOADER")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String BOOTLOADER
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get BOOTLOADER =>
+      const jni.JStringType().fromRef(_get_BOOTLOADER().object);
+
+  static final _get_BRAND =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__BRAND")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String BRAND
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get BRAND =>
+      const jni.JStringType().fromRef(_get_BRAND().object);
+
+  static final _get_CPU_ABI =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__CPU_ABI")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String CPU_ABI
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get CPU_ABI =>
+      const jni.JStringType().fromRef(_get_CPU_ABI().object);
+
+  static final _get_CPU_ABI2 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__CPU_ABI2")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String CPU_ABI2
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get CPU_ABI2 =>
+      const jni.JStringType().fromRef(_get_CPU_ABI2().object);
+
+  static final _get_DEVICE =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__DEVICE")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String DEVICE
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get DEVICE =>
+      const jni.JStringType().fromRef(_get_DEVICE().object);
+
+  static final _get_DISPLAY =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__DISPLAY")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String DISPLAY
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get DISPLAY =>
+      const jni.JStringType().fromRef(_get_DISPLAY().object);
+
+  static final _get_FINGERPRINT =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__FINGERPRINT")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String FINGERPRINT
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get FINGERPRINT =>
+      const jni.JStringType().fromRef(_get_FINGERPRINT().object);
+
+  static final _get_HARDWARE =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__HARDWARE")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String HARDWARE
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get HARDWARE =>
+      const jni.JStringType().fromRef(_get_HARDWARE().object);
+
+  static final _get_HOST =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("get_Build__HOST")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String HOST
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get HOST =>
+      const jni.JStringType().fromRef(_get_HOST().object);
+
+  static final _get_ID =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("get_Build__ID")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String ID
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get ID =>
+      const jni.JStringType().fromRef(_get_ID().object);
+
+  static final _get_MANUFACTURER =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__MANUFACTURER")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String MANUFACTURER
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get MANUFACTURER =>
+      const jni.JStringType().fromRef(_get_MANUFACTURER().object);
+
+  static final _get_MODEL =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__MODEL")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String MODEL
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get MODEL =>
+      const jni.JStringType().fromRef(_get_MODEL().object);
+
+  static final _get_ODM_SKU =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__ODM_SKU")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String ODM_SKU
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get ODM_SKU =>
+      const jni.JStringType().fromRef(_get_ODM_SKU().object);
+
+  static final _get_PRODUCT =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__PRODUCT")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String PRODUCT
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get PRODUCT =>
+      const jni.JStringType().fromRef(_get_PRODUCT().object);
+
+  static final _get_RADIO =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__RADIO")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String RADIO
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get RADIO =>
+      const jni.JStringType().fromRef(_get_RADIO().object);
+
+  static final _get_SERIAL =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__SERIAL")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String SERIAL
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get SERIAL =>
+      const jni.JStringType().fromRef(_get_SERIAL().object);
+
+  static final _get_SKU =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("get_Build__SKU")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String SKU
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get SKU =>
+      const jni.JStringType().fromRef(_get_SKU().object);
+
+  static final _get_SOC_MANUFACTURER =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__SOC_MANUFACTURER")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String SOC_MANUFACTURER
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get SOC_MANUFACTURER =>
+      const jni.JStringType().fromRef(_get_SOC_MANUFACTURER().object);
+
+  static final _get_SOC_MODEL =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__SOC_MODEL")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String SOC_MODEL
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get SOC_MODEL =>
+      const jni.JStringType().fromRef(_get_SOC_MODEL().object);
+
+  static final _get_SUPPORTED_32_BIT_ABIS =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__SUPPORTED_32_BIT_ABIS")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String[] SUPPORTED_32_BIT_ABIS
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<jni.JString> get SUPPORTED_32_BIT_ABIS =>
+      const jni.JArrayType(jni.JStringType())
+          .fromRef(_get_SUPPORTED_32_BIT_ABIS().object);
+
+  static final _get_SUPPORTED_64_BIT_ABIS =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__SUPPORTED_64_BIT_ABIS")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String[] SUPPORTED_64_BIT_ABIS
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<jni.JString> get SUPPORTED_64_BIT_ABIS =>
+      const jni.JArrayType(jni.JStringType())
+          .fromRef(_get_SUPPORTED_64_BIT_ABIS().object);
+
+  static final _get_SUPPORTED_ABIS =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Build__SUPPORTED_ABIS")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String[] SUPPORTED_ABIS
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<jni.JString> get SUPPORTED_ABIS =>
+      const jni.JArrayType(jni.JStringType())
+          .fromRef(_get_SUPPORTED_ABIS().object);
+
+  static final _get_TAGS =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("get_Build__TAGS")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String TAGS
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get TAGS =>
+      const jni.JStringType().fromRef(_get_TAGS().object);
+
+  static final _get_TIME =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("get_Build__TIME")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final long TIME
+  static int get TIME => _get_TIME().long;
+
+  static final _get_TYPE =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("get_Build__TYPE")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String TYPE
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get TYPE =>
+      const jni.JStringType().fromRef(_get_TYPE().object);
+
+  /// from: static public final java.lang.String UNKNOWN
+  static const UNKNOWN = r"""unknown""";
+
+  static final _get_USER =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("get_Build__USER")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.lang.String USER
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get USER =>
+      const jni.JStringType().fromRef(_get_USER().object);
+
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("Build__new0")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Build() {
+    return Build.fromRef(_new0().object);
+  }
+
+  static final _getSerial =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Build__getSerial")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public java.lang.String getSerial()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString getSerial() {
+    return const jni.JStringType().fromRef(_getSerial().object);
+  }
+
+  static final _getFingerprintedPartitions =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Build__getFingerprintedPartitions")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public java.util.List getFingerprintedPartitions()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JList<Build_Partition> getFingerprintedPartitions() {
+    return const jni.JListType($Build_PartitionType())
+        .fromRef(_getFingerprintedPartitions().object);
+  }
+
+  static final _getRadioVersion =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Build__getRadioVersion")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public java.lang.String getRadioVersion()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString getRadioVersion() {
+    return const jni.JStringType().fromRef(_getRadioVersion().object);
+  }
+}
+
+final class $BuildType extends jni.JObjType<Build> {
+  const $BuildType();
+
+  @override
+  String get signature => r"Landroid/os/Build;";
+
+  @override
+  Build fromRef(jni.JObjectPtr ref) => Build.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($BuildType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($BuildType) && other is $BuildType;
+  }
+}
+
+/// from: java.util.HashMap
+class HashMap<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObject {
+  @override
+  late final jni.JObjType<HashMap<$K, $V>> $type = type(K, V);
+
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  HashMap.fromRef(
+    this.K,
+    this.V,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $HashMapType<$K, $V>
+      type<$K extends jni.JObject, $V extends jni.JObject>(
+    jni.JObjType<$K> K,
+    jni.JObjType<$V> V,
+  ) {
+    return $HashMapType(
+      K,
+      V,
+    );
+  }
+
+  static final _new0 = jniLookup<
+              ffi.NativeFunction<jni.JniResult Function(ffi.Int32, ffi.Float)>>(
+          "HashMap__new0")
+      .asFunction<jni.JniResult Function(int, double)>();
+
+  /// from: public void <init>(int i, float f)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory HashMap(
+    int i,
+    double f, {
+    required jni.JObjType<$K> K,
+    required jni.JObjType<$V> V,
+  }) {
+    return HashMap.fromRef(K, V, _new0(i, f).object);
+  }
+
+  static final _new1 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Int32)>>(
+              "HashMap__new1")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: public void <init>(int i)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory HashMap.new1(
+    int i, {
+    required jni.JObjType<$K> K,
+    required jni.JObjType<$V> V,
+  }) {
+    return HashMap.fromRef(K, V, _new1(i).object);
+  }
+
+  static final _new2 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("HashMap__new2")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory HashMap.new2({
+    required jni.JObjType<$K> K,
+    required jni.JObjType<$V> V,
+  }) {
+    return HashMap.fromRef(K, V, _new2().object);
+  }
+
+  static final _new3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("HashMap__new3")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(java.util.Map map)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory HashMap.new3(
+    jni.JMap<$K, $V> map, {
+    jni.JObjType<$K>? K,
+    jni.JObjType<$V>? V,
+  }) {
+    K ??= jni.lowestCommonSuperType([
+      (map.$type as jni.JMapType).K,
+    ]) as jni.JObjType<$K>;
+    V ??= jni.lowestCommonSuperType([
+      (map.$type as jni.JMapType).V,
+    ]) as jni.JObjType<$V>;
+    return HashMap.fromRef(K, V, _new3(map.reference).object);
+  }
+
+  static final _size = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("HashMap__size")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int size()
+  int size() {
+    return _size(reference).integer;
+  }
+
+  static final _isEmpty = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "HashMap__isEmpty")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isEmpty()
+  bool isEmpty() {
+    return _isEmpty(reference).boolean;
+  }
+
+  static final _get0 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__get0")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V get(java.lang.Object object)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V get0(
+    jni.JObject object,
+  ) {
+    return V.fromRef(_get0(reference, object.reference).object);
+  }
+
+  static final _containsKey = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__containsKey")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean containsKey(java.lang.Object object)
+  bool containsKey(
+    jni.JObject object,
+  ) {
+    return _containsKey(reference, object.reference).boolean;
+  }
+
+  static final _put = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__put")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V put(K object, V object1)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V put(
+    $K object,
+    $V object1,
+  ) {
+    return V
+        .fromRef(_put(reference, object.reference, object1.reference).object);
+  }
+
+  static final _putAll = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__putAll")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void putAll(java.util.Map map)
+  void putAll(
+    jni.JMap<$K, $V> map,
+  ) {
+    return _putAll(reference, map.reference).check();
+  }
+
+  static final _remove = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__remove")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V remove(java.lang.Object object)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V remove(
+    jni.JObject object,
+  ) {
+    return V.fromRef(_remove(reference, object.reference).object);
+  }
+
+  static final _clear = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("HashMap__clear")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void clear()
+  void clear() {
+    return _clear(reference).check();
+  }
+
+  static final _containsValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__containsValue")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean containsValue(java.lang.Object object)
+  bool containsValue(
+    jni.JObject object,
+  ) {
+    return _containsValue(reference, object.reference).boolean;
+  }
+
+  static final _keySet = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("HashMap__keySet")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Set keySet()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JSet<$K> keySet() {
+    return jni.JSetType(K).fromRef(_keySet(reference).object);
+  }
+
+  static final _values = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("HashMap__values")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Collection values()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject values() {
+    return const jni.JObjectType().fromRef(_values(reference).object);
+  }
+
+  static final _entrySet = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "HashMap__entrySet")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Set entrySet()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JSet<jni.JObject> entrySet() {
+    return const jni.JSetType(jni.JObjectType())
+        .fromRef(_entrySet(reference).object);
+  }
+
+  static final _getOrDefault = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__getOrDefault")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V getOrDefault(java.lang.Object object, V object1)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V getOrDefault(
+    jni.JObject object,
+    $V object1,
+  ) {
+    return V.fromRef(
+        _getOrDefault(reference, object.reference, object1.reference).object);
+  }
+
+  static final _putIfAbsent = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__putIfAbsent")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V putIfAbsent(K object, V object1)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V putIfAbsent(
+    $K object,
+    $V object1,
+  ) {
+    return V.fromRef(
+        _putIfAbsent(reference, object.reference, object1.reference).object);
+  }
+
+  static final _remove1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__remove1")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean remove(java.lang.Object object, java.lang.Object object1)
+  bool remove1(
+    jni.JObject object,
+    jni.JObject object1,
+  ) {
+    return _remove1(reference, object.reference, object1.reference).boolean;
+  }
+
+  static final _replace = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__replace")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean replace(K object, V object1, V object2)
+  bool replace(
+    $K object,
+    $V object1,
+    $V object2,
+  ) {
+    return _replace(
+            reference, object.reference, object1.reference, object2.reference)
+        .boolean;
+  }
+
+  static final _replace1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__replace1")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V replace(K object, V object1)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V replace1(
+    $K object,
+    $V object1,
+  ) {
+    return V.fromRef(
+        _replace1(reference, object.reference, object1.reference).object);
+  }
+
+  static final _computeIfAbsent = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__computeIfAbsent")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V computeIfAbsent(K object, java.util.function.Function function)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V computeIfAbsent(
+    $K object,
+    jni.JObject function,
+  ) {
+    return V.fromRef(
+        _computeIfAbsent(reference, object.reference, function.reference)
+            .object);
+  }
+
+  static final _computeIfPresent = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__computeIfPresent")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V computeIfPresent(K object, java.util.function.BiFunction biFunction)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V computeIfPresent(
+    $K object,
+    jni.JObject biFunction,
+  ) {
+    return V.fromRef(
+        _computeIfPresent(reference, object.reference, biFunction.reference)
+            .object);
+  }
+
+  static final _compute = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__compute")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V compute(K object, java.util.function.BiFunction biFunction)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V compute(
+    $K object,
+    jni.JObject biFunction,
+  ) {
+    return V.fromRef(
+        _compute(reference, object.reference, biFunction.reference).object);
+  }
+
+  static final _merge = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__merge")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V merge(K object, V object1, java.util.function.BiFunction biFunction)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V merge(
+    $K object,
+    $V object1,
+    jni.JObject biFunction,
+  ) {
+    return V.fromRef(_merge(reference, object.reference, object1.reference,
+            biFunction.reference)
+        .object);
+  }
+
+  static final _forEach = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__forEach")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void forEach(java.util.function.BiConsumer biConsumer)
+  void forEach(
+    jni.JObject biConsumer,
+  ) {
+    return _forEach(reference, biConsumer.reference).check();
+  }
+
+  static final _replaceAll = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("HashMap__replaceAll")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void replaceAll(java.util.function.BiFunction biFunction)
+  void replaceAll(
+    jni.JObject biFunction,
+  ) {
+    return _replaceAll(reference, biFunction.reference).check();
+  }
+
+  static final _clone = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("HashMap__clone")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object clone()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject clone() {
+    return const jni.JObjectType().fromRef(_clone(reference).object);
+  }
+}
+
+final class $HashMapType<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObjType<HashMap<$K, $V>> {
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  const $HashMapType(
+    this.K,
+    this.V,
+  );
+
+  @override
+  String get signature => r"Ljava/util/HashMap;";
+
+  @override
+  HashMap<$K, $V> fromRef(jni.JObjectPtr ref) => HashMap.fromRef(K, V, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($HashMapType, K, V);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($HashMapType<$K, $V>) &&
+        other is $HashMapType<$K, $V> &&
+        K == other.K &&
+        V == other.V;
+  }
+}
diff --git a/pkgs/jnigen/example/in_app_java/lib/main.dart b/pkgs/jnigen/example/in_app_java/lib/main.dart
new file mode 100644
index 0000000..2257581
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/lib/main.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:jni/jni.dart';
+
+// The hierarchy created in generated code will mirror the java package
+// structure.
+import 'android_utils.dart';
+
+JObject activity = JObject.fromRef(Jni.getCurrentActivity());
+JObject context = JObject.fromRef(Jni.getCachedApplicationContext());
+
+final hashmap = HashMap.new2(K: JString.type, V: JString.type);
+
+final emojiCompat = EmojiCompat.get0();
+
+extension IntX on int {
+  JString toJString() {
+    return toString().toJString();
+  }
+}
+
+const sunglassEmoji = "😎";
+
+/// Display device model number and the number of times this was called
+/// as Toast.
+void showToast() {
+  final toastCount =
+      hashmap.getOrDefault("toastCount".toJString(), 0.toJString());
+  final newToastCount = (int.parse(toastCount.toDartString()) + 1).toJString();
+  hashmap.put("toastCount".toJString(), newToastCount);
+  final emoji = emojiCompat.hasEmojiGlyph(sunglassEmoji.toJString())
+      ? sunglassEmoji
+      : ':cool:';
+  final message =
+      '${newToastCount.toDartString()} - ${Build.MODEL.toDartString()} $emoji';
+  AndroidUtils.showToast(activity, message.toJString(), 0);
+}
+
+void main() {
+  EmojiCompat.init(context);
+  runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  const MyApp({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Flutter Demo',
+      theme: ThemeData(
+        primarySwatch: Colors.teal,
+      ),
+      home: const MyHomePage(title: 'Flutter Demo Home Page'),
+    );
+  }
+}
+
+class MyHomePage extends StatelessWidget {
+  const MyHomePage({Key? key, required this.title}) : super(key: key);
+
+  final String title;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(title),
+      ),
+      body: Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            ElevatedButton(
+              child: const Text('Show Device Model'),
+              onPressed: () => showToast(),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/pkgs/jnigen/example/in_app_java/pubspec.yaml b/pkgs/jnigen/example/in_app_java/pubspec.yaml
new file mode 100644
index 0000000..321cc99
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/pubspec.yaml
@@ -0,0 +1,67 @@
+name: in_app_java
+description: |
+  Example on how to use jnigen to bridge between java and dart in a flutter
+  project.
+
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+version: 1.0.0+1
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+
+dependencies:
+  flutter:
+    sdk: flutter
+  jni:
+    path: ../../../jni/
+
+  # The following adds the Cupertino Icons font to your application.
+  # Use with the CupertinoIcons class for iOS style icons.
+  cupertino_icons: ^1.0.2
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  jnigen:
+    path: ../../
+
+  flutter_lints: ^2.0.0
+
+flutter:
+
+  # The following line ensures that the Material Icons font is
+  # included with your application, so that you can use the icons in
+  # the material Icons class.
+  uses-material-design: true
+
+  # To add assets to your application, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware
+
+  # For details regarding adding assets from package dependencies, see
+  # https://flutter.dev/assets-and-images/#from-packages
+
+  # To add custom fonts to your application, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts from package dependencies,
+  # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jnigen/example/in_app_java/src/android_utils/.clang-format b/pkgs/jnigen/example/in_app_java/src/android_utils/.clang-format
new file mode 100644
index 0000000..a256c2f
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/src/android_utils/.clang-format
@@ -0,0 +1,15 @@
+# From dart SDK: https://github.com/dart-lang/sdk/blob/main/.clang-format
+
+# Defines the Chromium style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
+
+# clang-format doesn't seem to do a good job of this for longer comments.
+ReflowComments: 'false'
+
+# We have lots of these. Though we need to put them all in curly braces,
+# clang-format can't do that.
+AllowShortIfStatementsOnASingleLine: 'true'
+
+# Put escaped newlines into the rightmost column.
+AlignEscapedNewlinesLeft: false
diff --git a/pkgs/jnigen/example/in_app_java/src/android_utils/CMakeLists.txt b/pkgs/jnigen/example/in_app_java/src/android_utils/CMakeLists.txt
new file mode 100644
index 0000000..0c47c28
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/src/android_utils/CMakeLists.txt
@@ -0,0 +1,32 @@
+# jni_native_build (Build with jni:setup. Do not delete this line.)
+
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project(android_utils VERSION 0.0.1 LANGUAGES C)
+
+add_library(android_utils SHARED
+  "./android_utils.c"
+)
+
+set_target_properties(android_utils PROPERTIES
+  OUTPUT_NAME "android_utils"
+)
+
+target_compile_definitions(android_utils PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries(android_utils log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries(android_utils ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jnigen/example/in_app_java/src/android_utils/android_utils.c b/pkgs/jnigen/example/in_app_java/src/android_utils/android_utils.c
new file mode 100644
index 0000000..983838b
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/src/android_utils/android_utils.c
@@ -0,0 +1,2513 @@
+// Autogenerated by jnigen. DO NOT EDIT!
+
+#include <stdint.h>
+#include "dartjni.h"
+#include "jni.h"
+
+thread_local JNIEnv* jniEnv;
+JniContext* jni;
+
+JniContext* (*context_getter)(void);
+JNIEnv* (*env_getter)(void);
+
+void setJniGetters(JniContext* (*cg)(void), JNIEnv* (*eg)(void)) {
+  context_getter = cg;
+  env_getter = eg;
+}
+
+// com.example.in_app_java.AndroidUtils
+jclass _c_AndroidUtils = NULL;
+
+jmethodID _m_AndroidUtils__showToast = NULL;
+FFI_PLUGIN_EXPORT
+JniResult AndroidUtils__showToast(jobject mainActivity,
+                                  jobject text,
+                                  int32_t duration) {
+  load_env();
+  load_class_global_ref(&_c_AndroidUtils,
+                        "com/example/in_app_java/AndroidUtils");
+  if (_c_AndroidUtils == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_AndroidUtils, &_m_AndroidUtils__showToast, "showToast",
+                     "(Landroid/app/Activity;Ljava/lang/CharSequence;I)V");
+  if (_m_AndroidUtils__showToast == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_AndroidUtils,
+                                  _m_AndroidUtils__showToast, mainActivity,
+                                  text, duration);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// androidx.emoji2.text.EmojiCompat
+jclass _c_EmojiCompat = NULL;
+
+jmethodID _m_EmojiCompat__init = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__init(jobject context) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_EmojiCompat, &_m_EmojiCompat__init, "init",
+      "(Landroid/content/Context;)Landroidx/emoji2/text/EmojiCompat;");
+  if (_m_EmojiCompat__init == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_EmojiCompat, _m_EmojiCompat__init, context);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__init1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__init1(jobject context, jobject defaultFactory) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_EmojiCompat, &_m_EmojiCompat__init1, "init",
+                     "(Landroid/content/Context;Landroidx/emoji2/text/"
+                     "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigFactory;"
+                     ")Landroidx/emoji2/text/EmojiCompat;");
+  if (_m_EmojiCompat__init1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_EmojiCompat, _m_EmojiCompat__init1, context, defaultFactory);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__init2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__init2(jobject config) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_EmojiCompat, &_m_EmojiCompat__init2, "init",
+                     "(Landroidx/emoji2/text/EmojiCompat$Config;)Landroidx/"
+                     "emoji2/text/EmojiCompat;");
+  if (_m_EmojiCompat__init2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_EmojiCompat, _m_EmojiCompat__init2, config);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__isConfigured = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__isConfigured() {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_EmojiCompat, &_m_EmojiCompat__isConfigured,
+                     "isConfigured", "()Z");
+  if (_m_EmojiCompat__isConfigured == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallStaticBooleanMethod(
+      jniEnv, _c_EmojiCompat, _m_EmojiCompat__isConfigured);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__reset = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__reset(jobject config) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_EmojiCompat, &_m_EmojiCompat__reset, "reset",
+                     "(Landroidx/emoji2/text/EmojiCompat$Config;)Landroidx/"
+                     "emoji2/text/EmojiCompat;");
+  if (_m_EmojiCompat__reset == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_EmojiCompat, _m_EmojiCompat__reset, config);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__reset1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__reset1(jobject emojiCompat) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_EmojiCompat, &_m_EmojiCompat__reset1, "reset",
+      "(Landroidx/emoji2/text/EmojiCompat;)Landroidx/emoji2/text/EmojiCompat;");
+  if (_m_EmojiCompat__reset1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_EmojiCompat, _m_EmojiCompat__reset1, emojiCompat);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__skipDefaultConfigurationLookup = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__skipDefaultConfigurationLookup(uint8_t shouldSkip) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_EmojiCompat,
+                     &_m_EmojiCompat__skipDefaultConfigurationLookup,
+                     "skipDefaultConfigurationLookup", "(Z)V");
+  if (_m_EmojiCompat__skipDefaultConfigurationLookup == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(
+      jniEnv, _c_EmojiCompat, _m_EmojiCompat__skipDefaultConfigurationLookup,
+      shouldSkip);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__get0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__get0() {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_EmojiCompat, &_m_EmojiCompat__get0, "get",
+                     "()Landroidx/emoji2/text/EmojiCompat;");
+  if (_m_EmojiCompat__get0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_EmojiCompat,
+                                                      _m_EmojiCompat__get0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__load = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__load(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__load, "load", "()V");
+  if (_m_EmojiCompat__load == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_EmojiCompat__load);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__registerInitCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__registerInitCallback(jobject self_,
+                                            jobject initCallback) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__registerInitCallback,
+              "registerInitCallback",
+              "(Landroidx/emoji2/text/EmojiCompat$InitCallback;)V");
+  if (_m_EmojiCompat__registerInitCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_EmojiCompat__registerInitCallback,
+                            initCallback);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__unregisterInitCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__unregisterInitCallback(jobject self_,
+                                              jobject initCallback) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__unregisterInitCallback,
+              "unregisterInitCallback",
+              "(Landroidx/emoji2/text/EmojiCompat$InitCallback;)V");
+  if (_m_EmojiCompat__unregisterInitCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(
+      jniEnv, self_, _m_EmojiCompat__unregisterInitCallback, initCallback);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__getLoadState = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__getLoadState(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__getLoadState, "getLoadState",
+              "()I");
+  if (_m_EmojiCompat__getLoadState == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_EmojiCompat__getLoadState);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__isEmojiSpanIndicatorEnabled = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__isEmojiSpanIndicatorEnabled(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__isEmojiSpanIndicatorEnabled,
+              "isEmojiSpanIndicatorEnabled", "()Z");
+  if (_m_EmojiCompat__isEmojiSpanIndicatorEnabled == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_EmojiCompat__isEmojiSpanIndicatorEnabled);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__getEmojiSpanIndicatorColor = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__getEmojiSpanIndicatorColor(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__getEmojiSpanIndicatorColor,
+              "getEmojiSpanIndicatorColor", "()I");
+  if (_m_EmojiCompat__getEmojiSpanIndicatorColor == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_EmojiCompat__getEmojiSpanIndicatorColor);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__getEmojiStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__getEmojiStart(jobject self_,
+                                     jobject charSequence,
+                                     int32_t offset) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__getEmojiStart, "getEmojiStart",
+              "(Ljava/lang/CharSequence;I)I");
+  if (_m_EmojiCompat__getEmojiStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_EmojiCompat__getEmojiStart, charSequence, offset);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__getEmojiEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__getEmojiEnd(jobject self_,
+                                   jobject charSequence,
+                                   int32_t offset) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__getEmojiEnd, "getEmojiEnd",
+              "(Ljava/lang/CharSequence;I)I");
+  if (_m_EmojiCompat__getEmojiEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_EmojiCompat__getEmojiEnd, charSequence, offset);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__handleOnKeyDown = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__handleOnKeyDown(jobject editable,
+                                       int32_t keyCode,
+                                       jobject event) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_EmojiCompat, &_m_EmojiCompat__handleOnKeyDown,
+                     "handleOnKeyDown",
+                     "(Landroid/text/Editable;ILandroid/view/KeyEvent;)Z");
+  if (_m_EmojiCompat__handleOnKeyDown == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallStaticBooleanMethod(
+      jniEnv, _c_EmojiCompat, _m_EmojiCompat__handleOnKeyDown, editable,
+      keyCode, event);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__handleDeleteSurroundingText = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__handleDeleteSurroundingText(jobject inputConnection,
+                                                   jobject editable,
+                                                   int32_t beforeLength,
+                                                   int32_t afterLength,
+                                                   uint8_t inCodePoints) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_EmojiCompat,
+                     &_m_EmojiCompat__handleDeleteSurroundingText,
+                     "handleDeleteSurroundingText",
+                     "(Landroid/view/inputmethod/InputConnection;Landroid/text/"
+                     "Editable;IIZ)Z");
+  if (_m_EmojiCompat__handleDeleteSurroundingText == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallStaticBooleanMethod(
+      jniEnv, _c_EmojiCompat, _m_EmojiCompat__handleDeleteSurroundingText,
+      inputConnection, editable, beforeLength, afterLength, inCodePoints);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__hasEmojiGlyph = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__hasEmojiGlyph(jobject self_, jobject sequence) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__hasEmojiGlyph, "hasEmojiGlyph",
+              "(Ljava/lang/CharSequence;)Z");
+  if (_m_EmojiCompat__hasEmojiGlyph == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_EmojiCompat__hasEmojiGlyph, sequence);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__hasEmojiGlyph1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__hasEmojiGlyph1(jobject self_,
+                                      jobject sequence,
+                                      int32_t metadataVersion) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__hasEmojiGlyph1, "hasEmojiGlyph",
+              "(Ljava/lang/CharSequence;I)Z");
+  if (_m_EmojiCompat__hasEmojiGlyph1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_EmojiCompat__hasEmojiGlyph1, sequence, metadataVersion);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__getEmojiMatch = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__getEmojiMatch(jobject self_,
+                                     jobject sequence,
+                                     int32_t metadataVersion) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__getEmojiMatch, "getEmojiMatch",
+              "(Ljava/lang/CharSequence;I)I");
+  if (_m_EmojiCompat__getEmojiMatch == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_EmojiCompat__getEmojiMatch, sequence, metadataVersion);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat__process = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__process(jobject self_, jobject charSequence) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__process, "process",
+              "(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;");
+  if (_m_EmojiCompat__process == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat__process, charSequence);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__process1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__process1(jobject self_,
+                                jobject charSequence,
+                                int32_t start,
+                                int32_t end) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__process1, "process",
+              "(Ljava/lang/CharSequence;II)Ljava/lang/CharSequence;");
+  if (_m_EmojiCompat__process1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat__process1, charSequence, start, end);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__process2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__process2(jobject self_,
+                                jobject charSequence,
+                                int32_t start,
+                                int32_t end,
+                                int32_t maxEmojiCount) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__process2, "process",
+              "(Ljava/lang/CharSequence;III)Ljava/lang/CharSequence;");
+  if (_m_EmojiCompat__process2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_EmojiCompat__process2,
+                                  charSequence, start, end, maxEmojiCount);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__process3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__process3(jobject self_,
+                                jobject charSequence,
+                                int32_t start,
+                                int32_t end,
+                                int32_t maxEmojiCount,
+                                int32_t replaceStrategy) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__process3, "process",
+              "(Ljava/lang/CharSequence;IIII)Ljava/lang/CharSequence;");
+  if (_m_EmojiCompat__process3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat__process3, charSequence, start, end,
+      maxEmojiCount, replaceStrategy);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__getAssetSignature = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__getAssetSignature(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__getAssetSignature,
+              "getAssetSignature", "()Ljava/lang/String;");
+  if (_m_EmojiCompat__getAssetSignature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat__getAssetSignature);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat__updateEditorInfo = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat__updateEditorInfo(jobject self_, jobject outAttrs) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat");
+  if (_c_EmojiCompat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat, &_m_EmojiCompat__updateEditorInfo,
+              "updateEditorInfo", "(Landroid/view/inputmethod/EditorInfo;)V");
+  if (_m_EmojiCompat__updateEditorInfo == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_EmojiCompat__updateEditorInfo,
+                            outAttrs);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// androidx.emoji2.text.EmojiCompat$Config
+jclass _c_EmojiCompat_Config = NULL;
+
+jmethodID _m_EmojiCompat_Config__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__new0(jobject metadataLoader) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config, &_m_EmojiCompat_Config__new0, "<init>",
+              "(Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoader;)V");
+  if (_m_EmojiCompat_Config__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_EmojiCompat_Config,
+                           _m_EmojiCompat_Config__new0, metadataLoader);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__registerInitCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__registerInitCallback(jobject self_,
+                                                   jobject initCallback) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config,
+              &_m_EmojiCompat_Config__registerInitCallback,
+              "registerInitCallback",
+              "(Landroidx/emoji2/text/EmojiCompat$InitCallback;)Landroidx/"
+              "emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__registerInitCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__registerInitCallback, initCallback);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__unregisterInitCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__unregisterInitCallback(jobject self_,
+                                                     jobject initCallback) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config,
+              &_m_EmojiCompat_Config__unregisterInitCallback,
+              "unregisterInitCallback",
+              "(Landroidx/emoji2/text/EmojiCompat$InitCallback;)Landroidx/"
+              "emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__unregisterInitCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__unregisterInitCallback,
+      initCallback);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__setReplaceAll = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__setReplaceAll(jobject self_, uint8_t replaceAll) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config, &_m_EmojiCompat_Config__setReplaceAll,
+              "setReplaceAll", "(Z)Landroidx/emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__setReplaceAll == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__setReplaceAll, replaceAll);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__setUseEmojiAsDefaultStyle = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__setUseEmojiAsDefaultStyle(
+    jobject self_,
+    uint8_t useEmojiAsDefaultStyle) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config,
+              &_m_EmojiCompat_Config__setUseEmojiAsDefaultStyle,
+              "setUseEmojiAsDefaultStyle",
+              "(Z)Landroidx/emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__setUseEmojiAsDefaultStyle == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__setUseEmojiAsDefaultStyle,
+      useEmojiAsDefaultStyle);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__setUseEmojiAsDefaultStyle1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__setUseEmojiAsDefaultStyle1(
+    jobject self_,
+    uint8_t useEmojiAsDefaultStyle,
+    jobject emojiAsDefaultStyleExceptions) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config,
+              &_m_EmojiCompat_Config__setUseEmojiAsDefaultStyle1,
+              "setUseEmojiAsDefaultStyle",
+              "(ZLjava/util/List;)Landroidx/emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__setUseEmojiAsDefaultStyle1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__setUseEmojiAsDefaultStyle1,
+      useEmojiAsDefaultStyle, emojiAsDefaultStyleExceptions);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__setEmojiSpanIndicatorEnabled = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__setEmojiSpanIndicatorEnabled(
+    jobject self_,
+    uint8_t emojiSpanIndicatorEnabled) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config,
+              &_m_EmojiCompat_Config__setEmojiSpanIndicatorEnabled,
+              "setEmojiSpanIndicatorEnabled",
+              "(Z)Landroidx/emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__setEmojiSpanIndicatorEnabled == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__setEmojiSpanIndicatorEnabled,
+      emojiSpanIndicatorEnabled);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__setEmojiSpanIndicatorColor = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__setEmojiSpanIndicatorColor(jobject self_,
+                                                         int32_t color) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config,
+              &_m_EmojiCompat_Config__setEmojiSpanIndicatorColor,
+              "setEmojiSpanIndicatorColor",
+              "(I)Landroidx/emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__setEmojiSpanIndicatorColor == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__setEmojiSpanIndicatorColor, color);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__setMetadataLoadStrategy = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__setMetadataLoadStrategy(jobject self_,
+                                                      int32_t strategy) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config,
+              &_m_EmojiCompat_Config__setMetadataLoadStrategy,
+              "setMetadataLoadStrategy",
+              "(I)Landroidx/emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__setMetadataLoadStrategy == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__setMetadataLoadStrategy, strategy);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__setSpanFactory = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__setSpanFactory(jobject self_, jobject factory) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config, &_m_EmojiCompat_Config__setSpanFactory,
+              "setSpanFactory",
+              "(Landroidx/emoji2/text/EmojiCompat$SpanFactory;)Landroidx/"
+              "emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__setSpanFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__setSpanFactory, factory);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__setGlyphChecker = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__setGlyphChecker(jobject self_,
+                                              jobject glyphChecker) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config, &_m_EmojiCompat_Config__setGlyphChecker,
+              "setGlyphChecker",
+              "(Landroidx/emoji2/text/EmojiCompat$GlyphChecker;)Landroidx/"
+              "emoji2/text/EmojiCompat$Config;");
+  if (_m_EmojiCompat_Config__setGlyphChecker == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__setGlyphChecker, glyphChecker);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_Config__getMetadataRepoLoader = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_Config__getMetadataRepoLoader(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_Config,
+                        "androidx/emoji2/text/EmojiCompat$Config");
+  if (_c_EmojiCompat_Config == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_Config,
+              &_m_EmojiCompat_Config__getMetadataRepoLoader,
+              "getMetadataRepoLoader",
+              "()Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoader;");
+  if (_m_EmojiCompat_Config__getMetadataRepoLoader == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_Config__getMetadataRepoLoader);
+  return to_global_ref_result(_result);
+}
+
+// androidx.emoji2.text.EmojiCompat$MetadataRepoLoaderCallback
+jclass _c_EmojiCompat_MetadataRepoLoaderCallback = NULL;
+
+jmethodID _m_EmojiCompat_MetadataRepoLoaderCallback__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_MetadataRepoLoaderCallback__new0() {
+  load_env();
+  load_class_global_ref(
+      &_c_EmojiCompat_MetadataRepoLoaderCallback,
+      "androidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback");
+  if (_c_EmojiCompat_MetadataRepoLoaderCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_MetadataRepoLoaderCallback,
+              &_m_EmojiCompat_MetadataRepoLoaderCallback__new0, "<init>",
+              "()V");
+  if (_m_EmojiCompat_MetadataRepoLoaderCallback__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_EmojiCompat_MetadataRepoLoaderCallback,
+                           _m_EmojiCompat_MetadataRepoLoaderCallback__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_MetadataRepoLoaderCallback__onLoaded = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_MetadataRepoLoaderCallback__onLoaded(
+    jobject self_,
+    jobject metadataRepo) {
+  load_env();
+  load_class_global_ref(
+      &_c_EmojiCompat_MetadataRepoLoaderCallback,
+      "androidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback");
+  if (_c_EmojiCompat_MetadataRepoLoaderCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_MetadataRepoLoaderCallback,
+              &_m_EmojiCompat_MetadataRepoLoaderCallback__onLoaded, "onLoaded",
+              "(Landroidx/emoji2/text/MetadataRepo;)V");
+  if (_m_EmojiCompat_MetadataRepoLoaderCallback__onLoaded == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_EmojiCompat_MetadataRepoLoaderCallback__onLoaded,
+                            metadataRepo);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat_MetadataRepoLoaderCallback__onFailed = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_MetadataRepoLoaderCallback__onFailed(jobject self_,
+                                                           jobject throwable) {
+  load_env();
+  load_class_global_ref(
+      &_c_EmojiCompat_MetadataRepoLoaderCallback,
+      "androidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback");
+  if (_c_EmojiCompat_MetadataRepoLoaderCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_MetadataRepoLoaderCallback,
+              &_m_EmojiCompat_MetadataRepoLoaderCallback__onFailed, "onFailed",
+              "(Ljava/lang/Throwable;)V");
+  if (_m_EmojiCompat_MetadataRepoLoaderCallback__onFailed == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_EmojiCompat_MetadataRepoLoaderCallback__onFailed,
+                            throwable);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// androidx.emoji2.text.EmojiCompat$GlyphChecker
+jclass _c_EmojiCompat_GlyphChecker = NULL;
+
+jmethodID _m_EmojiCompat_GlyphChecker__hasGlyph = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_GlyphChecker__hasGlyph(jobject self_,
+                                             jobject charSequence,
+                                             int32_t start,
+                                             int32_t end,
+                                             int32_t sdkAdded) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_GlyphChecker,
+                        "androidx/emoji2/text/EmojiCompat$GlyphChecker");
+  if (_c_EmojiCompat_GlyphChecker == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_GlyphChecker,
+              &_m_EmojiCompat_GlyphChecker__hasGlyph, "hasGlyph",
+              "(Ljava/lang/CharSequence;III)Z");
+  if (_m_EmojiCompat_GlyphChecker__hasGlyph == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_EmojiCompat_GlyphChecker__hasGlyph, charSequence, start,
+      end, sdkAdded);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+// androidx.emoji2.text.EmojiCompat$MetadataRepoLoader
+jclass _c_EmojiCompat_MetadataRepoLoader = NULL;
+
+jmethodID _m_EmojiCompat_MetadataRepoLoader__load = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_MetadataRepoLoader__load(jobject self_,
+                                               jobject loaderCallback) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_MetadataRepoLoader,
+                        "androidx/emoji2/text/EmojiCompat$MetadataRepoLoader");
+  if (_c_EmojiCompat_MetadataRepoLoader == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_EmojiCompat_MetadataRepoLoader,
+      &_m_EmojiCompat_MetadataRepoLoader__load, "load",
+      "(Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoaderCallback;)V");
+  if (_m_EmojiCompat_MetadataRepoLoader__load == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(
+      jniEnv, self_, _m_EmojiCompat_MetadataRepoLoader__load, loaderCallback);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// androidx.emoji2.text.EmojiCompat$InitCallback
+jclass _c_EmojiCompat_InitCallback = NULL;
+
+jmethodID _m_EmojiCompat_InitCallback__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_InitCallback__new0() {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_InitCallback,
+                        "androidx/emoji2/text/EmojiCompat$InitCallback");
+  if (_c_EmojiCompat_InitCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_InitCallback, &_m_EmojiCompat_InitCallback__new0,
+              "<init>", "()V");
+  if (_m_EmojiCompat_InitCallback__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_EmojiCompat_InitCallback,
+                                         _m_EmojiCompat_InitCallback__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_InitCallback__onInitialized = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_InitCallback__onInitialized(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_InitCallback,
+                        "androidx/emoji2/text/EmojiCompat$InitCallback");
+  if (_c_EmojiCompat_InitCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_InitCallback,
+              &_m_EmojiCompat_InitCallback__onInitialized, "onInitialized",
+              "()V");
+  if (_m_EmojiCompat_InitCallback__onInitialized == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_EmojiCompat_InitCallback__onInitialized);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_EmojiCompat_InitCallback__onFailed = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_InitCallback__onFailed(jobject self_, jobject throwable) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_InitCallback,
+                        "androidx/emoji2/text/EmojiCompat$InitCallback");
+  if (_c_EmojiCompat_InitCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_InitCallback,
+              &_m_EmojiCompat_InitCallback__onFailed, "onFailed",
+              "(Ljava/lang/Throwable;)V");
+  if (_m_EmojiCompat_InitCallback__onFailed == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_EmojiCompat_InitCallback__onFailed, throwable);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// androidx.emoji2.text.EmojiCompat$DefaultSpanFactory
+jclass _c_EmojiCompat_DefaultSpanFactory = NULL;
+
+jmethodID _m_EmojiCompat_DefaultSpanFactory__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_DefaultSpanFactory__new0() {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_DefaultSpanFactory,
+                        "androidx/emoji2/text/EmojiCompat$DefaultSpanFactory");
+  if (_c_EmojiCompat_DefaultSpanFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_DefaultSpanFactory,
+              &_m_EmojiCompat_DefaultSpanFactory__new0, "<init>", "()V");
+  if (_m_EmojiCompat_DefaultSpanFactory__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_EmojiCompat_DefaultSpanFactory,
+                           _m_EmojiCompat_DefaultSpanFactory__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_EmojiCompat_DefaultSpanFactory__createSpan = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_DefaultSpanFactory__createSpan(jobject self_,
+                                                     jobject rasterizer) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_DefaultSpanFactory,
+                        "androidx/emoji2/text/EmojiCompat$DefaultSpanFactory");
+  if (_c_EmojiCompat_DefaultSpanFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_DefaultSpanFactory,
+              &_m_EmojiCompat_DefaultSpanFactory__createSpan, "createSpan",
+              "(Landroidx/emoji2/text/TypefaceEmojiRasterizer;)Landroidx/"
+              "emoji2/text/EmojiSpan;");
+  if (_m_EmojiCompat_DefaultSpanFactory__createSpan == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_DefaultSpanFactory__createSpan, rasterizer);
+  return to_global_ref_result(_result);
+}
+
+// androidx.emoji2.text.EmojiCompat$SpanFactory
+jclass _c_EmojiCompat_SpanFactory = NULL;
+
+jmethodID _m_EmojiCompat_SpanFactory__createSpan = NULL;
+FFI_PLUGIN_EXPORT
+JniResult EmojiCompat_SpanFactory__createSpan(jobject self_,
+                                              jobject rasterizer) {
+  load_env();
+  load_class_global_ref(&_c_EmojiCompat_SpanFactory,
+                        "androidx/emoji2/text/EmojiCompat$SpanFactory");
+  if (_c_EmojiCompat_SpanFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_EmojiCompat_SpanFactory,
+              &_m_EmojiCompat_SpanFactory__createSpan, "createSpan",
+              "(Landroidx/emoji2/text/TypefaceEmojiRasterizer;)Landroidx/"
+              "emoji2/text/EmojiSpan;");
+  if (_m_EmojiCompat_SpanFactory__createSpan == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_EmojiCompat_SpanFactory__createSpan, rasterizer);
+  return to_global_ref_result(_result);
+}
+
+// androidx.emoji2.text.DefaultEmojiCompatConfig
+jclass _c_DefaultEmojiCompatConfig = NULL;
+
+jmethodID _m_DefaultEmojiCompatConfig__create = NULL;
+FFI_PLUGIN_EXPORT
+JniResult DefaultEmojiCompatConfig__create(jobject context) {
+  load_env();
+  load_class_global_ref(&_c_DefaultEmojiCompatConfig,
+                        "androidx/emoji2/text/DefaultEmojiCompatConfig");
+  if (_c_DefaultEmojiCompatConfig == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_DefaultEmojiCompatConfig,
+                     &_m_DefaultEmojiCompatConfig__create, "create",
+                     "(Landroid/content/Context;)Landroidx/emoji2/text/"
+                     "FontRequestEmojiCompatConfig;");
+  if (_m_DefaultEmojiCompatConfig__create == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_DefaultEmojiCompatConfig, _m_DefaultEmojiCompatConfig__create,
+      context);
+  return to_global_ref_result(_result);
+}
+
+// androidx.emoji2.text.DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API28
+jclass _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28 = NULL;
+
+jmethodID
+    _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__new0 =
+        NULL;
+FFI_PLUGIN_EXPORT
+JniResult
+DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__new0() {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API28");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__new0,
+      "<init>", "()V");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__new0 ==
+      NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(
+      jniEnv, _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID
+    _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__getSigningSignatures1 =
+        NULL;
+FFI_PLUGIN_EXPORT
+JniResult
+DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__getSigningSignatures1(
+    jobject self_,
+    jobject packageManager,
+    jobject providerPackage) {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API28");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__getSigningSignatures1,
+      "getSigningSignatures",
+      "(Landroid/content/pm/PackageManager;Ljava/lang/String;)[Landroid/"
+      "content/pm/Signature;");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__getSigningSignatures1 ==
+      NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API28__getSigningSignatures1,
+      packageManager, providerPackage);
+  return to_global_ref_result(_result);
+}
+
+// androidx.emoji2.text.DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API19
+jclass _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19 = NULL;
+
+jmethodID
+    _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__new0 =
+        NULL;
+FFI_PLUGIN_EXPORT
+JniResult
+DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__new0() {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API19");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__new0,
+      "<init>", "()V");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__new0 ==
+      NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(
+      jniEnv, _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID
+    _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__queryIntentContentProviders =
+        NULL;
+FFI_PLUGIN_EXPORT
+JniResult
+DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__queryIntentContentProviders(
+    jobject self_,
+    jobject packageManager,
+    jobject intent,
+    int32_t flags) {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API19");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__queryIntentContentProviders,
+      "queryIntentContentProviders",
+      "(Landroid/content/pm/PackageManager;Landroid/content/Intent;I)Ljava/"
+      "util/List;");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__queryIntentContentProviders ==
+      NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__queryIntentContentProviders,
+      packageManager, intent, flags);
+  return to_global_ref_result(_result);
+}
+
+jmethodID
+    _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__getProviderInfo =
+        NULL;
+FFI_PLUGIN_EXPORT
+JniResult
+DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__getProviderInfo(
+    jobject self_,
+    jobject resolveInfo) {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper_API19");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__getProviderInfo,
+      "getProviderInfo",
+      "(Landroid/content/pm/ResolveInfo;)Landroid/content/pm/ProviderInfo;");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__getProviderInfo ==
+      NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper_API19__getProviderInfo,
+      resolveInfo);
+  return to_global_ref_result(_result);
+}
+
+// androidx.emoji2.text.DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper
+jclass _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper = NULL;
+
+jmethodID _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__new0 =
+    NULL;
+FFI_PLUGIN_EXPORT
+JniResult DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__new0() {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper,
+              &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__new0,
+              "<init>", "()V");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(
+      jniEnv, _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID
+    _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getSigningSignatures =
+        NULL;
+FFI_PLUGIN_EXPORT
+JniResult
+DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getSigningSignatures(
+    jobject self_,
+    jobject packageManager,
+    jobject providerPackage) {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getSigningSignatures,
+      "getSigningSignatures",
+      "(Landroid/content/pm/PackageManager;Ljava/lang/String;)[Landroid/"
+      "content/pm/Signature;");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getSigningSignatures ==
+      NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getSigningSignatures,
+      packageManager, providerPackage);
+  return to_global_ref_result(_result);
+}
+
+jmethodID
+    _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__queryIntentContentProviders =
+        NULL;
+FFI_PLUGIN_EXPORT
+JniResult
+DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__queryIntentContentProviders(
+    jobject self_,
+    jobject packageManager,
+    jobject intent,
+    int32_t flags) {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__queryIntentContentProviders,
+      "queryIntentContentProviders",
+      "(Landroid/content/pm/PackageManager;Landroid/content/Intent;I)Ljava/"
+      "util/List;");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__queryIntentContentProviders ==
+      NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__queryIntentContentProviders,
+      packageManager, intent, flags);
+  return to_global_ref_result(_result);
+}
+
+jmethodID
+    _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getProviderInfo =
+        NULL;
+FFI_PLUGIN_EXPORT
+JniResult
+DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getProviderInfo(
+    jobject self_,
+    jobject resolveInfo) {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getProviderInfo,
+      "getProviderInfo",
+      "(Landroid/content/pm/ResolveInfo;)Landroid/content/pm/ProviderInfo;");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getProviderInfo ==
+      NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigHelper__getProviderInfo,
+      resolveInfo);
+  return to_global_ref_result(_result);
+}
+
+// androidx.emoji2.text.DefaultEmojiCompatConfig$DefaultEmojiCompatConfigFactory
+jclass _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory = NULL;
+
+jmethodID _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__new0 =
+    NULL;
+FFI_PLUGIN_EXPORT
+JniResult DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__new0(
+    jobject helper) {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigFactory");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__new0,
+      "<init>",
+      "(Landroidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigHelper;)V");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(
+      jniEnv, _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__new0,
+      helper);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__create =
+    NULL;
+FFI_PLUGIN_EXPORT
+JniResult DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__create(
+    jobject self_,
+    jobject context) {
+  load_env();
+  load_class_global_ref(
+      &_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory,
+      "androidx/emoji2/text/"
+      "DefaultEmojiCompatConfig$DefaultEmojiCompatConfigFactory");
+  if (_c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory,
+      &_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__create,
+      "create",
+      "(Landroid/content/Context;)Landroidx/emoji2/text/EmojiCompat$Config;");
+  if (_m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__create ==
+      NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_,
+      _m_DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__create,
+      context);
+  return to_global_ref_result(_result);
+}
+
+// android.os.Build$Partition
+jclass _c_Build_Partition = NULL;
+
+jmethodID _m_Build_Partition__getName = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build_Partition__getName(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition");
+  if (_c_Build_Partition == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Build_Partition, &_m_Build_Partition__getName, "getName",
+              "()Ljava/lang/String;");
+  if (_m_Build_Partition__getName == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_Build_Partition__getName);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Build_Partition__getFingerprint = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build_Partition__getFingerprint(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition");
+  if (_c_Build_Partition == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Build_Partition, &_m_Build_Partition__getFingerprint,
+              "getFingerprint", "()Ljava/lang/String;");
+  if (_m_Build_Partition__getFingerprint == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_Build_Partition__getFingerprint);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Build_Partition__getBuildTimeMillis = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build_Partition__getBuildTimeMillis(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition");
+  if (_c_Build_Partition == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Build_Partition, &_m_Build_Partition__getBuildTimeMillis,
+              "getBuildTimeMillis", "()J");
+  if (_m_Build_Partition__getBuildTimeMillis == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int64_t _result = (*jniEnv)->CallLongMethod(
+      jniEnv, self_, _m_Build_Partition__getBuildTimeMillis);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Build_Partition__equals1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build_Partition__equals1(jobject self_, jobject object) {
+  load_env();
+  load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition");
+  if (_c_Build_Partition == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Build_Partition, &_m_Build_Partition__equals1, "equals",
+              "(Ljava/lang/Object;)Z");
+  if (_m_Build_Partition__equals1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_Build_Partition__equals1, object);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Build_Partition__hashCode1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build_Partition__hashCode1(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition");
+  if (_c_Build_Partition == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Build_Partition, &_m_Build_Partition__hashCode1, "hashCode",
+              "()I");
+  if (_m_Build_Partition__hashCode1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_Build_Partition__hashCode1);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+// android.os.Build$VERSION
+jclass _c_Build_VERSION = NULL;
+
+jmethodID _m_Build_VERSION__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build_VERSION__new0() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Build_VERSION, &_m_Build_VERSION__new0, "<init>", "()V");
+  if (_m_Build_VERSION__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_Build_VERSION, _m_Build_VERSION__new0);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build_VERSION__BASE_OS = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__BASE_OS() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION, &_f_Build_VERSION__BASE_OS, "BASE_OS",
+                    "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build_VERSION,
+                                                    _f_Build_VERSION__BASE_OS);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build_VERSION__CODENAME = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__CODENAME() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION, &_f_Build_VERSION__CODENAME, "CODENAME",
+                    "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build_VERSION,
+                                                    _f_Build_VERSION__CODENAME);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build_VERSION__INCREMENTAL = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__INCREMENTAL() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION, &_f_Build_VERSION__INCREMENTAL,
+                    "INCREMENTAL", "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(
+      jniEnv, _c_Build_VERSION, _f_Build_VERSION__INCREMENTAL);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build_VERSION__MEDIA_PERFORMANCE_CLASS = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__MEDIA_PERFORMANCE_CLASS() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION,
+                    &_f_Build_VERSION__MEDIA_PERFORMANCE_CLASS,
+                    "MEDIA_PERFORMANCE_CLASS", "I");
+  int32_t _result = (*jniEnv)->GetStaticIntField(
+      jniEnv, _c_Build_VERSION, _f_Build_VERSION__MEDIA_PERFORMANCE_CLASS);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jfieldID _f_Build_VERSION__PREVIEW_SDK_INT = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__PREVIEW_SDK_INT() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION, &_f_Build_VERSION__PREVIEW_SDK_INT,
+                    "PREVIEW_SDK_INT", "I");
+  int32_t _result = (*jniEnv)->GetStaticIntField(
+      jniEnv, _c_Build_VERSION, _f_Build_VERSION__PREVIEW_SDK_INT);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jfieldID _f_Build_VERSION__RELEASE = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__RELEASE() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION, &_f_Build_VERSION__RELEASE, "RELEASE",
+                    "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build_VERSION,
+                                                    _f_Build_VERSION__RELEASE);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build_VERSION__RELEASE_OR_CODENAME = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__RELEASE_OR_CODENAME() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION, &_f_Build_VERSION__RELEASE_OR_CODENAME,
+                    "RELEASE_OR_CODENAME", "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(
+      jniEnv, _c_Build_VERSION, _f_Build_VERSION__RELEASE_OR_CODENAME);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION,
+                    &_f_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY,
+                    "RELEASE_OR_PREVIEW_DISPLAY", "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(
+      jniEnv, _c_Build_VERSION, _f_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build_VERSION__SDK = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__SDK() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION, &_f_Build_VERSION__SDK, "SDK",
+                    "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build_VERSION,
+                                                    _f_Build_VERSION__SDK);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build_VERSION__SDK_INT = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__SDK_INT() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION, &_f_Build_VERSION__SDK_INT, "SDK_INT",
+                    "I");
+  int32_t _result = (*jniEnv)->GetStaticIntField(jniEnv, _c_Build_VERSION,
+                                                 _f_Build_VERSION__SDK_INT);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jfieldID _f_Build_VERSION__SECURITY_PATCH = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build_VERSION__SECURITY_PATCH() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION");
+  if (_c_Build_VERSION == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build_VERSION, &_f_Build_VERSION__SECURITY_PATCH,
+                    "SECURITY_PATCH", "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(
+      jniEnv, _c_Build_VERSION, _f_Build_VERSION__SECURITY_PATCH);
+  return to_global_ref_result(_result);
+}
+
+// android.os.Build$VERSION_CODES
+jclass _c_Build_VERSION_CODES = NULL;
+
+jmethodID _m_Build_VERSION_CODES__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build_VERSION_CODES__new0() {
+  load_env();
+  load_class_global_ref(&_c_Build_VERSION_CODES,
+                        "android/os/Build$VERSION_CODES");
+  if (_c_Build_VERSION_CODES == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Build_VERSION_CODES, &_m_Build_VERSION_CODES__new0, "<init>",
+              "()V");
+  if (_m_Build_VERSION_CODES__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Build_VERSION_CODES,
+                                         _m_Build_VERSION_CODES__new0);
+  return to_global_ref_result(_result);
+}
+
+// android.os.Build
+jclass _c_Build = NULL;
+
+jmethodID _m_Build__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build__new0() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Build, &_m_Build__new0, "<init>", "()V");
+  if (_m_Build__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Build, _m_Build__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Build__getSerial = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build__getSerial() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Build, &_m_Build__getSerial, "getSerial",
+                     "()Ljava/lang/String;");
+  if (_m_Build__getSerial == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_Build, _m_Build__getSerial);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Build__getFingerprintedPartitions = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build__getFingerprintedPartitions() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Build, &_m_Build__getFingerprintedPartitions,
+                     "getFingerprintedPartitions", "()Ljava/util/List;");
+  if (_m_Build__getFingerprintedPartitions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_Build, _m_Build__getFingerprintedPartitions);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Build__getRadioVersion = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Build__getRadioVersion() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Build, &_m_Build__getRadioVersion, "getRadioVersion",
+                     "()Ljava/lang/String;");
+  if (_m_Build__getRadioVersion == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_Build, _m_Build__getRadioVersion);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__BOARD = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__BOARD() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__BOARD, "BOARD", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__BOARD);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__BOOTLOADER = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__BOOTLOADER() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__BOOTLOADER, "BOOTLOADER",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__BOOTLOADER);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__BRAND = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__BRAND() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__BRAND, "BRAND", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__BRAND);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__CPU_ABI = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__CPU_ABI() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__CPU_ABI, "CPU_ABI",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__CPU_ABI);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__CPU_ABI2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__CPU_ABI2() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__CPU_ABI2, "CPU_ABI2",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__CPU_ABI2);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__DEVICE = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__DEVICE() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__DEVICE, "DEVICE",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__DEVICE);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__DISPLAY = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__DISPLAY() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__DISPLAY, "DISPLAY",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__DISPLAY);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__FINGERPRINT = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__FINGERPRINT() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__FINGERPRINT, "FINGERPRINT",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__FINGERPRINT);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__HARDWARE = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__HARDWARE() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__HARDWARE, "HARDWARE",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__HARDWARE);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__HOST = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__HOST() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__HOST, "HOST", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__HOST);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__ID = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__ID() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__ID, "ID", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__ID);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__MANUFACTURER = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__MANUFACTURER() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__MANUFACTURER, "MANUFACTURER",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__MANUFACTURER);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__MODEL = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__MODEL() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__MODEL, "MODEL", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__MODEL);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__ODM_SKU = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__ODM_SKU() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__ODM_SKU, "ODM_SKU",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__ODM_SKU);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__PRODUCT = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__PRODUCT() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__PRODUCT, "PRODUCT",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__PRODUCT);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__RADIO = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__RADIO() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__RADIO, "RADIO", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__RADIO);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__SERIAL = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__SERIAL() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__SERIAL, "SERIAL",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__SERIAL);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__SKU = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__SKU() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__SKU, "SKU", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__SKU);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__SOC_MANUFACTURER = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__SOC_MANUFACTURER() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__SOC_MANUFACTURER, "SOC_MANUFACTURER",
+                    "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build,
+                                                    _f_Build__SOC_MANUFACTURER);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__SOC_MODEL = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__SOC_MODEL() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__SOC_MODEL, "SOC_MODEL",
+                    "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__SOC_MODEL);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__SUPPORTED_32_BIT_ABIS = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__SUPPORTED_32_BIT_ABIS() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__SUPPORTED_32_BIT_ABIS,
+                    "SUPPORTED_32_BIT_ABIS", "[Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(
+      jniEnv, _c_Build, _f_Build__SUPPORTED_32_BIT_ABIS);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__SUPPORTED_64_BIT_ABIS = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__SUPPORTED_64_BIT_ABIS() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__SUPPORTED_64_BIT_ABIS,
+                    "SUPPORTED_64_BIT_ABIS", "[Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(
+      jniEnv, _c_Build, _f_Build__SUPPORTED_64_BIT_ABIS);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__SUPPORTED_ABIS = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__SUPPORTED_ABIS() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__SUPPORTED_ABIS, "SUPPORTED_ABIS",
+                    "[Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build,
+                                                    _f_Build__SUPPORTED_ABIS);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__TAGS = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__TAGS() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__TAGS, "TAGS", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__TAGS);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__TIME = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__TIME() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__TIME, "TIME", "J");
+  int64_t _result =
+      (*jniEnv)->GetStaticLongField(jniEnv, _c_Build, _f_Build__TIME);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+jfieldID _f_Build__TYPE = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__TYPE() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__TYPE, "TYPE", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__TYPE);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Build__USER = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Build__USER() {
+  load_env();
+  load_class_global_ref(&_c_Build, "android/os/Build");
+  if (_c_Build == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Build, &_f_Build__USER, "USER", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__USER);
+  return to_global_ref_result(_result);
+}
+
+// java.util.HashMap
+jclass _c_HashMap = NULL;
+
+jmethodID _m_HashMap__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__new0(int32_t i, float f) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__new0, "<init>", "(IF)V");
+  if (_m_HashMap__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_HashMap, _m_HashMap__new0, i, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__new1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__new1(int32_t i) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__new1, "<init>", "(I)V");
+  if (_m_HashMap__new1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_HashMap, _m_HashMap__new1, i);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__new2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__new2() {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__new2, "<init>", "()V");
+  if (_m_HashMap__new2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_HashMap, _m_HashMap__new2);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__new3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__new3(jobject map) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__new3, "<init>", "(Ljava/util/Map;)V");
+  if (_m_HashMap__new3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_HashMap, _m_HashMap__new3, map);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__size = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__size(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__size, "size", "()I");
+  if (_m_HashMap__size == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(jniEnv, self_, _m_HashMap__size);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__isEmpty = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__isEmpty(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__isEmpty, "isEmpty", "()Z");
+  if (_m_HashMap__isEmpty == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_HashMap__isEmpty);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__get0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__get0(jobject self_, jobject object) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__get0, "get",
+              "(Ljava/lang/Object;)Ljava/lang/Object;");
+  if (_m_HashMap__get0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_HashMap__get0, object);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__containsKey = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__containsKey(jobject self_, jobject object) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__containsKey, "containsKey",
+              "(Ljava/lang/Object;)Z");
+  if (_m_HashMap__containsKey == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_HashMap__containsKey, object);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__put = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__put(jobject self_, jobject object, jobject object1) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__put, "put",
+              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+  if (_m_HashMap__put == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_HashMap__put,
+                                                object, object1);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__putAll = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__putAll(jobject self_, jobject map) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__putAll, "putAll", "(Ljava/util/Map;)V");
+  if (_m_HashMap__putAll == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_HashMap__putAll, map);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__remove = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__remove(jobject self_, jobject object) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__remove, "remove",
+              "(Ljava/lang/Object;)Ljava/lang/Object;");
+  if (_m_HashMap__remove == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_HashMap__remove, object);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__clear = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__clear(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__clear, "clear", "()V");
+  if (_m_HashMap__clear == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_HashMap__clear);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__containsValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__containsValue(jobject self_, jobject object) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__containsValue, "containsValue",
+              "(Ljava/lang/Object;)Z");
+  if (_m_HashMap__containsValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_HashMap__containsValue, object);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__keySet = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__keySet(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__keySet, "keySet", "()Ljava/util/Set;");
+  if (_m_HashMap__keySet == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_HashMap__keySet);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__values = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__values(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__values, "values",
+              "()Ljava/util/Collection;");
+  if (_m_HashMap__values == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_HashMap__values);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__entrySet = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__entrySet(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__entrySet, "entrySet",
+              "()Ljava/util/Set;");
+  if (_m_HashMap__entrySet == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_HashMap__entrySet);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__getOrDefault = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__getOrDefault(jobject self_,
+                                jobject object,
+                                jobject object1) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__getOrDefault, "getOrDefault",
+              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+  if (_m_HashMap__getOrDefault == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_HashMap__getOrDefault, object, object1);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__putIfAbsent = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__putIfAbsent(jobject self_, jobject object, jobject object1) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__putIfAbsent, "putIfAbsent",
+              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+  if (_m_HashMap__putIfAbsent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_HashMap__putIfAbsent, object, object1);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__remove1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__remove1(jobject self_, jobject object, jobject object1) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__remove1, "remove",
+              "(Ljava/lang/Object;Ljava/lang/Object;)Z");
+  if (_m_HashMap__remove1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_HashMap__remove1, object, object1);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__replace = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__replace(jobject self_,
+                           jobject object,
+                           jobject object1,
+                           jobject object2) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__replace, "replace",
+              "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z");
+  if (_m_HashMap__replace == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_HashMap__replace, object, object1, object2);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__replace1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__replace1(jobject self_, jobject object, jobject object1) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__replace1, "replace",
+              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+  if (_m_HashMap__replace1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_HashMap__replace1, object, object1);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__computeIfAbsent = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__computeIfAbsent(jobject self_,
+                                   jobject object,
+                                   jobject function) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_HashMap, &_m_HashMap__computeIfAbsent, "computeIfAbsent",
+      "(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;");
+  if (_m_HashMap__computeIfAbsent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_HashMap__computeIfAbsent, object, function);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__computeIfPresent = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__computeIfPresent(jobject self_,
+                                    jobject object,
+                                    jobject biFunction) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_HashMap, &_m_HashMap__computeIfPresent, "computeIfPresent",
+      "(Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;");
+  if (_m_HashMap__computeIfPresent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_HashMap__computeIfPresent, object, biFunction);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__compute = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__compute(jobject self_, jobject object, jobject biFunction) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_HashMap, &_m_HashMap__compute, "compute",
+      "(Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;");
+  if (_m_HashMap__compute == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_HashMap__compute, object, biFunction);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__merge = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__merge(jobject self_,
+                         jobject object,
+                         jobject object1,
+                         jobject biFunction) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__merge, "merge",
+              "(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/"
+              "BiFunction;)Ljava/lang/Object;");
+  if (_m_HashMap__merge == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_HashMap__merge, object, object1, biFunction);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_HashMap__forEach = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__forEach(jobject self_, jobject biConsumer) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__forEach, "forEach",
+              "(Ljava/util/function/BiConsumer;)V");
+  if (_m_HashMap__forEach == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_HashMap__forEach, biConsumer);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__replaceAll = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__replaceAll(jobject self_, jobject biFunction) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__replaceAll, "replaceAll",
+              "(Ljava/util/function/BiFunction;)V");
+  if (_m_HashMap__replaceAll == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_HashMap__replaceAll, biFunction);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_HashMap__clone = NULL;
+FFI_PLUGIN_EXPORT
+JniResult HashMap__clone(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_HashMap, "java/util/HashMap");
+  if (_c_HashMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_HashMap, &_m_HashMap__clone, "clone", "()Ljava/lang/Object;");
+  if (_m_HashMap__clone == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_HashMap__clone);
+  return to_global_ref_result(_result);
+}
diff --git a/pkgs/jnigen/example/in_app_java/src/android_utils/dartjni.h b/pkgs/jnigen/example/in_app_java/src/android_utils/dartjni.h
new file mode 100644
index 0000000..8f1dc74
--- /dev/null
+++ b/pkgs/jnigen/example/in_app_java/src/android_utils/dartjni.h
@@ -0,0 +1,424 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h.
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
+#if _WIN32
+#define FFI_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FFI_PLUGIN_EXPORT
+#endif
+
+#if defined _WIN32
+#define thread_local __declspec(thread)
+#else
+#define thread_local __thread
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#ifdef __ANDROID__
+#define __ENVP_CAST (JNIEnv**)
+#else
+#define __ENVP_CAST (void**)
+#endif
+
+/// Locking functions for windows and pthread.
+
+#if defined _WIN32
+#include <windows.h>
+
+typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  InitializeCriticalSection(lock);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  EnterCriticalSection(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  LeaveCriticalSection(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  DeleteCriticalSection(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  // Not available.
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ ||         \
+    defined __GNUC__
+#include <pthread.h>
+
+typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  pthread_mutex_init(lock, NULL);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  pthread_mutex_lock(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  pthread_mutex_unlock(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  pthread_mutex_destroy(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  pthread_cond_destroy(cond);
+}
+
+#else
+
+#error "No locking/condition variable support; Possibly unsupported platform"
+
+#endif
+
+typedef struct CallbackResult {
+  MutexLock lock;
+  ConditionVariable cond;
+  int ready;
+  jobject object;
+} CallbackResult;
+
+typedef struct JniLocks {
+  MutexLock classLoadingLock;
+  MutexLock methodLoadingLock;
+  MutexLock fieldLoadingLock;
+} JniLocks;
+
+/// Represents the error when dart-jni layer has already spawned singleton VM.
+#define DART_JNI_SINGLETON_EXISTS (-99);
+
+/// Stores the global state of the JNI.
+typedef struct JniContext {
+  JavaVM* jvm;
+  jobject classLoader;
+  jmethodID loadClassMethod;
+  jobject currentActivity;
+  jobject appContext;
+  JniLocks locks;
+} JniContext;
+
+// jniEnv for this thread, used by inline functions in this header,
+// therefore declared as extern.
+extern thread_local JNIEnv* jniEnv;
+
+extern JniContext* jni;
+
+/// Types used by JNI API to distinguish between primitive types.
+enum JniType {
+  booleanType = 0,
+  byteType = 1,
+  shortType = 2,
+  charType = 3,
+  intType = 4,
+  longType = 5,
+  floatType = 6,
+  doubleType = 7,
+  objectType = 8,
+  voidType = 9,
+};
+
+/// Result type for use by JNI.
+///
+/// If [exception] is null, it means the result is valid.
+/// It's assumed that the caller knows the expected type in [result].
+typedef struct JniResult {
+  jvalue value;
+  jthrowable exception;
+} JniResult;
+
+/// Similar to [JniResult] but for class lookups.
+typedef struct JniClassLookupResult {
+  jclass value;
+  jthrowable exception;
+} JniClassLookupResult;
+
+/// Similar to [JniResult] but for method/field ID lookups.
+typedef struct JniPointerResult {
+  const void* value;
+  jthrowable exception;
+} JniPointerResult;
+
+/// JniExceptionDetails holds 2 jstring objects, one is the result of
+/// calling `toString` on exception object, other is stack trace;
+typedef struct JniExceptionDetails {
+  jstring message;
+  jstring stacktrace;
+} JniExceptionDetails;
+
+/// This struct contains functions which wrap method call / field access conveniently along with
+/// exception checking.
+///
+/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us
+/// to check for and clear the exception before returning to dart code, which requires these functions
+/// to return result types.
+typedef struct JniAccessorsStruct {
+  JniClassLookupResult (*getClass)(char* internalName);
+  JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature);
+  JniPointerResult (*getStaticFieldID)(jclass cls,
+                                       char* fieldName,
+                                       char* signature);
+  JniPointerResult (*getMethodID)(jclass cls,
+                                  char* methodName,
+                                  char* signature);
+  JniPointerResult (*getStaticMethodID)(jclass cls,
+                                        char* methodName,
+                                        char* signature);
+  JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args);
+  JniResult (*newPrimitiveArray)(jsize length, int type);
+  JniResult (*newObjectArray)(jsize length,
+                              jclass elementClass,
+                              jobject initialElement);
+  JniResult (*getArrayElement)(jarray array, int index, int type);
+  JniResult (*callMethod)(jobject obj,
+                          jmethodID methodID,
+                          int callType,
+                          jvalue* args);
+  JniResult (*callStaticMethod)(jclass cls,
+                                jmethodID methodID,
+                                int callType,
+                                jvalue* args);
+  JniResult (*getField)(jobject obj, jfieldID fieldID, int callType);
+  JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType);
+  JniExceptionDetails (*getExceptionDetails)(jthrowable exception);
+} JniAccessorsStruct;
+
+FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors();
+
+FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void);
+
+FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void);
+
+/// Spawn a JVM with given arguments.
+///
+/// Returns JNI_OK on success, and one of the documented JNI error codes on
+/// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple
+/// JVMs is made, even if the underlying API potentially supports multiple VMs.
+FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args);
+
+/// Load class through platform-specific mechanism.
+///
+/// Currently uses application classloader on android,
+/// and JNIEnv->FindClass on other platforms.
+FFI_PLUGIN_EXPORT jclass FindClass(const char* name);
+
+/// Returns Application classLoader (on Android),
+/// which can be used to load application and platform classes.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetClassLoader(void);
+
+/// Returns application context on Android.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetApplicationContext(void);
+
+/// Returns current activity of the app on Android.
+FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void);
+
+static inline void attach_thread() {
+  if (jniEnv == NULL) {
+    (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL);
+  }
+}
+
+/// Load class into [cls] using platform specific mechanism
+static inline void load_class_platform(jclass* cls, const char* name) {
+#ifdef __ANDROID__
+  jstring className = (*jniEnv)->NewStringUTF(jniEnv, name);
+  *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader,
+                                     jni->loadClassMethod, className);
+  (*jniEnv)->DeleteLocalRef(jniEnv, className);
+#else
+  *cls = (*jniEnv)->FindClass(jniEnv, name);
+#endif
+}
+
+static inline void load_class_local_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(cls, name);
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_class_global_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    jclass tmp = NULL;
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(&tmp, name);
+      if (!(*jniEnv)->ExceptionCheck(jniEnv)) {
+        *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp);
+        (*jniEnv)->DeleteLocalRef(jniEnv, tmp);
+      }
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_method(jclass cls,
+                               jmethodID* res,
+                               const char* name,
+                               const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_static_method(jclass cls,
+                                      jmethodID* res,
+                                      const char* name,
+                                      const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_field(jclass cls,
+                              jfieldID* res,
+                              const char* name,
+                              const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline void load_static_field(jclass cls,
+                                     jfieldID* res,
+                                     const char* name,
+                                     const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline jobject to_global_ref(jobject ref) {
+  jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref);
+  (*jniEnv)->DeleteLocalRef(jniEnv, ref);
+  return g;
+}
+
+// These functions are useful for C+Dart bindings, and not required for pure dart bindings.
+
+FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr();
+
+/// For use by jni_gen's generated code
+/// don't use these.
+
+// these 2 fn ptr vars will be defined by generated code library
+extern JniContext* (*context_getter)(void);
+extern JNIEnv* (*env_getter)(void);
+
+// this function will be exported by generated code library
+// it will set above 2 variables.
+FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void),
+                                     JNIEnv* (*eg)(void));
+
+static inline void load_env() {
+  if (jniEnv == NULL) {
+    jni = context_getter();
+    jniEnv = env_getter();
+  }
+}
+
+static inline jthrowable check_exception() {
+  jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv);
+  if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv);
+  if (exception == NULL) return NULL;
+  return to_global_ref(exception);
+}
+
+static inline JniResult to_global_ref_result(jobject ref) {
+  JniResult result;
+  result.exception = check_exception();
+  if (result.exception == NULL) {
+    result.value.l = to_global_ref(ref);
+  }
+  return result;
+}
+
+FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+
+FFI_PLUGIN_EXPORT
+JniResult DartException__ctor(jstring message);
+
+FFI_PLUGIN_EXPORT
+JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+                                 int64_t port,
+                                 int64_t functionPtr);
+
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
diff --git a/pkgs/jnigen/example/kotlin_plugin/.gitignore b/pkgs/jnigen/example/kotlin_plugin/.gitignore
new file mode 100644
index 0000000..96486fd
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/.gitignore
@@ -0,0 +1,30 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.packages
+build/
diff --git a/pkgs/jnigen/example/kotlin_plugin/.metadata b/pkgs/jnigen/example/kotlin_plugin/.metadata
new file mode 100644
index 0000000..2276419
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: 9b4416aaa79bf984d06aa40ab515a274a50a75a7
+  channel: beta
+
+project_type: package
diff --git a/pkgs/jnigen/example/kotlin_plugin/README.md b/pkgs/jnigen/example/kotlin_plugin/README.md
new file mode 100644
index 0000000..2a915c8
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/README.md
@@ -0,0 +1,38 @@
+# kotlin_plugin
+
+This example generates bindings for a Kotlin-based library. It showcases the conversion of `suspend fun` in Kotlin to `async` functions in Dart.
+
+The command to regenerate JNI bindings is:
+```
+flutter pub run jnigen --config jnigen.yaml # run from kotlin_plugin project root 
+```
+
+The `example/` app must be built at least once in _release_ mode (eg `flutter build apk`) before running jnigen. This is the equivalent of Gradle Sync in Android Studio, and enables `jnigen` to run a Gradle stub and determine release build's classpath, which contains the paths to relevant dependencies. Therefore a build must have been run after cleaning build directories, or updating Java dependencies. This is a known complexity of the Gradle build system, and if you know a solution, please contribute to issue discussion at #33.
+
+Note that `jnigen.yaml` of this example contains the option `suspend_fun_to_async: true`. This will generate `async` method bindings from Kotlin's `suspend fun`s.
+
+For Kotlin coroutines to work, `Jni.initDLApi()` must be run first.
+
+## Creating a new Kotlin plugin
+
+Running `flutter create --template=plugin_ffi --platform=android kotlin_plugin` creates the skeleton of an Android plugin.
+
+In order to use Kotlin, `build.gradle` must be modified in the following way:
+
+```gradle
+apply plugin: 'com.android.library'
+// Add the line below:
+apply plugin: 'kotlin-android'
+```
+
+```gradle
+android {
+    // Add the following block
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+    // ...
+}
+```
+
+Now the Kotlin code in `src/main/kotlin` is included in the plugin.
diff --git a/pkgs/jnigen/example/kotlin_plugin/analysis_options.yaml b/pkgs/jnigen/example/kotlin_plugin/analysis_options.yaml
new file mode 100644
index 0000000..f9b3034
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:flutter_lints/flutter.yaml
diff --git a/pkgs/jnigen/example/kotlin_plugin/android/.gitignore b/pkgs/jnigen/example/kotlin_plugin/android/.gitignore
new file mode 100644
index 0000000..161bdcd
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/pkgs/jnigen/example/kotlin_plugin/android/build.gradle b/pkgs/jnigen/example/kotlin_plugin/android/build.gradle
new file mode 100644
index 0000000..862afaf
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/android/build.gradle
@@ -0,0 +1,64 @@
+// The Android Gradle Plugin builds the native code with the Android NDK.
+
+group 'com.example.kotlin_plugin'
+version '1.0'
+
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        // The Android Gradle Plugin knows how to build native code with the NDK.
+        classpath 'com.android.tools.build:gradle:7.1.2'
+    }
+}
+
+rootProject.allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+    // Bumping the plugin compileSdkVersion requires all clients of this plugin
+    // to bump the version in their app.
+    compileSdkVersion 31
+
+    // Bumping the plugin ndkVersion requires all clients of this plugin to bump
+    // the version in their app and to download a newer version of the NDK.
+    ndkVersion "21.1.6352462"
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    // Invoke the shared CMake build with the Android Gradle Plugin.
+    externalNativeBuild {
+        cmake {
+            path "../src/CMakeLists.txt"
+
+            // The default CMake version for the Android Gradle Plugin is 3.10.2.
+            // https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
+            //
+            // The Flutter tooling requires that developers have CMake 3.10 or later
+            // installed. You should not increase this version, as doing so will cause
+            // the plugin to fail to compile for some customers of the plugin.
+            // version "3.10.2"
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    defaultConfig {
+        minSdkVersion 16
+    }
+}
diff --git a/pkgs/jnigen/example/kotlin_plugin/android/settings.gradle b/pkgs/jnigen/example/kotlin_plugin/android/settings.gradle
new file mode 100644
index 0000000..8e0a5b1
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'notification_plugin'
diff --git a/pkgs/jnigen/example/kotlin_plugin/android/src/main/AndroidManifest.xml b/pkgs/jnigen/example/kotlin_plugin/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d7d511a
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.example.kotlin_plugin">
+</manifest>
diff --git a/pkgs/jnigen/example/kotlin_plugin/android/src/main/kotlin/Example.kt b/pkgs/jnigen/example/kotlin_plugin/android/src/main/kotlin/Example.kt
new file mode 100644
index 0000000..531f899
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/android/src/main/kotlin/Example.kt
@@ -0,0 +1,10 @@
+import androidx.annotation.Keep
+import kotlinx.coroutines.*
+
+@Keep
+class Example {
+  public suspend fun thinkBeforeAnswering(): String {
+    delay(1000L)
+    return "42"
+  }
+}
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/.gitignore b/pkgs/jnigen/example/kotlin_plugin/example/.gitignore
new file mode 100644
index 0000000..24476c5
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/.gitignore
@@ -0,0 +1,44 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/.metadata b/pkgs/jnigen/example/kotlin_plugin/example/.metadata
new file mode 100644
index 0000000..b510dd9
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+  revision: 9b4416aaa79bf984d06aa40ab515a274a50a75a7
+  channel: beta
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: 9b4416aaa79bf984d06aa40ab515a274a50a75a7
+      base_revision: 9b4416aaa79bf984d06aa40ab515a274a50a75a7
+    - platform: android
+      create_revision: 9b4416aaa79bf984d06aa40ab515a274a50a75a7
+      base_revision: 9b4416aaa79bf984d06aa40ab515a274a50a75a7
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/README.md b/pkgs/jnigen/example/kotlin_plugin/example/README.md
new file mode 100644
index 0000000..2b3fce4
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/README.md
@@ -0,0 +1,16 @@
+# example
+
+A new Flutter project.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/analysis_options.yaml b/pkgs/jnigen/example/kotlin_plugin/example/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/.gitignore b/pkgs/jnigen/example/kotlin_plugin/example/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/build.gradle b/pkgs/jnigen/example/kotlin_plugin/example/android/app/build.gradle
new file mode 100644
index 0000000..7f1cb3e
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/build.gradle
@@ -0,0 +1,71 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion flutter.compileSdkVersion
+    ndkVersion flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "com.example.kotlin_plugin.example"
+        // You can update the following values to match your application needs.
+        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/proguard-rules.pro b/pkgs/jnigen/example/kotlin_plugin/example/android/app/proguard-rules.pro
new file mode 100644
index 0000000..8c1218b
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/proguard-rules.pro
@@ -0,0 +1 @@
+-keep class kotlin.coroutines.** { *; }
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/debug/AndroidManifest.xml b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..0d1fc86
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.kotlin_plugin.example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/AndroidManifest.xml b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9059f10
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.kotlin_plugin.example">
+   <application
+        android:label="example"
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/kotlin/com/example/kotlin_plugin/example/MainActivity.kt b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/kotlin/com/example/kotlin_plugin/example/MainActivity.kt
new file mode 100644
index 0000000..4826f82
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/kotlin/com/example/kotlin_plugin/example/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.kotlin_plugin.example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/drawable/launch_background.xml b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/values-night/styles.xml b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/values/styles.xml b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/profile/AndroidManifest.xml b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..2c06d9b
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.datastore.example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/build.gradle b/pkgs/jnigen/example/kotlin_plugin/example/android/build.gradle
new file mode 100644
index 0000000..713d7f6
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+    ext.kotlin_version = '1.7.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.2.0'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+tasks.register("clean", Delete) {
+    delete rootProject.buildDir
+}
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/gradle.properties b/pkgs/jnigen/example/kotlin_plugin/example/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/jnigen/example/kotlin_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..3c472b9
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/android/settings.gradle b/pkgs/jnigen/example/kotlin_plugin/example/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/lib/main.dart b/pkgs/jnigen/example/kotlin_plugin/example/lib/main.dart
new file mode 100644
index 0000000..50d1434
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/lib/main.dart
@@ -0,0 +1,98 @@
+import 'package:flutter/material.dart';
+import 'package:kotlin_plugin/kotlin_plugin.dart';
+import 'package:jni/jni.dart';
+
+void main() {
+  Jni.initDLApi();
+  runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  const MyApp({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Kotlin Plugin Example',
+      theme: ThemeData(
+        primarySwatch: Colors.blue,
+      ),
+      home: const MyHomePage(title: 'Kotlin Plugin Example Home Page'),
+    );
+  }
+}
+
+class MyHomePage extends StatefulWidget {
+  const MyHomePage({super.key, required this.title});
+
+  final String title;
+
+  @override
+  State<MyHomePage> createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+  Future<String>? answer;
+
+  late Example example;
+
+  @override
+  void initState() {
+    super.initState();
+    example = Example();
+  }
+
+  @override
+  void dispose() {
+    example.release();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(widget.title),
+      ),
+      body: Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: <Widget>[
+            const Text(
+              'What is the answer to life, the universe, and everything?',
+            ),
+            ElevatedButton(
+              onPressed: () {
+                setState(() {
+                  answer = example.thinkBeforeAnswering().then(
+                      (value) => value.toDartString(releaseOriginal: true));
+                });
+              },
+              child: const Text('Think...'),
+            ),
+            FutureBuilder<String>(
+              future: answer,
+              builder: (context, snapshot) {
+                switch (snapshot.connectionState) {
+                  case ConnectionState.none:
+                    return const SizedBox();
+                  case ConnectionState.waiting:
+                  case ConnectionState.active:
+                    return Text(
+                      'Thinking...',
+                      style: Theme.of(context).textTheme.headlineMedium,
+                    );
+                  case ConnectionState.done:
+                    return Text(
+                      snapshot.data ?? "I don't know!",
+                      style: Theme.of(context).textTheme.headlineMedium,
+                    );
+                }
+              },
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/pkgs/jnigen/example/kotlin_plugin/example/pubspec.yaml b/pkgs/jnigen/example/kotlin_plugin/example/pubspec.yaml
new file mode 100644
index 0000000..4c1c796
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/example/pubspec.yaml
@@ -0,0 +1,29 @@
+name: example
+description: A new Flutter project.
+
+publish_to: "none"
+
+version: 1.0.0+1
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+
+dependencies:
+  flutter:
+    sdk: flutter
+
+  kotlin_plugin:
+    path: ../
+  
+  jni:
+    path: ../../../../jni
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+
+  flutter_lints: ^2.0.0
+
+flutter:
+  uses-material-design: true
+
diff --git a/pkgs/jnigen/example/kotlin_plugin/jnigen.yaml b/pkgs/jnigen/example/kotlin_plugin/jnigen.yaml
new file mode 100644
index 0000000..60978f5
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/jnigen.yaml
@@ -0,0 +1,21 @@
+android_sdk_config:
+  add_gradle_deps: true
+  android_example: 'example/'
+
+summarizer:
+  backend: asm
+
+suspend_fun_to_async: true
+
+output:
+  c:
+    library_name: kotlin_plugin_bindings
+    path: src/
+  dart:
+    path: lib/kotlin_bindings.dart
+    structure: single_file
+
+log_level: all
+
+classes:
+  - 'Example'
diff --git a/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart b/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
new file mode 100644
index 0000000..e1e7abb
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_bindings.dart
@@ -0,0 +1,97 @@
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+// Auto-generated initialization code.
+
+final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
+    ProtectedJniExtensions.initGeneratedLibrary("kotlin_plugin_bindings");
+
+/// from: Example
+class Example extends jni.JObject {
+  @override
+  late final jni.JObjType<Example> $type = type;
+
+  Example.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $ExampleType();
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("Example__new0")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example() {
+    return Example.fromRef(_new0().object);
+  }
+
+  static final _thinkBeforeAnswering = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Example__thinkBeforeAnswering")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final java.lang.Object thinkBeforeAnswering(kotlin.coroutines.Continuation continuation)
+  /// The returned object must be released after use, by calling the [release] method.
+  Future<jni.JString> thinkBeforeAnswering() async {
+    final $p = ReceivePort();
+    final $c =
+        jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
+    _thinkBeforeAnswering(reference, $c.reference).object;
+    final $o = jni.JObjectPtr.fromAddress(await $p.first);
+    final $k = const jni.JStringType().getClass().reference;
+    if (!jni.Jni.env.IsInstanceOf($o, $k)) {
+      throw "Failed";
+    }
+    return const jni.JStringType().fromRef($o);
+  }
+}
+
+final class $ExampleType extends jni.JObjType<Example> {
+  const $ExampleType();
+
+  @override
+  String get signature => r"LExample;";
+
+  @override
+  Example fromRef(jni.JObjectPtr ref) => Example.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($ExampleType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($ExampleType) && other is $ExampleType;
+  }
+}
diff --git a/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_plugin.dart b/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_plugin.dart
new file mode 100644
index 0000000..5919c22
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/lib/kotlin_plugin.dart
@@ -0,0 +1,3 @@
+library kotlin_plugin;
+
+export 'kotlin_bindings.dart';
diff --git a/pkgs/jnigen/example/kotlin_plugin/pubspec.yaml b/pkgs/jnigen/example/kotlin_plugin/pubspec.yaml
new file mode 100644
index 0000000..fd89ad6
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/pubspec.yaml
@@ -0,0 +1,31 @@
+# Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+name: kotlin_plugin
+description: Example of using jnigen to generate bindings for Kotlin.
+version: 0.0.1
+publish_to: none
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+  flutter: ">=1.17.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  jni:
+    path: ../../../jni
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  flutter_lints: ^2.0.0
+  jnigen:
+    path: ../../
+
+flutter:
+  plugin:
+    platforms:
+      android:
+        ffiPlugin: true
diff --git a/pkgs/jnigen/example/kotlin_plugin/src/.clang-format b/pkgs/jnigen/example/kotlin_plugin/src/.clang-format
new file mode 100644
index 0000000..a256c2f
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/src/.clang-format
@@ -0,0 +1,15 @@
+# From dart SDK: https://github.com/dart-lang/sdk/blob/main/.clang-format
+
+# Defines the Chromium style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
+
+# clang-format doesn't seem to do a good job of this for longer comments.
+ReflowComments: 'false'
+
+# We have lots of these. Though we need to put them all in curly braces,
+# clang-format can't do that.
+AllowShortIfStatementsOnASingleLine: 'true'
+
+# Put escaped newlines into the rightmost column.
+AlignEscapedNewlinesLeft: false
diff --git a/pkgs/jnigen/example/kotlin_plugin/src/CMakeLists.txt b/pkgs/jnigen/example/kotlin_plugin/src/CMakeLists.txt
new file mode 100644
index 0000000..78d9dca
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/src/CMakeLists.txt
@@ -0,0 +1,32 @@
+# jni_native_build (Build with jni:setup. Do not delete this line.)
+
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project(kotlin_plugin_bindings VERSION 0.0.1 LANGUAGES C)
+
+add_library(kotlin_plugin_bindings SHARED
+  "./kotlin_plugin_bindings.c"
+)
+
+set_target_properties(kotlin_plugin_bindings PROPERTIES
+  OUTPUT_NAME "kotlin_plugin_bindings"
+)
+
+target_compile_definitions(kotlin_plugin_bindings PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries(kotlin_plugin_bindings log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries(kotlin_plugin_bindings ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jnigen/example/kotlin_plugin/src/dartjni.h b/pkgs/jnigen/example/kotlin_plugin/src/dartjni.h
new file mode 100644
index 0000000..8f1dc74
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/src/dartjni.h
@@ -0,0 +1,424 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h.
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
+#if _WIN32
+#define FFI_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FFI_PLUGIN_EXPORT
+#endif
+
+#if defined _WIN32
+#define thread_local __declspec(thread)
+#else
+#define thread_local __thread
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#ifdef __ANDROID__
+#define __ENVP_CAST (JNIEnv**)
+#else
+#define __ENVP_CAST (void**)
+#endif
+
+/// Locking functions for windows and pthread.
+
+#if defined _WIN32
+#include <windows.h>
+
+typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  InitializeCriticalSection(lock);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  EnterCriticalSection(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  LeaveCriticalSection(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  DeleteCriticalSection(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  // Not available.
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ ||         \
+    defined __GNUC__
+#include <pthread.h>
+
+typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  pthread_mutex_init(lock, NULL);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  pthread_mutex_lock(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  pthread_mutex_unlock(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  pthread_mutex_destroy(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  pthread_cond_destroy(cond);
+}
+
+#else
+
+#error "No locking/condition variable support; Possibly unsupported platform"
+
+#endif
+
+typedef struct CallbackResult {
+  MutexLock lock;
+  ConditionVariable cond;
+  int ready;
+  jobject object;
+} CallbackResult;
+
+typedef struct JniLocks {
+  MutexLock classLoadingLock;
+  MutexLock methodLoadingLock;
+  MutexLock fieldLoadingLock;
+} JniLocks;
+
+/// Represents the error when dart-jni layer has already spawned singleton VM.
+#define DART_JNI_SINGLETON_EXISTS (-99);
+
+/// Stores the global state of the JNI.
+typedef struct JniContext {
+  JavaVM* jvm;
+  jobject classLoader;
+  jmethodID loadClassMethod;
+  jobject currentActivity;
+  jobject appContext;
+  JniLocks locks;
+} JniContext;
+
+// jniEnv for this thread, used by inline functions in this header,
+// therefore declared as extern.
+extern thread_local JNIEnv* jniEnv;
+
+extern JniContext* jni;
+
+/// Types used by JNI API to distinguish between primitive types.
+enum JniType {
+  booleanType = 0,
+  byteType = 1,
+  shortType = 2,
+  charType = 3,
+  intType = 4,
+  longType = 5,
+  floatType = 6,
+  doubleType = 7,
+  objectType = 8,
+  voidType = 9,
+};
+
+/// Result type for use by JNI.
+///
+/// If [exception] is null, it means the result is valid.
+/// It's assumed that the caller knows the expected type in [result].
+typedef struct JniResult {
+  jvalue value;
+  jthrowable exception;
+} JniResult;
+
+/// Similar to [JniResult] but for class lookups.
+typedef struct JniClassLookupResult {
+  jclass value;
+  jthrowable exception;
+} JniClassLookupResult;
+
+/// Similar to [JniResult] but for method/field ID lookups.
+typedef struct JniPointerResult {
+  const void* value;
+  jthrowable exception;
+} JniPointerResult;
+
+/// JniExceptionDetails holds 2 jstring objects, one is the result of
+/// calling `toString` on exception object, other is stack trace;
+typedef struct JniExceptionDetails {
+  jstring message;
+  jstring stacktrace;
+} JniExceptionDetails;
+
+/// This struct contains functions which wrap method call / field access conveniently along with
+/// exception checking.
+///
+/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us
+/// to check for and clear the exception before returning to dart code, which requires these functions
+/// to return result types.
+typedef struct JniAccessorsStruct {
+  JniClassLookupResult (*getClass)(char* internalName);
+  JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature);
+  JniPointerResult (*getStaticFieldID)(jclass cls,
+                                       char* fieldName,
+                                       char* signature);
+  JniPointerResult (*getMethodID)(jclass cls,
+                                  char* methodName,
+                                  char* signature);
+  JniPointerResult (*getStaticMethodID)(jclass cls,
+                                        char* methodName,
+                                        char* signature);
+  JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args);
+  JniResult (*newPrimitiveArray)(jsize length, int type);
+  JniResult (*newObjectArray)(jsize length,
+                              jclass elementClass,
+                              jobject initialElement);
+  JniResult (*getArrayElement)(jarray array, int index, int type);
+  JniResult (*callMethod)(jobject obj,
+                          jmethodID methodID,
+                          int callType,
+                          jvalue* args);
+  JniResult (*callStaticMethod)(jclass cls,
+                                jmethodID methodID,
+                                int callType,
+                                jvalue* args);
+  JniResult (*getField)(jobject obj, jfieldID fieldID, int callType);
+  JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType);
+  JniExceptionDetails (*getExceptionDetails)(jthrowable exception);
+} JniAccessorsStruct;
+
+FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors();
+
+FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void);
+
+FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void);
+
+/// Spawn a JVM with given arguments.
+///
+/// Returns JNI_OK on success, and one of the documented JNI error codes on
+/// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple
+/// JVMs is made, even if the underlying API potentially supports multiple VMs.
+FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args);
+
+/// Load class through platform-specific mechanism.
+///
+/// Currently uses application classloader on android,
+/// and JNIEnv->FindClass on other platforms.
+FFI_PLUGIN_EXPORT jclass FindClass(const char* name);
+
+/// Returns Application classLoader (on Android),
+/// which can be used to load application and platform classes.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetClassLoader(void);
+
+/// Returns application context on Android.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetApplicationContext(void);
+
+/// Returns current activity of the app on Android.
+FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void);
+
+static inline void attach_thread() {
+  if (jniEnv == NULL) {
+    (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL);
+  }
+}
+
+/// Load class into [cls] using platform specific mechanism
+static inline void load_class_platform(jclass* cls, const char* name) {
+#ifdef __ANDROID__
+  jstring className = (*jniEnv)->NewStringUTF(jniEnv, name);
+  *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader,
+                                     jni->loadClassMethod, className);
+  (*jniEnv)->DeleteLocalRef(jniEnv, className);
+#else
+  *cls = (*jniEnv)->FindClass(jniEnv, name);
+#endif
+}
+
+static inline void load_class_local_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(cls, name);
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_class_global_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    jclass tmp = NULL;
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(&tmp, name);
+      if (!(*jniEnv)->ExceptionCheck(jniEnv)) {
+        *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp);
+        (*jniEnv)->DeleteLocalRef(jniEnv, tmp);
+      }
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_method(jclass cls,
+                               jmethodID* res,
+                               const char* name,
+                               const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_static_method(jclass cls,
+                                      jmethodID* res,
+                                      const char* name,
+                                      const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_field(jclass cls,
+                              jfieldID* res,
+                              const char* name,
+                              const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline void load_static_field(jclass cls,
+                                     jfieldID* res,
+                                     const char* name,
+                                     const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline jobject to_global_ref(jobject ref) {
+  jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref);
+  (*jniEnv)->DeleteLocalRef(jniEnv, ref);
+  return g;
+}
+
+// These functions are useful for C+Dart bindings, and not required for pure dart bindings.
+
+FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr();
+
+/// For use by jni_gen's generated code
+/// don't use these.
+
+// these 2 fn ptr vars will be defined by generated code library
+extern JniContext* (*context_getter)(void);
+extern JNIEnv* (*env_getter)(void);
+
+// this function will be exported by generated code library
+// it will set above 2 variables.
+FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void),
+                                     JNIEnv* (*eg)(void));
+
+static inline void load_env() {
+  if (jniEnv == NULL) {
+    jni = context_getter();
+    jniEnv = env_getter();
+  }
+}
+
+static inline jthrowable check_exception() {
+  jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv);
+  if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv);
+  if (exception == NULL) return NULL;
+  return to_global_ref(exception);
+}
+
+static inline JniResult to_global_ref_result(jobject ref) {
+  JniResult result;
+  result.exception = check_exception();
+  if (result.exception == NULL) {
+    result.value.l = to_global_ref(ref);
+  }
+  return result;
+}
+
+FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+
+FFI_PLUGIN_EXPORT
+JniResult DartException__ctor(jstring message);
+
+FFI_PLUGIN_EXPORT
+JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+                                 int64_t port,
+                                 int64_t functionPtr);
+
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
diff --git a/pkgs/jnigen/example/kotlin_plugin/src/kotlin_plugin_bindings.c b/pkgs/jnigen/example/kotlin_plugin/src/kotlin_plugin_bindings.c
new file mode 100644
index 0000000..28a2af6
--- /dev/null
+++ b/pkgs/jnigen/example/kotlin_plugin/src/kotlin_plugin_bindings.c
@@ -0,0 +1,50 @@
+// Autogenerated by jnigen. DO NOT EDIT!
+
+#include <stdint.h>
+#include "dartjni.h"
+#include "jni.h"
+
+thread_local JNIEnv* jniEnv;
+JniContext* jni;
+
+JniContext* (*context_getter)(void);
+JNIEnv* (*env_getter)(void);
+
+void setJniGetters(JniContext* (*cg)(void), JNIEnv* (*eg)(void)) {
+  context_getter = cg;
+  env_getter = eg;
+}
+
+// Example
+jclass _c_Example = NULL;
+
+jmethodID _m_Example__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__new0() {
+  load_env();
+  load_class_global_ref(&_c_Example, "Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__new0, "<init>", "()V");
+  if (_m_Example__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Example, _m_Example__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__thinkBeforeAnswering = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__thinkBeforeAnswering(jobject self_, jobject continuation) {
+  load_env();
+  load_class_global_ref(&_c_Example, "Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__thinkBeforeAnswering,
+              "thinkBeforeAnswering",
+              "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;");
+  if (_m_Example__thinkBeforeAnswering == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_Example__thinkBeforeAnswering, continuation);
+  return to_global_ref_result(_result);
+}
diff --git a/pkgs/jnigen/example/notification_plugin/.gitignore b/pkgs/jnigen/example/notification_plugin/.gitignore
new file mode 100644
index 0000000..96486fd
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/.gitignore
@@ -0,0 +1,30 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.packages
+build/
diff --git a/pkgs/jnigen/example/notification_plugin/.metadata b/pkgs/jnigen/example/notification_plugin/.metadata
new file mode 100644
index 0000000..d677252
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+  revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+  channel: stable
+
+project_type: plugin_ffi
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+      base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+    - platform: android
+      create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+      base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/pkgs/jnigen/example/notification_plugin/README.md b/pkgs/jnigen/example/notification_plugin/README.md
new file mode 100644
index 0000000..e1b3af5
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/README.md
@@ -0,0 +1,13 @@
+# notification_plugin
+
+Example of Android plugin project with jnigen.
+
+This plugin project contains [custom code](android/src/main/java/com/example/notification_plugin) which uses the Android libraries. The bindings are generated using [jnigen config](jnigen.yaml) and then used in [flutter example](example/lib/main.dart), with help of `package:jni` APIs.
+
+The command to regenerate JNI bindings is:
+```
+flutter pub run jnigen --config jnigen.yaml # run from notification_plugin project root 
+```
+
+The `example/` app must be built at least once in _release_ mode (eg `flutter build apk`) before running jnigen. This is the equivalent of Gradle Sync in Android Studio, and enables `jnigen` to run a Gradle stub and determine release build's classpath, which contains the paths to relevant dependencies. Therefore a build must have been run after cleaning build directories, or updating Java dependencies. This is a known complexity of the Gradle build system, and if you know a solution, please contribute to issue discussion at #33.
+
diff --git a/pkgs/jnigen/example/notification_plugin/analysis_options.yaml b/pkgs/jnigen/example/notification_plugin/analysis_options.yaml
new file mode 100644
index 0000000..a5744c1
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jnigen/example/notification_plugin/android/.gitignore b/pkgs/jnigen/example/notification_plugin/android/.gitignore
new file mode 100644
index 0000000..161bdcd
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/pkgs/jnigen/example/notification_plugin/android/build.gradle b/pkgs/jnigen/example/notification_plugin/android/build.gradle
new file mode 100644
index 0000000..7643ec5
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/android/build.gradle
@@ -0,0 +1,59 @@
+// The Android Gradle Plugin builds the native code with the Android NDK.
+
+group 'com.example.notification_plugin'
+version '1.0'
+
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        // The Android Gradle Plugin knows how to build native code with the NDK.
+        classpath 'com.android.tools.build:gradle:7.1.2'
+    }
+}
+
+rootProject.allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    // Bumping the plugin compileSdkVersion requires all clients of this plugin
+    // to bump the version in their app.
+    compileSdkVersion 31
+
+    // Bumping the plugin ndkVersion requires all clients of this plugin to bump
+    // the version in their app and to download a newer version of the NDK.
+    ndkVersion "21.1.6352462"
+
+    // Invoke the shared CMake build with the Android Gradle Plugin.
+    externalNativeBuild {
+        cmake {
+            path "../src/CMakeLists.txt"
+
+            // The default CMake version for the Android Gradle Plugin is 3.10.2.
+            // https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
+            //
+            // The Flutter tooling requires that developers have CMake 3.10 or later
+            // installed. You should not increase this version, as doing so will cause
+            // the plugin to fail to compile for some customers of the plugin.
+            // version "3.10.2"
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    defaultConfig {
+        minSdkVersion 16
+    }
+}
diff --git a/pkgs/jnigen/example/notification_plugin/android/settings.gradle b/pkgs/jnigen/example/notification_plugin/android/settings.gradle
new file mode 100644
index 0000000..8e0a5b1
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'notification_plugin'
diff --git a/pkgs/jnigen/example/notification_plugin/android/src/main/AndroidManifest.xml b/pkgs/jnigen/example/notification_plugin/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..01cb165
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.example.notification_plugin">
+</manifest>
diff --git a/pkgs/jnigen/example/notification_plugin/android/src/main/java/com/example/notification_plugin/Notifications.java b/pkgs/jnigen/example/notification_plugin/android/src/main/java/com/example/notification_plugin/Notifications.java
new file mode 100644
index 0000000..59f8b7a
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/android/src/main/java/com/example/notification_plugin/Notifications.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.example.notification_plugin;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import androidx.annotation.Keep;
+import androidx.core.app.NotificationCompat;
+
+@Keep
+public class Notifications {
+  public static void showNotification(
+      Context context, int notificationID, String title, String text) {
+    @SuppressWarnings("deprecation")
+    NotificationCompat.Builder builder =
+        new NotificationCompat.Builder(context)
+            .setSmallIcon(android.R.drawable.ic_dialog_info)
+            .setContentTitle(title)
+            .setContentText(text);
+    NotificationManager notificationManager =
+        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
+      String channelId = "my_channel";
+      NotificationChannel channel =
+          new NotificationChannel(
+              channelId, "My App's notifications", NotificationManager.IMPORTANCE_HIGH);
+      notificationManager.createNotificationChannel(channel);
+      builder.setChannelId(channelId);
+    }
+    notificationManager.notify(notificationID, builder.build());
+  }
+}
diff --git a/pkgs/jnigen/example/notification_plugin/example/.gitignore b/pkgs/jnigen/example/notification_plugin/example/.gitignore
new file mode 100644
index 0000000..24476c5
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/.gitignore
@@ -0,0 +1,44 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/pkgs/jnigen/example/notification_plugin/example/README.md b/pkgs/jnigen/example/notification_plugin/example/README.md
new file mode 100644
index 0000000..0ec9e1d
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/README.md
@@ -0,0 +1,4 @@
+# notification_plugin_example
+
+This app demonstrates how to use the notification_plugin plugin.
+
diff --git a/pkgs/jnigen/example/notification_plugin/example/analysis_options.yaml b/pkgs/jnigen/example/notification_plugin/example/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/.gitignore b/pkgs/jnigen/example/notification_plugin/example/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/build.gradle b/pkgs/jnigen/example/notification_plugin/example/android/app/build.gradle
new file mode 100644
index 0000000..7f66900
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/build.gradle
@@ -0,0 +1,71 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion flutter.compileSdkVersion
+    ndkVersion flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "com.example.notification_plugin_example"
+        // You can update the following values to match your application needs.
+        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/debug/AndroidManifest.xml b/pkgs/jnigen/example/notification_plugin/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..822a208
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.notification_plugin_example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/AndroidManifest.xml b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..60e7eab
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.notification_plugin_example">
+   <application
+        android:label="notification_plugin_example"
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/kotlin/com/example/notification_plugin_example/MainActivity.kt b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/kotlin/com/example/notification_plugin_example/MainActivity.kt
new file mode 100644
index 0000000..af88e15
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/kotlin/com/example/notification_plugin_example/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.notification_plugin_example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/drawable/launch_background.xml b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/values-night/styles.xml b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/values/styles.xml b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/app/src/profile/AndroidManifest.xml b/pkgs/jnigen/example/notification_plugin/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..822a208
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.notification_plugin_example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/build.gradle b/pkgs/jnigen/example/notification_plugin/example/android/build.gradle
new file mode 100644
index 0000000..3cdaac9
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+    ext.kotlin_version = '1.6.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.1.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+tasks.register("clean", Delete) {
+    delete rootProject.buildDir
+}
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/gradle.properties b/pkgs/jnigen/example/notification_plugin/example/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/jnigen/example/notification_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..cb24abd
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
diff --git a/pkgs/jnigen/example/notification_plugin/example/android/settings.gradle b/pkgs/jnigen/example/notification_plugin/example/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/pkgs/jnigen/example/notification_plugin/example/lib/main.dart b/pkgs/jnigen/example/notification_plugin/example/lib/main.dart
new file mode 100644
index 0000000..c834bcb
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/lib/main.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:jni/jni.dart';
+
+// The hierarchy created in generated code will mirror the java package
+// structure. This is an implementation convenience and we may allow
+// more customization in future.
+import 'package:notification_plugin/notifications.dart';
+
+JObject activity = JObject.fromRef(Jni.getCurrentActivity());
+
+int i = 0;
+
+void showNotification(String title, String text) {
+  i = i + 1;
+  var jTitle = JString.fromString(title);
+  var jText = JString.fromString(text);
+  Notifications.showNotification(activity, i, jTitle, jText);
+  jTitle.release();
+  jText.release();
+}
+
+void main() {
+  runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+  const MyApp({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Flutter Demo',
+      theme: ThemeData(
+        primarySwatch: Colors.teal,
+      ),
+      home: MyHomePage(title: 'Flutter Demo Home Page'),
+    );
+  }
+}
+
+class MyHomePage extends StatelessWidget {
+  MyHomePage({Key? key, required this.title}) : super(key: key);
+
+  final String title;
+
+  final _title = TextEditingController(text: 'Hello from JNI');
+  final _text = TextEditingController(text: '😀');
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(title),
+      ),
+      body: Center(
+        child: Padding(
+          padding: const EdgeInsets.all(16),
+          child: Column(
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              TextFormField(
+                controller: _title,
+                textCapitalization: TextCapitalization.sentences,
+                decoration:
+                    const InputDecoration(labelText: 'Notification title'),
+              ),
+              TextFormField(
+                controller: _text,
+                keyboardType: TextInputType.multiline,
+                minLines: 1,
+                maxLines: 4,
+                decoration:
+                    const InputDecoration(labelText: 'Notification text'),
+              ),
+              ElevatedButton(
+                child: const Text('Show Notification'),
+                onPressed: () => showNotification(_title.text, _text.text),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/pkgs/jnigen/example/notification_plugin/example/pubspec.yaml b/pkgs/jnigen/example/notification_plugin/example/pubspec.yaml
new file mode 100644
index 0000000..35f3357
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/example/pubspec.yaml
@@ -0,0 +1,89 @@
+name: notification_plugin_example
+description: Demonstrates how to use notification_plugin.
+
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+# In Windows, build-name is used as the major, minor, and patch parts
+# of the product and file versions while build-number is used as the build suffix.
+version: 1.0.0+1
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+
+dependencies:
+  flutter:
+    sdk: flutter
+
+  notification_plugin:
+    path: ../
+
+  jni:
+    path: ../../../../jni/
+  # The following adds the Cupertino Icons font to your application.
+  # Use with the CupertinoIcons class for iOS style icons.
+  cupertino_icons: ^1.0.2
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+
+  # The "flutter_lints" package below contains a set of recommended lints to
+  # encourage good coding practices. The lint set provided by the package is
+  # activated in the `analysis_options.yaml` file located at the root of your
+  # package. See that file for information about deactivating specific lint
+  # rules and activating additional ones.
+  flutter_lints: ^2.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+  # The following line ensures that the Material Icons font is
+  # included with your application, so that you can use the icons in
+  # the material Icons class.
+  uses-material-design: true
+
+  # To add assets to your application, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware
+
+  # For details regarding adding assets from package dependencies, see
+  # https://flutter.dev/assets-and-images/#from-packages
+
+  # To add custom fonts to your application, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts from package dependencies,
+  # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jnigen/example/notification_plugin/jnigen.yaml b/pkgs/jnigen/example/notification_plugin/jnigen.yaml
new file mode 100644
index 0000000..21f4106
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/jnigen.yaml
@@ -0,0 +1,24 @@
+android_sdk_config:
+  add_gradle_deps: true
+  android_example: 'example/'
+
+preamble: |
+  // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+  // for details. All rights reserved. Use of this source code is governed by a
+  // BSD-style license that can be found in the LICENSE file.
+
+source_path:
+  - 'android/src/main/java'
+classes:
+  - 'com.example.notification_plugin.Notifications'
+
+output:
+  c:
+    path: 'src/'
+    library_name: notification_plugin
+  dart:
+    path: 'lib/notifications.dart'
+    ## Output to single file instead of recreating source's file structure.
+    ## This will be useful to reduce clutter when binding a small number of
+    ## classes.
+    structure: 'single_file'
diff --git a/pkgs/jnigen/example/notification_plugin/lib/notifications.dart b/pkgs/jnigen/example/notification_plugin/lib/notifications.dart
new file mode 100644
index 0000000..d91a2f5
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/lib/notifications.dart
@@ -0,0 +1,102 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+// Auto-generated initialization code.
+
+final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
+    ProtectedJniExtensions.initGeneratedLibrary("notification_plugin");
+
+/// from: com.example.notification_plugin.Notifications
+class Notifications extends jni.JObject {
+  @override
+  late final jni.JObjType<Notifications> $type = type;
+
+  Notifications.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $NotificationsType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "Notifications__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Notifications() {
+    return Notifications.fromRef(_new0().object);
+  }
+
+  static final _showNotification = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Notifications__showNotification")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, int,
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public void showNotification(android.content.Context context, int notificationID, java.lang.String title, java.lang.String text)
+  static void showNotification(
+    jni.JObject context,
+    int notificationID,
+    jni.JString title,
+    jni.JString text,
+  ) {
+    return _showNotification(
+            context.reference, notificationID, title.reference, text.reference)
+        .check();
+  }
+}
+
+final class $NotificationsType extends jni.JObjType<Notifications> {
+  const $NotificationsType();
+
+  @override
+  String get signature => r"Lcom/example/notification_plugin/Notifications;";
+
+  @override
+  Notifications fromRef(jni.JObjectPtr ref) => Notifications.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($NotificationsType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($NotificationsType) &&
+        other is $NotificationsType;
+  }
+}
diff --git a/pkgs/jnigen/example/notification_plugin/pubspec.yaml b/pkgs/jnigen/example/notification_plugin/pubspec.yaml
new file mode 100644
index 0000000..1f6cf49
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/pubspec.yaml
@@ -0,0 +1,76 @@
+name: notification_plugin
+description: Example for Android plugin with custom Java code using jnigen.
+version: 0.0.1
+publish_to: none
+homepage: https://github.com/dart-lang/jnigen
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+  flutter: ">=2.11.0"
+
+dependencies:
+  jni:
+    path: ../../../jni/
+  flutter:
+    sdk: flutter
+  plugin_platform_interface: ^2.0.2
+
+dev_dependencies:
+  jnigen:
+    path: ../../
+  flutter_test:
+    sdk: flutter
+  flutter_lints: ^2.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+  # This section identifies this Flutter project as a plugin project.
+  # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
+  # which should be registered in the plugin registry. This is required for
+  # using method channels.
+  # The Android 'package' specifies package in which the registered class is.
+  # This is required for using method channels on Android.
+  # The 'ffiPlugin' specifies that native code should be built and bundled.
+  # This is required for using `dart:ffi`.
+  # All these are used by the tooling to maintain consistency when
+  # adding or updating assets for this project.
+  #
+  # Please refer to README.md for a detailed explanation.
+  plugin:
+    platforms:
+      android:
+        ffiPlugin: true
+
+  # To add assets to your plugin package, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+  #
+  # For details regarding assets in packages, see
+  # https://flutter.dev/assets-and-images/#from-packages
+  #
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware
+
+  # To add custom fonts to your plugin package, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts in packages, see
+  # https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jnigen/example/notification_plugin/src/.clang-format b/pkgs/jnigen/example/notification_plugin/src/.clang-format
new file mode 100644
index 0000000..a256c2f
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/src/.clang-format
@@ -0,0 +1,15 @@
+# From dart SDK: https://github.com/dart-lang/sdk/blob/main/.clang-format
+
+# Defines the Chromium style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
+
+# clang-format doesn't seem to do a good job of this for longer comments.
+ReflowComments: 'false'
+
+# We have lots of these. Though we need to put them all in curly braces,
+# clang-format can't do that.
+AllowShortIfStatementsOnASingleLine: 'true'
+
+# Put escaped newlines into the rightmost column.
+AlignEscapedNewlinesLeft: false
diff --git a/pkgs/jnigen/example/notification_plugin/src/CMakeLists.txt b/pkgs/jnigen/example/notification_plugin/src/CMakeLists.txt
new file mode 100644
index 0000000..39c9850
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/src/CMakeLists.txt
@@ -0,0 +1,32 @@
+# jni_native_build (Build with jni:setup. Do not delete this line.)
+
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project(notification_plugin VERSION 0.0.1 LANGUAGES C)
+
+add_library(notification_plugin SHARED
+  "./notification_plugin.c"
+)
+
+set_target_properties(notification_plugin PROPERTIES
+  OUTPUT_NAME "notification_plugin"
+)
+
+target_compile_definitions(notification_plugin PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries(notification_plugin log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries(notification_plugin ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jnigen/example/notification_plugin/src/dartjni.h b/pkgs/jnigen/example/notification_plugin/src/dartjni.h
new file mode 100644
index 0000000..8f1dc74
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/src/dartjni.h
@@ -0,0 +1,424 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h.
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
+#if _WIN32
+#define FFI_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FFI_PLUGIN_EXPORT
+#endif
+
+#if defined _WIN32
+#define thread_local __declspec(thread)
+#else
+#define thread_local __thread
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#ifdef __ANDROID__
+#define __ENVP_CAST (JNIEnv**)
+#else
+#define __ENVP_CAST (void**)
+#endif
+
+/// Locking functions for windows and pthread.
+
+#if defined _WIN32
+#include <windows.h>
+
+typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  InitializeCriticalSection(lock);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  EnterCriticalSection(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  LeaveCriticalSection(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  DeleteCriticalSection(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  // Not available.
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ ||         \
+    defined __GNUC__
+#include <pthread.h>
+
+typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  pthread_mutex_init(lock, NULL);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  pthread_mutex_lock(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  pthread_mutex_unlock(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  pthread_mutex_destroy(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  pthread_cond_destroy(cond);
+}
+
+#else
+
+#error "No locking/condition variable support; Possibly unsupported platform"
+
+#endif
+
+typedef struct CallbackResult {
+  MutexLock lock;
+  ConditionVariable cond;
+  int ready;
+  jobject object;
+} CallbackResult;
+
+typedef struct JniLocks {
+  MutexLock classLoadingLock;
+  MutexLock methodLoadingLock;
+  MutexLock fieldLoadingLock;
+} JniLocks;
+
+/// Represents the error when dart-jni layer has already spawned singleton VM.
+#define DART_JNI_SINGLETON_EXISTS (-99);
+
+/// Stores the global state of the JNI.
+typedef struct JniContext {
+  JavaVM* jvm;
+  jobject classLoader;
+  jmethodID loadClassMethod;
+  jobject currentActivity;
+  jobject appContext;
+  JniLocks locks;
+} JniContext;
+
+// jniEnv for this thread, used by inline functions in this header,
+// therefore declared as extern.
+extern thread_local JNIEnv* jniEnv;
+
+extern JniContext* jni;
+
+/// Types used by JNI API to distinguish between primitive types.
+enum JniType {
+  booleanType = 0,
+  byteType = 1,
+  shortType = 2,
+  charType = 3,
+  intType = 4,
+  longType = 5,
+  floatType = 6,
+  doubleType = 7,
+  objectType = 8,
+  voidType = 9,
+};
+
+/// Result type for use by JNI.
+///
+/// If [exception] is null, it means the result is valid.
+/// It's assumed that the caller knows the expected type in [result].
+typedef struct JniResult {
+  jvalue value;
+  jthrowable exception;
+} JniResult;
+
+/// Similar to [JniResult] but for class lookups.
+typedef struct JniClassLookupResult {
+  jclass value;
+  jthrowable exception;
+} JniClassLookupResult;
+
+/// Similar to [JniResult] but for method/field ID lookups.
+typedef struct JniPointerResult {
+  const void* value;
+  jthrowable exception;
+} JniPointerResult;
+
+/// JniExceptionDetails holds 2 jstring objects, one is the result of
+/// calling `toString` on exception object, other is stack trace;
+typedef struct JniExceptionDetails {
+  jstring message;
+  jstring stacktrace;
+} JniExceptionDetails;
+
+/// This struct contains functions which wrap method call / field access conveniently along with
+/// exception checking.
+///
+/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us
+/// to check for and clear the exception before returning to dart code, which requires these functions
+/// to return result types.
+typedef struct JniAccessorsStruct {
+  JniClassLookupResult (*getClass)(char* internalName);
+  JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature);
+  JniPointerResult (*getStaticFieldID)(jclass cls,
+                                       char* fieldName,
+                                       char* signature);
+  JniPointerResult (*getMethodID)(jclass cls,
+                                  char* methodName,
+                                  char* signature);
+  JniPointerResult (*getStaticMethodID)(jclass cls,
+                                        char* methodName,
+                                        char* signature);
+  JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args);
+  JniResult (*newPrimitiveArray)(jsize length, int type);
+  JniResult (*newObjectArray)(jsize length,
+                              jclass elementClass,
+                              jobject initialElement);
+  JniResult (*getArrayElement)(jarray array, int index, int type);
+  JniResult (*callMethod)(jobject obj,
+                          jmethodID methodID,
+                          int callType,
+                          jvalue* args);
+  JniResult (*callStaticMethod)(jclass cls,
+                                jmethodID methodID,
+                                int callType,
+                                jvalue* args);
+  JniResult (*getField)(jobject obj, jfieldID fieldID, int callType);
+  JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType);
+  JniExceptionDetails (*getExceptionDetails)(jthrowable exception);
+} JniAccessorsStruct;
+
+FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors();
+
+FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void);
+
+FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void);
+
+/// Spawn a JVM with given arguments.
+///
+/// Returns JNI_OK on success, and one of the documented JNI error codes on
+/// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple
+/// JVMs is made, even if the underlying API potentially supports multiple VMs.
+FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args);
+
+/// Load class through platform-specific mechanism.
+///
+/// Currently uses application classloader on android,
+/// and JNIEnv->FindClass on other platforms.
+FFI_PLUGIN_EXPORT jclass FindClass(const char* name);
+
+/// Returns Application classLoader (on Android),
+/// which can be used to load application and platform classes.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetClassLoader(void);
+
+/// Returns application context on Android.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetApplicationContext(void);
+
+/// Returns current activity of the app on Android.
+FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void);
+
+static inline void attach_thread() {
+  if (jniEnv == NULL) {
+    (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL);
+  }
+}
+
+/// Load class into [cls] using platform specific mechanism
+static inline void load_class_platform(jclass* cls, const char* name) {
+#ifdef __ANDROID__
+  jstring className = (*jniEnv)->NewStringUTF(jniEnv, name);
+  *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader,
+                                     jni->loadClassMethod, className);
+  (*jniEnv)->DeleteLocalRef(jniEnv, className);
+#else
+  *cls = (*jniEnv)->FindClass(jniEnv, name);
+#endif
+}
+
+static inline void load_class_local_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(cls, name);
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_class_global_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    jclass tmp = NULL;
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(&tmp, name);
+      if (!(*jniEnv)->ExceptionCheck(jniEnv)) {
+        *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp);
+        (*jniEnv)->DeleteLocalRef(jniEnv, tmp);
+      }
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_method(jclass cls,
+                               jmethodID* res,
+                               const char* name,
+                               const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_static_method(jclass cls,
+                                      jmethodID* res,
+                                      const char* name,
+                                      const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_field(jclass cls,
+                              jfieldID* res,
+                              const char* name,
+                              const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline void load_static_field(jclass cls,
+                                     jfieldID* res,
+                                     const char* name,
+                                     const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline jobject to_global_ref(jobject ref) {
+  jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref);
+  (*jniEnv)->DeleteLocalRef(jniEnv, ref);
+  return g;
+}
+
+// These functions are useful for C+Dart bindings, and not required for pure dart bindings.
+
+FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr();
+
+/// For use by jni_gen's generated code
+/// don't use these.
+
+// these 2 fn ptr vars will be defined by generated code library
+extern JniContext* (*context_getter)(void);
+extern JNIEnv* (*env_getter)(void);
+
+// this function will be exported by generated code library
+// it will set above 2 variables.
+FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void),
+                                     JNIEnv* (*eg)(void));
+
+static inline void load_env() {
+  if (jniEnv == NULL) {
+    jni = context_getter();
+    jniEnv = env_getter();
+  }
+}
+
+static inline jthrowable check_exception() {
+  jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv);
+  if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv);
+  if (exception == NULL) return NULL;
+  return to_global_ref(exception);
+}
+
+static inline JniResult to_global_ref_result(jobject ref) {
+  JniResult result;
+  result.exception = check_exception();
+  if (result.exception == NULL) {
+    result.value.l = to_global_ref(ref);
+  }
+  return result;
+}
+
+FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+
+FFI_PLUGIN_EXPORT
+JniResult DartException__ctor(jstring message);
+
+FFI_PLUGIN_EXPORT
+JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+                                 int64_t port,
+                                 int64_t functionPtr);
+
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
diff --git a/pkgs/jnigen/example/notification_plugin/src/notification_plugin.c b/pkgs/jnigen/example/notification_plugin/src/notification_plugin.c
new file mode 100644
index 0000000..9f1e577
--- /dev/null
+++ b/pkgs/jnigen/example/notification_plugin/src/notification_plugin.c
@@ -0,0 +1,61 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+#include <stdint.h>
+#include "dartjni.h"
+#include "jni.h"
+
+thread_local JNIEnv* jniEnv;
+JniContext* jni;
+
+JniContext* (*context_getter)(void);
+JNIEnv* (*env_getter)(void);
+
+void setJniGetters(JniContext* (*cg)(void), JNIEnv* (*eg)(void)) {
+  context_getter = cg;
+  env_getter = eg;
+}
+
+// com.example.notification_plugin.Notifications
+jclass _c_Notifications = NULL;
+
+jmethodID _m_Notifications__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Notifications__new0() {
+  load_env();
+  load_class_global_ref(&_c_Notifications,
+                        "com/example/notification_plugin/Notifications");
+  if (_c_Notifications == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Notifications, &_m_Notifications__new0, "<init>", "()V");
+  if (_m_Notifications__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_Notifications, _m_Notifications__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Notifications__showNotification = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Notifications__showNotification(jobject context,
+                                          int32_t notificationID,
+                                          jobject title,
+                                          jobject text) {
+  load_env();
+  load_class_global_ref(&_c_Notifications,
+                        "com/example/notification_plugin/Notifications");
+  if (_c_Notifications == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_Notifications, &_m_Notifications__showNotification, "showNotification",
+      "(Landroid/content/Context;ILjava/lang/String;Ljava/lang/String;)V");
+  if (_m_Notifications__showNotification == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_Notifications,
+                                  _m_Notifications__showNotification, context,
+                                  notificationID, title, text);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/.gitignore b/pkgs/jnigen/example/pdfbox_plugin/.gitignore
new file mode 100644
index 0000000..a117acd
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/.gitignore
@@ -0,0 +1,42 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.packages
+build/
+
+## Downloaded by jnigen
+/mvn_java/
+/mvn_jar/
+## Sometimes generated by maven.
+## TODO(#34) This is unwanted.
+/target/
+
+# Builds & downloads
+*.pdf
+*.jar
+*.exe
diff --git a/pkgs/jnigen/example/pdfbox_plugin/.metadata b/pkgs/jnigen/example/pdfbox_plugin/.metadata
new file mode 100644
index 0000000..9d23aa9
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/.metadata
@@ -0,0 +1,36 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+  revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+  channel: stable
+
+project_type: plugin_ffi
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: android
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: linux
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+    - platform: windows
+      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/pkgs/jnigen/example/pdfbox_plugin/README.md b/pkgs/jnigen/example/pdfbox_plugin/README.md
new file mode 100644
index 0000000..efa8caa
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/README.md
@@ -0,0 +1,8 @@
+# pdfbox_plugin
+
+Example of a JNI plugin using `jnigen` to auto-generate bindings for PDFBox java library.
+
+Bindings can be generated by running `dart run jnigen --config jnigen.yaml`. Required source code and JARs will be downloaded using maven (requires `mvn` command).
+
+`jnigen` is also available as an API which tooling scripts can use. See `tool/generate_bindings.dart` for the script equivalent of the YAML file.
+
diff --git a/pkgs/jnigen/example/pdfbox_plugin/analysis_options.yaml b/pkgs/jnigen/example/pdfbox_plugin/analysis_options.yaml
new file mode 100644
index 0000000..198e7e0
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/analysis_options.yaml
@@ -0,0 +1,4 @@
+#include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jnigen/example/pdfbox_plugin/android/.gitignore b/pkgs/jnigen/example/pdfbox_plugin/android/.gitignore
new file mode 100644
index 0000000..161bdcd
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/pkgs/jnigen/example/pdfbox_plugin/android/build.gradle b/pkgs/jnigen/example/pdfbox_plugin/android/build.gradle
new file mode 100644
index 0000000..9c3bca8
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/android/build.gradle
@@ -0,0 +1,59 @@
+// The Android Gradle Plugin builds the native code with the Android NDK.
+
+group 'com.example.pdfbox_plugin'
+version '1.0'
+
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        // The Android Gradle Plugin knows how to build native code with the NDK.
+        classpath 'com.android.tools.build:gradle:7.1.2'
+    }
+}
+
+rootProject.allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    // Bumping the plugin compileSdkVersion requires all clients of this plugin
+    // to bump the version in their app.
+    compileSdkVersion 31
+
+    // Bumping the plugin ndkVersion requires all clients of this plugin to bump
+    // the version in their app and to download a newer version of the NDK.
+    ndkVersion "21.1.6352462"
+
+    // Invoke the shared CMake build with the Android Gradle Plugin.
+    externalNativeBuild {
+        cmake {
+            path "../src/CMakeLists.txt"
+
+            // The default CMake version for the Android Gradle Plugin is 3.10.2.
+            // https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
+            //
+            // The Flutter tooling requires that developers have CMake 3.10 or later
+            // installed. You should not increase this version, as doing so will cause
+            // the plugin to fail to compile for some customers of the plugin.
+            // version "3.10.2"
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    defaultConfig {
+        minSdkVersion 16
+    }
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/android/settings.gradle b/pkgs/jnigen/example/pdfbox_plugin/android/settings.gradle
new file mode 100644
index 0000000..84ca2f9
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'pdfbox_plugin'
diff --git a/pkgs/jnigen/example/pdfbox_plugin/android/src/main/AndroidManifest.xml b/pkgs/jnigen/example/pdfbox_plugin/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ae0f6a3
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.example.pdfbox_plugin">
+</manifest>
diff --git a/pkgs/jnigen/example/pdfbox_plugin/dart_example/.gitignore b/pkgs/jnigen/example/pdfbox_plugin/dart_example/.gitignore
new file mode 100644
index 0000000..9644944
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/dart_example/.gitignore
@@ -0,0 +1,12 @@
+# Files and directories created by pub.
+.dart_tool/
+.packages
+
+# Conventional directory for build output.
+build/
+
+# PDF files
+*.pdf
+
+# AOT compiled
+*.exe
diff --git a/pkgs/jnigen/example/pdfbox_plugin/dart_example/CHANGELOG.md b/pkgs/jnigen/example/pdfbox_plugin/dart_example/CHANGELOG.md
new file mode 100644
index 0000000..effe43c
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/dart_example/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/pkgs/jnigen/example/pdfbox_plugin/dart_example/README.md b/pkgs/jnigen/example/pdfbox_plugin/dart_example/README.md
new file mode 100644
index 0000000..6a6755e
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/dart_example/README.md
@@ -0,0 +1,10 @@
+## Running
+After generating `pdfbox_plugin` bindings in the parent directory
+
+* setup native libraries: `dart run jni:setup`
+
+* `dart run bin/pdf_info.dart <Path_to_PDF_File>`
+
+Alternatively, it's possible to compile `bin/pdf_info.dart` and then run it.
+
+To run it from other directories, the `helperDir` and `classpath` parameters in `Jni.spawn` call should be adjusted appropriately.
diff --git a/pkgs/jnigen/example/pdfbox_plugin/dart_example/analysis_options.yaml b/pkgs/jnigen/example/pdfbox_plugin/dart_example/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/dart_example/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+#   rules:
+#     - camel_case_types
+
+# analyzer:
+#   exclude:
+#     - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jnigen/example/pdfbox_plugin/dart_example/bin/pdf_info.dart b/pkgs/jnigen/example/pdfbox_plugin/dart_example/bin/pdf_info.dart
new file mode 100644
index 0000000..5dfd585
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/dart_example/bin/pdf_info.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:path/path.dart';
+import 'package:jni/jni.dart';
+
+import 'package:pdfbox_plugin/pdfbox_plugin.dart';
+
+void writeInfo(String file) {
+  final inputFile = Jni.newInstance(
+      "java/io/FileInputStream", "(Ljava/lang/String;)V", [file]);
+  final pdDoc = PDDocument.load6(inputFile);
+  int pages = pdDoc.getNumberOfPages();
+  final info = pdDoc.getDocumentInformation();
+  final title = info.getTitle();
+  final subject = info.getSubject();
+  final author = info.getAuthor();
+  stderr.writeln('Number of pages: $pages');
+
+  if (!title.isNull) {
+    stderr.writeln('Title: ${title.toDartString()}');
+  }
+
+  if (!subject.isNull) {
+    stderr.writeln('Subject: ${subject.toDartString()}');
+  }
+
+  if (!author.isNull) {
+    stderr.writeln('Author: ${author.toDartString()}');
+  }
+
+  stderr.writeln('PDF Version: ${pdDoc.getVersion().toStringAsPrecision(2)}');
+}
+
+final jniLibsDir = join('build', 'jni_libs');
+
+const jarError = 'No JAR files were found.\n'
+    'Run `dart run jnigen:download_maven_jars --config jnigen.yaml` '
+    'in plugin directory.\n'
+    'Alternatively, regenerate JNI bindings in plugin directory, which will '
+    'automatically download the JAR files.';
+
+void main(List<String> arguments) {
+  final jarDir = join('..', 'mvn_jar');
+  List<String> jars;
+  try {
+    jars = Directory(jarDir)
+        .listSync()
+        .map((e) => e.path)
+        .where((path) => path.endsWith('.jar'))
+        .toList();
+  } on OSError catch (_) {
+    stderr.writeln(jarError);
+    return;
+  }
+  if (jars.isEmpty) {
+    stderr.writeln(jarError);
+    return;
+  }
+  Jni.spawn(dylibDir: jniLibsDir, classPath: jars);
+  if (arguments.length != 1) {
+    stderr.writeln('usage: dart run pdf_info:pdf_info <Path_to_PDF>');
+    exitCode = 1;
+    return;
+  }
+  writeInfo(arguments[0]);
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/dart_example/pubspec.yaml b/pkgs/jnigen/example/pdfbox_plugin/dart_example/pubspec.yaml
new file mode 100644
index 0000000..4af3cf1
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/dart_example/pubspec.yaml
@@ -0,0 +1,19 @@
+name: pdf_info
+description: Dart standalone example using jnigen PDFBox bindings.
+version: 1.0.0
+publish_to: none
+# homepage: https://www.example.com
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+
+dependencies:
+  path: ^1.8.0
+  jni:
+    path: ../../../../jni/
+  pdfbox_plugin:
+    path: ../
+
+dev_dependencies:
+  lints: ^2.0.0
+  test: ^1.16.0
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/.gitignore b/pkgs/jnigen/example/pdfbox_plugin/example/.gitignore
new file mode 100644
index 0000000..a8e938c
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/.gitignore
@@ -0,0 +1,47 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/README.md b/pkgs/jnigen/example/pdfbox_plugin/example/README.md
new file mode 100644
index 0000000..0f471be
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/README.md
@@ -0,0 +1,18 @@
+# pdfbox_plugin_example
+
+Demonstrates how to use the PDFBox bindings generated using `jnigen`. This is a Linux Flutter application. The sample application lists the PDF files in a directory with number of pages and title, also allowing to navigate between directories.
+
+First, it's required to have generated the dart and C bindings in the plugin directory, which also downloads the required JARs using maven. The bindings are not committed because generated code is several thousands of lines.
+
+On a Linux machine, following commands can be used to run the example application.
+
+```
+cd .. ## From this folder
+dart run jnigen --config jnigen.yaml ## Downloads PDFBox JARs and generates bindings.
+cd example/
+flutter run --release ## Opens the files list from home directory
+```
+
+It may take some time for PDFBox to process all PDFs in a directory. In the interest of simplicity, this example application displays the list after all PDFs are processed.
+
+Follow along the code in `lib/main.dart` to see how generated bindings are used.
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/analysis_options.yaml b/pkgs/jnigen/example/pdfbox_plugin/example/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/.gitignore b/pkgs/jnigen/example/pdfbox_plugin/example/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/build.gradle b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/build.gradle
new file mode 100644
index 0000000..c5791ba
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/build.gradle
@@ -0,0 +1,71 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion flutter.compileSdkVersion
+    ndkVersion flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "com.example.pdfbox_plugin_example"
+        // You can update the following values to match your application needs.
+        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/debug/AndroidManifest.xml b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..3789e50
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.pdfbox_plugin_example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/AndroidManifest.xml b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a46900a
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.pdfbox_plugin_example">
+   <application
+        android:label="pdfbox_plugin_example"
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/kotlin/com/example/pdfbox_plugin_example/MainActivity.kt b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/kotlin/com/example/pdfbox_plugin_example/MainActivity.kt
new file mode 100644
index 0000000..68d076c
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/kotlin/com/example/pdfbox_plugin_example/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.pdfbox_plugin_example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/drawable/launch_background.xml b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/values-night/styles.xml b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/values/styles.xml b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/profile/AndroidManifest.xml b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..3789e50
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.pdfbox_plugin_example">
+    <!-- The INTERNET permission is required for development. Specifically,
+         the Flutter tool needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/build.gradle b/pkgs/jnigen/example/pdfbox_plugin/example/android/build.gradle
new file mode 100644
index 0000000..3cdaac9
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+    ext.kotlin_version = '1.6.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.1.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+tasks.register("clean", Delete) {
+    delete rootProject.buildDir
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/gradle.properties b/pkgs/jnigen/example/pdfbox_plugin/example/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/jnigen/example/pdfbox_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..cc5527d
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/android/settings.gradle b/pkgs/jnigen/example/pdfbox_plugin/example/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/lib/main.dart b/pkgs/jnigen/example/pdfbox_plugin/example/lib/main.dart
new file mode 100644
index 0000000..ccc6563
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/lib/main.dart
@@ -0,0 +1,202 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'dart:io';
+import 'dart:async';
+
+import 'package:jni/jni.dart';
+import 'package:path/path.dart';
+
+import 'package:pdfbox_plugin/pdfbox_plugin.dart';
+
+Stream<String> files(String dir) => Directory(dir).list().map((e) => e.path);
+
+const jarError = 'No JAR files were found.\n'
+    'Run `dart run jnigen:download_maven_jars --config jnigen.yaml` '
+    'in plugin directory.\n'
+    'Alternatively, regenerate JNI bindings in plugin directory, which will '
+    'automatically download the JAR files.';
+
+void main() {
+  if (!Platform.isAndroid) {
+    // Assuming application is run from example/ folder
+    // It's required to manually provide the JAR files as classpath when
+    // spawning the JVM.
+    const jarDir = '../mvn_jar/';
+    List<String> jars;
+    try {
+      jars = Directory(jarDir)
+          .listSync()
+          .map((e) => e.path)
+          .where((path) => path.endsWith('.jar'))
+          .toList();
+    } on OSError catch (_) {
+      stderr.writeln(jarError);
+      return;
+    }
+    if (jars.isEmpty) {
+      stderr.writeln(jarError);
+      return;
+    }
+    Jni.spawn(classPath: jars);
+  }
+  runApp(const PDFInfoApp());
+}
+
+class PDFInfoApp extends StatefulWidget {
+  const PDFInfoApp({super.key});
+
+  @override
+  PDFInfoAppState createState() => PDFInfoAppState();
+}
+
+class PDFInfoAppState extends State<PDFInfoApp> {
+  bool _isLoading = true;
+  String _dir = '.';
+  List<String> _pdfs = [];
+  List<String> _dirs = [];
+
+  void setDir(String dir) async {
+    final pdfs = <String>[];
+    final dirs = <String>[];
+
+    setState(() => _isLoading = true);
+
+    await for (var item in Directory(dir).list()) {
+      final isDir = (await item.stat()).type == FileSystemEntityType.directory;
+      if (item.path.endsWith('.pdf') && !isDir) {
+        pdfs.add(item.path);
+      } else if (isDir) {
+        dirs.add(item.path);
+      }
+    }
+    setState(() {
+      _isLoading = false;
+      _dir = dir;
+      _pdfs = pdfs;
+      _dirs = dirs;
+    });
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    final dir = Platform.environment['HOME'] ?? '.';
+    setDir(dir);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final dirBaseName = basename(_dir);
+    return MaterialApp(
+      home: Scaffold(
+        appBar: AppBar(
+          title: Text('PDF Info: $dirBaseName'),
+        ),
+        body: SingleChildScrollView(
+          child: Container(
+              padding: const EdgeInsets.all(10),
+              child: _isLoading
+                  ? const Text('loading...')
+                  : Table(border: TableBorder.all(), children: [
+                      TableRow(children: [
+                        for (var heading in ['File', 'Title', 'Pages'])
+                          TableCell(
+                              child: _pad(Text(heading,
+                                  style: const TextStyle(
+                                      fontSize: 16,
+                                      decoration: TextDecoration.underline)))),
+                      ]),
+                      TableRow(children: [
+                        TableCell(
+                          child: _pad(
+                            InkWell(
+                              child: const Text('..'),
+                              onTap: () => setDir(dirname(_dir)),
+                            ),
+                          ),
+                        ),
+                        const TableCell(child: Text('')),
+                        const TableCell(child: Text('')),
+                      ]),
+                      for (var dir in _dirs) _dirTile(basename(dir)),
+                      for (var pdfname in _pdfs)
+                        _pdfInfo(PDFFileInfo.usingPDFBox(pdfname)),
+                    ])),
+        ),
+      ),
+    );
+  }
+
+  TableRow _dirTile(String target) {
+    return TableRow(children: [
+      TableCell(
+        child: _pad(
+          InkWell(
+            onTap: () => setDir(join(_dir, target)),
+            child: Text(
+              target,
+              style: const TextStyle(fontWeight: FontWeight.bold),
+            ),
+          ),
+        ),
+      ),
+      const TableCell(child: Text('-')),
+      const TableCell(child: Text('-')),
+    ]);
+  }
+}
+
+// It's generally a good practice to separate JNI calls from UI / model code.
+class PDFFileInfo {
+  String filename;
+  late String author, subject, title;
+  late int numPages;
+
+  /// Converts JString to dart string and deletes the original.
+  /// Also handles the case where the underlying string is Null.
+  String _fromJavaStr(JString jstr) {
+    if (jstr.reference == nullptr) {
+      return '(null)';
+    }
+    return jstr.toDartString(releaseOriginal: true);
+  }
+
+  PDFFileInfo.usingPDFBox(this.filename) {
+    // Since java.io is not directly available, use package:jni API to
+    // create a java.io.File object.
+    final inputFile =
+        Jni.newInstance("java/io/File", "(Ljava/lang/String;)V", [filename]);
+    // Static method call PDDocument.load -> PDDocument
+    final pdf = PDDocument.load(inputFile);
+    // Instance method call getNumberOfPages() -> int
+    numPages = pdf.getNumberOfPages();
+    // Instance method that returns an object
+    final info = pdf.getDocumentInformation();
+
+    /// java.lang.String is a special case and is mapped to JlString which is
+    /// a subclass of JlObject.
+    author = _fromJavaStr(info.getAuthor());
+    title = _fromJavaStr(info.getTitle());
+    subject = _fromJavaStr(info.getSubject());
+
+    pdf.close();
+  }
+}
+
+Padding _pad(Widget w) => Padding(
+    padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: w);
+
+TableRow _pdfInfo(PDFFileInfo info) {
+  return TableRow(children: [
+    TableCell(
+        child: _pad(Text(basename(info.filename),
+            style: const TextStyle(fontWeight: FontWeight.bold)))),
+    TableCell(child: _pad(Text(info.title))),
+    TableCell(
+        child: _pad(Text(info.numPages.toString(),
+            style: const TextStyle(color: Colors.grey)))),
+  ]);
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/linux/.gitignore b/pkgs/jnigen/example/pdfbox_plugin/example/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/linux/CMakeLists.txt b/pkgs/jnigen/example/pdfbox_plugin/example/linux/CMakeLists.txt
new file mode 100644
index 0000000..f36768d
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/linux/CMakeLists.txt
@@ -0,0 +1,138 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "pdfbox_plugin_example")
+# The unique GTK application identifier for this application. See:
+# https://wiki.gnome.org/HowDoI/ChooseApplicationID
+set(APPLICATION_ID "com.example.pdfbox_plugin")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(SET CMP0063 NEW)
+
+# Load bundled libraries from the lib/ directory relative to the binary.
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+  set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# Define build configuration options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  set(CMAKE_BUILD_TYPE "Debug" CACHE
+    STRING "Flutter build mode" FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+    "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_14)
+  target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+  target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Define the application target. To change its name, change BINARY_NAME above,
+# not the value here, or `flutter run` will no longer work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME}
+  "main.cc"
+  "my_application.cc"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add dependency libraries. Add any application-specific dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
+
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+  PROPERTIES
+  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+  file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+  " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
+  install(FILES "${bundled_library}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endforeach(bundled_library)
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+  install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/CMakeLists.txt b/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..d5bd016
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/CMakeLists.txt
@@ -0,0 +1,88 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+    set(NEW_LIST "")
+    foreach(element ${${LIST_NAME}})
+        list(APPEND NEW_LIST "${PREFIX}${element}")
+    endforeach(element)
+    set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "fl_basic_message_channel.h"
+  "fl_binary_codec.h"
+  "fl_binary_messenger.h"
+  "fl_dart_project.h"
+  "fl_engine.h"
+  "fl_json_message_codec.h"
+  "fl_json_method_codec.h"
+  "fl_message_codec.h"
+  "fl_method_call.h"
+  "fl_method_channel.h"
+  "fl_method_codec.h"
+  "fl_method_response.h"
+  "fl_plugin_registrar.h"
+  "fl_plugin_registry.h"
+  "fl_standard_message_codec.h"
+  "fl_standard_method_codec.h"
+  "fl_string_codec.h"
+  "fl_value.h"
+  "fl_view.h"
+  "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+  PkgConfig::GTK
+  PkgConfig::GLIB
+  PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+      ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/generated_plugin_registrant.cc b/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..e71a16d
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,11 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/generated_plugin_registrant.h b/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..e0f0a47
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/generated_plugin_registrant.h
@@ -0,0 +1,15 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter_linux/flutter_linux.h>
+
+// Registers Flutter plugins.
+void fl_register_plugins(FlPluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/generated_plugins.cmake b/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..d2cbe73
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,25 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+  jni
+  pdfbox_plugin
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/linux/main.cc b/pkgs/jnigen/example/pdfbox_plugin/example/linux/main.cc
new file mode 100644
index 0000000..e7c5c54
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/linux/main.cc
@@ -0,0 +1,6 @@
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+  g_autoptr(MyApplication) app = my_application_new();
+  return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/linux/my_application.cc b/pkgs/jnigen/example/pdfbox_plugin/example/linux/my_application.cc
new file mode 100644
index 0000000..b13351b
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/linux/my_application.cc
@@ -0,0 +1,104 @@
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+  GtkApplication parent_instance;
+  char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+  MyApplication* self = MY_APPLICATION(application);
+  GtkWindow* window =
+      GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+  // Use a header bar when running in GNOME as this is the common style used
+  // by applications and is the setup most users will be using (e.g. Ubuntu
+  // desktop).
+  // If running on X and not using GNOME then just use a traditional title bar
+  // in case the window manager does more exotic layout, e.g. tiling.
+  // If running on Wayland assume the header bar will work (may need changing
+  // if future cases occur).
+  gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+  GdkScreen* screen = gtk_window_get_screen(window);
+  if (GDK_IS_X11_SCREEN(screen)) {
+    const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+    if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+      use_header_bar = FALSE;
+    }
+  }
+#endif
+  if (use_header_bar) {
+    GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+    gtk_widget_show(GTK_WIDGET(header_bar));
+    gtk_header_bar_set_title(header_bar, "pdfbox_plugin_example");
+    gtk_header_bar_set_show_close_button(header_bar, TRUE);
+    gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+  } else {
+    gtk_window_set_title(window, "pdfbox_plugin_example");
+  }
+
+  gtk_window_set_default_size(window, 1280, 720);
+  gtk_widget_show(GTK_WIDGET(window));
+
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+  fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
+
+  FlView* view = fl_view_new(project);
+  gtk_widget_show(GTK_WIDGET(view));
+  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+  fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+  gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
+  MyApplication* self = MY_APPLICATION(application);
+  // Strip out the first argument as it is the binary name.
+  self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+  g_autoptr(GError) error = nullptr;
+  if (!g_application_register(application, nullptr, &error)) {
+     g_warning("Failed to register: %s", error->message);
+     *exit_status = 1;
+     return TRUE;
+  }
+
+  g_application_activate(application);
+  *exit_status = 0;
+
+  return TRUE;
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+  MyApplication* self = MY_APPLICATION(object);
+  g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+  G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+  G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
+  G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+  return MY_APPLICATION(g_object_new(my_application_get_type(),
+                                     "application-id", APPLICATION_ID,
+                                     "flags", G_APPLICATION_NON_UNIQUE,
+                                     nullptr));
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/linux/my_application.h b/pkgs/jnigen/example/pdfbox_plugin/example/linux/my_application.h
new file mode 100644
index 0000000..72271d5
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/linux/my_application.h
@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+                     GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif  // FLUTTER_MY_APPLICATION_H_
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/pubspec.yaml b/pkgs/jnigen/example/pdfbox_plugin/example/pubspec.yaml
new file mode 100644
index 0000000..13d0df0
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/pubspec.yaml
@@ -0,0 +1,99 @@
+name: pdfbox_example
+description: Demonstrates how to use pdfbox_plugin.
+
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+version: 1.0.0+1
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+  flutter:
+    sdk: flutter
+
+  jni:
+    path: ../../../../jni/
+  path: ^1.8.0
+  pdfbox_plugin:
+    # When depending on this package from a real application you should use:
+    #   pdfbox_plugin: ^x.y.z
+    # See https://dart.dev/tools/pub/dependencies#version-constraints
+    # The example app is bundled with the plugin so we use a path dependency on
+    # the parent directory to use the current plugin's version.
+    path: ../
+
+  # The following adds the Cupertino Icons font to your application.
+  # Use with the CupertinoIcons class for iOS style icons.
+  cupertino_icons: ^1.0.2
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+
+  # The "flutter_lints" package below contains a set of recommended lints to
+  # encourage good coding practices. The lint set provided by the package is
+  # activated in the `analysis_options.yaml` file located at the root of your
+  # package. See that file for information about deactivating specific lint
+  # rules and activating additional ones.
+  flutter_lints: ^2.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+  # The following line ensures that the Material Icons font is
+  # included with your application, so that you can use the icons in
+  # the material Icons class.
+  uses-material-design: true
+
+  # To add assets to your application, add an assets section, like this:
+  # assets:
+  #   - images/a_dot_burr.jpeg
+  #   - images/a_dot_ham.jpeg
+
+  # An image asset can refer to one or more resolution-specific "variants", see
+  # https://flutter.dev/assets-and-images/#resolution-aware
+
+  # For details regarding adding assets from package dependencies, see
+  # https://flutter.dev/assets-and-images/#from-packages
+
+  # To add custom fonts to your application, add a fonts section here,
+  # in this "flutter" section. Each entry in this list should have a
+  # "family" key with the font family name, and a "fonts" key with a
+  # list giving the asset and other descriptors for the font. For
+  # example:
+  # fonts:
+  #   - family: Schyler
+  #     fonts:
+  #       - asset: fonts/Schyler-Regular.ttf
+  #       - asset: fonts/Schyler-Italic.ttf
+  #         style: italic
+  #   - family: Trajan Pro
+  #     fonts:
+  #       - asset: fonts/TrajanPro.ttf
+  #       - asset: fonts/TrajanPro_Bold.ttf
+  #         weight: 700
+  #
+  # For details regarding fonts from package dependencies,
+  # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/.gitignore b/pkgs/jnigen/example/pdfbox_plugin/example/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/CMakeLists.txt b/pkgs/jnigen/example/pdfbox_plugin/example/windows/CMakeLists.txt
new file mode 100644
index 0000000..c233397
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/CMakeLists.txt
@@ -0,0 +1,101 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.14)
+project(pdfbox_plugin_example LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "pdfbox_plugin_example")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(SET CMP0063 NEW)
+
+# Define build configuration option.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+# Define settings for the Profile build mode.
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build; see runner/CMakeLists.txt.
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/CMakeLists.txt b/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..930d207
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/CMakeLists.txt
@@ -0,0 +1,104 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.14)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+  "flutter_texture_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/generated_plugin_registrant.cc b/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..8b6d468
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,11 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/generated_plugin_registrant.h b/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..dc139d8
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,15 @@
+//
+//  Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter/plugin_registry.h>
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/generated_plugins.cmake b/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..facaded
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,25 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+  jni
+  pdfbox_plugin
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/CMakeLists.txt b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..b9e550f
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.14)
+project(runner LANGUAGES CXX)
+
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Disable Windows macros that collide with C++ standard library functions.
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+
+# Add dependency libraries and include directories. Add any application-specific
+# dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/Runner.rc b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/Runner.rc
new file mode 100644
index 0000000..05d079f
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "com.example" "\0"
+            VALUE "FileDescription", "pdfbox_plugin_example" "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "pdfbox_plugin_example" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0"
+            VALUE "OriginalFilename", "pdfbox_plugin_example.exe" "\0"
+            VALUE "ProductName", "pdfbox_plugin_example" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/flutter_window.cpp b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..b43b909
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/flutter_window.cpp
@@ -0,0 +1,61 @@
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(const flutter::DartProject& project)
+    : project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opportunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/flutter_window.h b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/flutter_window.h
new file mode 100644
index 0000000..6da0652
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/flutter_window.h
@@ -0,0 +1,33 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow hosting a Flutter view running |project|.
+  explicit FlutterWindow(const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/main.cpp b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/main.cpp
new file mode 100644
index 0000000..44bdbc6
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/main.cpp
@@ -0,0 +1,43 @@
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  flutter::DartProject project(L"data");
+
+  std::vector<std::string> command_line_arguments =
+      GetCommandLineArguments();
+
+  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+  FlutterWindow window(project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"pdfbox_plugin_example", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  ::MSG msg;
+  while (::GetMessage(&msg, nullptr, 0, 0)) {
+    ::TranslateMessage(&msg);
+    ::DispatchMessage(&msg);
+  }
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/resource.h b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/resource.h
new file mode 100644
index 0000000..66a65d1
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON                    101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        102
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1001
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/resources/app_icon.ico b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/runner.exe.manifest b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/utils.cpp b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/utils.cpp
new file mode 100644
index 0000000..f5bf9fa
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/utils.cpp
@@ -0,0 +1,64 @@
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE *unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
+
+std::vector<std::string> GetCommandLineArguments() {
+  // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+  int argc;
+  wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+  if (argv == nullptr) {
+    return std::vector<std::string>();
+  }
+
+  std::vector<std::string> command_line_arguments;
+
+  // Skip the first argument as it's the binary name.
+  for (int i = 1; i < argc; i++) {
+    command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+  }
+
+  ::LocalFree(argv);
+
+  return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+  if (utf16_string == nullptr) {
+    return std::string();
+  }
+  int target_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+      -1, nullptr, 0, nullptr, nullptr);
+  std::string utf8_string;
+  if (target_length == 0 || target_length > utf8_string.max_size()) {
+    return utf8_string;
+  }
+  utf8_string.resize(target_length);
+  int converted_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+      -1, utf8_string.data(),
+      target_length, nullptr, nullptr);
+  if (converted_length == 0) {
+    return std::string();
+  }
+  return utf8_string;
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/utils.h b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/utils.h
new file mode 100644
index 0000000..3879d54
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/utils.h
@@ -0,0 +1,19 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include <string>
+#include <vector>
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector<std::string>,
+// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
+std::vector<std::string> GetCommandLineArguments();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/win32_window.cpp b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c10f08d
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/win32_window.cpp
@@ -0,0 +1,245 @@
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() {
+  ++g_active_window_count;
+}
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title,
+                                const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window,
+                                      UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd,
+                            UINT const message,
+                            WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE: {
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+    }
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() {
+  return window_handle_;
+}
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/win32_window.h b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/win32_window.h
new file mode 100644
index 0000000..17ba431
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/example/windows/runner/win32_window.h
@@ -0,0 +1,98 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title,
+                     const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window,
+                                 UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window,
+                                  UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/pkgs/jnigen/example/pdfbox_plugin/jnigen.yaml b/pkgs/jnigen/example/pdfbox_plugin/jnigen.yaml
new file mode 100644
index 0000000..584a3bf
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/jnigen.yaml
@@ -0,0 +1,80 @@
+## String to be pasted verbatim into generated bindings
+preamble: |
+  // Generated from Apache PDFBox library which is licensed under the Apache License 2.0.
+  // The following copyright from the original authors applies.
+  //
+  // Licensed to the Apache Software Foundation (ASF) under one or more
+  // contributor license agreements.  See the NOTICE file distributed with
+  // this work for additional information regarding copyright ownership.
+  // The ASF licenses this file to You 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.
+
+## Output configuration
+output:
+  c:
+    ## Path to write generated C bindings
+    path: 'src/'
+    ## C files can be stored in a different sub-directory inside root.
+    ##
+    ## We have a guideline to keep all generated code in third_party/ since the
+    ## original project's license applies to generated code. So we specify
+    ## third_party/ as c_subdir while keeping generated CMakeLists.txt in src/.
+    subdir: 'third_party/'
+    ## Name of the generated library. This is a required parameter, and used
+    ## for the name of the shared library and CMake configuration.
+    library_name: 'pdfbox_plugin'
+  dart:
+    ## Generated dart bindings will be written to this path. They will follow
+    ## the same folder hierarchy as the original Java code.
+    path: 'lib/src/third_party/'
+
+## Classes / packages for which bindings need to be generated.
+classes:
+  - 'org.apache.pdfbox.pdmodel.PDDocument'
+  - 'org.apache.pdfbox.pdmodel.PDDocumentInformation'
+  - 'org.apache.pdfbox.text.PDFTextStripper'
+
+## Exclude a problematic static field. Static fields are usually converted
+## directly to dart static fields. In the current implementation some string
+## escaping problems may occur.
+##
+## See issue #31 (https://github.com/dart-lang/jnigen/issues/31) for details.
+## (This field does not appear in bindings unless full package bindings are
+## generated using a `-Dclasses` override.)
+##
+## TODO(#31): For string fields, it may be better to generate a field getter
+## than a direct literal.
+exclude:
+  fields:
+    - 'org.apache.pdfbox.contentstream.operator.OperatorName#SHOW_TEXT_LINE_AND_SPACE'
+
+## Dependencies to be downloaded using Maven (Invokes `mvn` command). These
+## dependencies are always downloaded along with their transitive dependencies.
+##
+## Dependencies should be specified using groupID:artifactID:version format
+## The downloaded dependencies are automatically added to classpath of the
+## Java API scanning process. There's no need to specify these again in
+## sourcepath and classpath.
+##
+## Note that when maven based tooling is used, the first run often has to fetch
+## all the dependencies and might take some time. However, maven caches the
+## artifacts in local repository, thus subsequent runs will be faster.
+maven_downloads:
+  ## For these dependencies, both source and JARs are downloaded.
+  source_deps:
+    - 'org.apache.pdfbox:pdfbox:2.0.26'
+  ## Runtime dependencies for which bindings aren't generated directly.
+  ## Only JARs are downloaded.
+  jar_only_deps:
+    - 'org.bouncycastle:bcmail-jdk15on:1.70'
+    - 'org.bouncycastle:bcprov-jdk15on:1.70'
+
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/pdfbox_plugin.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/pdfbox_plugin.dart
new file mode 100644
index 0000000..3cdeeff
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/pdfbox_plugin.dart
@@ -0,0 +1,6 @@
+/// File merely exporting the generated bindings from lib/src/third_party
+library pdfbox_plugin;
+
+export 'src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart';
+export 'src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart';
+export 'src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart';
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/_init.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/_init.dart
new file mode 100644
index 0000000..57aea60
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/_init.dart
@@ -0,0 +1,25 @@
+// Generated from Apache PDFBox library which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+
+// Auto-generated initialization code.
+
+final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
+    ProtectedJniExtensions.initGeneratedLibrary("pdfbox_plugin");
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart
new file mode 100644
index 0000000..e8777cb
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocument.dart
@@ -0,0 +1,1536 @@
+// Generated from Apache PDFBox library which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+import "PDDocumentInformation.dart" as pddocumentinformation_;
+import "../../../../_init.dart";
+
+/// from: org.apache.pdfbox.pdmodel.PDDocument
+///
+/// This is the in-memory representation of the PDF document.
+/// The \#close() method must be called once the document is no longer needed.
+///@author Ben Litchfield
+class PDDocument extends jni.JObject {
+  @override
+  late final jni.JObjType<PDDocument> $type = type;
+
+  PDDocument.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $PDDocumentType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "PDDocument__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Creates an empty PDF document.
+  /// You need to add at least one page for the document to be valid.
+  factory PDDocument() {
+    return PDDocument.fromRef(_new0().object);
+  }
+
+  static final _new1 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__new1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(org.apache.pdfbox.io.MemoryUsageSetting memUsageSetting)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Creates an empty PDF document.
+  /// You need to add at least one page for the document to be valid.
+  ///@param memUsageSetting defines how memory is used for buffering PDF streams
+  factory PDDocument.new1(
+    jni.JObject memUsageSetting,
+  ) {
+    return PDDocument.fromRef(_new1(memUsageSetting.reference).object);
+  }
+
+  static final _new2 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__new2")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(org.apache.pdfbox.cos.COSDocument doc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor that uses an existing document. The COSDocument that is passed in must be valid.
+  ///@param doc The COSDocument that this document wraps.
+  factory PDDocument.new2(
+    jni.JObject doc,
+  ) {
+    return PDDocument.fromRef(_new2(doc.reference).object);
+  }
+
+  static final _new3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__new3")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(org.apache.pdfbox.cos.COSDocument doc, org.apache.pdfbox.io.RandomAccessRead source)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor that uses an existing document. The COSDocument that is passed in must be valid.
+  ///@param doc The COSDocument that this document wraps.
+  ///@param source the parser which is used to read the pdf
+  factory PDDocument.new3(
+    jni.JObject doc,
+    jni.JObject source,
+  ) {
+    return PDDocument.fromRef(_new3(doc.reference, source.reference).object);
+  }
+
+  static final _new4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__new4")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(org.apache.pdfbox.cos.COSDocument doc, org.apache.pdfbox.io.RandomAccessRead source, org.apache.pdfbox.pdmodel.encryption.AccessPermission permission)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor that uses an existing document. The COSDocument that is passed in must be valid.
+  ///@param doc The COSDocument that this document wraps.
+  ///@param source the parser which is used to read the pdf
+  ///@param permission he access permissions of the pdf
+  factory PDDocument.new4(
+    jni.JObject doc,
+    jni.JObject source,
+    jni.JObject permission,
+  ) {
+    return PDDocument.fromRef(
+        _new4(doc.reference, source.reference, permission.reference).object);
+  }
+
+  static final _addPage = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__addPage")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void addPage(org.apache.pdfbox.pdmodel.PDPage page)
+  ///
+  /// This will add a page to the document. This is a convenience method, that will add the page to the root of the
+  /// hierarchy and set the parent of the page to the root.
+  ///@param page The page to add to the document.
+  void addPage(
+    jni.JObject page,
+  ) {
+    return _addPage(reference, page.reference).check();
+  }
+
+  static final _addSignature = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__addSignature")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void addSignature(org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature sigObject)
+  ///
+  /// Add parameters of signature to be created externally using default signature options. See
+  /// \#saveIncrementalForExternalSigning(OutputStream) method description on external
+  /// signature creation scenario details.
+  ///
+  /// Only one signature may be added in a document. To sign several times,
+  /// load document, add signature, save incremental and close again.
+  ///@param sigObject is the PDSignatureField model
+  ///@throws IOException if there is an error creating required fields
+  ///@throws IllegalStateException if one attempts to add several signature
+  /// fields.
+  void addSignature(
+    jni.JObject sigObject,
+  ) {
+    return _addSignature(reference, sigObject.reference).check();
+  }
+
+  static final _addSignature1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__addSignature1")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void addSignature(org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature sigObject, org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions options)
+  ///
+  /// Add parameters of signature to be created externally. See
+  /// \#saveIncrementalForExternalSigning(OutputStream) method description on external
+  /// signature creation scenario details.
+  ///
+  /// Only one signature may be added in a document. To sign several times,
+  /// load document, add signature, save incremental and close again.
+  ///@param sigObject is the PDSignatureField model
+  ///@param options signature options
+  ///@throws IOException if there is an error creating required fields
+  ///@throws IllegalStateException if one attempts to add several signature
+  /// fields.
+  void addSignature1(
+    jni.JObject sigObject,
+    jni.JObject options,
+  ) {
+    return _addSignature1(reference, sigObject.reference, options.reference)
+        .check();
+  }
+
+  static final _addSignature2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__addSignature2")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void addSignature(org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature sigObject, org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface signatureInterface)
+  ///
+  /// Add a signature to be created using the instance of given interface.
+  ///
+  /// Only one signature may be added in a document. To sign several times,
+  /// load document, add signature, save incremental and close again.
+  ///@param sigObject is the PDSignatureField model
+  ///@param signatureInterface is an interface whose implementation provides
+  /// signing capabilities. Can be null if external signing if used.
+  ///@throws IOException if there is an error creating required fields
+  ///@throws IllegalStateException if one attempts to add several signature
+  /// fields.
+  void addSignature2(
+    jni.JObject sigObject,
+    jni.JObject signatureInterface,
+  ) {
+    return _addSignature2(
+            reference, sigObject.reference, signatureInterface.reference)
+        .check();
+  }
+
+  static final _addSignature3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__addSignature3")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void addSignature(org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature sigObject, org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface signatureInterface, org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions options)
+  ///
+  /// This will add a signature to the document. If the 0-based page number in the options
+  /// parameter is smaller than 0 or larger than max, the nearest valid page number will be used
+  /// (i.e. 0 or max) and no exception will be thrown.
+  ///
+  /// Only one signature may be added in a document. To sign several times,
+  /// load document, add signature, save incremental and close again.
+  ///@param sigObject is the PDSignatureField model
+  ///@param signatureInterface is an interface whose implementation provides
+  /// signing capabilities. Can be null if external signing if used.
+  ///@param options signature options
+  ///@throws IOException if there is an error creating required fields
+  ///@throws IllegalStateException if one attempts to add several signature
+  /// fields.
+  void addSignature3(
+    jni.JObject sigObject,
+    jni.JObject signatureInterface,
+    jni.JObject options,
+  ) {
+    return _addSignature3(reference, sigObject.reference,
+            signatureInterface.reference, options.reference)
+        .check();
+  }
+
+  static final _addSignatureField = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__addSignatureField")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void addSignatureField(java.util.List<org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField> sigFields, org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface signatureInterface, org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions options)
+  ///
+  /// This will add a list of signature fields to the document.
+  ///@param sigFields are the PDSignatureFields that should be added to the document
+  ///@param signatureInterface is an interface whose implementation provides
+  /// signing capabilities. Can be null if external signing if used.
+  ///@param options signature options
+  ///@throws IOException if there is an error creating required fields
+  ///@deprecated The method is misleading, because only one signature may be
+  /// added in a document. The method will be removed in the future.
+  void addSignatureField(
+    jni.JList<jni.JObject> sigFields,
+    jni.JObject signatureInterface,
+    jni.JObject options,
+  ) {
+    return _addSignatureField(reference, sigFields.reference,
+            signatureInterface.reference, options.reference)
+        .check();
+  }
+
+  static final _removePage = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__removePage")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void removePage(org.apache.pdfbox.pdmodel.PDPage page)
+  ///
+  /// Remove the page from the document.
+  ///@param page The page to remove from the document.
+  void removePage(
+    jni.JObject page,
+  ) {
+    return _removePage(reference, page.reference).check();
+  }
+
+  static final _removePage1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>, ffi.Int32)>>("PDDocument__removePage1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void removePage(int pageNumber)
+  ///
+  /// Remove the page from the document.
+  ///@param pageNumber 0 based index to page number.
+  void removePage1(
+    int pageNumber,
+  ) {
+    return _removePage1(reference, pageNumber).check();
+  }
+
+  static final _importPage = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__importPage")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.PDPage importPage(org.apache.pdfbox.pdmodel.PDPage page)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will import and copy the contents from another location. Currently the content stream is
+  /// stored in a scratch file. The scratch file is associated with the document. If you are adding
+  /// a page to this document from another document and want to copy the contents to this
+  /// document's scratch file then use this method otherwise just use the \#addPage addPage()
+  /// method.
+  ///
+  /// Unlike \#addPage addPage(), this method creates a new PDPage object. If your page has
+  /// annotations, and if these link to pages not in the target document, then the target document
+  /// might become huge. What you need to do is to delete page references of such annotations. See
+  /// <a href="http://stackoverflow.com/a/35477351/535646">here</a> for how to do this.
+  ///
+  /// Inherited (global) resources are ignored because these can contain resources not needed for
+  /// this page which could bloat your document, see
+  /// <a href="https://issues.apache.org/jira/browse/PDFBOX-28">PDFBOX-28</a> and related issues.
+  /// If you need them, call <code>importedPage.setResources(page.getResources());</code>
+  ///
+  /// This method should only be used to import a page from a loaded document, not from a generated
+  /// document because these can contain unfinished parts, e.g. font subsetting information.
+  ///@param page The page to import.
+  ///@return The page that was imported.
+  ///@throws IOException If there is an error copying the page.
+  jni.JObject importPage(
+    jni.JObject page,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_importPage(reference, page.reference).object);
+  }
+
+  static final _getDocument = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getDocument")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.cos.COSDocument getDocument()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the low level document.
+  ///@return The document that this layer sits on top of.
+  jni.JObject getDocument() {
+    return const jni.JObjectType().fromRef(_getDocument(reference).object);
+  }
+
+  static final _getDocumentInformation = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getDocumentInformation")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.PDDocumentInformation getDocumentInformation()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the document info dictionary. If it doesn't exist, an empty document info
+  /// dictionary is created in the document trailer.
+  ///
+  /// In PDF 2.0 this is deprecated except for two entries, /CreationDate and /ModDate. For any other
+  /// document level metadata, a metadata stream should be used instead, see
+  /// PDDocumentCatalog\#getMetadata().
+  ///@return The documents /Info dictionary, never null.
+  pddocumentinformation_.PDDocumentInformation getDocumentInformation() {
+    return const pddocumentinformation_.$PDDocumentInformationType()
+        .fromRef(_getDocumentInformation(reference).object);
+  }
+
+  static final _setDocumentInformation = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__setDocumentInformation")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setDocumentInformation(org.apache.pdfbox.pdmodel.PDDocumentInformation info)
+  ///
+  /// This will set the document information for this document.
+  ///
+  /// In PDF 2.0 this is deprecated except for two entries, /CreationDate and /ModDate. For any other
+  /// document level metadata, a metadata stream should be used instead, see
+  /// PDDocumentCatalog\#setMetadata(org.apache.pdfbox.pdmodel.common.PDMetadata) PDDocumentCatalog\#setMetadata(PDMetadata).
+  ///@param info The updated document information.
+  void setDocumentInformation(
+    pddocumentinformation_.PDDocumentInformation info,
+  ) {
+    return _setDocumentInformation(reference, info.reference).check();
+  }
+
+  static final _getDocumentCatalog = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getDocumentCatalog")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.PDDocumentCatalog getDocumentCatalog()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the document CATALOG. This is guaranteed to not return null.
+  ///@return The documents /Root dictionary
+  jni.JObject getDocumentCatalog() {
+    return const jni.JObjectType()
+        .fromRef(_getDocumentCatalog(reference).object);
+  }
+
+  static final _isEncrypted = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__isEncrypted")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isEncrypted()
+  ///
+  /// This will tell if this document is encrypted or not.
+  ///@return true If this document is encrypted.
+  bool isEncrypted() {
+    return _isEncrypted(reference).boolean;
+  }
+
+  static final _getEncryption = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getEncryption")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.encryption.PDEncryption getEncryption()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the encryption dictionary for this document. This will still return the parameters if the document
+  /// was decrypted. As the encryption architecture in PDF documents is pluggable this returns an abstract class,
+  /// but the only supported subclass at this time is a
+  /// PDStandardEncryption object.
+  ///@return The encryption dictionary(most likely a PDStandardEncryption object)
+  jni.JObject getEncryption() {
+    return const jni.JObjectType().fromRef(_getEncryption(reference).object);
+  }
+
+  static final _setEncryptionDictionary = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__setEncryptionDictionary")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setEncryptionDictionary(org.apache.pdfbox.pdmodel.encryption.PDEncryption encryption)
+  ///
+  /// This will set the encryption dictionary for this document.
+  ///@param encryption The encryption dictionary(most likely a PDStandardEncryption object)
+  ///@throws IOException If there is an error determining which security handler to use.
+  void setEncryptionDictionary(
+    jni.JObject encryption,
+  ) {
+    return _setEncryptionDictionary(reference, encryption.reference).check();
+  }
+
+  static final _getLastSignatureDictionary = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getLastSignatureDictionary")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature getLastSignatureDictionary()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will return the last signature from the field tree. Note that this may not be the
+  /// last in time when empty signature fields are created first but signed after other fields.
+  ///@return the last signature as <code>PDSignatureField</code>.
+  ///@throws IOException if no document catalog can be found.
+  jni.JObject getLastSignatureDictionary() {
+    return const jni.JObjectType()
+        .fromRef(_getLastSignatureDictionary(reference).object);
+  }
+
+  static final _getSignatureFields = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getSignatureFields")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.List<org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField> getSignatureFields()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Retrieve all signature fields from the document.
+  ///@return a <code>List</code> of <code>PDSignatureField</code>s
+  ///@throws IOException if no document catalog can be found.
+  jni.JList<jni.JObject> getSignatureFields() {
+    return const jni.JListType(jni.JObjectType())
+        .fromRef(_getSignatureFields(reference).object);
+  }
+
+  static final _getSignatureDictionaries = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getSignatureDictionaries")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.List<org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature> getSignatureDictionaries()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Retrieve all signature dictionaries from the document.
+  ///@return a <code>List</code> of <code>PDSignatureField</code>s
+  ///@throws IOException if no document catalog can be found.
+  jni.JList<jni.JObject> getSignatureDictionaries() {
+    return const jni.JListType(jni.JObjectType())
+        .fromRef(_getSignatureDictionaries(reference).object);
+  }
+
+  static final _registerTrueTypeFontForClosing = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__registerTrueTypeFontForClosing")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void registerTrueTypeFontForClosing(org.apache.fontbox.ttf.TrueTypeFont ttf)
+  ///
+  /// For internal PDFBox use when creating PDF documents: register a TrueTypeFont to make sure it
+  /// is closed when the PDDocument is closed to avoid memory leaks. Users don't have to call this
+  /// method, it is done by the appropriate PDFont classes.
+  ///@param ttf
+  void registerTrueTypeFontForClosing(
+    jni.JObject ttf,
+  ) {
+    return _registerTrueTypeFontForClosing(reference, ttf.reference).check();
+  }
+
+  static final _load = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__load")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.File file)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. Unrestricted main memory will be used for buffering PDF streams.
+  ///@param file file to be loaded
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the file required a non-empty password.
+  ///@throws IOException in case of a file reading or parsing error
+  static PDDocument load(
+    jni.JObject file,
+  ) {
+    return const $PDDocumentType().fromRef(_load(file.reference).object);
+  }
+
+  static final _load1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.File file, org.apache.pdfbox.io.MemoryUsageSetting memUsageSetting)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF.
+  ///@param file file to be loaded
+  ///@param memUsageSetting defines how memory is used for buffering PDF streams
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the file required a non-empty password.
+  ///@throws IOException in case of a file reading or parsing error
+  static PDDocument load1(
+    jni.JObject file,
+    jni.JObject memUsageSetting,
+  ) {
+    return const $PDDocumentType()
+        .fromRef(_load1(file.reference, memUsageSetting.reference).object);
+  }
+
+  static final _load2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.File file, java.lang.String password)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. Unrestricted main memory will be used for buffering PDF streams.
+  ///@param file file to be loaded
+  ///@param password password to be used for decryption
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the password is incorrect.
+  ///@throws IOException in case of a file reading or parsing error
+  static PDDocument load2(
+    jni.JObject file,
+    jni.JString password,
+  ) {
+    return const $PDDocumentType()
+        .fromRef(_load2(file.reference, password.reference).object);
+  }
+
+  static final _load3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load3")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.File file, java.lang.String password, org.apache.pdfbox.io.MemoryUsageSetting memUsageSetting)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF.
+  ///@param file file to be loaded
+  ///@param password password to be used for decryption
+  ///@param memUsageSetting defines how memory is used for buffering PDF streams
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the password is incorrect.
+  ///@throws IOException in case of a file reading or parsing error
+  static PDDocument load3(
+    jni.JObject file,
+    jni.JString password,
+    jni.JObject memUsageSetting,
+  ) {
+    return const $PDDocumentType().fromRef(
+        _load3(file.reference, password.reference, memUsageSetting.reference)
+            .object);
+  }
+
+  static final _load4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load4")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.File file, java.lang.String password, java.io.InputStream keyStore, java.lang.String alias)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. Unrestricted main memory will be used for buffering PDF streams.
+  ///@param file file to be loaded
+  ///@param password password to be used for decryption
+  ///@param keyStore key store to be used for decryption when using public key security
+  ///@param alias alias to be used for decryption when using public key security
+  ///@return loaded document
+  ///@throws IOException in case of a file reading or parsing error
+  static PDDocument load4(
+    jni.JObject file,
+    jni.JString password,
+    jni.JObject keyStore,
+    jni.JString alias,
+  ) {
+    return const $PDDocumentType().fromRef(_load4(file.reference,
+            password.reference, keyStore.reference, alias.reference)
+        .object);
+  }
+
+  static final _load5 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load5")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.File file, java.lang.String password, java.io.InputStream keyStore, java.lang.String alias, org.apache.pdfbox.io.MemoryUsageSetting memUsageSetting)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF.
+  ///@param file file to be loaded
+  ///@param password password to be used for decryption
+  ///@param keyStore key store to be used for decryption when using public key security
+  ///@param alias alias to be used for decryption when using public key security
+  ///@param memUsageSetting defines how memory is used for buffering PDF streams
+  ///@return loaded document
+  ///@throws IOException in case of a file reading or parsing error
+  static PDDocument load5(
+    jni.JObject file,
+    jni.JString password,
+    jni.JObject keyStore,
+    jni.JString alias,
+    jni.JObject memUsageSetting,
+  ) {
+    return const $PDDocumentType().fromRef(_load5(
+            file.reference,
+            password.reference,
+            keyStore.reference,
+            alias.reference,
+            memUsageSetting.reference)
+        .object);
+  }
+
+  static final _load6 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__load6")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.InputStream input)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. The given input stream is copied to the memory to enable random access to the
+  /// pdf. Unrestricted main memory will be used for buffering PDF streams.
+  ///@param input stream that contains the document. Don't forget to close it after loading.
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the PDF required a non-empty password.
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load6(
+    jni.JObject input,
+  ) {
+    return const $PDDocumentType().fromRef(_load6(input.reference).object);
+  }
+
+  static final _load7 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load7")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.InputStream input, org.apache.pdfbox.io.MemoryUsageSetting memUsageSetting)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. Depending on the memory settings parameter the given input stream is either
+  /// copied to main memory or to a temporary file to enable random access to the pdf.
+  ///@param input stream that contains the document. Don't forget to close it after loading.
+  ///@param memUsageSetting defines how memory is used for buffering input stream and PDF streams
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the PDF required a non-empty password.
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load7(
+    jni.JObject input,
+    jni.JObject memUsageSetting,
+  ) {
+    return const $PDDocumentType()
+        .fromRef(_load7(input.reference, memUsageSetting.reference).object);
+  }
+
+  static final _load8 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load8")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.InputStream input, java.lang.String password)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. The given input stream is copied to the memory to enable random access to the
+  /// pdf. Unrestricted main memory will be used for buffering PDF streams.
+  ///@param input stream that contains the document. Don't forget to close it after loading.
+  ///@param password password to be used for decryption
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the password is incorrect.
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load8(
+    jni.JObject input,
+    jni.JString password,
+  ) {
+    return const $PDDocumentType()
+        .fromRef(_load8(input.reference, password.reference).object);
+  }
+
+  static final _load9 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load9")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.InputStream input, java.lang.String password, java.io.InputStream keyStore, java.lang.String alias)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. The given input stream is copied to the memory to enable random access to the
+  /// pdf. Unrestricted main memory will be used for buffering PDF streams.
+  ///@param input stream that contains the document. Don't forget to close it after loading.
+  ///@param password password to be used for decryption
+  ///@param keyStore key store to be used for decryption when using public key security
+  ///@param alias alias to be used for decryption when using public key security
+  ///@return loaded document
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load9(
+    jni.JObject input,
+    jni.JString password,
+    jni.JObject keyStore,
+    jni.JString alias,
+  ) {
+    return const $PDDocumentType().fromRef(_load9(input.reference,
+            password.reference, keyStore.reference, alias.reference)
+        .object);
+  }
+
+  static final _load10 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load10")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.InputStream input, java.lang.String password, org.apache.pdfbox.io.MemoryUsageSetting memUsageSetting)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. Depending on the memory settings parameter the given input stream is either
+  /// copied to main memory or to a temporary file to enable random access to the pdf.
+  ///@param input stream that contains the document. Don't forget to close it after loading.
+  ///@param password password to be used for decryption
+  ///@param memUsageSetting defines how memory is used for buffering input stream and PDF streams
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the password is incorrect.
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load10(
+    jni.JObject input,
+    jni.JString password,
+    jni.JObject memUsageSetting,
+  ) {
+    return const $PDDocumentType().fromRef(
+        _load10(input.reference, password.reference, memUsageSetting.reference)
+            .object);
+  }
+
+  static final _load11 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load11")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(java.io.InputStream input, java.lang.String password, java.io.InputStream keyStore, java.lang.String alias, org.apache.pdfbox.io.MemoryUsageSetting memUsageSetting)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. Depending on the memory settings parameter the given input stream is either
+  /// copied to memory or to a temporary file to enable random access to the pdf.
+  ///@param input stream that contains the document. Don't forget to close it after loading.
+  ///@param password password to be used for decryption
+  ///@param keyStore key store to be used for decryption when using public key security
+  ///@param alias alias to be used for decryption when using public key security
+  ///@param memUsageSetting defines how memory is used for buffering input stream and PDF streams
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the password is incorrect.
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load11(
+    jni.JObject input,
+    jni.JString password,
+    jni.JObject keyStore,
+    jni.JString alias,
+    jni.JObject memUsageSetting,
+  ) {
+    return const $PDDocumentType().fromRef(_load11(
+            input.reference,
+            password.reference,
+            keyStore.reference,
+            alias.reference,
+            memUsageSetting.reference)
+        .object);
+  }
+
+  static final _load12 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__load12")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(byte[] input)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. Unrestricted main memory will be used for buffering PDF streams.
+  ///@param input byte array that contains the document.
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the PDF required a non-empty password.
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load12(
+    jni.JArray<jni.jbyte> input,
+  ) {
+    return const $PDDocumentType().fromRef(_load12(input.reference).object);
+  }
+
+  static final _load13 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load13")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(byte[] input, java.lang.String password)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. Unrestricted main memory will be used for buffering PDF streams.
+  ///@param input byte array that contains the document.
+  ///@param password password to be used for decryption
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the password is incorrect.
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load13(
+    jni.JArray<jni.jbyte> input,
+    jni.JString password,
+  ) {
+    return const $PDDocumentType()
+        .fromRef(_load13(input.reference, password.reference).object);
+  }
+
+  static final _load14 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load14")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(byte[] input, java.lang.String password, java.io.InputStream keyStore, java.lang.String alias)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF. Unrestricted main memory will be used for buffering PDF streams.
+  ///@param input byte array that contains the document.
+  ///@param password password to be used for decryption
+  ///@param keyStore key store to be used for decryption when using public key security
+  ///@param alias alias to be used for decryption when using public key security
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the password is incorrect.
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load14(
+    jni.JArray<jni.jbyte> input,
+    jni.JString password,
+    jni.JObject keyStore,
+    jni.JString alias,
+  ) {
+    return const $PDDocumentType().fromRef(_load14(input.reference,
+            password.reference, keyStore.reference, alias.reference)
+        .object);
+  }
+
+  static final _load15 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__load15")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public org.apache.pdfbox.pdmodel.PDDocument load(byte[] input, java.lang.String password, java.io.InputStream keyStore, java.lang.String alias, org.apache.pdfbox.io.MemoryUsageSetting memUsageSetting)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Parses a PDF.
+  ///@param input byte array that contains the document.
+  ///@param password password to be used for decryption
+  ///@param keyStore key store to be used for decryption when using public key security
+  ///@param alias alias to be used for decryption when using public key security
+  ///@param memUsageSetting defines how memory is used for buffering input stream and PDF streams
+  ///@return loaded document
+  ///@throws InvalidPasswordException If the password is incorrect.
+  ///@throws IOException In case of a reading or parsing error.
+  static PDDocument load15(
+    jni.JArray<jni.jbyte> input,
+    jni.JString password,
+    jni.JObject keyStore,
+    jni.JString alias,
+    jni.JObject memUsageSetting,
+  ) {
+    return const $PDDocumentType().fromRef(_load15(
+            input.reference,
+            password.reference,
+            keyStore.reference,
+            alias.reference,
+            memUsageSetting.reference)
+        .object);
+  }
+
+  static final _save = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__save")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void save(java.lang.String fileName)
+  ///
+  /// Save the document to a file.
+  ///
+  /// If encryption has been activated (with
+  /// \#protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy) protect(ProtectionPolicy)),
+  /// do not use the document after saving because the contents are now encrypted.
+  ///@param fileName The file to save as.
+  ///@throws IOException if the output could not be written
+  void save(
+    jni.JString fileName,
+  ) {
+    return _save(reference, fileName.reference).check();
+  }
+
+  static final _save1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__save1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void save(java.io.File file)
+  ///
+  /// Save the document to a file.
+  ///
+  /// If encryption has been activated (with
+  /// \#protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy) protect(ProtectionPolicy)),
+  /// do not use the document after saving because the contents are now encrypted.
+  ///@param file The file to save as.
+  ///@throws IOException if the output could not be written
+  void save1(
+    jni.JObject file,
+  ) {
+    return _save1(reference, file.reference).check();
+  }
+
+  static final _save2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__save2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void save(java.io.OutputStream output)
+  ///
+  /// This will save the document to an output stream.
+  ///
+  /// If encryption has been activated (with
+  /// \#protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy) protect(ProtectionPolicy)),
+  /// do not use the document after saving because the contents are now encrypted.
+  ///@param output The stream to write to. It will be closed when done. It is recommended to wrap
+  /// it in a java.io.BufferedOutputStream, unless it is already buffered.
+  ///@throws IOException if the output could not be written
+  void save2(
+    jni.JObject output,
+  ) {
+    return _save2(reference, output.reference).check();
+  }
+
+  static final _saveIncremental = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__saveIncremental")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void saveIncremental(java.io.OutputStream output)
+  ///
+  /// Save the PDF as an incremental update. This is only possible if the PDF was loaded from a
+  /// file or a stream, not if the document was created in PDFBox itself. There must be a path of
+  /// objects that have COSUpdateInfo\#isNeedToBeUpdated() set, starting from the document
+  /// catalog. For signatures this is taken care by PDFBox itself.
+  ///
+  /// Other usages of this method are for experienced users only. You will usually never need it.
+  /// It is useful only if you are required to keep the current revision and append the changes. A
+  /// typical use case is changing a signed file without invalidating the signature.
+  ///@param output stream to write to. It will be closed when done. It
+  /// <i>__must never__</i> point to the source file or that one will be
+  /// harmed!
+  ///@throws IOException if the output could not be written
+  ///@throws IllegalStateException if the document was not loaded from a file or a stream.
+  void saveIncremental(
+    jni.JObject output,
+  ) {
+    return _saveIncremental(reference, output.reference).check();
+  }
+
+  static final _saveIncremental1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__saveIncremental1")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void saveIncremental(java.io.OutputStream output, java.util.Set<org.apache.pdfbox.cos.COSDictionary> objectsToWrite)
+  ///
+  /// Save the PDF as an incremental update. This is only possible if the PDF was loaded from a
+  /// file or a stream, not if the document was created in PDFBox itself. This allows to include
+  /// objects even if there is no path of objects that have
+  /// COSUpdateInfo\#isNeedToBeUpdated() set so the incremental update gets smaller. Only
+  /// dictionaries are supported; if you need to update other objects classes, then add their
+  /// parent dictionary.
+  ///
+  /// This method is for experienced users only. You will usually never need it. It is useful only
+  /// if you are required to keep the current revision and append the changes. A typical use case
+  /// is changing a signed file without invalidating the signature. To know which objects are
+  /// getting changed, you need to have some understanding of the PDF specification, and look at
+  /// the saved file with an editor to verify that you are updating the correct objects. You should
+  /// also inspect the page and document structures of the file with PDFDebugger.
+  ///@param output stream to write to. It will be closed when done. It
+  /// <i>__must never__</i> point to the source file or that one will be harmed!
+  ///@param objectsToWrite objects that __must__ be part of the incremental saving.
+  ///@throws IOException if the output could not be written
+  ///@throws IllegalStateException if the document was not loaded from a file or a stream.
+  void saveIncremental1(
+    jni.JObject output,
+    jni.JSet<jni.JObject> objectsToWrite,
+  ) {
+    return _saveIncremental1(
+            reference, output.reference, objectsToWrite.reference)
+        .check();
+  }
+
+  static final _saveIncrementalForExternalSigning = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__saveIncrementalForExternalSigning")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.interactive.digitalsignature.ExternalSigningSupport saveIncrementalForExternalSigning(java.io.OutputStream output)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  ///
+  /// __(This is a new feature for 2.0.3. The API for external signing might change based on feedback after release!)__
+  ///
+  /// Save PDF incrementally without closing for external signature creation scenario. The general
+  /// sequence is:
+  /// <pre>
+  ///    PDDocument pdDocument = ...;
+  ///    OutputStream outputStream = ...;
+  ///    SignatureOptions signatureOptions = ...; // options to specify fine tuned signature options or null for defaults
+  ///    PDSignature pdSignature = ...;
+  ///
+  ///    // add signature parameters to be used when creating signature dictionary
+  ///    pdDocument.addSignature(pdSignature, signatureOptions);
+  ///    // prepare PDF for signing and obtain helper class to be used
+  ///    ExternalSigningSupport externalSigningSupport = pdDocument.saveIncrementalForExternalSigning(outputStream);
+  ///    // get data to be signed
+  ///    InputStream dataToBeSigned = externalSigningSupport.getContent();
+  ///    // invoke signature service
+  ///    byte[] signature = sign(dataToBeSigned);
+  ///    // set resulted CMS signature
+  ///    externalSigningSupport.setSignature(signature);
+  ///
+  ///    // last step is to close the document
+  ///    pdDocument.close();
+  /// </pre>
+  ///
+  /// Note that after calling this method, only {@code close()} method may invoked for
+  /// {@code PDDocument} instance and only AFTER ExternalSigningSupport instance is used.
+  ///
+  ///
+  ///@param output stream to write the final PDF. It will be closed when the
+  /// document is closed. It <i>__must never__</i> point to the source file
+  /// or that one will be harmed!
+  ///@return instance to be used for external signing and setting CMS signature
+  ///@throws IOException if the output could not be written
+  ///@throws IllegalStateException if the document was not loaded from a file or a stream or
+  /// signature options were not set.
+  jni.JObject saveIncrementalForExternalSigning(
+    jni.JObject output,
+  ) {
+    return const jni.JObjectType().fromRef(
+        _saveIncrementalForExternalSigning(reference, output.reference).object);
+  }
+
+  static final _getPage = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>, ffi.Int32)>>("PDDocument__getPage")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.PDPage getPage(int pageIndex)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the page at the given 0-based index.
+  ///
+  /// This method is too slow to get all the pages from a large PDF document
+  /// (1000 pages or more). For such documents, use the iterator of
+  /// PDDocument\#getPages() instead.
+  ///@param pageIndex the 0-based page index
+  ///@return the page at the given index.
+  jni.JObject getPage(
+    int pageIndex,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_getPage(reference, pageIndex).object);
+  }
+
+  static final _getPages = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getPages")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.PDPageTree getPages()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the page tree.
+  ///@return the page tree
+  jni.JObject getPages() {
+    return const jni.JObjectType().fromRef(_getPages(reference).object);
+  }
+
+  static final _getNumberOfPages = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getNumberOfPages")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getNumberOfPages()
+  ///
+  /// This will return the total page count of the PDF document.
+  ///@return The total number of pages in the PDF document.
+  int getNumberOfPages() {
+    return _getNumberOfPages(reference).integer;
+  }
+
+  static final _close = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__close")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void close()
+  ///
+  /// This will close the underlying COSDocument object.
+  ///@throws IOException If there is an error releasing resources.
+  void close() {
+    return _close(reference).check();
+  }
+
+  static final _protect = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__protect")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void protect(org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy policy)
+  ///
+  /// Protects the document with a protection policy. The document content will be really
+  /// encrypted when it will be saved. This method only marks the document for encryption. It also
+  /// calls \#setAllSecurityToBeRemoved(boolean) with a false argument if it was set to true
+  /// previously and logs a warning.
+  ///
+  /// Do not use the document after saving, because the structures are encrypted.
+  ///@see org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy
+  ///@see org.apache.pdfbox.pdmodel.encryption.PublicKeyProtectionPolicy
+  ///@param policy The protection policy.
+  ///@throws IOException if there isn't any suitable security handler.
+  void protect(
+    jni.JObject policy,
+  ) {
+    return _protect(reference, policy.reference).check();
+  }
+
+  static final _getCurrentAccessPermission = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getCurrentAccessPermission")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.encryption.AccessPermission getCurrentAccessPermission()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the access permissions granted when the document was decrypted. If the document was not decrypted this
+  /// method returns the access permission for a document owner (ie can do everything). The returned object is in read
+  /// only mode so that permissions cannot be changed. Methods providing access to content should rely on this object
+  /// to verify if the current user is allowed to proceed.
+  ///@return the access permissions for the current user on the document.
+  jni.JObject getCurrentAccessPermission() {
+    return const jni.JObjectType()
+        .fromRef(_getCurrentAccessPermission(reference).object);
+  }
+
+  static final _isAllSecurityToBeRemoved = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__isAllSecurityToBeRemoved")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isAllSecurityToBeRemoved()
+  ///
+  /// Indicates if all security is removed or not when writing the pdf.
+  ///@return returns true if all security shall be removed otherwise false
+  bool isAllSecurityToBeRemoved() {
+    return _isAllSecurityToBeRemoved(reference).boolean;
+  }
+
+  static final _setAllSecurityToBeRemoved = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Uint8)>>("PDDocument__setAllSecurityToBeRemoved")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setAllSecurityToBeRemoved(boolean removeAllSecurity)
+  ///
+  /// Activates/Deactivates the removal of all security when writing the pdf.
+  ///@param removeAllSecurity remove all security if set to true
+  void setAllSecurityToBeRemoved(
+    bool removeAllSecurity,
+  ) {
+    return _setAllSecurityToBeRemoved(reference, removeAllSecurity ? 1 : 0)
+        .check();
+  }
+
+  static final _getDocumentId = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getDocumentId")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Long getDocumentId()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Provides the document ID.
+  ///@return the document ID
+  jni.JLong getDocumentId() {
+    return const jni.JLongType().fromRef(_getDocumentId(reference).object);
+  }
+
+  static final _setDocumentId = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__setDocumentId")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setDocumentId(java.lang.Long docId)
+  ///
+  /// Sets the document ID to the given value.
+  ///@param docId the new document ID
+  void setDocumentId(
+    jni.JLong docId,
+  ) {
+    return _setDocumentId(reference, docId.reference).check();
+  }
+
+  static final _getVersion = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getVersion")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public float getVersion()
+  ///
+  /// Returns the PDF specification version this document conforms to.
+  ///@return the PDF version (e.g. 1.4f)
+  double getVersion() {
+    return _getVersion(reference).float;
+  }
+
+  static final _setVersion = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>, ffi.Float)>>("PDDocument__setVersion")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, double)>();
+
+  /// from: public void setVersion(float newVersion)
+  ///
+  /// Sets the PDF specification version for this document.
+  ///@param newVersion the new PDF version (e.g. 1.4f)
+  void setVersion(
+    double newVersion,
+  ) {
+    return _setVersion(reference, newVersion).check();
+  }
+
+  static final _getResourceCache = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocument__getResourceCache")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.ResourceCache getResourceCache()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the resource cache associated with this document, or null if there is none.
+  ///@return the resource cache or null.
+  jni.JObject getResourceCache() {
+    return const jni.JObjectType().fromRef(_getResourceCache(reference).object);
+  }
+
+  static final _setResourceCache = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocument__setResourceCache")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setResourceCache(org.apache.pdfbox.pdmodel.ResourceCache resourceCache)
+  ///
+  /// Sets the resource cache associated with this document.
+  ///@param resourceCache A resource cache, or null.
+  void setResourceCache(
+    jni.JObject resourceCache,
+  ) {
+    return _setResourceCache(reference, resourceCache.reference).check();
+  }
+}
+
+final class $PDDocumentType extends jni.JObjType<PDDocument> {
+  const $PDDocumentType();
+
+  @override
+  String get signature => r"Lorg/apache/pdfbox/pdmodel/PDDocument;";
+
+  @override
+  PDDocument fromRef(jni.JObjectPtr ref) => PDDocument.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($PDDocumentType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($PDDocumentType) && other is $PDDocumentType;
+  }
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart
new file mode 100644
index 0000000..b05c031
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/PDDocumentInformation.dart
@@ -0,0 +1,525 @@
+// Generated from Apache PDFBox library which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+import "../../../../_init.dart";
+
+/// from: org.apache.pdfbox.pdmodel.PDDocumentInformation
+///
+/// This is the document metadata.  Each getXXX method will return the entry if
+/// it exists or null if it does not exist.  If you pass in null for the setXXX
+/// method then it will clear the value.
+///@author Ben Litchfield
+///@author Gerardo Ortiz
+class PDDocumentInformation extends jni.JObject {
+  @override
+  late final jni.JObjType<PDDocumentInformation> $type = type;
+
+  PDDocumentInformation.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $PDDocumentInformationType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "PDDocumentInformation__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Default Constructor.
+  factory PDDocumentInformation() {
+    return PDDocumentInformation.fromRef(_new0().object);
+  }
+
+  static final _new1 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__new1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(org.apache.pdfbox.cos.COSDictionary dic)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor that is used for a preexisting dictionary.
+  ///@param dic The underlying dictionary.
+  factory PDDocumentInformation.new1(
+    jni.JObject dic,
+  ) {
+    return PDDocumentInformation.fromRef(_new1(dic.reference).object);
+  }
+
+  static final _getCOSObject = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getCOSObject")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.cos.COSDictionary getCOSObject()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the underlying dictionary that this object wraps.
+  ///@return The underlying info dictionary.
+  jni.JObject getCOSObject() {
+    return const jni.JObjectType().fromRef(_getCOSObject(reference).object);
+  }
+
+  static final _getPropertyStringValue = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getPropertyStringValue")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object getPropertyStringValue(java.lang.String propertyKey)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Return the properties String value.
+  ///
+  /// Allows to retrieve the
+  /// low level date for validation purposes.
+  ///
+  ///
+  ///@param propertyKey the dictionaries key
+  ///@return the properties value
+  jni.JObject getPropertyStringValue(
+    jni.JString propertyKey,
+  ) {
+    return const jni.JObjectType().fromRef(
+        _getPropertyStringValue(reference, propertyKey.reference).object);
+  }
+
+  static final _getTitle = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getTitle")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getTitle()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the title of the document.  This will return null if no title exists.
+  ///@return The title of the document.
+  jni.JString getTitle() {
+    return const jni.JStringType().fromRef(_getTitle(reference).object);
+  }
+
+  static final _setTitle = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocumentInformation__setTitle")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setTitle(java.lang.String title)
+  ///
+  /// This will set the title of the document.
+  ///@param title The new title for the document.
+  void setTitle(
+    jni.JString title,
+  ) {
+    return _setTitle(reference, title.reference).check();
+  }
+
+  static final _getAuthor = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getAuthor")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getAuthor()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the author of the document.  This will return null if no author exists.
+  ///@return The author of the document.
+  jni.JString getAuthor() {
+    return const jni.JStringType().fromRef(_getAuthor(reference).object);
+  }
+
+  static final _setAuthor = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocumentInformation__setAuthor")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setAuthor(java.lang.String author)
+  ///
+  /// This will set the author of the document.
+  ///@param author The new author for the document.
+  void setAuthor(
+    jni.JString author,
+  ) {
+    return _setAuthor(reference, author.reference).check();
+  }
+
+  static final _getSubject = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getSubject")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getSubject()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the subject of the document.  This will return null if no subject exists.
+  ///@return The subject of the document.
+  jni.JString getSubject() {
+    return const jni.JStringType().fromRef(_getSubject(reference).object);
+  }
+
+  static final _setSubject = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocumentInformation__setSubject")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setSubject(java.lang.String subject)
+  ///
+  /// This will set the subject of the document.
+  ///@param subject The new subject for the document.
+  void setSubject(
+    jni.JString subject,
+  ) {
+    return _setSubject(reference, subject.reference).check();
+  }
+
+  static final _getKeywords = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getKeywords")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getKeywords()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the keywords of the document.  This will return null if no keywords exists.
+  ///@return The keywords of the document.
+  jni.JString getKeywords() {
+    return const jni.JStringType().fromRef(_getKeywords(reference).object);
+  }
+
+  static final _setKeywords = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocumentInformation__setKeywords")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setKeywords(java.lang.String keywords)
+  ///
+  /// This will set the keywords of the document.
+  ///@param keywords The new keywords for the document.
+  void setKeywords(
+    jni.JString keywords,
+  ) {
+    return _setKeywords(reference, keywords.reference).check();
+  }
+
+  static final _getCreator = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getCreator")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getCreator()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the creator of the document.  This will return null if no creator exists.
+  ///@return The creator of the document.
+  jni.JString getCreator() {
+    return const jni.JStringType().fromRef(_getCreator(reference).object);
+  }
+
+  static final _setCreator = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocumentInformation__setCreator")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setCreator(java.lang.String creator)
+  ///
+  /// This will set the creator of the document.
+  ///@param creator The new creator for the document.
+  void setCreator(
+    jni.JString creator,
+  ) {
+    return _setCreator(reference, creator.reference).check();
+  }
+
+  static final _getProducer = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getProducer")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getProducer()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the producer of the document.  This will return null if no producer exists.
+  ///@return The producer of the document.
+  jni.JString getProducer() {
+    return const jni.JStringType().fromRef(_getProducer(reference).object);
+  }
+
+  static final _setProducer = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocumentInformation__setProducer")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setProducer(java.lang.String producer)
+  ///
+  /// This will set the producer of the document.
+  ///@param producer The new producer for the document.
+  void setProducer(
+    jni.JString producer,
+  ) {
+    return _setProducer(reference, producer.reference).check();
+  }
+
+  static final _getCreationDate = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getCreationDate")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Calendar getCreationDate()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the creation date of the document.  This will return null if no creation date exists.
+  ///@return The creation date of the document.
+  jni.JObject getCreationDate() {
+    return const jni.JObjectType().fromRef(_getCreationDate(reference).object);
+  }
+
+  static final _setCreationDate = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__setCreationDate")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setCreationDate(java.util.Calendar date)
+  ///
+  /// This will set the creation date of the document.
+  ///@param date The new creation date for the document.
+  void setCreationDate(
+    jni.JObject date,
+  ) {
+    return _setCreationDate(reference, date.reference).check();
+  }
+
+  static final _getModificationDate = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getModificationDate")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Calendar getModificationDate()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the modification date of the document.  This will return null if no modification date exists.
+  ///@return The modification date of the document.
+  jni.JObject getModificationDate() {
+    return const jni.JObjectType()
+        .fromRef(_getModificationDate(reference).object);
+  }
+
+  static final _setModificationDate = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__setModificationDate")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setModificationDate(java.util.Calendar date)
+  ///
+  /// This will set the modification date of the document.
+  ///@param date The new modification date for the document.
+  void setModificationDate(
+    jni.JObject date,
+  ) {
+    return _setModificationDate(reference, date.reference).check();
+  }
+
+  static final _getTrapped = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getTrapped")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getTrapped()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the trapped value for the document.
+  /// This will return null if one is not found.
+  ///@return The trapped value for the document.
+  jni.JString getTrapped() {
+    return const jni.JStringType().fromRef(_getTrapped(reference).object);
+  }
+
+  static final _getMetadataKeys = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getMetadataKeys")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Set<java.lang.String> getMetadataKeys()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the keys of all metadata information fields for the document.
+  ///@return all metadata key strings.
+  ///@since Apache PDFBox 1.3.0
+  jni.JSet<jni.JString> getMetadataKeys() {
+    return const jni.JSetType(jni.JStringType())
+        .fromRef(_getMetadataKeys(reference).object);
+  }
+
+  static final _getCustomMetadataValue = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__getCustomMetadataValue")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getCustomMetadataValue(java.lang.String fieldName)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the value of a custom metadata information field for the document.
+  ///  This will return null if one is not found.
+  ///@param fieldName Name of custom metadata field from pdf document.
+  ///@return String Value of metadata field
+  jni.JString getCustomMetadataValue(
+    jni.JString fieldName,
+  ) {
+    return const jni.JStringType().fromRef(
+        _getCustomMetadataValue(reference, fieldName.reference).object);
+  }
+
+  static final _setCustomMetadataValue = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDDocumentInformation__setCustomMetadataValue")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setCustomMetadataValue(java.lang.String fieldName, java.lang.String fieldValue)
+  ///
+  /// Set the custom metadata value.
+  ///@param fieldName The name of the custom metadata field.
+  ///@param fieldValue The value to the custom metadata field.
+  void setCustomMetadataValue(
+    jni.JString fieldName,
+    jni.JString fieldValue,
+  ) {
+    return _setCustomMetadataValue(
+            reference, fieldName.reference, fieldValue.reference)
+        .check();
+  }
+
+  static final _setTrapped = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDDocumentInformation__setTrapped")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setTrapped(java.lang.String value)
+  ///
+  /// This will set the trapped of the document.  This will be
+  /// 'True', 'False', or 'Unknown'.
+  ///@param value The new trapped value for the document.
+  ///@throws IllegalArgumentException if the parameter is invalid.
+  void setTrapped(
+    jni.JString value,
+  ) {
+    return _setTrapped(reference, value.reference).check();
+  }
+}
+
+final class $PDDocumentInformationType
+    extends jni.JObjType<PDDocumentInformation> {
+  const $PDDocumentInformationType();
+
+  @override
+  String get signature => r"Lorg/apache/pdfbox/pdmodel/PDDocumentInformation;";
+
+  @override
+  PDDocumentInformation fromRef(jni.JObjectPtr ref) =>
+      PDDocumentInformation.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($PDDocumentInformationType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($PDDocumentInformationType) &&
+        other is $PDDocumentInformationType;
+  }
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/_package.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/_package.dart
new file mode 100644
index 0000000..d2b3929
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/pdmodel/_package.dart
@@ -0,0 +1,2 @@
+export "PDDocument.dart";
+export "PDDocumentInformation.dart";
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart
new file mode 100644
index 0000000..fcdfda2
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/PDFTextStripper.dart
@@ -0,0 +1,1425 @@
+// Generated from Apache PDFBox library which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+import "../pdmodel/PDDocument.dart" as pddocument_;
+import "../../../../_init.dart";
+
+/// from: org.apache.pdfbox.text.PDFTextStripper
+///
+/// This class will take a pdf document and strip out all of the text and ignore the formatting and such. Please note; it
+/// is up to clients of this class to verify that a specific user has the correct permissions to extract text from the
+/// PDF document.
+///
+/// The basic flow of this process is that we get a document and use a series of processXXX() functions that work on
+/// smaller and smaller chunks of the page. Eventually, we fully process each page and then print it.
+///@author Ben Litchfield
+class PDFTextStripper extends jni.JObject {
+  @override
+  late final jni.JObjType<PDFTextStripper> $type = type;
+
+  PDFTextStripper.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $PDFTextStripperType();
+  static final _get_LINE_SEPARATOR = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_PDFTextStripper__LINE_SEPARATOR")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  /// from: protected final java.lang.String LINE_SEPARATOR
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// The platform's line separator.
+  jni.JString get LINE_SEPARATOR =>
+      const jni.JStringType().fromRef(_get_LINE_SEPARATOR(reference).object);
+
+  static final _get_charactersByArticle = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_PDFTextStripper__charactersByArticle")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_charactersByArticle = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>(
+          "set_PDFTextStripper__charactersByArticle")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected java.util.ArrayList<java.util.List<org.apache.pdfbox.text.TextPosition>> charactersByArticle
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// The charactersByArticle is used to extract text by article divisions. For example a PDF that has two columns like
+  /// a newspaper, we want to extract the first column and then the second column. In this example the PDF would have 2
+  /// beads(or articles), one for each column. The size of the charactersByArticle would be 5, because not all text on
+  /// the screen will fall into one of the articles. The five divisions are shown below
+  ///
+  /// Text before first article
+  /// first article text
+  /// text between first article and second article
+  /// second article text
+  /// text after second article
+  ///
+  /// Most PDFs won't have any beads, so charactersByArticle will contain a single entry.
+  jni.JObject get charactersByArticle => const jni.JObjectType()
+      .fromRef(_get_charactersByArticle(reference).object);
+
+  /// from: protected java.util.ArrayList<java.util.List<org.apache.pdfbox.text.TextPosition>> charactersByArticle
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// The charactersByArticle is used to extract text by article divisions. For example a PDF that has two columns like
+  /// a newspaper, we want to extract the first column and then the second column. In this example the PDF would have 2
+  /// beads(or articles), one for each column. The size of the charactersByArticle would be 5, because not all text on
+  /// the screen will fall into one of the articles. The five divisions are shown below
+  ///
+  /// Text before first article
+  /// first article text
+  /// text between first article and second article
+  /// second article text
+  /// text after second article
+  ///
+  /// Most PDFs won't have any beads, so charactersByArticle will contain a single entry.
+  set charactersByArticle(jni.JObject value) =>
+      _set_charactersByArticle(reference, value.reference).check();
+
+  static final _get_document = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_PDFTextStripper__document")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_document = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Pointer<ffi.Void>)>>("set_PDFTextStripper__document")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected org.apache.pdfbox.pdmodel.PDDocument document
+  /// The returned object must be released after use, by calling the [release] method.
+  pddocument_.PDDocument get document => const pddocument_.$PDDocumentType()
+      .fromRef(_get_document(reference).object);
+
+  /// from: protected org.apache.pdfbox.pdmodel.PDDocument document
+  /// The returned object must be released after use, by calling the [release] method.
+  set document(pddocument_.PDDocument value) =>
+      _set_document(reference, value.reference).check();
+
+  static final _get_output = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_PDFTextStripper__output")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_output = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Pointer<ffi.Void>)>>("set_PDFTextStripper__output")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected java.io.Writer output
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject get output =>
+      const jni.JObjectType().fromRef(_get_output(reference).object);
+
+  /// from: protected java.io.Writer output
+  /// The returned object must be released after use, by calling the [release] method.
+  set output(jni.JObject value) =>
+      _set_output(reference, value.reference).check();
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "PDFTextStripper__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Instantiate a new PDFTextStripper object.
+  ///@throws IOException If there is an error loading the properties.
+  factory PDFTextStripper() {
+    return PDFTextStripper.fromRef(_new0().object);
+  }
+
+  static final _getText = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__getText")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getText(org.apache.pdfbox.pdmodel.PDDocument doc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will return the text of a document. See writeText. <br>
+  /// NOTE: The document must not be encrypted when coming into this method.
+  ///
+  /// IMPORTANT: By default, text extraction is done in the same sequence as the text in the PDF page content stream.
+  /// PDF is a graphic format, not a text format, and unlike HTML, it has no requirements that text one on page
+  /// be rendered in a certain order. The order is the one that was determined by the software that created the
+  /// PDF. To get text sorted from left to right and top to botton, use \#setSortByPosition(boolean).
+  ///@param doc The document to get the text from.
+  ///@return The text of the PDF document.
+  ///@throws IOException if the doc state is invalid or it is encrypted.
+  jni.JString getText(
+    pddocument_.PDDocument doc,
+  ) {
+    return const jni.JStringType()
+        .fromRef(_getText(reference, doc.reference).object);
+  }
+
+  static final _writeText = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__writeText")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void writeText(org.apache.pdfbox.pdmodel.PDDocument doc, java.io.Writer outputStream)
+  ///
+  /// This will take a PDDocument and write the text of that document to the print writer.
+  ///@param doc The document to get the data from.
+  ///@param outputStream The location to put the text.
+  ///@throws IOException If the doc is in an invalid state.
+  void writeText(
+    pddocument_.PDDocument doc,
+    jni.JObject outputStream,
+  ) {
+    return _writeText(reference, doc.reference, outputStream.reference).check();
+  }
+
+  static final _processPages = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__processPages")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void processPages(org.apache.pdfbox.pdmodel.PDPageTree pages)
+  ///
+  /// This will process all of the pages and the text that is in them.
+  ///@param pages The pages object in the document.
+  ///@throws IOException If there is an error parsing the text.
+  void processPages(
+    jni.JObject pages,
+  ) {
+    return _processPages(reference, pages.reference).check();
+  }
+
+  static final _startDocument = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__startDocument")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void startDocument(org.apache.pdfbox.pdmodel.PDDocument document)
+  ///
+  /// This method is available for subclasses of this class. It will be called before processing of the document start.
+  ///@param document The PDF document that is being processed.
+  ///@throws IOException If an IO error occurs.
+  void startDocument(
+    pddocument_.PDDocument document,
+  ) {
+    return _startDocument(reference, document.reference).check();
+  }
+
+  static final _endDocument = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__endDocument")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void endDocument(org.apache.pdfbox.pdmodel.PDDocument document)
+  ///
+  /// This method is available for subclasses of this class. It will be called after processing of the document
+  /// finishes.
+  ///@param document The PDF document that is being processed.
+  ///@throws IOException If an IO error occurs.
+  void endDocument(
+    pddocument_.PDDocument document,
+  ) {
+    return _endDocument(reference, document.reference).check();
+  }
+
+  static final _processPage = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__processPage")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void processPage(org.apache.pdfbox.pdmodel.PDPage page)
+  ///
+  /// This will process the contents of a page.
+  ///@param page The page to process.
+  ///@throws IOException If there is an error processing the page.
+  void processPage(
+    jni.JObject page,
+  ) {
+    return _processPage(reference, page.reference).check();
+  }
+
+  static final _startArticle = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__startArticle")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void startArticle()
+  ///
+  /// Start a new article, which is typically defined as a column on a single page (also referred to as a bead). This
+  /// assumes that the primary direction of text is left to right. Default implementation is to do nothing. Subclasses
+  /// may provide additional information.
+  ///@throws IOException If there is any error writing to the stream.
+  void startArticle() {
+    return _startArticle(reference).check();
+  }
+
+  static final _startArticle1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Uint8)>>("PDFTextStripper__startArticle1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: protected void startArticle(boolean isLTR)
+  ///
+  /// Start a new article, which is typically defined as a column on a single page (also referred to as a bead).
+  /// Default implementation is to do nothing. Subclasses may provide additional information.
+  ///@param isLTR true if primary direction of text is left to right.
+  ///@throws IOException If there is any error writing to the stream.
+  void startArticle1(
+    bool isLTR,
+  ) {
+    return _startArticle1(reference, isLTR ? 1 : 0).check();
+  }
+
+  static final _endArticle = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__endArticle")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void endArticle()
+  ///
+  /// End an article. Default implementation is to do nothing. Subclasses may provide additional information.
+  ///@throws IOException If there is any error writing to the stream.
+  void endArticle() {
+    return _endArticle(reference).check();
+  }
+
+  static final _startPage = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__startPage")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void startPage(org.apache.pdfbox.pdmodel.PDPage page)
+  ///
+  /// Start a new page. Default implementation is to do nothing. Subclasses may provide additional information.
+  ///@param page The page we are about to process.
+  ///@throws IOException If there is any error writing to the stream.
+  void startPage(
+    jni.JObject page,
+  ) {
+    return _startPage(reference, page.reference).check();
+  }
+
+  static final _endPage = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__endPage")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void endPage(org.apache.pdfbox.pdmodel.PDPage page)
+  ///
+  /// End a page. Default implementation is to do nothing. Subclasses may provide additional information.
+  ///@param page The page we are about to process.
+  ///@throws IOException If there is any error writing to the stream.
+  void endPage(
+    jni.JObject page,
+  ) {
+    return _endPage(reference, page.reference).check();
+  }
+
+  static final _writePage = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__writePage")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writePage()
+  ///
+  /// This will print the text of the processed page to "output". It will estimate, based on the coordinates of the
+  /// text, where newlines and word spacings should be placed. The text will be sorted only if that feature was
+  /// enabled.
+  ///@throws IOException If there is an error writing the text.
+  void writePage() {
+    return _writePage(reference).check();
+  }
+
+  static final _writeLineSeparator = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__writeLineSeparator")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writeLineSeparator()
+  ///
+  /// Write the line separator value to the output stream.
+  ///@throws IOException If there is a problem writing out the line separator to the document.
+  void writeLineSeparator() {
+    return _writeLineSeparator(reference).check();
+  }
+
+  static final _writeWordSeparator = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__writeWordSeparator")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writeWordSeparator()
+  ///
+  /// Write the word separator value to the output stream.
+  ///@throws IOException If there is a problem writing out the word separator to the document.
+  void writeWordSeparator() {
+    return _writeWordSeparator(reference).check();
+  }
+
+  static final _writeCharacters = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__writeCharacters")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writeCharacters(org.apache.pdfbox.text.TextPosition text)
+  ///
+  /// Write the string in TextPosition to the output stream.
+  ///@param text The text to write to the stream.
+  ///@throws IOException If there is an error when writing the text.
+  void writeCharacters(
+    jni.JObject text,
+  ) {
+    return _writeCharacters(reference, text.reference).check();
+  }
+
+  static final _writeString = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__writeString")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writeString(java.lang.String text, java.util.List<org.apache.pdfbox.text.TextPosition> textPositions)
+  ///
+  /// Write a Java string to the output stream. The default implementation will ignore the <code>textPositions</code>
+  /// and just calls \#writeString(String).
+  ///@param text The text to write to the stream.
+  ///@param textPositions The TextPositions belonging to the text.
+  ///@throws IOException If there is an error when writing the text.
+  void writeString(
+    jni.JString text,
+    jni.JList<jni.JObject> textPositions,
+  ) {
+    return _writeString(reference, text.reference, textPositions.reference)
+        .check();
+  }
+
+  static final _writeString1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__writeString1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writeString(java.lang.String text)
+  ///
+  /// Write a Java string to the output stream.
+  ///@param text The text to write to the stream.
+  ///@throws IOException If there is an error when writing the text.
+  void writeString1(
+    jni.JString text,
+  ) {
+    return _writeString1(reference, text.reference).check();
+  }
+
+  static final _processTextPosition = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__processTextPosition")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void processTextPosition(org.apache.pdfbox.text.TextPosition text)
+  ///
+  /// This will process a TextPosition object and add the text to the list of characters on a page. It takes care of
+  /// overlapping text.
+  ///@param text The text to process.
+  void processTextPosition(
+    jni.JObject text,
+  ) {
+    return _processTextPosition(reference, text.reference).check();
+  }
+
+  static final _getStartPage = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getStartPage")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getStartPage()
+  ///
+  /// This is the page that the text extraction will start on. The pages start at page 1. For example in a 5 page PDF
+  /// document, if the start page is 1 then all pages will be extracted. If the start page is 4 then pages 4 and 5 will
+  /// be extracted. The default value is 1.
+  ///@return Value of property startPage.
+  int getStartPage() {
+    return _getStartPage(reference).integer;
+  }
+
+  static final _setStartPage = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("PDFTextStripper__setStartPage")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setStartPage(int startPageValue)
+  ///
+  /// This will set the first page to be extracted by this class.
+  ///@param startPageValue New value of 1-based startPage property.
+  void setStartPage(
+    int startPageValue,
+  ) {
+    return _setStartPage(reference, startPageValue).check();
+  }
+
+  static final _getEndPage = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getEndPage")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getEndPage()
+  ///
+  /// This will get the last page that will be extracted. This is inclusive, for example if a 5 page PDF an endPage
+  /// value of 5 would extract the entire document, an end page of 2 would extract pages 1 and 2. This defaults to
+  /// Integer.MAX_VALUE such that all pages of the pdf will be extracted.
+  ///@return Value of property endPage.
+  int getEndPage() {
+    return _getEndPage(reference).integer;
+  }
+
+  static final _setEndPage = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("PDFTextStripper__setEndPage")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setEndPage(int endPageValue)
+  ///
+  /// This will set the last page to be extracted by this class.
+  ///@param endPageValue New value of 1-based endPage property.
+  void setEndPage(
+    int endPageValue,
+  ) {
+    return _setEndPage(reference, endPageValue).check();
+  }
+
+  static final _setLineSeparator = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setLineSeparator")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setLineSeparator(java.lang.String separator)
+  ///
+  /// Set the desired line separator for output text. The line.separator system property is used if the line separator
+  /// preference is not set explicitly using this method.
+  ///@param separator The desired line separator string.
+  void setLineSeparator(
+    jni.JString separator,
+  ) {
+    return _setLineSeparator(reference, separator.reference).check();
+  }
+
+  static final _getLineSeparator = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getLineSeparator")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getLineSeparator()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the line separator.
+  ///@return The desired line separator string.
+  jni.JString getLineSeparator() {
+    return const jni.JStringType().fromRef(_getLineSeparator(reference).object);
+  }
+
+  static final _getWordSeparator = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getWordSeparator")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getWordSeparator()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// This will get the word separator.
+  ///@return The desired word separator string.
+  jni.JString getWordSeparator() {
+    return const jni.JStringType().fromRef(_getWordSeparator(reference).object);
+  }
+
+  static final _setWordSeparator = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setWordSeparator")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setWordSeparator(java.lang.String separator)
+  ///
+  /// Set the desired word separator for output text. The PDFBox text extraction algorithm will output a space
+  /// character if there is enough space between two words. By default a space character is used. If you need and
+  /// accurate count of characters that are found in a PDF document then you might want to set the word separator to
+  /// the empty string.
+  ///@param separator The desired page separator string.
+  void setWordSeparator(
+    jni.JString separator,
+  ) {
+    return _setWordSeparator(reference, separator.reference).check();
+  }
+
+  static final _getSuppressDuplicateOverlappingText = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getSuppressDuplicateOverlappingText")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean getSuppressDuplicateOverlappingText()
+  ///
+  /// @return Returns the suppressDuplicateOverlappingText.
+  bool getSuppressDuplicateOverlappingText() {
+    return _getSuppressDuplicateOverlappingText(reference).boolean;
+  }
+
+  static final _getCurrentPageNo = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getCurrentPageNo")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected int getCurrentPageNo()
+  ///
+  /// Get the current page number that is being processed.
+  ///@return A 1 based number representing the current page.
+  int getCurrentPageNo() {
+    return _getCurrentPageNo(reference).integer;
+  }
+
+  static final _getOutput = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getOutput")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected java.io.Writer getOutput()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// The output stream that is being written to.
+  ///@return The stream that output is being written to.
+  jni.JObject getOutput() {
+    return const jni.JObjectType().fromRef(_getOutput(reference).object);
+  }
+
+  static final _getCharactersByArticle = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getCharactersByArticle")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected java.util.List<java.util.List<org.apache.pdfbox.text.TextPosition>> getCharactersByArticle()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Character strings are grouped by articles. It is quite common that there will only be a single article. This
+  /// returns a List that contains List objects, the inner lists will contain TextPosition objects.
+  ///@return A double List of TextPositions for all text strings on the page.
+  jni.JList<jni.JList<jni.JObject>> getCharactersByArticle() {
+    return const jni.JListType(jni.JListType(jni.JObjectType()))
+        .fromRef(_getCharactersByArticle(reference).object);
+  }
+
+  static final _setSuppressDuplicateOverlappingText = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Uint8)>>(
+          "PDFTextStripper__setSuppressDuplicateOverlappingText")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setSuppressDuplicateOverlappingText(boolean suppressDuplicateOverlappingTextValue)
+  ///
+  /// By default the text stripper will attempt to remove text that overlapps each other. Word paints the same
+  /// character several times in order to make it look bold. By setting this to false all text will be extracted, which
+  /// means that certain sections will be duplicated, but better performance will be noticed.
+  ///@param suppressDuplicateOverlappingTextValue The suppressDuplicateOverlappingText to set.
+  void setSuppressDuplicateOverlappingText(
+    bool suppressDuplicateOverlappingTextValue,
+  ) {
+    return _setSuppressDuplicateOverlappingText(
+            reference, suppressDuplicateOverlappingTextValue ? 1 : 0)
+        .check();
+  }
+
+  static final _getSeparateByBeads = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getSeparateByBeads")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean getSeparateByBeads()
+  ///
+  /// This will tell if the text stripper should separate by beads.
+  ///@return If the text will be grouped by beads.
+  bool getSeparateByBeads() {
+    return _getSeparateByBeads(reference).boolean;
+  }
+
+  static final _setShouldSeparateByBeads = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Uint8)>>("PDFTextStripper__setShouldSeparateByBeads")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setShouldSeparateByBeads(boolean aShouldSeparateByBeads)
+  ///
+  /// Set if the text stripper should group the text output by a list of beads. The default value is true!
+  ///@param aShouldSeparateByBeads The new grouping of beads.
+  void setShouldSeparateByBeads(
+    bool aShouldSeparateByBeads,
+  ) {
+    return _setShouldSeparateByBeads(reference, aShouldSeparateByBeads ? 1 : 0)
+        .check();
+  }
+
+  static final _getEndBookmark = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getEndBookmark")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem getEndBookmark()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Get the bookmark where text extraction should end, inclusive. Default is null.
+  ///@return The ending bookmark.
+  jni.JObject getEndBookmark() {
+    return const jni.JObjectType().fromRef(_getEndBookmark(reference).object);
+  }
+
+  static final _setEndBookmark = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setEndBookmark")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setEndBookmark(org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem aEndBookmark)
+  ///
+  /// Set the bookmark where the text extraction should stop.
+  ///@param aEndBookmark The ending bookmark.
+  void setEndBookmark(
+    jni.JObject aEndBookmark,
+  ) {
+    return _setEndBookmark(reference, aEndBookmark.reference).check();
+  }
+
+  static final _getStartBookmark = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getStartBookmark")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem getStartBookmark()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Get the bookmark where text extraction should start, inclusive. Default is null.
+  ///@return The starting bookmark.
+  jni.JObject getStartBookmark() {
+    return const jni.JObjectType().fromRef(_getStartBookmark(reference).object);
+  }
+
+  static final _setStartBookmark = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setStartBookmark")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setStartBookmark(org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem aStartBookmark)
+  ///
+  /// Set the bookmark where text extraction should start, inclusive.
+  ///@param aStartBookmark The starting bookmark.
+  void setStartBookmark(
+    jni.JObject aStartBookmark,
+  ) {
+    return _setStartBookmark(reference, aStartBookmark.reference).check();
+  }
+
+  static final _getAddMoreFormatting = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getAddMoreFormatting")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean getAddMoreFormatting()
+  ///
+  /// This will tell if the text stripper should add some more text formatting.
+  ///@return true if some more text formatting will be added
+  bool getAddMoreFormatting() {
+    return _getAddMoreFormatting(reference).boolean;
+  }
+
+  static final _setAddMoreFormatting = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Uint8)>>("PDFTextStripper__setAddMoreFormatting")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setAddMoreFormatting(boolean newAddMoreFormatting)
+  ///
+  /// There will some additional text formatting be added if addMoreFormatting is set to true. Default is false.
+  ///@param newAddMoreFormatting Tell PDFBox to add some more text formatting
+  void setAddMoreFormatting(
+    bool newAddMoreFormatting,
+  ) {
+    return _setAddMoreFormatting(reference, newAddMoreFormatting ? 1 : 0)
+        .check();
+  }
+
+  static final _getSortByPosition = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getSortByPosition")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean getSortByPosition()
+  ///
+  /// This will tell if the text stripper should sort the text tokens before writing to the stream.
+  ///@return true If the text tokens will be sorted before being written.
+  bool getSortByPosition() {
+    return _getSortByPosition(reference).boolean;
+  }
+
+  static final _setSortByPosition = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Uint8)>>("PDFTextStripper__setSortByPosition")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setSortByPosition(boolean newSortByPosition)
+  ///
+  /// The order of the text tokens in a PDF file may not be in the same as they appear visually on the screen. For
+  /// example, a PDF writer may write out all text by font, so all bold or larger text, then make a second pass and
+  /// write out the normal text.<br>
+  /// The default is to __not__ sort by position.<br>
+  /// <br>
+  /// A PDF writer could choose to write each character in a different order. By default PDFBox does __not__ sort
+  /// the text tokens before processing them due to performance reasons.
+  ///@param newSortByPosition Tell PDFBox to sort the text positions.
+  void setSortByPosition(
+    bool newSortByPosition,
+  ) {
+    return _setSortByPosition(reference, newSortByPosition ? 1 : 0).check();
+  }
+
+  static final _getSpacingTolerance = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getSpacingTolerance")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public float getSpacingTolerance()
+  ///
+  /// Get the current space width-based tolerance value that is being used to estimate where spaces in text should be
+  /// added. Note that the default value for this has been determined from trial and error.
+  ///@return The current tolerance / scaling factor
+  double getSpacingTolerance() {
+    return _getSpacingTolerance(reference).float;
+  }
+
+  static final _setSpacingTolerance = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Float)>>("PDFTextStripper__setSpacingTolerance")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, double)>();
+
+  /// from: public void setSpacingTolerance(float spacingToleranceValue)
+  ///
+  /// Set the space width-based tolerance value that is used to estimate where spaces in text should be added. Note
+  /// that the default value for this has been determined from trial and error. Setting this value larger will reduce
+  /// the number of spaces added.
+  ///@param spacingToleranceValue tolerance / scaling factor to use
+  void setSpacingTolerance(
+    double spacingToleranceValue,
+  ) {
+    return _setSpacingTolerance(reference, spacingToleranceValue).check();
+  }
+
+  static final _getAverageCharTolerance = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getAverageCharTolerance")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public float getAverageCharTolerance()
+  ///
+  /// Get the current character width-based tolerance value that is being used to estimate where spaces in text should
+  /// be added. Note that the default value for this has been determined from trial and error.
+  ///@return The current tolerance / scaling factor
+  double getAverageCharTolerance() {
+    return _getAverageCharTolerance(reference).float;
+  }
+
+  static final _setAverageCharTolerance = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Float)>>("PDFTextStripper__setAverageCharTolerance")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, double)>();
+
+  /// from: public void setAverageCharTolerance(float averageCharToleranceValue)
+  ///
+  /// Set the character width-based tolerance value that is used to estimate where spaces in text should be added. Note
+  /// that the default value for this has been determined from trial and error. Setting this value larger will reduce
+  /// the number of spaces added.
+  ///@param averageCharToleranceValue average tolerance / scaling factor to use
+  void setAverageCharTolerance(
+    double averageCharToleranceValue,
+  ) {
+    return _setAverageCharTolerance(reference, averageCharToleranceValue)
+        .check();
+  }
+
+  static final _getIndentThreshold = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getIndentThreshold")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public float getIndentThreshold()
+  ///
+  /// returns the multiple of whitespace character widths for the current text which the current line start can be
+  /// indented from the previous line start beyond which the current line start is considered to be a paragraph start.
+  ///@return the number of whitespace character widths to use when detecting paragraph indents.
+  double getIndentThreshold() {
+    return _getIndentThreshold(reference).float;
+  }
+
+  static final _setIndentThreshold = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Float)>>("PDFTextStripper__setIndentThreshold")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, double)>();
+
+  /// from: public void setIndentThreshold(float indentThresholdValue)
+  ///
+  /// sets the multiple of whitespace character widths for the current text which the current line start can be
+  /// indented from the previous line start beyond which the current line start is considered to be a paragraph start.
+  /// The default value is 2.0.
+  ///@param indentThresholdValue the number of whitespace character widths to use when detecting paragraph indents.
+  void setIndentThreshold(
+    double indentThresholdValue,
+  ) {
+    return _setIndentThreshold(reference, indentThresholdValue).check();
+  }
+
+  static final _getDropThreshold = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getDropThreshold")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public float getDropThreshold()
+  ///
+  /// the minimum whitespace, as a multiple of the max height of the current characters beyond which the current line
+  /// start is considered to be a paragraph start.
+  ///@return the character height multiple for max allowed whitespace between lines in the same paragraph.
+  double getDropThreshold() {
+    return _getDropThreshold(reference).float;
+  }
+
+  static final _setDropThreshold = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Float)>>("PDFTextStripper__setDropThreshold")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, double)>();
+
+  /// from: public void setDropThreshold(float dropThresholdValue)
+  ///
+  /// sets the minimum whitespace, as a multiple of the max height of the current characters beyond which the current
+  /// line start is considered to be a paragraph start. The default value is 2.5.
+  ///@param dropThresholdValue the character height multiple for max allowed whitespace between lines in the same
+  /// paragraph.
+  void setDropThreshold(
+    double dropThresholdValue,
+  ) {
+    return _setDropThreshold(reference, dropThresholdValue).check();
+  }
+
+  static final _getParagraphStart = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getParagraphStart")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getParagraphStart()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the string which will be used at the beginning of a paragraph.
+  ///@return the paragraph start string
+  jni.JString getParagraphStart() {
+    return const jni.JStringType()
+        .fromRef(_getParagraphStart(reference).object);
+  }
+
+  static final _setParagraphStart = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setParagraphStart")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setParagraphStart(java.lang.String s)
+  ///
+  /// Sets the string which will be used at the beginning of a paragraph.
+  ///@param s the paragraph start string
+  void setParagraphStart(
+    jni.JString s,
+  ) {
+    return _setParagraphStart(reference, s.reference).check();
+  }
+
+  static final _getParagraphEnd = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getParagraphEnd")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getParagraphEnd()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the string which will be used at the end of a paragraph.
+  ///@return the paragraph end string
+  jni.JString getParagraphEnd() {
+    return const jni.JStringType().fromRef(_getParagraphEnd(reference).object);
+  }
+
+  static final _setParagraphEnd = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setParagraphEnd")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setParagraphEnd(java.lang.String s)
+  ///
+  /// Sets the string which will be used at the end of a paragraph.
+  ///@param s the paragraph end string
+  void setParagraphEnd(
+    jni.JString s,
+  ) {
+    return _setParagraphEnd(reference, s.reference).check();
+  }
+
+  static final _getPageStart = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getPageStart")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getPageStart()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the string which will be used at the beginning of a page.
+  ///@return the page start string
+  jni.JString getPageStart() {
+    return const jni.JStringType().fromRef(_getPageStart(reference).object);
+  }
+
+  static final _setPageStart = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setPageStart")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setPageStart(java.lang.String pageStartValue)
+  ///
+  /// Sets the string which will be used at the beginning of a page.
+  ///@param pageStartValue the page start string
+  void setPageStart(
+    jni.JString pageStartValue,
+  ) {
+    return _setPageStart(reference, pageStartValue.reference).check();
+  }
+
+  static final _getPageEnd = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getPageEnd")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getPageEnd()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the string which will be used at the end of a page.
+  ///@return the page end string
+  jni.JString getPageEnd() {
+    return const jni.JStringType().fromRef(_getPageEnd(reference).object);
+  }
+
+  static final _setPageEnd = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setPageEnd")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setPageEnd(java.lang.String pageEndValue)
+  ///
+  /// Sets the string which will be used at the end of a page.
+  ///@param pageEndValue the page end string
+  void setPageEnd(
+    jni.JString pageEndValue,
+  ) {
+    return _setPageEnd(reference, pageEndValue.reference).check();
+  }
+
+  static final _getArticleStart = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getArticleStart")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getArticleStart()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the string which will be used at the beginning of an article.
+  ///@return the article start string
+  jni.JString getArticleStart() {
+    return const jni.JStringType().fromRef(_getArticleStart(reference).object);
+  }
+
+  static final _setArticleStart = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setArticleStart")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setArticleStart(java.lang.String articleStartValue)
+  ///
+  /// Sets the string which will be used at the beginning of an article.
+  ///@param articleStartValue the article start string
+  void setArticleStart(
+    jni.JString articleStartValue,
+  ) {
+    return _setArticleStart(reference, articleStartValue.reference).check();
+  }
+
+  static final _getArticleEnd = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getArticleEnd")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getArticleEnd()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Returns the string which will be used at the end of an article.
+  ///@return the article end string
+  jni.JString getArticleEnd() {
+    return const jni.JStringType().fromRef(_getArticleEnd(reference).object);
+  }
+
+  static final _setArticleEnd = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__setArticleEnd")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setArticleEnd(java.lang.String articleEndValue)
+  ///
+  /// Sets the string which will be used at the end of an article.
+  ///@param articleEndValue the article end string
+  void setArticleEnd(
+    jni.JString articleEndValue,
+  ) {
+    return _setArticleEnd(reference, articleEndValue.reference).check();
+  }
+
+  static final _writeParagraphSeparator = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__writeParagraphSeparator")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writeParagraphSeparator()
+  ///
+  /// writes the paragraph separator string to the output.
+  ///@throws IOException if something went wrong
+  void writeParagraphSeparator() {
+    return _writeParagraphSeparator(reference).check();
+  }
+
+  static final _writeParagraphStart = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__writeParagraphStart")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writeParagraphStart()
+  ///
+  /// Write something (if defined) at the start of a paragraph.
+  ///@throws IOException if something went wrong
+  void writeParagraphStart() {
+    return _writeParagraphStart(reference).check();
+  }
+
+  static final _writeParagraphEnd = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__writeParagraphEnd")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writeParagraphEnd()
+  ///
+  /// Write something (if defined) at the end of a paragraph.
+  ///@throws IOException if something went wrong
+  void writeParagraphEnd() {
+    return _writeParagraphEnd(reference).check();
+  }
+
+  static final _writePageStart = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__writePageStart")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writePageStart()
+  ///
+  /// Write something (if defined) at the start of a page.
+  ///@throws IOException if something went wrong
+  void writePageStart() {
+    return _writePageStart(reference).check();
+  }
+
+  static final _writePageEnd = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__writePageEnd")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void writePageEnd()
+  ///
+  /// Write something (if defined) at the end of a page.
+  ///@throws IOException if something went wrong
+  void writePageEnd() {
+    return _writePageEnd(reference).check();
+  }
+
+  static final _setListItemPatterns = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__setListItemPatterns")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void setListItemPatterns(java.util.List<java.util.regex.Pattern> patterns)
+  ///
+  /// use to supply a different set of regular expression patterns for matching list item starts.
+  ///@param patterns list of patterns
+  void setListItemPatterns(
+    jni.JList<jni.JObject> patterns,
+  ) {
+    return _setListItemPatterns(reference, patterns.reference).check();
+  }
+
+  static final _getListItemPatterns = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "PDFTextStripper__getListItemPatterns")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected java.util.List<java.util.regex.Pattern> getListItemPatterns()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// returns a list of regular expression Patterns representing different common list item formats. For example
+  /// numbered items of form:
+  /// <ol>
+  /// <li>some text</li>
+  /// <li>more text</li>
+  /// </ol>
+  /// or
+  /// <ul>
+  /// <li>some text</li>
+  /// <li>more text</li>
+  /// </ul>
+  /// etc., all begin with some character pattern. The pattern "\\d+\." (matches "1.", "2.", ...) or "\[\\d+\]"
+  /// (matches "[1]", "[2]", ...).
+  ///
+  /// This method returns a list of such regular expression Patterns.
+  ///@return a list of Pattern objects.
+  jni.JList<jni.JObject> getListItemPatterns() {
+    return const jni.JListType(jni.JObjectType())
+        .fromRef(_getListItemPatterns(reference).object);
+  }
+
+  static final _matchPattern = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("PDFTextStripper__matchPattern")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static protected java.util.regex.Pattern matchPattern(java.lang.String string, java.util.List<java.util.regex.Pattern> patterns)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// iterates over the specified list of Patterns until it finds one that matches the specified string. Then returns
+  /// the Pattern.
+  ///
+  /// Order of the supplied list of patterns is important as most common patterns should come first. Patterns should be
+  /// strict in general, and all will be used with case sensitivity on.
+  ///
+  ///
+  ///@param string the string to be searched
+  ///@param patterns list of patterns
+  ///@return matching pattern
+  static jni.JObject matchPattern(
+    jni.JString string,
+    jni.JList<jni.JObject> patterns,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_matchPattern(string.reference, patterns.reference).object);
+  }
+}
+
+final class $PDFTextStripperType extends jni.JObjType<PDFTextStripper> {
+  const $PDFTextStripperType();
+
+  @override
+  String get signature => r"Lorg/apache/pdfbox/text/PDFTextStripper;";
+
+  @override
+  PDFTextStripper fromRef(jni.JObjectPtr ref) => PDFTextStripper.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($PDFTextStripperType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($PDFTextStripperType) &&
+        other is $PDFTextStripperType;
+  }
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/_package.dart b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/_package.dart
new file mode 100644
index 0000000..07c57f7
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/lib/src/third_party/org/apache/pdfbox/text/_package.dart
@@ -0,0 +1 @@
+export "PDFTextStripper.dart";
diff --git a/pkgs/jnigen/example/pdfbox_plugin/linux/CMakeLists.txt b/pkgs/jnigen/example/pdfbox_plugin/linux/CMakeLists.txt
new file mode 100644
index 0000000..986e388
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/linux/CMakeLists.txt
@@ -0,0 +1,22 @@
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+# Project-level configuration.
+set(PROJECT_NAME "pdfbox_plugin")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+# Invoke the build for native code shared with the other target platforms.
+# This can be changed to accomodate different builds.
+add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
+
+# List of absolute paths to libraries that should be bundled with the plugin.
+# This list could contain prebuilt libraries, or libraries created by an
+# external build triggered from this build file.
+set(pdfbox_plugin_bundled_libraries
+  # Defined in ../src/CMakeLists.txt.
+  # This can be changed to accomodate different builds.
+  $<TARGET_FILE:pdfbox_plugin>
+  PARENT_SCOPE
+)
diff --git a/pkgs/jnigen/example/pdfbox_plugin/pubspec.yaml b/pkgs/jnigen/example/pdfbox_plugin/pubspec.yaml
new file mode 100644
index 0000000..a269d5a
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/pubspec.yaml
@@ -0,0 +1,38 @@
+name: pdfbox_plugin
+description: |
+  Example of using jnigen to generate bindings for a non-trivial Java library.
+version: 0.0.1
+publish_to: none
+homepage: https://github.com/dart-lang/jnigen
+
+environment:
+  sdk: '>=3.1.0 <4.0.0'
+  #flutter: ">=2.11.0"
+
+dependencies:
+  jni:
+    path: ../../../jni/
+  #flutter:
+    #sdk: flutter
+  plugin_platform_interface: ^2.0.2
+
+dev_dependencies:
+  ## Path dependency for sake of the example
+  jnigen:
+    path: ../../
+  test: any
+  lints: ^2.0.0
+
+flutter:
+  # A JNI plugin uses FFI for calling into C code. Thus the relevant shared
+  # library artifacts must be bundled with final application.
+  # Please refer to README.md for a detailed explanation.
+  plugin:
+    platforms:
+      android:
+        ffiPlugin: true
+      linux:
+        ffiPlugin: true
+      windows:
+        ffiPlugin: true
+
diff --git a/pkgs/jnigen/example/pdfbox_plugin/src/CMakeLists.txt b/pkgs/jnigen/example/pdfbox_plugin/src/CMakeLists.txt
new file mode 100644
index 0000000..f353765
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/src/CMakeLists.txt
@@ -0,0 +1,32 @@
+# jni_native_build (Build with jni:setup. Do not delete this line.)
+
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project(pdfbox_plugin VERSION 0.0.1 LANGUAGES C)
+
+add_library(pdfbox_plugin SHARED
+  "third_party//pdfbox_plugin.c"
+)
+
+set_target_properties(pdfbox_plugin PROPERTIES
+  OUTPUT_NAME "pdfbox_plugin"
+)
+
+target_compile_definitions(pdfbox_plugin PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries(pdfbox_plugin log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries(pdfbox_plugin ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jnigen/example/pdfbox_plugin/src/third_party/.clang-format b/pkgs/jnigen/example/pdfbox_plugin/src/third_party/.clang-format
new file mode 100644
index 0000000..a256c2f
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/src/third_party/.clang-format
@@ -0,0 +1,15 @@
+# From dart SDK: https://github.com/dart-lang/sdk/blob/main/.clang-format
+
+# Defines the Chromium style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
+
+# clang-format doesn't seem to do a good job of this for longer comments.
+ReflowComments: 'false'
+
+# We have lots of these. Though we need to put them all in curly braces,
+# clang-format can't do that.
+AllowShortIfStatementsOnASingleLine: 'true'
+
+# Put escaped newlines into the rightmost column.
+AlignEscapedNewlinesLeft: false
diff --git a/pkgs/jnigen/example/pdfbox_plugin/src/third_party/dartjni.h b/pkgs/jnigen/example/pdfbox_plugin/src/third_party/dartjni.h
new file mode 100644
index 0000000..8f1dc74
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/src/third_party/dartjni.h
@@ -0,0 +1,424 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h.
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
+#if _WIN32
+#define FFI_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FFI_PLUGIN_EXPORT
+#endif
+
+#if defined _WIN32
+#define thread_local __declspec(thread)
+#else
+#define thread_local __thread
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#ifdef __ANDROID__
+#define __ENVP_CAST (JNIEnv**)
+#else
+#define __ENVP_CAST (void**)
+#endif
+
+/// Locking functions for windows and pthread.
+
+#if defined _WIN32
+#include <windows.h>
+
+typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  InitializeCriticalSection(lock);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  EnterCriticalSection(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  LeaveCriticalSection(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  DeleteCriticalSection(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  // Not available.
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ ||         \
+    defined __GNUC__
+#include <pthread.h>
+
+typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  pthread_mutex_init(lock, NULL);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  pthread_mutex_lock(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  pthread_mutex_unlock(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  pthread_mutex_destroy(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  pthread_cond_destroy(cond);
+}
+
+#else
+
+#error "No locking/condition variable support; Possibly unsupported platform"
+
+#endif
+
+typedef struct CallbackResult {
+  MutexLock lock;
+  ConditionVariable cond;
+  int ready;
+  jobject object;
+} CallbackResult;
+
+typedef struct JniLocks {
+  MutexLock classLoadingLock;
+  MutexLock methodLoadingLock;
+  MutexLock fieldLoadingLock;
+} JniLocks;
+
+/// Represents the error when dart-jni layer has already spawned singleton VM.
+#define DART_JNI_SINGLETON_EXISTS (-99);
+
+/// Stores the global state of the JNI.
+typedef struct JniContext {
+  JavaVM* jvm;
+  jobject classLoader;
+  jmethodID loadClassMethod;
+  jobject currentActivity;
+  jobject appContext;
+  JniLocks locks;
+} JniContext;
+
+// jniEnv for this thread, used by inline functions in this header,
+// therefore declared as extern.
+extern thread_local JNIEnv* jniEnv;
+
+extern JniContext* jni;
+
+/// Types used by JNI API to distinguish between primitive types.
+enum JniType {
+  booleanType = 0,
+  byteType = 1,
+  shortType = 2,
+  charType = 3,
+  intType = 4,
+  longType = 5,
+  floatType = 6,
+  doubleType = 7,
+  objectType = 8,
+  voidType = 9,
+};
+
+/// Result type for use by JNI.
+///
+/// If [exception] is null, it means the result is valid.
+/// It's assumed that the caller knows the expected type in [result].
+typedef struct JniResult {
+  jvalue value;
+  jthrowable exception;
+} JniResult;
+
+/// Similar to [JniResult] but for class lookups.
+typedef struct JniClassLookupResult {
+  jclass value;
+  jthrowable exception;
+} JniClassLookupResult;
+
+/// Similar to [JniResult] but for method/field ID lookups.
+typedef struct JniPointerResult {
+  const void* value;
+  jthrowable exception;
+} JniPointerResult;
+
+/// JniExceptionDetails holds 2 jstring objects, one is the result of
+/// calling `toString` on exception object, other is stack trace;
+typedef struct JniExceptionDetails {
+  jstring message;
+  jstring stacktrace;
+} JniExceptionDetails;
+
+/// This struct contains functions which wrap method call / field access conveniently along with
+/// exception checking.
+///
+/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us
+/// to check for and clear the exception before returning to dart code, which requires these functions
+/// to return result types.
+typedef struct JniAccessorsStruct {
+  JniClassLookupResult (*getClass)(char* internalName);
+  JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature);
+  JniPointerResult (*getStaticFieldID)(jclass cls,
+                                       char* fieldName,
+                                       char* signature);
+  JniPointerResult (*getMethodID)(jclass cls,
+                                  char* methodName,
+                                  char* signature);
+  JniPointerResult (*getStaticMethodID)(jclass cls,
+                                        char* methodName,
+                                        char* signature);
+  JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args);
+  JniResult (*newPrimitiveArray)(jsize length, int type);
+  JniResult (*newObjectArray)(jsize length,
+                              jclass elementClass,
+                              jobject initialElement);
+  JniResult (*getArrayElement)(jarray array, int index, int type);
+  JniResult (*callMethod)(jobject obj,
+                          jmethodID methodID,
+                          int callType,
+                          jvalue* args);
+  JniResult (*callStaticMethod)(jclass cls,
+                                jmethodID methodID,
+                                int callType,
+                                jvalue* args);
+  JniResult (*getField)(jobject obj, jfieldID fieldID, int callType);
+  JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType);
+  JniExceptionDetails (*getExceptionDetails)(jthrowable exception);
+} JniAccessorsStruct;
+
+FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors();
+
+FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void);
+
+FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void);
+
+/// Spawn a JVM with given arguments.
+///
+/// Returns JNI_OK on success, and one of the documented JNI error codes on
+/// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple
+/// JVMs is made, even if the underlying API potentially supports multiple VMs.
+FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args);
+
+/// Load class through platform-specific mechanism.
+///
+/// Currently uses application classloader on android,
+/// and JNIEnv->FindClass on other platforms.
+FFI_PLUGIN_EXPORT jclass FindClass(const char* name);
+
+/// Returns Application classLoader (on Android),
+/// which can be used to load application and platform classes.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetClassLoader(void);
+
+/// Returns application context on Android.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetApplicationContext(void);
+
+/// Returns current activity of the app on Android.
+FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void);
+
+static inline void attach_thread() {
+  if (jniEnv == NULL) {
+    (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL);
+  }
+}
+
+/// Load class into [cls] using platform specific mechanism
+static inline void load_class_platform(jclass* cls, const char* name) {
+#ifdef __ANDROID__
+  jstring className = (*jniEnv)->NewStringUTF(jniEnv, name);
+  *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader,
+                                     jni->loadClassMethod, className);
+  (*jniEnv)->DeleteLocalRef(jniEnv, className);
+#else
+  *cls = (*jniEnv)->FindClass(jniEnv, name);
+#endif
+}
+
+static inline void load_class_local_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(cls, name);
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_class_global_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    jclass tmp = NULL;
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(&tmp, name);
+      if (!(*jniEnv)->ExceptionCheck(jniEnv)) {
+        *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp);
+        (*jniEnv)->DeleteLocalRef(jniEnv, tmp);
+      }
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_method(jclass cls,
+                               jmethodID* res,
+                               const char* name,
+                               const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_static_method(jclass cls,
+                                      jmethodID* res,
+                                      const char* name,
+                                      const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_field(jclass cls,
+                              jfieldID* res,
+                              const char* name,
+                              const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline void load_static_field(jclass cls,
+                                     jfieldID* res,
+                                     const char* name,
+                                     const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline jobject to_global_ref(jobject ref) {
+  jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref);
+  (*jniEnv)->DeleteLocalRef(jniEnv, ref);
+  return g;
+}
+
+// These functions are useful for C+Dart bindings, and not required for pure dart bindings.
+
+FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr();
+
+/// For use by jni_gen's generated code
+/// don't use these.
+
+// these 2 fn ptr vars will be defined by generated code library
+extern JniContext* (*context_getter)(void);
+extern JNIEnv* (*env_getter)(void);
+
+// this function will be exported by generated code library
+// it will set above 2 variables.
+FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void),
+                                     JNIEnv* (*eg)(void));
+
+static inline void load_env() {
+  if (jniEnv == NULL) {
+    jni = context_getter();
+    jniEnv = env_getter();
+  }
+}
+
+static inline jthrowable check_exception() {
+  jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv);
+  if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv);
+  if (exception == NULL) return NULL;
+  return to_global_ref(exception);
+}
+
+static inline JniResult to_global_ref_result(jobject ref) {
+  JniResult result;
+  result.exception = check_exception();
+  if (result.exception == NULL) {
+    result.value.l = to_global_ref(ref);
+  }
+  return result;
+}
+
+FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+
+FFI_PLUGIN_EXPORT
+JniResult DartException__ctor(jstring message);
+
+FFI_PLUGIN_EXPORT
+JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+                                 int64_t port,
+                                 int64_t functionPtr);
+
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
diff --git a/pkgs/jnigen/example/pdfbox_plugin/src/third_party/pdfbox_plugin.c b/pkgs/jnigen/example/pdfbox_plugin/src/third_party/pdfbox_plugin.c
new file mode 100644
index 0000000..6b11133
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/src/third_party/pdfbox_plugin.c
@@ -0,0 +1,2872 @@
+// Generated from Apache PDFBox library which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+#include <stdint.h>
+#include "dartjni.h"
+#include "jni.h"
+
+thread_local JNIEnv* jniEnv;
+JniContext* jni;
+
+JniContext* (*context_getter)(void);
+JNIEnv* (*env_getter)(void);
+
+void setJniGetters(JniContext* (*cg)(void), JNIEnv* (*eg)(void)) {
+  context_getter = cg;
+  env_getter = eg;
+}
+
+// org.apache.pdfbox.pdmodel.PDDocument
+jclass _c_PDDocument = NULL;
+
+jmethodID _m_PDDocument__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__new0() {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__new0, "<init>", "()V");
+  if (_m_PDDocument__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_PDDocument, _m_PDDocument__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__new1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__new1(jobject memUsageSetting) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__new1, "<init>",
+              "(Lorg/apache/pdfbox/io/MemoryUsageSetting;)V");
+  if (_m_PDDocument__new1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_PDDocument,
+                                         _m_PDDocument__new1, memUsageSetting);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__new2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__new2(jobject doc) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__new2, "<init>",
+              "(Lorg/apache/pdfbox/cos/COSDocument;)V");
+  if (_m_PDDocument__new2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_PDDocument, _m_PDDocument__new2, doc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__new3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__new3(jobject doc, jobject source) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__new3, "<init>",
+              "(Lorg/apache/pdfbox/cos/COSDocument;Lorg/apache/pdfbox/io/"
+              "RandomAccessRead;)V");
+  if (_m_PDDocument__new3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_PDDocument,
+                                         _m_PDDocument__new3, doc, source);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__new4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__new4(jobject doc, jobject source, jobject permission) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__new4, "<init>",
+              "(Lorg/apache/pdfbox/cos/COSDocument;Lorg/apache/pdfbox/io/"
+              "RandomAccessRead;Lorg/apache/pdfbox/pdmodel/encryption/"
+              "AccessPermission;)V");
+  if (_m_PDDocument__new4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(
+      jniEnv, _c_PDDocument, _m_PDDocument__new4, doc, source, permission);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__addPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__addPage(jobject self_, jobject page) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__addPage, "addPage",
+              "(Lorg/apache/pdfbox/pdmodel/PDPage;)V");
+  if (_m_PDDocument__addPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__addPage, page);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__addSignature = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__addSignature(jobject self_, jobject sigObject) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__addSignature, "addSignature",
+              "(Lorg/apache/pdfbox/pdmodel/interactive/digitalsignature/"
+              "PDSignature;)V");
+  if (_m_PDDocument__addSignature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__addSignature,
+                            sigObject);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__addSignature1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__addSignature1(jobject self_,
+                                    jobject sigObject,
+                                    jobject options) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__addSignature1, "addSignature",
+              "(Lorg/apache/pdfbox/pdmodel/interactive/digitalsignature/"
+              "PDSignature;Lorg/apache/pdfbox/pdmodel/interactive/"
+              "digitalsignature/SignatureOptions;)V");
+  if (_m_PDDocument__addSignature1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__addSignature1,
+                            sigObject, options);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__addSignature2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__addSignature2(jobject self_,
+                                    jobject sigObject,
+                                    jobject signatureInterface) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__addSignature2, "addSignature",
+              "(Lorg/apache/pdfbox/pdmodel/interactive/digitalsignature/"
+              "PDSignature;Lorg/apache/pdfbox/pdmodel/interactive/"
+              "digitalsignature/SignatureInterface;)V");
+  if (_m_PDDocument__addSignature2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__addSignature2,
+                            sigObject, signatureInterface);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__addSignature3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__addSignature3(jobject self_,
+                                    jobject sigObject,
+                                    jobject signatureInterface,
+                                    jobject options) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__addSignature3, "addSignature",
+              "(Lorg/apache/pdfbox/pdmodel/interactive/digitalsignature/"
+              "PDSignature;Lorg/apache/pdfbox/pdmodel/interactive/"
+              "digitalsignature/SignatureInterface;Lorg/apache/pdfbox/pdmodel/"
+              "interactive/digitalsignature/SignatureOptions;)V");
+  if (_m_PDDocument__addSignature3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__addSignature3,
+                            sigObject, signatureInterface, options);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__addSignatureField = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__addSignatureField(jobject self_,
+                                        jobject sigFields,
+                                        jobject signatureInterface,
+                                        jobject options) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__addSignatureField,
+              "addSignatureField",
+              "(Ljava/util/List;Lorg/apache/pdfbox/pdmodel/interactive/"
+              "digitalsignature/SignatureInterface;Lorg/apache/pdfbox/pdmodel/"
+              "interactive/digitalsignature/SignatureOptions;)V");
+  if (_m_PDDocument__addSignatureField == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__addSignatureField,
+                            sigFields, signatureInterface, options);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__removePage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__removePage(jobject self_, jobject page) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__removePage, "removePage",
+              "(Lorg/apache/pdfbox/pdmodel/PDPage;)V");
+  if (_m_PDDocument__removePage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__removePage, page);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__removePage1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__removePage1(jobject self_, int32_t pageNumber) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__removePage1, "removePage", "(I)V");
+  if (_m_PDDocument__removePage1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__removePage1,
+                            pageNumber);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__importPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__importPage(jobject self_, jobject page) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_PDDocument, &_m_PDDocument__importPage, "importPage",
+      "(Lorg/apache/pdfbox/pdmodel/PDPage;)Lorg/apache/pdfbox/pdmodel/PDPage;");
+  if (_m_PDDocument__importPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__importPage, page);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__getDocument = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getDocument(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getDocument, "getDocument",
+              "()Lorg/apache/pdfbox/cos/COSDocument;");
+  if (_m_PDDocument__getDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_PDDocument__getDocument);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__getDocumentInformation = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getDocumentInformation(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getDocumentInformation,
+              "getDocumentInformation",
+              "()Lorg/apache/pdfbox/pdmodel/PDDocumentInformation;");
+  if (_m_PDDocument__getDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__getDocumentInformation);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__setDocumentInformation = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__setDocumentInformation(jobject self_, jobject info) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__setDocumentInformation,
+              "setDocumentInformation",
+              "(Lorg/apache/pdfbox/pdmodel/PDDocumentInformation;)V");
+  if (_m_PDDocument__setDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDDocument__setDocumentInformation, info);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__getDocumentCatalog = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getDocumentCatalog(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getDocumentCatalog,
+              "getDocumentCatalog",
+              "()Lorg/apache/pdfbox/pdmodel/PDDocumentCatalog;");
+  if (_m_PDDocument__getDocumentCatalog == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__getDocumentCatalog);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__isEncrypted = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__isEncrypted(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__isEncrypted, "isEncrypted", "()Z");
+  if (_m_PDDocument__isEncrypted == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_PDDocument__isEncrypted);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__getEncryption = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getEncryption(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getEncryption, "getEncryption",
+              "()Lorg/apache/pdfbox/pdmodel/encryption/PDEncryption;");
+  if (_m_PDDocument__getEncryption == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_PDDocument__getEncryption);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__setEncryptionDictionary = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__setEncryptionDictionary(jobject self_,
+                                              jobject encryption) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__setEncryptionDictionary,
+              "setEncryptionDictionary",
+              "(Lorg/apache/pdfbox/pdmodel/encryption/PDEncryption;)V");
+  if (_m_PDDocument__setEncryptionDictionary == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDDocument__setEncryptionDictionary, encryption);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__getLastSignatureDictionary = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getLastSignatureDictionary(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_PDDocument, &_m_PDDocument__getLastSignatureDictionary,
+      "getLastSignatureDictionary",
+      "()Lorg/apache/pdfbox/pdmodel/interactive/digitalsignature/PDSignature;");
+  if (_m_PDDocument__getLastSignatureDictionary == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__getLastSignatureDictionary);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__getSignatureFields = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getSignatureFields(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getSignatureFields,
+              "getSignatureFields", "()Ljava/util/List;");
+  if (_m_PDDocument__getSignatureFields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__getSignatureFields);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__getSignatureDictionaries = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getSignatureDictionaries(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getSignatureDictionaries,
+              "getSignatureDictionaries", "()Ljava/util/List;");
+  if (_m_PDDocument__getSignatureDictionaries == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__getSignatureDictionaries);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__registerTrueTypeFontForClosing = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__registerTrueTypeFontForClosing(jobject self_,
+                                                     jobject ttf) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__registerTrueTypeFontForClosing,
+              "registerTrueTypeFontForClosing",
+              "(Lorg/apache/fontbox/ttf/TrueTypeFont;)V");
+  if (_m_PDDocument__registerTrueTypeFontForClosing == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDDocument__registerTrueTypeFontForClosing, ttf);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__load = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load(jobject file) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_PDDocument, &_m_PDDocument__load, "load",
+                     "(Ljava/io/File;)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load, file);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load1(jobject file, jobject memUsageSetting) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load1, "load",
+      "(Ljava/io/File;Lorg/apache/pdfbox/io/MemoryUsageSetting;)Lorg/apache/"
+      "pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load1, file, memUsageSetting);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load2(jobject file, jobject password) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_PDDocument, &_m_PDDocument__load2, "load",
+                     "(Ljava/io/File;Ljava/lang/String;)Lorg/apache/pdfbox/"
+                     "pdmodel/PDDocument;");
+  if (_m_PDDocument__load2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load2, file, password);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load3(jobject file,
+                            jobject password,
+                            jobject memUsageSetting) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load3, "load",
+      "(Ljava/io/File;Ljava/lang/String;Lorg/apache/pdfbox/io/"
+      "MemoryUsageSetting;)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load3, file, password,
+      memUsageSetting);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load4(jobject file,
+                            jobject password,
+                            jobject keyStore,
+                            jobject alias) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load4, "load",
+      "(Ljava/io/File;Ljava/lang/String;Ljava/io/InputStream;Ljava/lang/"
+      "String;)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load4, file, password, keyStore,
+      alias);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load5 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load5(jobject file,
+                            jobject password,
+                            jobject keyStore,
+                            jobject alias,
+                            jobject memUsageSetting) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load5, "load",
+      "(Ljava/io/File;Ljava/lang/String;Ljava/io/InputStream;Ljava/lang/"
+      "String;Lorg/apache/pdfbox/io/MemoryUsageSetting;)Lorg/apache/pdfbox/"
+      "pdmodel/PDDocument;");
+  if (_m_PDDocument__load5 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load5, file, password, keyStore,
+      alias, memUsageSetting);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load6 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load6(jobject input) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load6, "load",
+      "(Ljava/io/InputStream;)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load6 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load6, input);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load7 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load7(jobject input, jobject memUsageSetting) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load7, "load",
+      "(Ljava/io/InputStream;Lorg/apache/pdfbox/io/MemoryUsageSetting;)Lorg/"
+      "apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load7 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load7, input, memUsageSetting);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load8 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load8(jobject input, jobject password) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_PDDocument, &_m_PDDocument__load8, "load",
+                     "(Ljava/io/InputStream;Ljava/lang/String;)Lorg/apache/"
+                     "pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load8 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load8, input, password);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load9 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load9(jobject input,
+                            jobject password,
+                            jobject keyStore,
+                            jobject alias) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load9, "load",
+      "(Ljava/io/InputStream;Ljava/lang/String;Ljava/io/InputStream;Ljava/lang/"
+      "String;)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load9 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load9, input, password, keyStore,
+      alias);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load10 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load10(jobject input,
+                             jobject password,
+                             jobject memUsageSetting) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load10, "load",
+      "(Ljava/io/InputStream;Ljava/lang/String;Lorg/apache/pdfbox/io/"
+      "MemoryUsageSetting;)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load10 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load10, input, password,
+      memUsageSetting);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load11 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load11(jobject input,
+                             jobject password,
+                             jobject keyStore,
+                             jobject alias,
+                             jobject memUsageSetting) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load11, "load",
+      "(Ljava/io/InputStream;Ljava/lang/String;Ljava/io/InputStream;Ljava/lang/"
+      "String;Lorg/apache/pdfbox/io/MemoryUsageSetting;)Lorg/apache/pdfbox/"
+      "pdmodel/PDDocument;");
+  if (_m_PDDocument__load11 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load11, input, password, keyStore,
+      alias, memUsageSetting);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load12 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load12(jobject input) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_PDDocument, &_m_PDDocument__load12, "load",
+                     "([B)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load12 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load12, input);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load13 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load13(jobject input, jobject password) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load13, "load",
+      "([BLjava/lang/String;)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load13 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load13, input, password);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load14 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load14(jobject input,
+                             jobject password,
+                             jobject keyStore,
+                             jobject alias) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_PDDocument, &_m_PDDocument__load14, "load",
+                     "([BLjava/lang/String;Ljava/io/InputStream;Ljava/lang/"
+                     "String;)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load14 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load14, input, password, keyStore,
+      alias);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__load15 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__load15(jobject input,
+                             jobject password,
+                             jobject keyStore,
+                             jobject alias,
+                             jobject memUsageSetting) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDDocument, &_m_PDDocument__load15, "load",
+      "([BLjava/lang/String;Ljava/io/InputStream;Ljava/lang/String;Lorg/apache/"
+      "pdfbox/io/MemoryUsageSetting;)Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  if (_m_PDDocument__load15 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDDocument, _m_PDDocument__load15, input, password, keyStore,
+      alias, memUsageSetting);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__save = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__save(jobject self_, jobject fileName) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__save, "save",
+              "(Ljava/lang/String;)V");
+  if (_m_PDDocument__save == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__save, fileName);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__save1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__save1(jobject self_, jobject file) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__save1, "save",
+              "(Ljava/io/File;)V");
+  if (_m_PDDocument__save1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__save1, file);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__save2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__save2(jobject self_, jobject output) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__save2, "save",
+              "(Ljava/io/OutputStream;)V");
+  if (_m_PDDocument__save2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__save2, output);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__saveIncremental = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__saveIncremental(jobject self_, jobject output) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__saveIncremental, "saveIncremental",
+              "(Ljava/io/OutputStream;)V");
+  if (_m_PDDocument__saveIncremental == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__saveIncremental,
+                            output);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__saveIncremental1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__saveIncremental1(jobject self_,
+                                       jobject output,
+                                       jobject objectsToWrite) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__saveIncremental1,
+              "saveIncremental", "(Ljava/io/OutputStream;Ljava/util/Set;)V");
+  if (_m_PDDocument__saveIncremental1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__saveIncremental1,
+                            output, objectsToWrite);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__saveIncrementalForExternalSigning = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__saveIncrementalForExternalSigning(jobject self_,
+                                                        jobject output) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__saveIncrementalForExternalSigning,
+              "saveIncrementalForExternalSigning",
+              "(Ljava/io/OutputStream;)Lorg/apache/pdfbox/pdmodel/interactive/"
+              "digitalsignature/ExternalSigningSupport;");
+  if (_m_PDDocument__saveIncrementalForExternalSigning == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__saveIncrementalForExternalSigning, output);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__getPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getPage(jobject self_, int32_t pageIndex) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getPage, "getPage",
+              "(I)Lorg/apache/pdfbox/pdmodel/PDPage;");
+  if (_m_PDDocument__getPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__getPage, pageIndex);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__getPages = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getPages(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getPages, "getPages",
+              "()Lorg/apache/pdfbox/pdmodel/PDPageTree;");
+  if (_m_PDDocument__getPages == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_PDDocument__getPages);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__getNumberOfPages = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getNumberOfPages(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getNumberOfPages,
+              "getNumberOfPages", "()I");
+  if (_m_PDDocument__getNumberOfPages == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_PDDocument__getNumberOfPages);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__close = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__close(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__close, "close", "()V");
+  if (_m_PDDocument__close == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__close);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__protect = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__protect(jobject self_, jobject policy) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__protect, "protect",
+              "(Lorg/apache/pdfbox/pdmodel/encryption/ProtectionPolicy;)V");
+  if (_m_PDDocument__protect == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__protect, policy);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__getCurrentAccessPermission = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getCurrentAccessPermission(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getCurrentAccessPermission,
+              "getCurrentAccessPermission",
+              "()Lorg/apache/pdfbox/pdmodel/encryption/AccessPermission;");
+  if (_m_PDDocument__getCurrentAccessPermission == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__getCurrentAccessPermission);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__isAllSecurityToBeRemoved = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__isAllSecurityToBeRemoved(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__isAllSecurityToBeRemoved,
+              "isAllSecurityToBeRemoved", "()Z");
+  if (_m_PDDocument__isAllSecurityToBeRemoved == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_PDDocument__isAllSecurityToBeRemoved);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__setAllSecurityToBeRemoved = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__setAllSecurityToBeRemoved(jobject self_,
+                                                uint8_t removeAllSecurity) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__setAllSecurityToBeRemoved,
+              "setAllSecurityToBeRemoved", "(Z)V");
+  if (_m_PDDocument__setAllSecurityToBeRemoved == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDDocument__setAllSecurityToBeRemoved,
+                            removeAllSecurity);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__getDocumentId = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getDocumentId(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getDocumentId, "getDocumentId",
+              "()Ljava/lang/Long;");
+  if (_m_PDDocument__getDocumentId == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_PDDocument__getDocumentId);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__setDocumentId = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__setDocumentId(jobject self_, jobject docId) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__setDocumentId, "setDocumentId",
+              "(Ljava/lang/Long;)V");
+  if (_m_PDDocument__setDocumentId == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__setDocumentId, docId);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__getVersion = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getVersion(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getVersion, "getVersion", "()F");
+  if (_m_PDDocument__getVersion == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  float _result =
+      (*jniEnv)->CallFloatMethod(jniEnv, self_, _m_PDDocument__getVersion);
+  return (JniResult){.value = {.f = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__setVersion = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__setVersion(jobject self_, float newVersion) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__setVersion, "setVersion", "(F)V");
+  if (_m_PDDocument__setVersion == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__setVersion,
+                            newVersion);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocument__getResourceCache = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__getResourceCache(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__getResourceCache,
+              "getResourceCache",
+              "()Lorg/apache/pdfbox/pdmodel/ResourceCache;");
+  if (_m_PDDocument__getResourceCache == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocument__getResourceCache);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocument__setResourceCache = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocument__setResourceCache(jobject self_, jobject resourceCache) {
+  load_env();
+  load_class_global_ref(&_c_PDDocument, "org/apache/pdfbox/pdmodel/PDDocument");
+  if (_c_PDDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocument, &_m_PDDocument__setResourceCache,
+              "setResourceCache",
+              "(Lorg/apache/pdfbox/pdmodel/ResourceCache;)V");
+  if (_m_PDDocument__setResourceCache == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocument__setResourceCache,
+                            resourceCache);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// org.apache.pdfbox.pdmodel.PDDocumentInformation
+jclass _c_PDDocumentInformation = NULL;
+
+jmethodID _m_PDDocumentInformation__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__new0() {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__new0,
+              "<init>", "()V");
+  if (_m_PDDocumentInformation__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_PDDocumentInformation,
+                                         _m_PDDocumentInformation__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__new1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__new1(jobject dic) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__new1,
+              "<init>", "(Lorg/apache/pdfbox/cos/COSDictionary;)V");
+  if (_m_PDDocumentInformation__new1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_PDDocumentInformation,
+                                         _m_PDDocumentInformation__new1, dic);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__getCOSObject = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getCOSObject(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__getCOSObject,
+              "getCOSObject", "()Lorg/apache/pdfbox/cos/COSDictionary;");
+  if (_m_PDDocumentInformation__getCOSObject == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getCOSObject);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__getPropertyStringValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getPropertyStringValue(jobject self_,
+                                                        jobject propertyKey) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation,
+              &_m_PDDocumentInformation__getPropertyStringValue,
+              "getPropertyStringValue",
+              "(Ljava/lang/String;)Ljava/lang/Object;");
+  if (_m_PDDocumentInformation__getPropertyStringValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getPropertyStringValue,
+      propertyKey);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__getTitle = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getTitle(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__getTitle,
+              "getTitle", "()Ljava/lang/String;");
+  if (_m_PDDocumentInformation__getTitle == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getTitle);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__setTitle = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setTitle(jobject self_, jobject title) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__setTitle,
+              "setTitle", "(Ljava/lang/String;)V");
+  if (_m_PDDocumentInformation__setTitle == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocumentInformation__setTitle,
+                            title);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocumentInformation__getAuthor = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getAuthor(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__getAuthor,
+              "getAuthor", "()Ljava/lang/String;");
+  if (_m_PDDocumentInformation__getAuthor == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getAuthor);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__setAuthor = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setAuthor(jobject self_, jobject author) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__setAuthor,
+              "setAuthor", "(Ljava/lang/String;)V");
+  if (_m_PDDocumentInformation__setAuthor == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocumentInformation__setAuthor,
+                            author);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocumentInformation__getSubject = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getSubject(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__getSubject,
+              "getSubject", "()Ljava/lang/String;");
+  if (_m_PDDocumentInformation__getSubject == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getSubject);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__setSubject = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setSubject(jobject self_, jobject subject) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__setSubject,
+              "setSubject", "(Ljava/lang/String;)V");
+  if (_m_PDDocumentInformation__setSubject == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocumentInformation__setSubject,
+                            subject);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocumentInformation__getKeywords = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getKeywords(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__getKeywords,
+              "getKeywords", "()Ljava/lang/String;");
+  if (_m_PDDocumentInformation__getKeywords == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getKeywords);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__setKeywords = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setKeywords(jobject self_, jobject keywords) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__setKeywords,
+              "setKeywords", "(Ljava/lang/String;)V");
+  if (_m_PDDocumentInformation__setKeywords == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDDocumentInformation__setKeywords, keywords);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocumentInformation__getCreator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getCreator(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__getCreator,
+              "getCreator", "()Ljava/lang/String;");
+  if (_m_PDDocumentInformation__getCreator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getCreator);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__setCreator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setCreator(jobject self_, jobject creator) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__setCreator,
+              "setCreator", "(Ljava/lang/String;)V");
+  if (_m_PDDocumentInformation__setCreator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocumentInformation__setCreator,
+                            creator);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocumentInformation__getProducer = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getProducer(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__getProducer,
+              "getProducer", "()Ljava/lang/String;");
+  if (_m_PDDocumentInformation__getProducer == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getProducer);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__setProducer = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setProducer(jobject self_, jobject producer) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__setProducer,
+              "setProducer", "(Ljava/lang/String;)V");
+  if (_m_PDDocumentInformation__setProducer == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDDocumentInformation__setProducer, producer);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocumentInformation__getCreationDate = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getCreationDate(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation,
+              &_m_PDDocumentInformation__getCreationDate, "getCreationDate",
+              "()Ljava/util/Calendar;");
+  if (_m_PDDocumentInformation__getCreationDate == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getCreationDate);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__setCreationDate = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setCreationDate(jobject self_, jobject date) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation,
+              &_m_PDDocumentInformation__setCreationDate, "setCreationDate",
+              "(Ljava/util/Calendar;)V");
+  if (_m_PDDocumentInformation__setCreationDate == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDDocumentInformation__setCreationDate, date);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocumentInformation__getModificationDate = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getModificationDate(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation,
+              &_m_PDDocumentInformation__getModificationDate,
+              "getModificationDate", "()Ljava/util/Calendar;");
+  if (_m_PDDocumentInformation__getModificationDate == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getModificationDate);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__setModificationDate = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setModificationDate(jobject self_,
+                                                     jobject date) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation,
+              &_m_PDDocumentInformation__setModificationDate,
+              "setModificationDate", "(Ljava/util/Calendar;)V");
+  if (_m_PDDocumentInformation__setModificationDate == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(
+      jniEnv, self_, _m_PDDocumentInformation__setModificationDate, date);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocumentInformation__getTrapped = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getTrapped(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__getTrapped,
+              "getTrapped", "()Ljava/lang/String;");
+  if (_m_PDDocumentInformation__getTrapped == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getTrapped);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__getMetadataKeys = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getMetadataKeys(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation,
+              &_m_PDDocumentInformation__getMetadataKeys, "getMetadataKeys",
+              "()Ljava/util/Set;");
+  if (_m_PDDocumentInformation__getMetadataKeys == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getMetadataKeys);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__getCustomMetadataValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__getCustomMetadataValue(jobject self_,
+                                                        jobject fieldName) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation,
+              &_m_PDDocumentInformation__getCustomMetadataValue,
+              "getCustomMetadataValue",
+              "(Ljava/lang/String;)Ljava/lang/String;");
+  if (_m_PDDocumentInformation__getCustomMetadataValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDDocumentInformation__getCustomMetadataValue,
+      fieldName);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDDocumentInformation__setCustomMetadataValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setCustomMetadataValue(jobject self_,
+                                                        jobject fieldName,
+                                                        jobject fieldValue) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation,
+              &_m_PDDocumentInformation__setCustomMetadataValue,
+              "setCustomMetadataValue",
+              "(Ljava/lang/String;Ljava/lang/String;)V");
+  if (_m_PDDocumentInformation__setCustomMetadataValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDDocumentInformation__setCustomMetadataValue,
+                            fieldName, fieldValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDDocumentInformation__setTrapped = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDDocumentInformation__setTrapped(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_PDDocumentInformation,
+                        "org/apache/pdfbox/pdmodel/PDDocumentInformation");
+  if (_c_PDDocumentInformation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDDocumentInformation, &_m_PDDocumentInformation__setTrapped,
+              "setTrapped", "(Ljava/lang/String;)V");
+  if (_m_PDDocumentInformation__setTrapped == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDDocumentInformation__setTrapped,
+                            value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// org.apache.pdfbox.text.PDFTextStripper
+jclass _c_PDFTextStripper = NULL;
+
+jmethodID _m_PDFTextStripper__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__new0() {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__new0, "<init>", "()V");
+  if (_m_PDFTextStripper__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_PDFTextStripper,
+                                         _m_PDFTextStripper__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__getText = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getText(jobject self_, jobject doc) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getText, "getText",
+              "(Lorg/apache/pdfbox/pdmodel/PDDocument;)Ljava/lang/String;");
+  if (_m_PDFTextStripper__getText == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getText, doc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__writeText = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writeText(jobject self_,
+                                     jobject doc,
+                                     jobject outputStream) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writeText, "writeText",
+              "(Lorg/apache/pdfbox/pdmodel/PDDocument;Ljava/io/Writer;)V");
+  if (_m_PDFTextStripper__writeText == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__writeText, doc,
+                            outputStream);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__processPages = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__processPages(jobject self_, jobject pages) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__processPages,
+              "processPages", "(Lorg/apache/pdfbox/pdmodel/PDPageTree;)V");
+  if (_m_PDFTextStripper__processPages == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__processPages,
+                            pages);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__startDocument = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__startDocument(jobject self_, jobject document) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__startDocument,
+              "startDocument", "(Lorg/apache/pdfbox/pdmodel/PDDocument;)V");
+  if (_m_PDFTextStripper__startDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__startDocument,
+                            document);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__endDocument = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__endDocument(jobject self_, jobject document) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__endDocument,
+              "endDocument", "(Lorg/apache/pdfbox/pdmodel/PDDocument;)V");
+  if (_m_PDFTextStripper__endDocument == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__endDocument,
+                            document);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__processPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__processPage(jobject self_, jobject page) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__processPage,
+              "processPage", "(Lorg/apache/pdfbox/pdmodel/PDPage;)V");
+  if (_m_PDFTextStripper__processPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__processPage,
+                            page);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__startArticle = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__startArticle(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__startArticle,
+              "startArticle", "()V");
+  if (_m_PDFTextStripper__startArticle == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__startArticle);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__startArticle1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__startArticle1(jobject self_, uint8_t isLTR) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__startArticle1,
+              "startArticle", "(Z)V");
+  if (_m_PDFTextStripper__startArticle1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__startArticle1,
+                            isLTR);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__endArticle = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__endArticle(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__endArticle, "endArticle",
+              "()V");
+  if (_m_PDFTextStripper__endArticle == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__endArticle);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__startPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__startPage(jobject self_, jobject page) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__startPage, "startPage",
+              "(Lorg/apache/pdfbox/pdmodel/PDPage;)V");
+  if (_m_PDFTextStripper__startPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__startPage, page);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__endPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__endPage(jobject self_, jobject page) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__endPage, "endPage",
+              "(Lorg/apache/pdfbox/pdmodel/PDPage;)V");
+  if (_m_PDFTextStripper__endPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__endPage, page);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writePage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writePage(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writePage, "writePage",
+              "()V");
+  if (_m_PDFTextStripper__writePage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__writePage);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writeLineSeparator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writeLineSeparator(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writeLineSeparator,
+              "writeLineSeparator", "()V");
+  if (_m_PDFTextStripper__writeLineSeparator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__writeLineSeparator);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writeWordSeparator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writeWordSeparator(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writeWordSeparator,
+              "writeWordSeparator", "()V");
+  if (_m_PDFTextStripper__writeWordSeparator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__writeWordSeparator);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writeCharacters = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writeCharacters(jobject self_, jobject text) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writeCharacters,
+              "writeCharacters", "(Lorg/apache/pdfbox/text/TextPosition;)V");
+  if (_m_PDFTextStripper__writeCharacters == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__writeCharacters,
+                            text);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writeString = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writeString(jobject self_,
+                                       jobject text,
+                                       jobject textPositions) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writeString,
+              "writeString", "(Ljava/lang/String;Ljava/util/List;)V");
+  if (_m_PDFTextStripper__writeString == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__writeString,
+                            text, textPositions);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writeString1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writeString1(jobject self_, jobject text) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writeString1,
+              "writeString", "(Ljava/lang/String;)V");
+  if (_m_PDFTextStripper__writeString1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__writeString1,
+                            text);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__processTextPosition = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__processTextPosition(jobject self_, jobject text) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__processTextPosition,
+              "processTextPosition",
+              "(Lorg/apache/pdfbox/text/TextPosition;)V");
+  if (_m_PDFTextStripper__processTextPosition == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__processTextPosition, text);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getStartPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getStartPage(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getStartPage,
+              "getStartPage", "()I");
+  if (_m_PDFTextStripper__getStartPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_PDFTextStripper__getStartPage);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setStartPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setStartPage(jobject self_, int32_t startPageValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setStartPage,
+              "setStartPage", "(I)V");
+  if (_m_PDFTextStripper__setStartPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setStartPage,
+                            startPageValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getEndPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getEndPage(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getEndPage, "getEndPage",
+              "()I");
+  if (_m_PDFTextStripper__getEndPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_PDFTextStripper__getEndPage);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setEndPage = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setEndPage(jobject self_, int32_t endPageValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setEndPage, "setEndPage",
+              "(I)V");
+  if (_m_PDFTextStripper__setEndPage == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setEndPage,
+                            endPageValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setLineSeparator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setLineSeparator(jobject self_, jobject separator) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setLineSeparator,
+              "setLineSeparator", "(Ljava/lang/String;)V");
+  if (_m_PDFTextStripper__setLineSeparator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setLineSeparator,
+                            separator);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getLineSeparator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getLineSeparator(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getLineSeparator,
+              "getLineSeparator", "()Ljava/lang/String;");
+  if (_m_PDFTextStripper__getLineSeparator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getLineSeparator);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__getWordSeparator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getWordSeparator(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getWordSeparator,
+              "getWordSeparator", "()Ljava/lang/String;");
+  if (_m_PDFTextStripper__getWordSeparator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getWordSeparator);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setWordSeparator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setWordSeparator(jobject self_, jobject separator) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setWordSeparator,
+              "setWordSeparator", "(Ljava/lang/String;)V");
+  if (_m_PDFTextStripper__setWordSeparator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setWordSeparator,
+                            separator);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getSuppressDuplicateOverlappingText = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getSuppressDuplicateOverlappingText(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper,
+              &_m_PDFTextStripper__getSuppressDuplicateOverlappingText,
+              "getSuppressDuplicateOverlappingText", "()Z");
+  if (_m_PDFTextStripper__getSuppressDuplicateOverlappingText == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_PDFTextStripper__getSuppressDuplicateOverlappingText);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getCurrentPageNo = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getCurrentPageNo(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getCurrentPageNo,
+              "getCurrentPageNo", "()I");
+  if (_m_PDFTextStripper__getCurrentPageNo == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_PDFTextStripper__getCurrentPageNo);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getOutput = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getOutput(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getOutput, "getOutput",
+              "()Ljava/io/Writer;");
+  if (_m_PDFTextStripper__getOutput == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_PDFTextStripper__getOutput);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__getCharactersByArticle = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getCharactersByArticle(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getCharactersByArticle,
+              "getCharactersByArticle", "()Ljava/util/List;");
+  if (_m_PDFTextStripper__getCharactersByArticle == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getCharactersByArticle);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setSuppressDuplicateOverlappingText = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setSuppressDuplicateOverlappingText(
+    jobject self_,
+    uint8_t suppressDuplicateOverlappingTextValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper,
+              &_m_PDFTextStripper__setSuppressDuplicateOverlappingText,
+              "setSuppressDuplicateOverlappingText", "(Z)V");
+  if (_m_PDFTextStripper__setSuppressDuplicateOverlappingText == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(
+      jniEnv, self_, _m_PDFTextStripper__setSuppressDuplicateOverlappingText,
+      suppressDuplicateOverlappingTextValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getSeparateByBeads = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getSeparateByBeads(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getSeparateByBeads,
+              "getSeparateByBeads", "()Z");
+  if (_m_PDFTextStripper__getSeparateByBeads == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_PDFTextStripper__getSeparateByBeads);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setShouldSeparateByBeads = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setShouldSeparateByBeads(
+    jobject self_,
+    uint8_t aShouldSeparateByBeads) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setShouldSeparateByBeads,
+              "setShouldSeparateByBeads", "(Z)V");
+  if (_m_PDFTextStripper__setShouldSeparateByBeads == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__setShouldSeparateByBeads,
+                            aShouldSeparateByBeads);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getEndBookmark = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getEndBookmark(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getEndBookmark,
+              "getEndBookmark",
+              "()Lorg/apache/pdfbox/pdmodel/interactive/documentnavigation/"
+              "outline/PDOutlineItem;");
+  if (_m_PDFTextStripper__getEndBookmark == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getEndBookmark);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setEndBookmark = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setEndBookmark(jobject self_, jobject aEndBookmark) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setEndBookmark,
+              "setEndBookmark",
+              "(Lorg/apache/pdfbox/pdmodel/interactive/documentnavigation/"
+              "outline/PDOutlineItem;)V");
+  if (_m_PDFTextStripper__setEndBookmark == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setEndBookmark,
+                            aEndBookmark);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getStartBookmark = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getStartBookmark(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getStartBookmark,
+              "getStartBookmark",
+              "()Lorg/apache/pdfbox/pdmodel/interactive/documentnavigation/"
+              "outline/PDOutlineItem;");
+  if (_m_PDFTextStripper__getStartBookmark == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getStartBookmark);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setStartBookmark = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setStartBookmark(jobject self_,
+                                            jobject aStartBookmark) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setStartBookmark,
+              "setStartBookmark",
+              "(Lorg/apache/pdfbox/pdmodel/interactive/documentnavigation/"
+              "outline/PDOutlineItem;)V");
+  if (_m_PDFTextStripper__setStartBookmark == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setStartBookmark,
+                            aStartBookmark);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getAddMoreFormatting = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getAddMoreFormatting(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getAddMoreFormatting,
+              "getAddMoreFormatting", "()Z");
+  if (_m_PDFTextStripper__getAddMoreFormatting == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_PDFTextStripper__getAddMoreFormatting);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setAddMoreFormatting = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setAddMoreFormatting(jobject self_,
+                                                uint8_t newAddMoreFormatting) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setAddMoreFormatting,
+              "setAddMoreFormatting", "(Z)V");
+  if (_m_PDFTextStripper__setAddMoreFormatting == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__setAddMoreFormatting,
+                            newAddMoreFormatting);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getSortByPosition = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getSortByPosition(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getSortByPosition,
+              "getSortByPosition", "()Z");
+  if (_m_PDFTextStripper__getSortByPosition == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_PDFTextStripper__getSortByPosition);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setSortByPosition = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setSortByPosition(jobject self_,
+                                             uint8_t newSortByPosition) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setSortByPosition,
+              "setSortByPosition", "(Z)V");
+  if (_m_PDFTextStripper__setSortByPosition == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(
+      jniEnv, self_, _m_PDFTextStripper__setSortByPosition, newSortByPosition);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getSpacingTolerance = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getSpacingTolerance(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getSpacingTolerance,
+              "getSpacingTolerance", "()F");
+  if (_m_PDFTextStripper__getSpacingTolerance == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  float _result = (*jniEnv)->CallFloatMethod(
+      jniEnv, self_, _m_PDFTextStripper__getSpacingTolerance);
+  return (JniResult){.value = {.f = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setSpacingTolerance = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setSpacingTolerance(jobject self_,
+                                               float spacingToleranceValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setSpacingTolerance,
+              "setSpacingTolerance", "(F)V");
+  if (_m_PDFTextStripper__setSpacingTolerance == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__setSpacingTolerance,
+                            spacingToleranceValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getAverageCharTolerance = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getAverageCharTolerance(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getAverageCharTolerance,
+              "getAverageCharTolerance", "()F");
+  if (_m_PDFTextStripper__getAverageCharTolerance == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  float _result = (*jniEnv)->CallFloatMethod(
+      jniEnv, self_, _m_PDFTextStripper__getAverageCharTolerance);
+  return (JniResult){.value = {.f = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setAverageCharTolerance = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setAverageCharTolerance(
+    jobject self_,
+    float averageCharToleranceValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setAverageCharTolerance,
+              "setAverageCharTolerance", "(F)V");
+  if (_m_PDFTextStripper__setAverageCharTolerance == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__setAverageCharTolerance,
+                            averageCharToleranceValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getIndentThreshold = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getIndentThreshold(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getIndentThreshold,
+              "getIndentThreshold", "()F");
+  if (_m_PDFTextStripper__getIndentThreshold == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  float _result = (*jniEnv)->CallFloatMethod(
+      jniEnv, self_, _m_PDFTextStripper__getIndentThreshold);
+  return (JniResult){.value = {.f = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setIndentThreshold = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setIndentThreshold(jobject self_,
+                                              float indentThresholdValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setIndentThreshold,
+              "setIndentThreshold", "(F)V");
+  if (_m_PDFTextStripper__setIndentThreshold == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__setIndentThreshold,
+                            indentThresholdValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getDropThreshold = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getDropThreshold(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getDropThreshold,
+              "getDropThreshold", "()F");
+  if (_m_PDFTextStripper__getDropThreshold == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  float _result = (*jniEnv)->CallFloatMethod(
+      jniEnv, self_, _m_PDFTextStripper__getDropThreshold);
+  return (JniResult){.value = {.f = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setDropThreshold = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setDropThreshold(jobject self_,
+                                            float dropThresholdValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setDropThreshold,
+              "setDropThreshold", "(F)V");
+  if (_m_PDFTextStripper__setDropThreshold == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setDropThreshold,
+                            dropThresholdValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getParagraphStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getParagraphStart(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getParagraphStart,
+              "getParagraphStart", "()Ljava/lang/String;");
+  if (_m_PDFTextStripper__getParagraphStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getParagraphStart);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setParagraphStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setParagraphStart(jobject self_, jobject s) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setParagraphStart,
+              "setParagraphStart", "(Ljava/lang/String;)V");
+  if (_m_PDFTextStripper__setParagraphStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__setParagraphStart, s);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getParagraphEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getParagraphEnd(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getParagraphEnd,
+              "getParagraphEnd", "()Ljava/lang/String;");
+  if (_m_PDFTextStripper__getParagraphEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getParagraphEnd);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setParagraphEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setParagraphEnd(jobject self_, jobject s) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setParagraphEnd,
+              "setParagraphEnd", "(Ljava/lang/String;)V");
+  if (_m_PDFTextStripper__setParagraphEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setParagraphEnd,
+                            s);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getPageStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getPageStart(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getPageStart,
+              "getPageStart", "()Ljava/lang/String;");
+  if (_m_PDFTextStripper__getPageStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getPageStart);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setPageStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setPageStart(jobject self_, jobject pageStartValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setPageStart,
+              "setPageStart", "(Ljava/lang/String;)V");
+  if (_m_PDFTextStripper__setPageStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setPageStart,
+                            pageStartValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getPageEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getPageEnd(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getPageEnd, "getPageEnd",
+              "()Ljava/lang/String;");
+  if (_m_PDFTextStripper__getPageEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+                                                _m_PDFTextStripper__getPageEnd);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setPageEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setPageEnd(jobject self_, jobject pageEndValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setPageEnd, "setPageEnd",
+              "(Ljava/lang/String;)V");
+  if (_m_PDFTextStripper__setPageEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setPageEnd,
+                            pageEndValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getArticleStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getArticleStart(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getArticleStart,
+              "getArticleStart", "()Ljava/lang/String;");
+  if (_m_PDFTextStripper__getArticleStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getArticleStart);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setArticleStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setArticleStart(jobject self_,
+                                           jobject articleStartValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setArticleStart,
+              "setArticleStart", "(Ljava/lang/String;)V");
+  if (_m_PDFTextStripper__setArticleStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setArticleStart,
+                            articleStartValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getArticleEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getArticleEnd(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getArticleEnd,
+              "getArticleEnd", "()Ljava/lang/String;");
+  if (_m_PDFTextStripper__getArticleEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getArticleEnd);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__setArticleEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setArticleEnd(jobject self_,
+                                         jobject articleEndValue) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setArticleEnd,
+              "setArticleEnd", "(Ljava/lang/String;)V");
+  if (_m_PDFTextStripper__setArticleEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__setArticleEnd,
+                            articleEndValue);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writeParagraphSeparator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writeParagraphSeparator(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writeParagraphSeparator,
+              "writeParagraphSeparator", "()V");
+  if (_m_PDFTextStripper__writeParagraphSeparator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__writeParagraphSeparator);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writeParagraphStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writeParagraphStart(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writeParagraphStart,
+              "writeParagraphStart", "()V");
+  if (_m_PDFTextStripper__writeParagraphStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__writeParagraphStart);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writeParagraphEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writeParagraphEnd(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writeParagraphEnd,
+              "writeParagraphEnd", "()V");
+  if (_m_PDFTextStripper__writeParagraphEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__writeParagraphEnd);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writePageStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writePageStart(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writePageStart,
+              "writePageStart", "()V");
+  if (_m_PDFTextStripper__writePageStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__writePageStart);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__writePageEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__writePageEnd(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__writePageEnd,
+              "writePageEnd", "()V");
+  if (_m_PDFTextStripper__writePageEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_PDFTextStripper__writePageEnd);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__setListItemPatterns = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__setListItemPatterns(jobject self_,
+                                               jobject patterns) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__setListItemPatterns,
+              "setListItemPatterns", "(Ljava/util/List;)V");
+  if (_m_PDFTextStripper__setListItemPatterns == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_PDFTextStripper__setListItemPatterns, patterns);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_PDFTextStripper__getListItemPatterns = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__getListItemPatterns(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_PDFTextStripper, &_m_PDFTextStripper__getListItemPatterns,
+              "getListItemPatterns", "()Ljava/util/List;");
+  if (_m_PDFTextStripper__getListItemPatterns == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_PDFTextStripper__getListItemPatterns);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_PDFTextStripper__matchPattern = NULL;
+FFI_PLUGIN_EXPORT
+JniResult PDFTextStripper__matchPattern(jobject string, jobject patterns) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_PDFTextStripper, &_m_PDFTextStripper__matchPattern, "matchPattern",
+      "(Ljava/lang/String;Ljava/util/List;)Ljava/util/regex/Pattern;");
+  if (_m_PDFTextStripper__matchPattern == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_PDFTextStripper, _m_PDFTextStripper__matchPattern, string,
+      patterns);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_PDFTextStripper__LINE_SEPARATOR = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_PDFTextStripper__LINE_SEPARATOR(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_PDFTextStripper, &_f_PDFTextStripper__LINE_SEPARATOR,
+             "LINE_SEPARATOR", "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetObjectField(
+      jniEnv, self_, _f_PDFTextStripper__LINE_SEPARATOR);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_PDFTextStripper__charactersByArticle = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_PDFTextStripper__charactersByArticle(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_PDFTextStripper, &_f_PDFTextStripper__charactersByArticle,
+             "charactersByArticle", "Ljava/util/ArrayList;");
+  jobject _result = (*jniEnv)->GetObjectField(
+      jniEnv, self_, _f_PDFTextStripper__charactersByArticle);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_PDFTextStripper__charactersByArticle(jobject self_,
+                                                   jobject value) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_PDFTextStripper, &_f_PDFTextStripper__charactersByArticle,
+             "charactersByArticle", "Ljava/util/ArrayList;");
+  (*jniEnv)->SetObjectField(jniEnv, self_,
+                            _f_PDFTextStripper__charactersByArticle, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_PDFTextStripper__document = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_PDFTextStripper__document(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_PDFTextStripper, &_f_PDFTextStripper__document, "document",
+             "Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  jobject _result =
+      (*jniEnv)->GetObjectField(jniEnv, self_, _f_PDFTextStripper__document);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_PDFTextStripper__document(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_PDFTextStripper, &_f_PDFTextStripper__document, "document",
+             "Lorg/apache/pdfbox/pdmodel/PDDocument;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_PDFTextStripper__document, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_PDFTextStripper__output = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_PDFTextStripper__output(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_PDFTextStripper, &_f_PDFTextStripper__output, "output",
+             "Ljava/io/Writer;");
+  jobject _result =
+      (*jniEnv)->GetObjectField(jniEnv, self_, _f_PDFTextStripper__output);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_PDFTextStripper__output(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_PDFTextStripper,
+                        "org/apache/pdfbox/text/PDFTextStripper");
+  if (_c_PDFTextStripper == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_PDFTextStripper, &_f_PDFTextStripper__output, "output",
+             "Ljava/io/Writer;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_PDFTextStripper__output, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
diff --git a/pkgs/jnigen/example/pdfbox_plugin/windows/.gitignore b/pkgs/jnigen/example/pdfbox_plugin/windows/.gitignore
new file mode 100644
index 0000000..b3eb2be
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/pkgs/jnigen/example/pdfbox_plugin/windows/CMakeLists.txt b/pkgs/jnigen/example/pdfbox_plugin/windows/CMakeLists.txt
new file mode 100644
index 0000000..3d9bd5c
--- /dev/null
+++ b/pkgs/jnigen/example/pdfbox_plugin/windows/CMakeLists.txt
@@ -0,0 +1,23 @@
+# The Flutter tooling requires that developers have a version of Visual Studio
+# installed that includes CMake 3.14 or later. You should not increase this
+# version, as doing so will cause the plugin to fail to compile for some
+# customers of the plugin.
+cmake_minimum_required(VERSION 3.14)
+
+# Project-level configuration.
+set(PROJECT_NAME "pdfbox_plugin")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+# Invoke the build for native code shared with the other target platforms.
+# This can be changed to accomodate different builds.
+add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared")
+
+# List of absolute paths to libraries that should be bundled with the plugin.
+# This list could contain prebuilt libraries, or libraries created by an
+# external build triggered from this build file.
+set(pdfbox_plugin_bundled_libraries
+  # Defined in ../src/CMakeLists.txt.
+  # This can be changed to accomodate different builds.
+  $<TARGET_FILE:pdfbox_plugin>
+  PARENT_SCOPE
+)
diff --git a/pkgs/jnigen/java/.gitignore b/pkgs/jnigen/java/.gitignore
new file mode 100644
index 0000000..5ccba17
--- /dev/null
+++ b/pkgs/jnigen/java/.gitignore
@@ -0,0 +1,12 @@
+target/*
+*.class
+*.jar
+
+.idea/compiler.xml
+.idea/dictionaries
+.idea/inspectionProfiles/
+.idea/jarRepositories.xml
+.idea/misc.xml
+.idea/runConfigurations.xml
+
+.classpath ## Used for experimenting with JShell REPL
diff --git a/pkgs/jnigen/java/README.md b/pkgs/jnigen/java/README.md
new file mode 100644
index 0000000..8b15049
--- /dev/null
+++ b/pkgs/jnigen/java/README.md
@@ -0,0 +1,51 @@
+## ApiSummarizer
+
+An early version of ApiSummarizer.
+
+It analyzes java source code / jars and outputs a JSON representation of the public API.
+
+It's currently used in `jnigen` to get the information of the Java API.
+
+## Build
+
+When using it via `jnigen`, the `jnigen:setup` script will take care of building the jar in appropriate location.
+
+To build the jar manually, run `mvn compile` in project root. To build the jar and run the tests as well, run `mvn test`. The jar will be created in `target/` directory.
+
+## Command line
+
+```
+usage: java -jar <JAR> [-s <SOURCE_DIR=.>] [-c <CLASSES_JAR>]
+<CLASS_OR_PACKAGE_NAMES>
+Class or package names should be fully qualified.
+
+-b,--backend <arg>        backend to use for summary generation ('doclet'
+or 'asm').
+-c,--classes <arg>        paths to search for compiled classes
+-D,--doctool-args <arg>   Arguments to pass to the documentation tool
+-M,--use-modules          use Java modules
+-m,--module-names <arg>   comma separated list of module names
+-r,--recursive            Include dependencies of classes
+-s,--sources <arg>        paths to search for source files
+-v,--verbose              Enable verbose output
+```
+
+Here class or package names are specified as fully qualified names, for example `org.apache.pdfbox.pdmodel.PDDocument` will load `org/apache/pdfbox/pdmodel/PDDocument.java`. It assumes the package naming reflects directory structure. If such mapping results in a directory, for example `android.os` is given and a directory `android/os` is found under the source path, it is considered as a package and all Java source files under that directory are loaded recursively.
+
+Note that some options are directly forwarded to the underlying tool.
+
+ApiSummarizer's current use is in `jnigen` for obtaining public API of java packages. Only the features strictly required for that purpose are focused upon.
+
+## Running tests
+
+Run `mvn surefire:test`
+
+There are not many tests at the moment. We plan to add some later.
+
+## ASM backend
+
+The main backend is based on javadoc API and generates summary based on java sources. A more experimental ASM backend also exists, and works somewhat okay-ish. It can summarize the compiled JARs. However, compiled jars without debug information do not include method parameter names. Some basic renaming is applied, i.e If type is `Object`, the parameter name will be output as `object` if an actual name is absent.
+
+## TODO
+
+See issue #23.
diff --git a/pkgs/jnigen/java/pom.xml b/pkgs/jnigen/java/pom.xml
new file mode 100644
index 0000000..226bb03
--- /dev/null
+++ b/pkgs/jnigen/java/pom.xml
@@ -0,0 +1,110 @@
+<?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>com.github.dart_lang.jnigen</groupId>
+    <artifactId>ApiSummarizer</artifactId>
+    <description>Summarize public APIs of java packages in JSON or protobuf.</description>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <jackson.version>2.14.0-rc2</jackson.version>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<buildDir>${user.dir}</buildDir>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-cli</groupId>
+            <artifactId>commons-cli</artifactId>
+            <version>1.5.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.ow2.asm</groupId>
+            <artifactId>asm-tree</artifactId>
+            <version>9.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-metadata-jvm</artifactId>
+            <version>0.6.2</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.13.2</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+		<directory>${buildDir}/target</directory>
+        <finalName>ApiSummarizer</finalName>
+        <pluginManagement><!-- Lock down plugins versions to avoid using Maven defaults -->
+            <plugins>
+                <plugin>
+                    <artifactId>maven-clean-plugin</artifactId>
+                    <version>3.2.0</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-resources-plugin</artifactId>
+                    <version>3.3.1</version>
+                </plugin>
+                <plugin>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <version>3.3.0</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>3.0.0-M7</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.5.0</version>
+                <configuration>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                    <appendAssemblyId>false</appendAssemblyId>
+                    <archive>
+                        <manifest>
+                            <mainClass>com.github.dart_lang.jnigen.apisummarizer.Main</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.10.1</version>
+                <configuration>
+                    <source>11</source>
+                    <target>11</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java
new file mode 100644
index 0000000..0077fd7
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer;
+
+import com.github.dart_lang.jnigen.apisummarizer.disasm.AsmSummarizer;
+import com.github.dart_lang.jnigen.apisummarizer.doclet.SummarizerDoclet;
+import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
+import com.github.dart_lang.jnigen.apisummarizer.util.ClassFinder;
+import com.github.dart_lang.jnigen.apisummarizer.util.InputStreamProvider;
+import com.github.dart_lang.jnigen.apisummarizer.util.JsonWriter;
+import com.github.dart_lang.jnigen.apisummarizer.util.StreamUtil;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import javax.tools.DocumentationTool;
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+import jdk.javadoc.doclet.Doclet;
+
+public class Main {
+  public enum Backend {
+    /** Produce API descriptions from source files using Doclet API. */
+    DOCLET,
+    /** Produce API descriptions from class files under classpath. */
+    ASM,
+    /** Prefer source but fall back to JARs in classpath if sources not found. */
+    AUTO,
+  }
+
+  static SummarizerOptions options;
+
+  public static List<ClassDecl> runDocletWithClass(
+      DocumentationTool javaDoc,
+      Class<? extends Doclet> docletClass,
+      List<JavaFileObject> fileObjects,
+      SummarizerOptions options) {
+    var fileManager = javaDoc.getStandardFileManager(null, null, null);
+    var cli = new ArrayList<String>();
+    cli.add((options.useModules ? "--module-" : "--") + "source-path=" + options.sourcePath);
+    if (options.classPath != null) {
+      cli.add("--class-path=" + options.classPath);
+    }
+    cli.addAll(List.of("-encoding", "utf8"));
+
+    if (options.toolArgs != null) {
+      cli.addAll(List.of(options.toolArgs.split(" ")));
+    }
+
+    javaDoc.getTask(null, fileManager, System.err::println, docletClass, cli, fileObjects).call();
+
+    return SummarizerDoclet.getClasses();
+  }
+
+  public static List<ClassDecl> runDoclet(
+      DocumentationTool javaDoc, List<JavaFileObject> javaFileObjects, SummarizerOptions options) {
+    return runDocletWithClass(javaDoc, SummarizerDoclet.class, javaFileObjects, options);
+  }
+
+  public static void main(String[] args) throws FileNotFoundException {
+    options = SummarizerOptions.parseArgs(args);
+    OutputStream output;
+
+    if (options.outputFile == null || options.outputFile.equals("-")) {
+      output = System.out;
+    } else {
+      output = new FileOutputStream(options.outputFile);
+    }
+    List<String> sourcePaths =
+        options.sourcePath != null
+            ? Arrays.asList(options.sourcePath.split(File.pathSeparator))
+            : List.of();
+    List<String> classPaths =
+        options.classPath != null
+            ? Arrays.asList(options.classPath.split(File.pathSeparator))
+            : List.of();
+
+    var javaDoc = ToolProvider.getSystemDocumentationTool();
+
+    var sourceClasses = new LinkedHashMap<String, List<JavaFileObject>>();
+    var binaryClasses = new LinkedHashMap<String, List<InputStreamProvider>>();
+
+    for (var qualifiedName : options.args) {
+      sourceClasses.put(qualifiedName, null);
+      binaryClasses.put(qualifiedName, null);
+    }
+
+    if (options.backend != Backend.ASM) {
+      ClassFinder.findJavaSources(
+          sourceClasses, sourcePaths, javaDoc.getStandardFileManager(null, null, null));
+    }
+
+    // remove found classes from binaryClasses, so that they don't need to be searched again.
+    // TODO: Tidy up this logic, move to ClassFinder class
+    for (var qualifiedName : options.args) {
+      if (sourceClasses.get(qualifiedName) != null) {
+        binaryClasses.remove(qualifiedName);
+      }
+    }
+
+    if (options.backend != Backend.DOCLET) {
+      ClassFinder.findJavaClasses(binaryClasses, classPaths);
+    }
+
+    // remove duplicates (found as both source & binary), and determine if any class is not found.
+    var notFound = new ArrayList<String>();
+    for (var qualifiedName : options.args) {
+      var foundSource = sourceClasses.get(qualifiedName) != null;
+      var foundBinary = binaryClasses.get(qualifiedName) != null;
+      if (foundSource) {
+        binaryClasses.remove(qualifiedName);
+      }
+      if (!foundBinary && !foundSource) {
+        notFound.add(qualifiedName);
+      }
+    }
+
+    if (!notFound.isEmpty()) {
+      System.err.println("Not found: " + notFound);
+      System.exit(1);
+    }
+
+    var classStreamProviders = StreamUtil.flattenListValues(binaryClasses);
+    var sourceFiles = StreamUtil.flattenListValues(sourceClasses);
+
+    switch (options.backend) {
+      case DOCLET:
+        JsonWriter.writeJSON(runDoclet(javaDoc, sourceFiles, options), output);
+        break;
+      case ASM:
+        JsonWriter.writeJSON(AsmSummarizer.run(classStreamProviders), output);
+        break;
+      case AUTO:
+        List<ClassDecl> decls = new ArrayList<>();
+        if (!sourceFiles.isEmpty()) {
+          decls.addAll(runDoclet(javaDoc, sourceFiles, options));
+        }
+        if (!classStreamProviders.isEmpty()) {
+          decls.addAll(AsmSummarizer.run(classStreamProviders));
+        }
+        JsonWriter.writeJSON(decls, output);
+        break;
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/SummarizerOptions.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/SummarizerOptions.java
new file mode 100644
index 0000000..8a3e545
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/SummarizerOptions.java
@@ -0,0 +1,80 @@
+package com.github.dart_lang.jnigen.apisummarizer;
+
+import java.util.Arrays;
+import org.apache.commons.cli.*;
+
+public class SummarizerOptions {
+  private static final CommandLineParser parser = new DefaultParser();
+  String sourcePath;
+  String classPath;
+  boolean useModules;
+  Main.Backend backend;
+  String modulesList;
+  String toolArgs;
+  boolean verbose;
+  String outputFile;
+  String[] args;
+
+  SummarizerOptions() {}
+
+  public static SummarizerOptions fromCommandLine(CommandLine cmd) {
+    var opts = new SummarizerOptions();
+    opts.sourcePath = cmd.getOptionValue("sources", null);
+    var backendString = cmd.getOptionValue("backend", "auto");
+    opts.backend = Main.Backend.valueOf(backendString.toUpperCase());
+    opts.classPath = cmd.getOptionValue("classes", null);
+    opts.useModules = cmd.hasOption("use-modules");
+    opts.modulesList = cmd.getOptionValue("module-names", null);
+    opts.toolArgs = cmd.getOptionValue("doctool-args", null);
+    opts.outputFile = cmd.getOptionValue("output-file", null);
+    opts.args = cmd.getArgs();
+    if (opts.args.length == 0) {
+      throw new IllegalArgumentException("Need one or more class or package names as arguments");
+    }
+    return opts;
+  }
+
+  public static SummarizerOptions parseArgs(String[] args) {
+    var options = new Options();
+    Option sources = new Option("s", "sources", true, "paths to search for source files");
+    Option classes = new Option("c", "classes", true, "paths to search for compiled classes");
+    Option backend =
+        new Option(
+            "b",
+            "backend",
+            true,
+            "backend to use for summary generation ('doclet', 'asm' or 'auto' (default)).");
+    Option useModules = new Option("M", "use-modules", false, "use Java modules");
+    Option moduleNames =
+        new Option("m", "module-names", true, "comma separated list of module names");
+    Option doctoolArgs =
+        new Option("D", "doctool-args", true, "arguments to pass to the documentation tool");
+    Option outputFile =
+        new Option("o", "output-file", true, "write JSON to file instead of stdout");
+    Option[] allOptions = {
+      sources, classes, backend, useModules, moduleNames, doctoolArgs, outputFile
+    };
+    Arrays.stream(allOptions).forEach(options::addOption);
+
+    HelpFormatter help = new HelpFormatter();
+
+    CommandLine cmd;
+
+    try {
+      cmd = parser.parse(options, args);
+      if (cmd.getArgs().length < 1) {
+        throw new ParseException("Need to specify paths to source files");
+      }
+    } catch (ParseException e) {
+      System.out.println(e.getMessage());
+      help.printHelp(
+          "java -jar <JAR> [-s <SOURCE_DIR=.>] "
+              + "[-c <CLASSES_JAR>] <CLASS_OR_PACKAGE_NAMES>\n"
+              + "Class or package names should be fully qualified.\n\n",
+          options);
+      System.exit(1);
+      throw new RuntimeException("Unreachable code");
+    }
+    return fromCommandLine(cmd);
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmAnnotatedElementVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmAnnotatedElementVisitor.java
new file mode 100644
index 0000000..0a723b3
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmAnnotatedElementVisitor.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.JavaAnnotation;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Type;
+
+// This interface removes some repetitive code using default methods
+
+public interface AsmAnnotatedElementVisitor {
+  void addAnnotation(JavaAnnotation annotation);
+
+  default AnnotationVisitor visitAnnotationDefault(String descriptor, boolean visible) {
+    var annotation = new JavaAnnotation();
+    var aType = Type.getType(descriptor);
+    annotation.binaryName = aType.getClassName();
+    addAnnotation(annotation);
+    return new AsmAnnotationVisitor(annotation);
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmAnnotationVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmAnnotationVisitor.java
new file mode 100644
index 0000000..1323a45
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmAnnotationVisitor.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.JavaAnnotation;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Type;
+
+public class AsmAnnotationVisitor extends AnnotationVisitor {
+
+  JavaAnnotation annotation;
+
+  protected AsmAnnotationVisitor(JavaAnnotation annotation) {
+    super(AsmConstants.API);
+    this.annotation = annotation;
+  }
+
+  @Override
+  public void visit(String name, Object value) {
+    annotation.properties.put(name, value);
+  }
+
+  @Override
+  public void visitEnum(String name, String descriptor, String value) {
+    annotation.properties.put(
+        name, new JavaAnnotation.EnumVal(Type.getType(descriptor).getClassName(), value));
+  }
+
+  @Override
+  public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+    var type = Type.getType(descriptor);
+    var nested = new JavaAnnotation();
+    nested.binaryName = type.getClassName();
+    annotation.properties.put(name, nested);
+    return new AsmAnnotationVisitor(nested);
+  }
+
+  @Override
+  public AnnotationVisitor visitArray(String name) {
+    List<Object> list = new ArrayList<>();
+    annotation.properties.put(name, list);
+    return new AnnotationArrayVisitor(list);
+  }
+
+  public static class AnnotationArrayVisitor extends AnnotationVisitor {
+    List<Object> list;
+
+    protected AnnotationArrayVisitor(List<Object> list) {
+      super(AsmConstants.API);
+      this.list = list;
+    }
+
+    @Override
+    public void visit(String unused, Object value) {
+      list.add(value);
+    }
+
+    @Override
+    public void visitEnum(String unused, String descriptor, String value) {
+      var type = Type.getType(descriptor);
+      list.add(new JavaAnnotation.EnumVal(type.getClassName(), value));
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String unused, String descriptor) {
+      var type = Type.getType(descriptor);
+      var nested = new JavaAnnotation();
+      nested.binaryName = type.getClassName();
+      list.add(nested);
+      return new AsmAnnotationVisitor(nested);
+    }
+
+    @Override
+    public AnnotationVisitor visitArray(String unused) {
+      List<Object> nested = new ArrayList<>();
+      list.add(nested);
+      return new AnnotationArrayVisitor(nested);
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassSignatureVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassSignatureVisitor.java
new file mode 100644
index 0000000..8805c7f
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassSignatureVisitor.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
+import com.github.dart_lang.jnigen.apisummarizer.elements.TypeParam;
+import com.github.dart_lang.jnigen.apisummarizer.elements.TypeUsage;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+public class AsmClassSignatureVisitor extends SignatureVisitor {
+  private final ClassDecl decl;
+  private int interfaceIndex = -1;
+
+  public AsmClassSignatureVisitor(ClassDecl decl) {
+    super(AsmConstants.API);
+    this.decl = decl;
+  }
+
+  @Override
+  public void visitFormalTypeParameter(String name) {
+    var typeParam = new TypeParam();
+    typeParam.name = name;
+    decl.typeParams.add(typeParam);
+  }
+
+  @Override
+  public SignatureVisitor visitClassBound() {
+    var typeUsage = new TypeUsage();
+    // ClassDecl initially has no type parameters. In visitFormalTypeParameter we add them
+    // and sequentially visitClassBound and visitInterfaceBound.
+    decl.typeParams.get(decl.typeParams.size() - 1).bounds.add(typeUsage);
+    return new AsmTypeUsageSignatureVisitor(typeUsage);
+  }
+
+  @Override
+  public SignatureVisitor visitInterfaceBound() {
+    var typeUsage = new TypeUsage();
+    decl.typeParams.get(decl.typeParams.size() - 1).bounds.add(typeUsage);
+    return new AsmTypeUsageSignatureVisitor(typeUsage);
+  }
+
+  @Override
+  public SignatureVisitor visitSuperclass() {
+    return new AsmTypeUsageSignatureVisitor(decl.superclass);
+  }
+
+  @Override
+  public SignatureVisitor visitInterface() {
+    interfaceIndex++;
+    return new AsmTypeUsageSignatureVisitor(decl.interfaces.get(interfaceIndex));
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java
new file mode 100644
index 0000000..d5a730b
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java
@@ -0,0 +1,180 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import static org.objectweb.asm.Opcodes.ACC_ENUM;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.*;
+import com.github.dart_lang.jnigen.apisummarizer.util.SkipException;
+import com.github.dart_lang.jnigen.apisummarizer.util.StreamUtil;
+import java.util.*;
+import java.util.stream.Collectors;
+import org.objectweb.asm.*;
+import org.objectweb.asm.signature.SignatureReader;
+
+public class AsmClassVisitor extends ClassVisitor implements AsmAnnotatedElementVisitor {
+  private static Param param(
+      Type type, String name, @SuppressWarnings("SameParameterValue") String signature) {
+    var param = new Param();
+    param.name = name;
+    param.type = TypeUtils.typeUsage(type, signature);
+    return param;
+  }
+
+  public List<ClassDecl> getVisited() {
+    return visited;
+  }
+
+  List<ClassDecl> visited = new ArrayList<>();
+  Stack<ClassDecl> visiting = new Stack<>();
+
+  /// Actual access for the inner classes as originally defined.
+  Map<String, Integer> actualAccess = new HashMap<>();
+
+  public AsmClassVisitor() {
+    super(AsmConstants.API);
+  }
+
+  @Override
+  public void visit(
+      int version,
+      int access,
+      String name,
+      String signature,
+      String superName,
+      String[] interfaces) {
+    var current = new ClassDecl();
+    visiting.push(current);
+    current.binaryName = name.replace('/', '.');
+    current.modifiers = TypeUtils.access(actualAccess.getOrDefault(current.binaryName, access));
+    current.declKind = TypeUtils.declKind(access);
+    current.superclass = TypeUtils.typeUsage(Type.getObjectType(superName), null);
+    current.interfaces =
+        StreamUtil.map(interfaces, i -> TypeUtils.typeUsage(Type.getObjectType(i), null));
+    if (signature != null) {
+      var reader = new SignatureReader(signature);
+      reader.accept(new AsmClassSignatureVisitor(current));
+    }
+    super.visit(version, access, name, signature, superName, interfaces);
+  }
+
+  @Override
+  public void visitInnerClass(String name, String outerName, String innerName, int access) {
+    var binaryName = name.replace('/', '.');
+    actualAccess.put(binaryName, access);
+    var alreadyVisitedInnerClass =
+        visited.stream()
+            .filter(decl -> decl.binaryName.equals(binaryName))
+            .collect(Collectors.toList());
+    // If the order of visit is outer first inner second.
+    // We still want to correct the modifiers.
+    if (!alreadyVisitedInnerClass.isEmpty()) {
+      alreadyVisitedInnerClass.get(0).modifiers = TypeUtils.access(access);
+    }
+  }
+
+  @Override
+  public FieldVisitor visitField(
+      int access, String name, String descriptor, String signature, Object value) {
+    if (name.contains("$")) {
+      return null;
+    }
+
+    var field = new Field();
+    field.name = name;
+    field.type = TypeUtils.typeUsage(Type.getType(descriptor), signature);
+    field.defaultValue = value;
+    field.modifiers = TypeUtils.access(access);
+    if ((access & ACC_ENUM) != 0) {
+      peekVisiting().values.add(name);
+    }
+    if (signature != null) {
+      var reader = new SignatureReader(signature);
+      reader.accept(new AsmTypeUsageSignatureVisitor(field.type));
+    }
+    peekVisiting().fields.add(field);
+    return new AsmFieldVisitor(field);
+  }
+
+  @Override
+  public MethodVisitor visitMethod(
+      int access, String name, String descriptor, String signature, String[] exceptions) {
+    var method = new Method();
+    if (name.contains("$")) {
+      return null;
+    }
+    method.name = name;
+    method.descriptor = descriptor;
+    var type = Type.getType(descriptor);
+    var params = new ArrayList<Param>();
+    var paramTypes = type.getArgumentTypes();
+    var paramNames = new HashMap<String, Integer>();
+    for (var pt : paramTypes) {
+      var paramName = TypeUtils.defaultParamName(pt);
+      if (paramNames.containsKey(paramName)) {
+        var nth = paramNames.get(paramName);
+        paramNames.put(paramName, nth + 1);
+        paramName = paramName + nth;
+      } else {
+        paramNames.put(paramName, 1);
+      }
+      params.add(param(pt, paramName, null));
+    }
+    method.returnType = TypeUtils.typeUsage(type.getReturnType(), signature);
+    method.modifiers = TypeUtils.access(access);
+    method.params = params;
+    if (signature != null) {
+      var reader = new SignatureReader(signature);
+      reader.accept(new AsmMethodSignatureVisitor(method));
+    }
+    peekVisiting().methods.add(method);
+    return new AsmMethodVisitor(method);
+  }
+
+  @Override
+  public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+    if (descriptor.equals("Lkotlin/Metadata;")) {
+      return new KotlinMetadataAnnotationVisitor(peekVisiting());
+    }
+    return super.visitAnnotation(descriptor, visible);
+  }
+
+  @Override
+  public void addAnnotation(JavaAnnotation annotation) {
+    peekVisiting().annotations.add(annotation);
+  }
+
+  @Override
+  public AnnotationVisitor visitAnnotationDefault(String descriptor, boolean visible) {
+    return super.visitAnnotation(descriptor, visible);
+  }
+
+  @Override
+  public AnnotationVisitor visitTypeAnnotation(
+      int typeRef, TypePath typePath, String descriptor, boolean visible) {
+    return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+  }
+
+  @Override
+  public void visitEnd() {
+    visited.add(popVisiting());
+  }
+
+  private ClassDecl peekVisiting() {
+    try {
+      return visiting.peek();
+    } catch (EmptyStackException e) {
+      throw new SkipException("Error: stack was empty when visitEnd was called.");
+    }
+  }
+
+  private ClassDecl popVisiting() {
+    try {
+      return visiting.pop();
+    } catch (EmptyStackException e) {
+      throw new SkipException("Error: stack was empty when visitEnd was called.");
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmConstants.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmConstants.java
new file mode 100644
index 0000000..e3d8165
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmConstants.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import static org.objectweb.asm.Opcodes.ASM9;
+
+public class AsmConstants {
+  static final int API = ASM9;
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmFieldVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmFieldVisitor.java
new file mode 100644
index 0000000..eb72302
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmFieldVisitor.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.Field;
+import com.github.dart_lang.jnigen.apisummarizer.elements.JavaAnnotation;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.FieldVisitor;
+
+public class AsmFieldVisitor extends FieldVisitor implements AsmAnnotatedElementVisitor {
+  Field field;
+
+  public AsmFieldVisitor(Field field) {
+    super(AsmConstants.API);
+    this.field = field;
+  }
+
+  @Override
+  public void addAnnotation(JavaAnnotation annotation) {
+    field.annotations.add(annotation);
+  }
+
+  @Override
+  public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+    return AsmAnnotatedElementVisitor.super.visitAnnotationDefault(descriptor, visible);
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodSignatureVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodSignatureVisitor.java
new file mode 100644
index 0000000..5410acf
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodSignatureVisitor.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.Method;
+import com.github.dart_lang.jnigen.apisummarizer.elements.TypeParam;
+import com.github.dart_lang.jnigen.apisummarizer.elements.TypeUsage;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+public class AsmMethodSignatureVisitor extends SignatureVisitor {
+  private final Method method;
+  private final List<TypeUsage> paramTypes = new ArrayList<>();
+
+  public AsmMethodSignatureVisitor(Method method) {
+    super(AsmConstants.API);
+    this.method = method;
+  }
+
+  @Override
+  public void visitFormalTypeParameter(String name) {
+    var typeParam = new TypeParam();
+    typeParam.name = name;
+    method.typeParams.add(typeParam);
+  }
+
+  @Override
+  public SignatureVisitor visitClassBound() {
+    var typeUsage = new TypeUsage();
+    // Method initially has no type parameters. In visitFormalTypeParameter we add them
+    // and sequentially visitClassBound and visitInterfaceBound.
+    method.typeParams.get(method.typeParams.size() - 1).bounds.add(typeUsage);
+    return new AsmTypeUsageSignatureVisitor(typeUsage);
+  }
+
+  @Override
+  public SignatureVisitor visitInterfaceBound() {
+    var typeUsage = new TypeUsage();
+    method.typeParams.get(method.typeParams.size() - 1).bounds.add(typeUsage);
+    return new AsmTypeUsageSignatureVisitor(typeUsage);
+  }
+
+  @Override
+  public SignatureVisitor visitReturnType() {
+    // Visiting params finished.
+    //
+    // In case of non-static inner class constructor, there is an extra parent parameter
+    // at the beginning which is not accounted for by the signature. So we fill the types
+    // for the last [paramTypes.size()] params.
+    int startIndex = method.params.size() - paramTypes.size();
+    for (int i = 0; i < paramTypes.size(); ++i) {
+      method.params.get(startIndex + i).type = paramTypes.get(i);
+    }
+    return new AsmTypeUsageSignatureVisitor(method.returnType);
+  }
+
+  @Override
+  public SignatureVisitor visitParameterType() {
+    paramTypes.add(new TypeUsage());
+    return new AsmTypeUsageSignatureVisitor(paramTypes.get(paramTypes.size() - 1));
+  }
+
+  @Override
+  public SignatureVisitor visitExceptionType() {
+    // Do nothing.
+    return new AsmTypeUsageSignatureVisitor(new TypeUsage());
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodVisitor.java
new file mode 100644
index 0000000..6d66fd6
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmMethodVisitor.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.JavaAnnotation;
+import com.github.dart_lang.jnigen.apisummarizer.elements.Method;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.TypePath;
+
+public class AsmMethodVisitor extends MethodVisitor implements AsmAnnotatedElementVisitor {
+  Method method;
+  List<String> paramNames = new ArrayList<>();
+
+  protected AsmMethodVisitor(Method method) {
+    super(AsmConstants.API);
+    this.method = method;
+  }
+
+  @Override
+  public void visitParameter(String name, int access) {
+    paramNames.add(name);
+  }
+
+  @Override
+  public void addAnnotation(JavaAnnotation annotation) {
+    method.annotations.add(annotation);
+  }
+
+  @Override
+  public AnnotationVisitor visitAnnotationDefault(String descriptor, boolean visible) {
+    return AsmAnnotatedElementVisitor.super.visitAnnotationDefault(descriptor, visible);
+  }
+
+  @Override
+  public AnnotationVisitor visitTypeAnnotation(
+      int typeRef, TypePath typePath, String descriptor, boolean visible) {
+    // TODO(#23): Collect annotation on type parameter
+    return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+  }
+
+  @Override
+  public AnnotationVisitor visitParameterAnnotation(
+      int parameter, String descriptor, boolean visible) {
+    // TODO(#23): collect and attach it to parameters
+    return super.visitParameterAnnotation(parameter, descriptor, visible);
+  }
+
+  @Override
+  public void visitEnd() {
+    if (paramNames.size() == method.params.size()) {
+      for (int i = 0; i < paramNames.size(); i++) {
+        method.params.get(i).name = paramNames.get(i);
+      }
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java
new file mode 100644
index 0000000..485c442
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import static com.github.dart_lang.jnigen.apisummarizer.util.ExceptionUtil.wrapCheckedException;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
+import com.github.dart_lang.jnigen.apisummarizer.util.InputStreamProvider;
+import java.util.List;
+import org.objectweb.asm.ClassReader;
+
+public class AsmSummarizer {
+
+  public static List<ClassDecl> run(List<InputStreamProvider> inputProviders) {
+    var visitor = new AsmClassVisitor();
+    for (var provider : inputProviders) {
+      var inputStream = provider.getInputStream();
+      var classReader = wrapCheckedException(ClassReader::new, inputStream);
+      classReader.accept(visitor, 0);
+      provider.close();
+    }
+    return visitor.getVisited();
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmTypeUsageSignatureVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmTypeUsageSignatureVisitor.java
new file mode 100644
index 0000000..6ddda45
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmTypeUsageSignatureVisitor.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.TypeUsage;
+import java.util.ArrayList;
+import org.objectweb.asm.signature.SignatureVisitor;
+
+public class AsmTypeUsageSignatureVisitor extends SignatureVisitor {
+  private final TypeUsage typeUsage;
+
+  public AsmTypeUsageSignatureVisitor(TypeUsage typeUsage) {
+    super(AsmConstants.API);
+    this.typeUsage = typeUsage;
+  }
+
+  @Override
+  public void visitBaseType(char descriptor) {
+    typeUsage.kind = TypeUsage.Kind.PRIMITIVE;
+    var name = "";
+    switch (descriptor) {
+      case 'Z':
+        name = "boolean";
+        break;
+      case 'B':
+        name = "byte";
+        break;
+      case 'C':
+        name = "char";
+        break;
+      case 'D':
+        name = "double";
+        break;
+      case 'F':
+        name = "float";
+        break;
+      case 'I':
+        name = "int";
+        break;
+      case 'J':
+        name = "long";
+        break;
+      case 'L':
+        name = "object";
+        break;
+      case 'S':
+        name = "short";
+        break;
+      case 'V':
+        name = "void";
+        break;
+    }
+    typeUsage.shorthand = name;
+    typeUsage.type = new TypeUsage.PrimitiveType(name);
+  }
+
+  @Override
+  public SignatureVisitor visitArrayType() {
+    typeUsage.kind = TypeUsage.Kind.ARRAY;
+    typeUsage.shorthand = "java.lang.Object[]";
+    var elementType = new TypeUsage();
+    typeUsage.type = new TypeUsage.Array(elementType);
+    return new AsmTypeUsageSignatureVisitor(elementType);
+  }
+
+  @Override
+  public void visitTypeVariable(String name) {
+    typeUsage.kind = TypeUsage.Kind.TYPE_VARIABLE;
+    typeUsage.shorthand = name;
+    typeUsage.type = new TypeUsage.TypeVar(name);
+  }
+
+  @Override
+  public void visitClassType(String name) {
+    typeUsage.kind = TypeUsage.Kind.DECLARED;
+    typeUsage.shorthand = name.substring(0, name.length()).replace('/', '.');
+    var components = name.split("[/$]");
+    var simpleName = components[components.length - 1];
+    typeUsage.type = new TypeUsage.DeclaredType(typeUsage.shorthand, simpleName, new ArrayList<>());
+  }
+
+  @Override
+  public SignatureVisitor visitTypeArgument(char wildcard) {
+    // TODO(#141) support wildcards
+    // TODO(#144) support extend/super clauses
+    assert (typeUsage.type instanceof TypeUsage.DeclaredType);
+    var typeArg = new TypeUsage();
+    ((TypeUsage.DeclaredType) typeUsage.type).params.add(typeArg);
+    return new AsmTypeUsageSignatureVisitor(typeArg);
+  }
+
+  @Override
+  public void visitInnerClassType(String name) {
+    typeUsage.shorthand += "." + name;
+    ((TypeUsage.DeclaredType) typeUsage.type).binaryName += "$" + name;
+    ((TypeUsage.DeclaredType) typeUsage.type).simpleName = name;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/KotlinMetadataAnnotationVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/KotlinMetadataAnnotationVisitor.java
new file mode 100644
index 0000000..c4aa8a1
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/KotlinMetadataAnnotationVisitor.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
+import com.github.dart_lang.jnigen.apisummarizer.elements.KotlinClass;
+import java.util.ArrayList;
+import java.util.List;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.objectweb.asm.AnnotationVisitor;
+
+/**
+ * The format of Kotlin's metadata can be found here:
+ * https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-metadata/
+ */
+public class KotlinMetadataAnnotationVisitor extends AnnotationVisitor {
+  private ClassDecl decl;
+
+  private int kind;
+  private int[] metadataVersion;
+  private List<String> data1 = new ArrayList<>();
+  private List<String> data2 = new ArrayList<>();
+  private String extraString;
+  private String packageName;
+  private int extraInt;
+
+  public KotlinMetadataAnnotationVisitor(ClassDecl decl) {
+    super(AsmConstants.API);
+    this.decl = decl;
+  }
+
+  @Override
+  public void visit(String name, Object value) {
+    switch (name) {
+      case "k":
+        kind = (int) value;
+        return;
+      case "mv":
+        metadataVersion = (int[]) value;
+        return;
+      case "xs":
+        extraString = (String) value;
+        return;
+      case "pn":
+        packageName = (String) value;
+        return;
+      case "xi":
+        extraInt = (int) value;
+    }
+  }
+
+  @Override
+  public AnnotationVisitor visitArray(String name) {
+    List<String> arr;
+    switch (name) {
+      case "d1":
+        arr = data1;
+        break;
+      case "d2":
+        arr = data2;
+        break;
+      default:
+        return super.visitArray(name);
+    }
+    return new AnnotationVisitor(AsmConstants.API) {
+      @Override
+      public void visit(String name, Object value) {
+        arr.add((String) value);
+        super.visit(name, value);
+      }
+    };
+  }
+
+  @Override
+  public void visitEnd() {
+    var header =
+        new KotlinClassHeader(
+            kind,
+            metadataVersion,
+            data1.toArray(String[]::new),
+            data2.toArray(String[]::new),
+            extraString,
+            packageName,
+            extraInt);
+    var metadata = KotlinClassMetadata.read(header);
+    if (metadata instanceof KotlinClassMetadata.Class) {
+      decl.kotlinClass =
+          KotlinClass.fromKmClass(((KotlinClassMetadata.Class) metadata).toKmClass());
+    } else if (metadata instanceof KotlinClassMetadata.FileFacade) {
+      // TODO(#301): Handle file facades.
+    } else if (metadata instanceof KotlinClassMetadata.SyntheticClass) {
+      // Ignore synthetic classes such as lambdas.
+    } else if (metadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
+      // Ignore multi-file classes
+    } else if (metadata instanceof KotlinClassMetadata.MultiFileClassPart) {
+      // Ignore multi-file classes
+    } else if (metadata instanceof KotlinClassMetadata.Unknown) {
+      // Unsupported
+    }
+    super.visitEnd();
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/TypeUtils.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/TypeUtils.java
new file mode 100644
index 0000000..8f18565
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/TypeUtils.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.disasm;
+
+import static org.objectweb.asm.Opcodes.*;
+import static org.objectweb.asm.Type.ARRAY;
+import static org.objectweb.asm.Type.OBJECT;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.DeclKind;
+import com.github.dart_lang.jnigen.apisummarizer.elements.TypeUsage;
+import com.github.dart_lang.jnigen.apisummarizer.util.SkipException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.objectweb.asm.Type;
+
+class TypeUtils {
+
+  public static String simpleName(Type type) {
+    var internalName = type.getInternalName();
+    if (type.getInternalName().length() == 1) {
+      return type.getClassName();
+    }
+    var components = internalName.split("[/$]");
+    if (components.length == 0) {
+      throw new SkipException("Cannot derive simple name: " + internalName);
+    }
+    return components[components.length - 1];
+  }
+
+  public static TypeUsage typeUsage(Type type, @SuppressWarnings("unused") String signature) {
+    var usage = new TypeUsage();
+    usage.shorthand = type.getClassName();
+    switch (type.getSort()) {
+      case OBJECT:
+        usage.kind = TypeUsage.Kind.DECLARED;
+        usage.type =
+            new TypeUsage.DeclaredType(
+                type.getInternalName().replace('/', '.'), TypeUtils.simpleName(type), null);
+        break;
+      case ARRAY:
+        usage.kind = TypeUsage.Kind.ARRAY;
+        usage.type = new TypeUsage.Array(TypeUtils.typeUsage(type.getElementType(), null));
+        break;
+      default:
+        usage.kind = TypeUsage.Kind.PRIMITIVE;
+        usage.type = new TypeUsage.PrimitiveType(type.getClassName());
+    }
+    // TODO(#23): generics
+    return usage;
+  }
+
+  public static Set<String> access(int access) {
+    var result = new HashSet<String>();
+    for (var ac : acc.entrySet()) {
+      if ((ac.getValue() & access) != 0) {
+        result.add(ac.getKey());
+      }
+    }
+    return result;
+  }
+
+  private static final Map<String, Integer> acc = new HashMap<>();
+
+  static {
+    acc.put("static", ACC_STATIC);
+    acc.put("private", ACC_PRIVATE);
+    acc.put("protected", ACC_PROTECTED);
+    acc.put("public", ACC_PUBLIC);
+    acc.put("abstract", ACC_ABSTRACT);
+    acc.put("final", ACC_FINAL);
+    acc.put("native", ACC_NATIVE);
+  }
+
+  static DeclKind declKind(int access) {
+    if ((access & ACC_ENUM) != 0) return DeclKind.ENUM;
+    if ((access & ACC_INTERFACE) != 0) return DeclKind.INTERFACE;
+    if ((access & ACC_ANNOTATION) != 0) return DeclKind.ANNOTATION_TYPE;
+    return DeclKind.CLASS;
+  }
+
+  static String defaultParamName(Type type) {
+    switch (type.getSort()) {
+      case ARRAY:
+        return defaultParamName(type.getElementType()) + 's';
+      case OBJECT:
+        return unCapitalize(simpleName(type));
+      case Type.METHOD:
+        throw new SkipException("unexpected method type" + type);
+      default: // Primitive type
+        var typeCh = type.getInternalName().charAt(0);
+        return String.valueOf(Character.toLowerCase(typeCh));
+    }
+  }
+
+  private static String unCapitalize(String s) {
+    var first = Character.toLowerCase(s.charAt(0));
+    if (s.length() == 1) {
+      return String.valueOf(first);
+    }
+    return first + s.substring(1);
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/AnnotationVisitor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/AnnotationVisitor.java
new file mode 100644
index 0000000..f5343ee
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/AnnotationVisitor.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.doclet;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+
+public class AnnotationVisitor implements AnnotationValueVisitor<Object, Void> {
+  private final ElementBuilders builders;
+  AstEnv env;
+
+  public AnnotationVisitor(ElementBuilders builders) {
+    this.builders = builders;
+    this.env = builders.env;
+  }
+
+  @Override
+  public Object visit(AnnotationValue annotationValue, Void unused) {
+    return null;
+  }
+
+  @Override
+  public Object visitBoolean(boolean b, Void unused) {
+    return b;
+  }
+
+  @Override
+  public Object visitByte(byte b, Void unused) {
+    return b;
+  }
+
+  @Override
+  public Object visitChar(char c, Void unused) {
+    return c;
+  }
+
+  @Override
+  public Object visitDouble(double v, Void unused) {
+    return v;
+  }
+
+  @Override
+  public Object visitFloat(float v, Void unused) {
+    return v;
+  }
+
+  @Override
+  public Object visitInt(int i, Void unused) {
+    return i;
+  }
+
+  @Override
+  public Object visitLong(long l, Void unused) {
+    return l;
+  }
+
+  @Override
+  public Object visitShort(short i, Void unused) {
+    return i;
+  }
+
+  @Override
+  public Object visitString(String s, Void unused) {
+    return s;
+  }
+
+  @Override
+  public Object visitType(TypeMirror typeMirror, Void unused) {
+    return builders.typeUsage(typeMirror);
+  }
+
+  @Override
+  public Object visitEnumConstant(VariableElement variableElement, Void unused) {
+    // TODO(#23): Perhaps simple name is not enough. We need to return qualified
+    // name + enum constant name for completeness.
+    return variableElement.getSimpleName();
+  }
+
+  @Override
+  public Object visitAnnotation(AnnotationMirror mirror, Void unused) {
+    return builders.annotation(mirror);
+  }
+
+  @Override
+  public Object visitArray(List<? extends AnnotationValue> list, Void unused) {
+    return list.stream().map(x -> x.accept(this, null)).collect(Collectors.toList());
+  }
+
+  @Override
+  public Object visitUnknown(AnnotationValue annotationValue, Void unused) {
+    return null;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/AstEnv.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/AstEnv.java
new file mode 100644
index 0000000..71e31c4
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/AstEnv.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.doclet;
+
+import com.sun.source.util.DocTrees;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import jdk.javadoc.doclet.DocletEnvironment;
+
+/** Class to hold utility classes initialized from DocletEnvironment. */
+public class AstEnv {
+  public final Types types;
+  public final Elements elements;
+  public final DocTrees trees;
+
+  public AstEnv(Types types, Elements elements, DocTrees trees) {
+    this.types = types;
+    this.elements = elements;
+    this.trees = trees;
+  }
+
+  public static AstEnv fromEnvironment(DocletEnvironment env) {
+    return new AstEnv(env.getTypeUtils(), env.getElementUtils(), env.getDocTrees());
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/ElementBuilders.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/ElementBuilders.java
new file mode 100644
index 0000000..675da7b
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/ElementBuilders.java
@@ -0,0 +1,204 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.doclet;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.*;
+import com.github.dart_lang.jnigen.apisummarizer.util.StreamUtil;
+import com.sun.source.doctree.DocCommentTree;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.lang.model.element.*;
+import javax.lang.model.type.*;
+
+public class ElementBuilders {
+  AstEnv env;
+
+  public ElementBuilders(AstEnv env) {
+    this.env = env;
+  }
+
+  private void fillInFromTypeElement(TypeElement e, ClassDecl c) {
+    c.modifiers = e.getModifiers().stream().map(Modifier::toString).collect(Collectors.toSet());
+    c.binaryName = env.elements.getBinaryName(e).toString();
+    switch (e.getKind()) {
+      case INTERFACE:
+        c.declKind = DeclKind.INTERFACE;
+        break;
+      case CLASS:
+        c.declKind = DeclKind.CLASS;
+        break;
+      case ENUM:
+        c.declKind = DeclKind.ENUM;
+        break;
+      case ANNOTATION_TYPE:
+        c.declKind = DeclKind.ANNOTATION_TYPE;
+        break;
+      default:
+        throw new RuntimeException(
+            "Unexpected element kind " + e.getKind() + " on " + c.binaryName);
+    }
+    c.javadoc = docComment(env.trees.getDocCommentTree(e));
+    c.typeParams = StreamUtil.map(e.getTypeParameters(), this::typeParam);
+    var superclass = e.getSuperclass();
+    if (superclass instanceof DeclaredType) {
+      c.superclass = typeUsage(superclass);
+    }
+    c.annotations = StreamUtil.map(e.getAnnotationMirrors(), this::annotation);
+    c.interfaces = StreamUtil.map(e.getInterfaces(), this::typeUsage);
+  }
+
+  public ClassDecl classDecl(TypeElement e) {
+    var c = new ClassDecl();
+    fillInFromTypeElement(e, c);
+    return c;
+  }
+
+  public Field field(VariableElement e) {
+    assert e.getKind() == ElementKind.FIELD;
+    var field = new Field();
+    field.name = e.getSimpleName().toString();
+    field.modifiers = e.getModifiers().stream().map(Modifier::toString).collect(Collectors.toSet());
+    field.defaultValue = e.getConstantValue();
+    if (field.defaultValue instanceof Character) {
+      field.defaultValue = (int) (Character) field.defaultValue;
+    }
+    field.type = typeUsage(e.asType());
+    field.javadoc = docComment(env.trees.getDocCommentTree(e));
+    field.annotations = annotations(e.getAnnotationMirrors());
+    return field;
+  }
+
+  public List<JavaAnnotation> annotations(List<? extends AnnotationMirror> mirrors) {
+    return mirrors.stream().map(this::annotation).collect(Collectors.toList());
+  }
+
+  public JavaAnnotation annotation(AnnotationMirror mirror) {
+    var annotation = new JavaAnnotation();
+    var type = mirror.getAnnotationType();
+    var typeElement = (TypeElement) (env.types.asElement(type));
+    annotation.binaryName = env.elements.getBinaryName(typeElement).toString();
+    var values = env.elements.getElementValuesWithDefaults(mirror);
+    if (values.isEmpty()) {
+      return annotation;
+    }
+
+    // This is not perfect, but some metadata is better than none.
+    annotation.properties = new HashMap<>();
+    for (var key : values.keySet()) {
+      var val = values.get(key);
+      var obj = val.getValue();
+      // TODO(#23): Accurately represent more complex annotation values
+      if (obj instanceof Number) {
+        annotation.properties.put(key.getSimpleName().toString(), obj);
+      } else {
+        annotation.properties.put(key.getSimpleName().toString(), obj.toString());
+      }
+    }
+    return annotation;
+  }
+
+  public JavaDocComment docComment(DocCommentTree tree) {
+    if (tree == null) {
+      return null;
+    }
+    return new JavaDocComment(tree.toString());
+  }
+
+  public TypeParam typeParam(TypeParameterElement tpe) {
+    var tp = new TypeParam();
+    tp.name = tpe.getSimpleName().toString();
+    tp.bounds = tpe.getBounds().stream().map(this::typeUsage).collect(Collectors.toList());
+    return tp;
+  }
+
+  public Param param(VariableElement e) {
+    var param = new Param();
+    param.javadoc = docComment(env.trees.getDocCommentTree(e));
+    param.name = e.getSimpleName().toString();
+    param.type = typeUsage(e.asType());
+    param.annotations = annotations(e.getAnnotationMirrors());
+    return param;
+  }
+
+  public TypeUsage typeUsage(TypeMirror type) {
+    var u = new TypeUsage();
+    u.shorthand = type.toString();
+    var element = env.types.asElement(type);
+    switch (type.getKind()) {
+      case DECLARED:
+        // Unique name that's binary name not qualified name
+        // (It's somewhat confusing but qualified name does not need to be unique,
+        // because of nesting)
+        u.kind = TypeUsage.Kind.DECLARED;
+        var name =
+            element instanceof TypeElement
+                ? env.elements.getBinaryName((TypeElement) element).toString()
+                : element.getSimpleName().toString();
+        List<TypeUsage> params = null;
+        if (type instanceof DeclaredType) { // it will be
+          params =
+              ((DeclaredType) type)
+                  .getTypeArguments().stream().map(this::typeUsage).collect(Collectors.toList());
+        }
+        u.type = new TypeUsage.DeclaredType(name, element.getSimpleName().toString(), params);
+        break;
+      case TYPEVAR:
+        u.kind = TypeUsage.Kind.TYPE_VARIABLE;
+        // TODO(#23): Encode bounds of type variable.
+        // A straightforward approach will cause infinite recursion very
+        // easily. Another approach I can think of is only encoding the
+        // erasure of the type variable per JLS.
+        u.type = new TypeUsage.TypeVar(element.getSimpleName().toString());
+        break;
+      case ARRAY:
+        u.kind = TypeUsage.Kind.ARRAY;
+        var arr = ((ArrayType) type);
+        u.type = new TypeUsage.Array(typeUsage(arr.getComponentType()));
+        break;
+      case VOID:
+        u.type = new TypeUsage.PrimitiveType("void");
+        u.kind = TypeUsage.Kind.PRIMITIVE;
+        break;
+      case WILDCARD:
+        u.kind = TypeUsage.Kind.WILDCARD;
+        var wildcard = ((WildcardType) type);
+        var extendsBound = wildcard.getExtendsBound();
+        var superBound = wildcard.getSuperBound();
+        u.type =
+            new TypeUsage.Wildcard(
+                extendsBound != null ? typeUsage(extendsBound) : null,
+                superBound != null ? typeUsage(superBound) : null);
+        break;
+      case INTERSECTION:
+        u.kind = TypeUsage.Kind.INTERSECTION;
+        u.type =
+            new TypeUsage.Intersection(
+                ((IntersectionType) type)
+                    .getBounds().stream().map(this::typeUsage).collect(Collectors.toList()));
+        break;
+      default:
+        u.kind = TypeUsage.Kind.PRIMITIVE;
+        if (type instanceof PrimitiveType) {
+          u.type = new TypeUsage.PrimitiveType(type.toString());
+        } else {
+          System.out.println("Unsupported type: " + type);
+          // throw exception.
+        }
+    }
+    return u;
+  }
+
+  public Method method(ExecutableElement e) {
+    var m = new Method();
+    m.name = e.getSimpleName().toString();
+    m.modifiers = e.getModifiers().stream().map(Modifier::toString).collect(Collectors.toSet());
+    m.typeParams = e.getTypeParameters().stream().map(this::typeParam).collect(Collectors.toList());
+    m.returnType = typeUsage(e.getReturnType());
+    m.javadoc = docComment(env.trees.getDocCommentTree(e));
+    m.annotations = annotations(e.getAnnotationMirrors());
+    return m;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/SummarizerDoclet.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/SummarizerDoclet.java
new file mode 100644
index 0000000..abbc7cc
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/SummarizerDoclet.java
@@ -0,0 +1,165 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.doclet;
+
+import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
+import com.github.dart_lang.jnigen.apisummarizer.elements.Method;
+import com.github.dart_lang.jnigen.apisummarizer.elements.Package;
+import com.github.dart_lang.jnigen.apisummarizer.util.Log;
+import com.github.dart_lang.jnigen.apisummarizer.util.SkipException;
+import java.util.*;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.*;
+import javax.lang.model.util.ElementScanner9;
+import jdk.javadoc.doclet.Doclet;
+import jdk.javadoc.doclet.DocletEnvironment;
+import jdk.javadoc.doclet.Reporter;
+
+public class SummarizerDoclet implements Doclet {
+  private AstEnv utils;
+
+  @Override
+  public void init(Locale locale, Reporter reporter) {}
+
+  @Override
+  public String getName() {
+    return "ApiSummarizer";
+  }
+
+  @Override
+  public Set<? extends Option> getSupportedOptions() {
+    return Collections.emptySet();
+  }
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.RELEASE_11;
+  }
+
+  public static List<ClassDecl> getClasses() {
+    return classes;
+  }
+
+  public static List<ClassDecl> classes;
+
+  @Override
+  public boolean run(DocletEnvironment docletEnvironment) {
+    Log.info("Initializing doclet");
+    utils = AstEnv.fromEnvironment(docletEnvironment);
+    SummarizingScanner scanner = new SummarizingScanner();
+    docletEnvironment.getSpecifiedElements().forEach(e -> scanner.scan(e, new SummaryCollector()));
+    classes = scanner.types;
+    return true;
+  }
+
+  public static class SummaryCollector {
+    Stack<Package> packages = new Stack<>();
+    Stack<ClassDecl> types = new Stack<>();
+    Method method;
+  }
+
+  public class SummarizingScanner extends ElementScanner9<Void, SummaryCollector> {
+    List<Package> packages = new ArrayList<>();
+    List<ClassDecl> types = new ArrayList<>();
+    ElementBuilders builders = new ElementBuilders(utils);
+
+    // Each element in collector is a stack
+    // which is used to get topmost element
+    // and append the child to it.
+    // Eg: A variable element is always appended to topmost
+    // class
+    @Override
+    public Void scan(Element e, SummaryCollector collector) {
+      return super.scan(e, collector);
+    }
+
+    @Override
+    public Void visitPackage(PackageElement e, SummaryCollector collector) {
+      Log.info("Visiting package: %s", e.getQualifiedName());
+      collector.packages.push(new Package());
+      System.out.println("package: " + e.getQualifiedName());
+      var result = super.visitPackage(e, collector);
+      var collectedPackage = collector.packages.pop();
+      packages.add(collectedPackage);
+      return result;
+    }
+
+    @Override
+    public Void visitType(TypeElement e, SummaryCollector collector) {
+      if (!collector.types.isEmpty()) {
+        return null;
+      }
+      Log.info("Visiting class: %s, %s", e.getQualifiedName(), collector.types);
+      switch (e.getKind()) {
+        case CLASS:
+        case INTERFACE:
+        case ENUM:
+          try {
+            var cls = builders.classDecl(e);
+            collector.types.push(cls);
+            super.visitType(e, collector);
+            types.add(collector.types.pop());
+          } catch (SkipException skip) {
+            Log.info("Skip type: %s", e.getQualifiedName());
+          }
+          break;
+        case ANNOTATION_TYPE:
+          Log.info("Skip annotation type: %s", e.getQualifiedName());
+          break;
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitVariable(VariableElement e, SummaryCollector collector) {
+      var vk = e.getKind();
+      var cls = collector.types.peek();
+      switch (vk) {
+        case ENUM_CONSTANT:
+          cls.values.add(e.getSimpleName().toString());
+          break;
+        case FIELD:
+          cls.fields.add(builders.field(e));
+          break;
+        case PARAMETER:
+          if (collector.method == null) {
+            throw new RuntimeException("Parameter encountered outside executable element");
+          }
+          var method = collector.method;
+          method.params.add(builders.param(e));
+          break;
+        default:
+          System.out.println("Unknown type of variable element: " + vk);
+      }
+      return null;
+    }
+
+    @Override
+    public Void visitExecutable(ExecutableElement element, SummaryCollector collector) {
+      var cls = collector.types.peek();
+      switch (element.getKind()) {
+        case METHOD:
+        case CONSTRUCTOR:
+          try {
+            var method = builders.method(element);
+            collector.method = method;
+            super.visitExecutable(element, collector);
+            collector.method = null;
+            cls.methods.add(method);
+          } catch (SkipException skip) {
+            Log.info("Skip method: %s", element.getSimpleName());
+          }
+          break;
+        case STATIC_INIT:
+          cls.hasStaticInit = true;
+          break;
+        case INSTANCE_INIT:
+          cls.hasInstanceInit = true;
+          break;
+      }
+      return null;
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java
new file mode 100644
index 0000000..746b913
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/ClassDecl.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class or interface declaration.
+ *
+ * <p>Here's an example for various kinds of names stored in this structure: { simpleName :
+ * "Example", binaryName : "dev.dart.sample.Example", parentName : null, packageName :
+ * "dev.dart.sample", }
+ */
+public class ClassDecl {
+  public DeclKind declKind;
+
+  /** Modifiers eg: static, public and abstract. */
+  public Set<String> modifiers;
+
+  /**
+   * Unique, fully qualified name of the class, it's like a qualified name used in a program but
+   * uses $ instead of dot (.) before nested classes.
+   */
+  public String binaryName;
+
+  public List<TypeParam> typeParams = new ArrayList<>();
+  public List<Method> methods = new ArrayList<>();
+  public List<Field> fields = new ArrayList<>();
+  public TypeUsage superclass;
+  public List<TypeUsage> interfaces = new ArrayList<>();
+  public boolean hasStaticInit;
+  public boolean hasInstanceInit;
+  public JavaDocComment javadoc;
+  public List<JavaAnnotation> annotations;
+  public KotlinClass kotlinClass;
+
+  /** In case of enum, names of enum constants */
+  public List<String> values = new ArrayList<>();
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/DeclKind.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/DeclKind.java
new file mode 100644
index 0000000..e2e533e
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/DeclKind.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+public enum DeclKind {
+  CLASS,
+  ENUM,
+  INTERFACE,
+  ANNOTATION_TYPE
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Field.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Field.java
new file mode 100644
index 0000000..ced5cea
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Field.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class Field {
+  public Set<String> modifiers = new HashSet<>();
+  public String name;
+  public TypeUsage type;
+  public Object defaultValue;
+
+  public JavaDocComment javadoc;
+  public List<JavaAnnotation> annotations = new ArrayList<>();
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/JavaAnnotation.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/JavaAnnotation.java
new file mode 100644
index 0000000..c463cba
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/JavaAnnotation.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class JavaAnnotation {
+  public String binaryName;
+  public Map<String, Object> properties = new HashMap<>();
+
+  public static class EnumVal {
+    public String enumClass;
+    public String value;
+
+    public EnumVal(String enumClass, String value) {
+      this.enumClass = enumClass;
+      this.value = value;
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/JavaDocComment.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/JavaDocComment.java
new file mode 100644
index 0000000..c882c97
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/JavaDocComment.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+public class JavaDocComment {
+  // TODO(#28): Build a detailed tree representation of JavaDocComment
+  // which can be processed by tools in other languages as well.
+  public String comment;
+
+  public JavaDocComment(String comment) {
+    this.comment = comment;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinClass.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinClass.java
new file mode 100644
index 0000000..22906f7
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinClass.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinClass {
+  public String name;
+  public String moduleName;
+  public List<KotlinFunction> functions;
+  public List<KotlinProperty> properties;
+  public List<KotlinConstructor> constructors;
+  public List<KotlinTypeParameter> typeParameters;
+  public List<KotlinType> contextReceiverTypes;
+  public List<KotlinType> superTypes;
+  public List<String> nestedClasses;
+  public List<String> enumEntries;
+  public List<String> sealedClasses;
+  public String companionObject;
+  public String inlineClassUnderlyingPropertyName;
+  public KotlinType inlineClassUnderlyingType;
+  public int flags;
+  public int jvmFlags;
+
+  public static KotlinClass fromKmClass(KmClass c) {
+    var klass = new KotlinClass();
+    klass.name = c.getName();
+    klass.moduleName = JvmExtensionsKt.getModuleName(c);
+    klass.functions =
+        c.getFunctions().stream().map(KotlinFunction::fromKmFunction).collect(Collectors.toList());
+    klass.properties =
+        c.getProperties().stream().map(KotlinProperty::fromKmProperty).collect(Collectors.toList());
+    klass.constructors =
+        c.getConstructors().stream()
+            .map(KotlinConstructor::fromKmConstructor)
+            .collect(Collectors.toList());
+    klass.typeParameters =
+        c.getTypeParameters().stream()
+            .map(KotlinTypeParameter::fromKmTypeParameter)
+            .collect(Collectors.toList());
+    klass.contextReceiverTypes =
+        c.getContextReceiverTypes().stream()
+            .map(KotlinType::fromKmType)
+            .collect(Collectors.toList());
+    klass.superTypes =
+        c.getSupertypes().stream().map(KotlinType::fromKmType).collect(Collectors.toList());
+    klass.enumEntries = c.getEnumEntries();
+    klass.flags = c.getFlags();
+    klass.jvmFlags = JvmExtensionsKt.getJvmFlags(c);
+    klass.nestedClasses = c.getNestedClasses();
+    klass.companionObject = c.getCompanionObject();
+    klass.inlineClassUnderlyingPropertyName = c.getInlineClassUnderlyingPropertyName();
+    klass.inlineClassUnderlyingType = KotlinType.fromKmType(c.getInlineClassUnderlyingType());
+    klass.sealedClasses = c.getSealedSubclasses();
+    return klass;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinConstructor.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinConstructor.java
new file mode 100644
index 0000000..dae3a2f
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinConstructor.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinConstructor {
+  public String name;
+  public String descriptor;
+  public List<KotlinValueParameter> valueParameters;
+  public int flags;
+
+  public static KotlinConstructor fromKmConstructor(KmConstructor c) {
+    var ctor = new KotlinConstructor();
+    ctor.flags = c.getFlags();
+    var signature = JvmExtensionsKt.getSignature(c);
+    ctor.name = signature == null ? null : signature.getName();
+    ctor.descriptor = signature == null ? null : signature.getDesc();
+    ctor.valueParameters =
+        c.getValueParameters().stream()
+            .map(KotlinValueParameter::fromKmValueParameter)
+            .collect(Collectors.toList());
+    return ctor;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinFunction.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinFunction.java
new file mode 100644
index 0000000..87cdc7b
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinFunction.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.Flag;
+import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinFunction {
+  /** Name in the byte code. */
+  public String name;
+
+  public String descriptor;
+
+  /** Name in the Kotlin's metadata. */
+  public String kotlinName;
+
+  public List<KotlinValueParameter> valueParameters;
+  public KotlinType returnType;
+  public KotlinType receiverParameterType;
+  public List<KotlinType> contextReceiverTypes;
+  public List<KotlinTypeParameter> typeParameters;
+  public int flags;
+  public boolean isSuspend;
+
+  public static KotlinFunction fromKmFunction(KmFunction f) {
+    var fun = new KotlinFunction();
+    var signature = JvmExtensionsKt.getSignature(f);
+    fun.descriptor = signature == null ? null : signature.getDesc();
+    fun.name = signature == null ? null : signature.getName();
+    fun.kotlinName = f.getName();
+    fun.flags = f.getFlags();
+    // Processing the information needed from the flags.
+    fun.isSuspend = Flag.Function.IS_SUSPEND.invoke(fun.flags);
+    fun.valueParameters =
+        f.getValueParameters().stream()
+            .map(KotlinValueParameter::fromKmValueParameter)
+            .collect(Collectors.toList());
+    fun.returnType = KotlinType.fromKmType(f.getReturnType());
+    fun.receiverParameterType = KotlinType.fromKmType(f.getReceiverParameterType());
+    fun.contextReceiverTypes =
+        f.getContextReceiverTypes().stream()
+            .map(KotlinType::fromKmType)
+            .collect(Collectors.toList());
+    fun.typeParameters =
+        f.getTypeParameters().stream()
+            .map(KotlinTypeParameter::fromKmTypeParameter)
+            .collect(Collectors.toList());
+    return fun;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinProperty.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinProperty.java
new file mode 100644
index 0000000..2858be4
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinProperty.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinProperty {
+  public String fieldName;
+  public String fieldDescriptor;
+
+  /** Getter's name in the byte code. */
+  public String getterName;
+
+  public String getterDescriptor;
+
+  /** Setter's name in the byte code. */
+  public String setterName;
+
+  public String setterDescriptor;
+
+  /** Name in the Kotlin's metadata. */
+  public String kotlinName;
+
+  public KotlinType returnType;
+  public KotlinType receiverParameterType;
+  public List<KotlinType> contextReceiverTypes;
+  public int jvmFlags;
+  public int flags;
+  public int setterFlags;
+  public int getterFlags;
+  public List<KotlinTypeParameter> typeParameters;
+  public KotlinValueParameter setterParameter;
+
+  public static KotlinProperty fromKmProperty(KmProperty p) {
+    var prop = new KotlinProperty();
+    var fieldSignature = JvmExtensionsKt.getFieldSignature(p);
+    prop.fieldDescriptor = fieldSignature == null ? null : fieldSignature.getDesc();
+    prop.fieldName = fieldSignature == null ? null : fieldSignature.getName();
+    var getterSignature = JvmExtensionsKt.getGetterSignature(p);
+    prop.getterDescriptor = getterSignature == null ? null : getterSignature.getDesc();
+    prop.getterName = getterSignature == null ? null : getterSignature.getName();
+    var setterSignature = JvmExtensionsKt.getSetterSignature(p);
+    prop.setterDescriptor = setterSignature == null ? null : setterSignature.getDesc();
+    prop.setterName = setterSignature == null ? null : setterSignature.getName();
+    prop.kotlinName = p.getName();
+    prop.returnType = KotlinType.fromKmType(p.getReturnType());
+    prop.receiverParameterType = KotlinType.fromKmType(p.getReceiverParameterType());
+    prop.contextReceiverTypes =
+        p.getContextReceiverTypes().stream()
+            .map(KotlinType::fromKmType)
+            .collect(Collectors.toList());
+    prop.jvmFlags = JvmExtensionsKt.getJvmFlags(p);
+    prop.flags = p.getFlags();
+    prop.setterFlags = p.getSetterFlags();
+    prop.getterFlags = p.getGetterFlags();
+    prop.typeParameters =
+        p.getTypeParameters().stream()
+            .map(KotlinTypeParameter::fromKmTypeParameter)
+            .collect(Collectors.toList());
+    prop.setterParameter = KotlinValueParameter.fromKmValueParameter(p.getSetterParameter());
+    return prop;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinType.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinType.java
new file mode 100644
index 0000000..8e91122
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinType.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmClassifier;
+import kotlinx.metadata.KmType;
+
+public class KotlinType {
+  public int flags;
+  public String kind;
+  public String name;
+  public int id;
+  public List<KotlinTypeProjection> arguments;
+
+  public static KotlinType fromKmType(KmType t) {
+    if (t == null) return null;
+    var type = new KotlinType();
+    type.flags = t.getFlags();
+    var classifier = t.getClassifier();
+    if (classifier instanceof KmClassifier.Class) {
+      type.kind = "class";
+      type.name = ((KmClassifier.Class) classifier).getName();
+    } else if (classifier instanceof KmClassifier.TypeAlias) {
+      type.kind = "typeAlias";
+      type.name = ((KmClassifier.TypeAlias) classifier).getName();
+    } else if (classifier instanceof KmClassifier.TypeParameter) {
+      type.kind = "typeParameter";
+      type.id = ((KmClassifier.TypeParameter) classifier).getId();
+    }
+    type.arguments =
+        t.getArguments().stream()
+            .map(KotlinTypeProjection::fromKmTypeProjection)
+            .collect(Collectors.toList());
+    return type;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeParameter.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeParameter.java
new file mode 100644
index 0000000..449aef1
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeParameter.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmVariance;
+
+public class KotlinTypeParameter {
+  public String name;
+  public int id;
+  public int flags;
+  public List<KotlinType> upperBounds;
+  public KmVariance variance;
+
+  public static KotlinTypeParameter fromKmTypeParameter(KmTypeParameter t) {
+    var typeParam = new KotlinTypeParameter();
+    typeParam.name = t.getName();
+    typeParam.id = t.getId();
+    typeParam.flags = t.getFlags();
+    typeParam.upperBounds =
+        t.getUpperBounds().stream().map(KotlinType::fromKmType).collect(Collectors.toList());
+    typeParam.variance = t.getVariance();
+    return typeParam;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeProjection.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeProjection.java
new file mode 100644
index 0000000..364634c
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinTypeProjection.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmVariance;
+
+public class KotlinTypeProjection {
+  public KotlinType type;
+  public KmVariance variance;
+
+  public static KotlinTypeProjection fromKmTypeProjection(KmTypeProjection t) {
+    var proj = new KotlinTypeProjection();
+    proj.type = KotlinType.fromKmType(t.getType());
+    proj.variance = t.getVariance();
+    return proj;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinValueParameter.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinValueParameter.java
new file mode 100644
index 0000000..1fba28d
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/KotlinValueParameter.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import kotlinx.metadata.KmValueParameter;
+
+public class KotlinValueParameter {
+  public String name;
+  public int flags;
+  public KotlinType type;
+  public KotlinType varargElementType;
+
+  public static KotlinValueParameter fromKmValueParameter(KmValueParameter p) {
+    if (p == null) return null;
+    var param = new KotlinValueParameter();
+    param.name = p.getName();
+    param.flags = p.getFlags();
+    param.type = KotlinType.fromKmType(p.getType());
+    param.varargElementType = KotlinType.fromKmType(p.getVarargElementType());
+    return param;
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java
new file mode 100644
index 0000000..33bb0fb
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Method.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class Method {
+  public Set<String> modifiers = new HashSet<>();
+  public String name;
+  public String descriptor;
+  public List<TypeParam> typeParams = new ArrayList<>();
+  public List<Param> params = new ArrayList<>();
+  public TypeUsage returnType;
+
+  public JavaDocComment javadoc;
+  public List<JavaAnnotation> annotations = new ArrayList<>();
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Package.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Package.java
new file mode 100644
index 0000000..ca2bfce
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Package.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+public class Package {
+  public String name;
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Param.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Param.java
new file mode 100644
index 0000000..433f6bf
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/Param.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Param {
+  public String name;
+  public TypeUsage type;
+
+  public JavaDocComment javadoc;
+  public List<JavaAnnotation> annotations = new ArrayList<>();
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/TypeParam.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/TypeParam.java
new file mode 100644
index 0000000..8172ee6
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/TypeParam.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TypeParam {
+  public String name;
+  public List<TypeUsage> bounds = new ArrayList<>();
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/TypeUsage.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/TypeUsage.java
new file mode 100644
index 0000000..a60407c
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/elements/TypeUsage.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.elements;
+
+import java.util.List;
+
+public class TypeUsage {
+  public TypeUsage(String shorthand, Kind kind, ReferredType type) {
+    this.shorthand = shorthand;
+    this.kind = kind;
+    this.type = type;
+  }
+
+  public TypeUsage() {}
+
+  public enum Kind {
+    DECLARED,
+    TYPE_VARIABLE,
+    WILDCARD,
+    ARRAY,
+    INTERSECTION,
+    PRIMITIVE,
+  }
+
+  // Could've made it just a type hierarchy, but client code parsing JSON
+  // needs to know the type beforehand, before it can deserialize the `type` field.
+  public String shorthand;
+  public Kind kind;
+  public ReferredType type;
+
+  public abstract static class ReferredType {}
+
+  public static class PrimitiveType extends ReferredType {
+    public String name;
+
+    public PrimitiveType(String name) {
+      this.name = name;
+    }
+  }
+
+  public static class DeclaredType extends ReferredType {
+    public String binaryName;
+    public String simpleName;
+    public List<TypeUsage> params;
+
+    public DeclaredType(String binaryName, String simpleName, List<TypeUsage> params) {
+      this.binaryName = binaryName;
+      this.simpleName = simpleName;
+      this.params = params;
+    }
+  }
+
+  public static class TypeVar extends ReferredType {
+    public String name;
+
+    public TypeVar(String name) {
+      this.name = name;
+    }
+  }
+
+  public static class Wildcard extends ReferredType {
+    public TypeUsage extendsBound, superBound;
+
+    public Wildcard(TypeUsage extendsBound, TypeUsage superBound) {
+      this.extendsBound = extendsBound;
+      this.superBound = superBound;
+    }
+  }
+
+  public static class Intersection extends ReferredType {
+    public List<TypeUsage> types;
+
+    public Intersection(List<TypeUsage> types) {
+      this.types = types;
+    }
+  }
+
+  public static class Array extends ReferredType {
+    public TypeUsage type;
+
+    public Array(TypeUsage type) {
+      this.type = type;
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java
new file mode 100644
index 0000000..23c1ca5
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java
@@ -0,0 +1,199 @@
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+import static com.github.dart_lang.jnigen.apisummarizer.util.ExceptionUtil.wrapCheckedException;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+
+public class ClassFinder {
+  // If class is A$B$C, simpleName can be B$C or A$B$C. Doesn't matter much, because
+  // A can't be anonymous class.
+  private static boolean isNonAnonymousNestedClassName(String simpleName) {
+    String[] nestedParts = simpleName.split("\\$");
+    return Arrays.stream(nestedParts).allMatch(part -> part.matches("[a-zA-Z_][a-zA-Z0-9_]*"));
+  }
+
+  public static boolean isNestedClassOf(String pathString, String fqnWithSlashes, String suffix) {
+    var fqnWithSlashesDollarSign = fqnWithSlashes + "$";
+    if (!pathString.startsWith(fqnWithSlashesDollarSign) || !pathString.endsWith(suffix)) {
+      return false;
+    }
+    String nested =
+        pathString.substring(
+            fqnWithSlashesDollarSign.length(), pathString.length() - suffix.length());
+    return isNonAnonymousNestedClassName(nested);
+  }
+
+  private static boolean isNonAnonymousClassFullPath(String path) {
+    String[] pathParts = path.split("[/\\\\]");
+    String simpleNameWithExt = pathParts[pathParts.length - 1];
+    int extIndex = simpleNameWithExt.indexOf('.');
+    assert extIndex != -1 : "Should've passed full path with extension to this method";
+    String simpleName = simpleNameWithExt.substring(0, extIndex);
+    return isNonAnonymousNestedClassName(simpleName);
+  }
+
+  // Finds [fqn] and its children with [suffix] in [entries].
+  public static Optional<List<String>> findClassAndChildren(
+      TreeSet<String> entries, String fqn, String sep, String suffix) {
+    String fqnWithSlashes = fqn.replace(".", sep);
+    String fqnWithSlashesSuffix = fqnWithSlashes + suffix;
+    String fqnWithSlashesSlash = fqnWithSlashes + sep;
+    String fqnWithSlashesDollarSign = fqnWithSlashes + "$";
+    if (entries.contains(fqnWithSlashesSuffix)) {
+      List<String> classes = new ArrayList<>();
+      // Add nested classes first, because they're alphabetically first
+      entries.tailSet(fqnWithSlashesDollarSign).stream()
+          .takeWhile(entry -> entry.startsWith(fqnWithSlashesDollarSign))
+          // Note: filter comes after takeWhile, because it can filter out additional elements
+          // eg: Class$1 - but there may be valid nested classes after Class$1.
+          .filter(entry -> isNestedClassOf(entry, fqnWithSlashes, suffix))
+          .forEach(classes::add);
+      classes.add(fqnWithSlashesSuffix);
+      return Optional.of(classes);
+    }
+
+    // consider fqnWithSlashes as a directory
+    List<String> children =
+        entries.tailSet(fqnWithSlashesSlash).stream()
+            // takeWhile instead of filter - the difference is O(log n + k) instead of O(n)
+            // so always use takeWhile when doing a treeSet subset stream.
+            .takeWhile(entry -> entry.startsWith(fqnWithSlashesSlash))
+            .filter(entry -> entry.endsWith(suffix))
+            .filter(ClassFinder::isNonAnonymousClassFullPath)
+            .collect(Collectors.toList());
+    return children.isEmpty() ? Optional.empty() : Optional.of(children);
+  }
+
+  public static <E> void findFilesInPath(
+      String searchLocation,
+      String suffix,
+      Map<String, List<E>> classes,
+      Function<List<Path>, List<E>> mapper) {
+    Path searchPath = Path.of(searchLocation);
+
+    TreeSet<String> filePaths;
+    try (var walk = Files.walk(searchPath)) {
+      filePaths =
+          walk.map(searchPath::relativize)
+              .map(Path::toString)
+              .collect(Collectors.toCollection(TreeSet::new));
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+
+    for (var className : classes.keySet()) {
+      if (classes.get(className) != null) { // Already found by other method of searching
+        continue;
+      }
+      var resultPaths = findClassAndChildren(filePaths, className, File.separator, suffix);
+      if (resultPaths.isPresent()) {
+        // [filePaths] and [resultPaths] are relativized to searchPath.
+        // perform opposite operation (resolve) to get full paths.
+        var fullPaths =
+            resultPaths.get().stream().map(searchPath::resolve).collect(Collectors.toList());
+        classes.put(className, mapper.apply(fullPaths));
+      }
+    }
+  }
+
+  public static <E> boolean findFilesInJar(
+      Map<String, List<E>> classes,
+      JarFile jar,
+      String suffix,
+      BiFunction<JarFile, List<ZipEntry>, List<E>> mapper) {
+
+    // It appears JAR file entries are always separated by "/"
+    var jarSeparator = "/";
+    var entryNames =
+        jar.stream().map(JarEntry::getName).collect(Collectors.toCollection(TreeSet::new));
+    boolean foundClassesInThisJar = false;
+    for (var fqn : classes.keySet()) {
+      if (classes.get(fqn) != null) { // already found
+        continue;
+      }
+      var resultPaths = findClassAndChildren(entryNames, fqn, jarSeparator, suffix);
+      if (resultPaths.isPresent()) {
+        var jarEntries = resultPaths.get().stream().map(jar::getEntry).collect(Collectors.toList());
+        classes.put(fqn, mapper.apply(jar, jarEntries));
+        foundClassesInThisJar = true;
+      }
+    }
+    return foundClassesInThisJar;
+  }
+
+  public static <T> void find(
+      Map<String, List<T>> classes,
+      List<String> searchPaths,
+      String suffix,
+      Function<List<Path>, List<T>> fileMapper,
+      BiFunction<JarFile, List<ZipEntry>, List<T>> entryMapper) {
+    for (var searchPath : searchPaths) {
+      File searchFile = new File(searchPath);
+      if (searchFile.isDirectory()) {
+        findFilesInPath(searchPath, suffix, classes, fileMapper);
+      } else if (searchFile.isFile() && searchPath.endsWith(".jar")) {
+        var jarFile = wrapCheckedException(JarFile::new, searchPath);
+        var useful = findFilesInJar(classes, jarFile, suffix, entryMapper);
+        if (!useful) {
+          wrapCheckedException(jarFile::close);
+        }
+      }
+    }
+  }
+
+  private static List<JavaFileObject> getJavaFileObjectsFromFiles(
+      List<Path> paths, StandardJavaFileManager fm) {
+    var result = new ArrayList<JavaFileObject>();
+    var files = StreamUtil.map(paths, Path::toFile);
+    fm.getJavaFileObjectsFromFiles(files).forEach(result::add);
+    return result;
+  }
+
+  private static List<JavaFileObject> getJavaFileObjectsFromJar(
+      JarFile jarFile, List<ZipEntry> entries) {
+    return StreamUtil.map(entries, (entry) -> new JarEntryFileObject(jarFile, entry));
+  }
+
+  private static List<InputStreamProvider> getInputStreamProvidersFromFiles(List<Path> files) {
+    return StreamUtil.map(files, (path) -> new FileInputStreamProvider(path.toFile()));
+  }
+
+  private static List<InputStreamProvider> getInputStreamProvidersFromJar(
+      JarFile jarFile, List<ZipEntry> entries) {
+    return StreamUtil.map(entries, entry -> new JarEntryInputStreamProvider(jarFile, entry));
+  }
+
+  public static void findJavaSources(
+      Map<String, List<JavaFileObject>> classes,
+      List<String> searchPaths,
+      StandardJavaFileManager fm) {
+    find(
+        classes,
+        searchPaths,
+        ".java",
+        files -> getJavaFileObjectsFromFiles(files, fm),
+        ClassFinder::getJavaFileObjectsFromJar);
+  }
+
+  public static void findJavaClasses(
+      Map<String, List<InputStreamProvider>> classes, List<String> searchPaths) {
+    find(
+        classes,
+        searchPaths,
+        ".class",
+        ClassFinder::getInputStreamProvidersFromFiles,
+        ClassFinder::getInputStreamProvidersFromJar);
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ExceptionUtil.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ExceptionUtil.java
new file mode 100644
index 0000000..6b4c8bd
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ExceptionUtil.java
@@ -0,0 +1,33 @@
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+public class ExceptionUtil {
+  @FunctionalInterface
+  public interface CheckedFunction<T, R> {
+    R function(T value) throws Exception;
+  }
+
+  @FunctionalInterface
+  public interface CheckedRunnable {
+    void run() throws Exception;
+  }
+
+  /**
+   * Wraps a function throwing a checked exception and throws all checked exceptions as runtime
+   * exceptions.
+   */
+  public static <T, R> R wrapCheckedException(CheckedFunction<T, R> function, T value) {
+    try {
+      return function.function(value);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public static void wrapCheckedException(CheckedRunnable runnable) {
+    try {
+      runnable.run();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/FileInputStreamProvider.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/FileInputStreamProvider.java
new file mode 100644
index 0000000..da5cadf
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/FileInputStreamProvider.java
@@ -0,0 +1,38 @@
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+import java.io.*;
+
+/** Implementation of InputStreamProvider backed by a File. */
+public class FileInputStreamProvider implements InputStreamProvider {
+  File file;
+  InputStream stream;
+
+  public FileInputStreamProvider(File file) {
+    this.file = file;
+  }
+
+  @Override
+  public InputStream getInputStream() {
+    if (stream == null) {
+      try {
+        stream = new FileInputStream(file);
+      } catch (FileNotFoundException e) {
+        throw new RuntimeException(e);
+      }
+    }
+    return stream;
+  }
+
+  @Override
+  public void close() {
+    if (stream == null) {
+      return;
+    }
+    try {
+      stream.close();
+      stream = null;
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/InputStreamProvider.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/InputStreamProvider.java
new file mode 100644
index 0000000..85ba15b
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/InputStreamProvider.java
@@ -0,0 +1,17 @@
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+import java.io.InputStream;
+
+/**
+ * Implementers of this interface provide an InputStream on-demand for writing, and provide a way to
+ * close the same. <br>
+ * The implementation doesn't need to be thread-safe, since this is only used in AsmSummarizer,
+ * which reads the classes serially.
+ */
+public interface InputStreamProvider {
+  /** Return the input stream, initializing it if needed. */
+  InputStream getInputStream();
+
+  /** close the underlying InputStream. */
+  void close();
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryFileObject.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryFileObject.java
new file mode 100644
index 0000000..49f187f
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryFileObject.java
@@ -0,0 +1,41 @@
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import javax.tools.SimpleJavaFileObject;
+
+/** Implements JavaFileObject for use by Doclet summarizer. */
+class JarEntryFileObject extends SimpleJavaFileObject {
+  JarFile jarFile;
+  String relativePath;
+
+  protected JarEntryFileObject(JarFile jarFile, ZipEntry entry) {
+    super(URI.create(new File(jarFile.getName()).toURI() + "/" + entry.getName()), Kind.SOURCE);
+    this.jarFile = jarFile;
+    this.relativePath = entry.getName();
+  }
+
+  private int getEntrySize(ZipEntry entry) {
+    long limit = 1024L * 1024L * 16L; // Arbitrary limit, how long can be a source file?
+    long size = entry.getSize();
+    return (int) Long.min(size, limit);
+  }
+
+  @Override
+  public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+    var entry = jarFile.getEntry(relativePath);
+    var out = new ByteArrayOutputStream(getEntrySize(entry));
+
+    try (var stream = jarFile.getInputStream(entry)) {
+      stream.transferTo(out);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return out.toString(StandardCharsets.UTF_8);
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryInputStreamProvider.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryInputStreamProvider.java
new file mode 100644
index 0000000..cba3f76
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryInputStreamProvider.java
@@ -0,0 +1,29 @@
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+public class JarEntryInputStreamProvider implements InputStreamProvider {
+
+  private final JarFile jarFile;
+  private final ZipEntry zipEntry;
+
+  public JarEntryInputStreamProvider(JarFile jarFile, ZipEntry zipEntry) {
+    this.jarFile = jarFile;
+    this.zipEntry = zipEntry;
+  }
+
+  @Override
+  public InputStream getInputStream() {
+    try {
+      return jarFile.getInputStream(zipEntry);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public void close() {}
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JsonWriter.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JsonWriter.java
new file mode 100644
index 0000000..112e241
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JsonWriter.java
@@ -0,0 +1,24 @@
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+public class JsonWriter {
+  public static void writeJSON(List<ClassDecl> classes, OutputStream output) {
+    var mapper = new ObjectMapper();
+    Log.info("Writing JSON for %d classes", classes.size());
+    mapper.enable(SerializationFeature.INDENT_OUTPUT);
+    mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+    try {
+      mapper.writeValue(output, classes);
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+    Log.info("Finished");
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/Log.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/Log.java
new file mode 100644
index 0000000..dfb48e1
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/Log.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class Log {
+  private static final Logger logger = Logger.getLogger("ApiSummarizer");
+
+  private static void log(Level level, String format, Object... args) {
+    String formatted = String.format(format, args);
+    logger.log(level, formatted);
+  }
+
+  public static void info(String format, Object... args) {
+    log(Level.INFO, format, args);
+  }
+
+  public static void warning(String format, Object... args) {
+    log(Level.WARNING, format, args);
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/SkipException.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/SkipException.java
new file mode 100644
index 0000000..1f253b0
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/SkipException.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+// Generic skip exception when the code cannot decide how to handle an element.
+// The caller in some above layer can catch this and skip to appropriate extent.
+public class SkipException extends RuntimeException {
+  public SkipException(String message) {
+    super(message);
+  }
+}
diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/StreamUtil.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/StreamUtil.java
new file mode 100644
index 0000000..30a8fa0
--- /dev/null
+++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/StreamUtil.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.apisummarizer.util;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class StreamUtil {
+  public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
+    return list.stream().map(function).collect(Collectors.toList());
+  }
+
+  public static <T, R> List<R> map(T[] array, Function<T, R> function) {
+    return Arrays.stream(array).map(function).collect(Collectors.toList());
+  }
+
+  public static <K, E> List<E> flattenListValues(Map<K, List<E>> map) {
+    return map.values().stream()
+        .filter(Objects::nonNull)
+        .flatMap(Collection::stream)
+        .collect(Collectors.toList());
+  }
+}
diff --git a/pkgs/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/ClassFinderTest.java b/pkgs/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/ClassFinderTest.java
new file mode 100644
index 0000000..4209e3a
--- /dev/null
+++ b/pkgs/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/ClassFinderTest.java
@@ -0,0 +1,95 @@
+package com.github.dart_lang.jnigen.apisummarizer;
+
+import static com.github.dart_lang.jnigen.apisummarizer.util.ClassFinder.findClassAndChildren;
+import static com.github.dart_lang.jnigen.apisummarizer.util.ClassFinder.isNestedClassOf;
+import static org.junit.Assert.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+public class ClassFinderTest {
+  @Test
+  public void testNestedClassCheck() {
+    assertTrue(
+        "Single nested class",
+        isNestedClassOf("com/abc/Class$Nested.class", "com/abc/Class", ".class"));
+    assertTrue(
+        "Nested twice",
+        isNestedClassOf("com/abc/Class$Nested$Twice.class", "com/abc/Class", ".class"));
+    assertTrue(
+        "Single nested class - backslash separator",
+        isNestedClassOf("com\\abc\\Class$Nested.class", "com\\abc\\Class", ".class"));
+    assertFalse(
+        "Anon inner class", isNestedClassOf("com/abc/Class$1.class", "com/abc/Class", ".class"));
+    assertFalse(
+        "Anon inner class inside nested class",
+        isNestedClassOf("com/abc/Class$Nested$1.class", "com/abc/Class", ".class"));
+    assertFalse(
+        "Different class name",
+        isNestedClassOf("com/abc/AClass$Nested.class", "com/abc/Class", ".class"));
+  }
+
+  private Optional<List<String>> pathListOf(String sep, String... paths) {
+    List<String> pathList =
+        Arrays.stream(paths).map(path -> path.replace("/", sep)).collect(Collectors.toList());
+    return Optional.of(pathList);
+  }
+
+  @Test
+  public void testFindChildren() {
+    TreeSet<String> entriesWithSlash =
+        new TreeSet<>(
+            List.of(
+                "random/os/App.class",
+                "random/os/App$1.class",
+                "random/os/App$1$3.class",
+                "random/os/Process.class",
+                "random/os/Process$Fork.class",
+                "random/os/Process$Fork$1.class",
+                "random/os/Process$Fork$A$B$C$2.class",
+                "random/widget/Dialog.class",
+                "random/widget/Dialog$Button.class",
+                "random/widget/Dialog$Button$2.class",
+                "random/widget/Dialog$Button$Color.class",
+                "random/widget/Dialogue$Button.class",
+                "random/time/Clock.class",
+                "random/time/Clock$1.class",
+                "random/time/Calendar.class",
+                "random/time/Calendar$Month$1.class",
+                "random/time/Calendar$Month.class"));
+    TreeSet<String> entriesWithBackslash =
+        entriesWithSlash.stream()
+            .map(x -> x.replace('/', '\\'))
+            .collect(Collectors.toCollection(TreeSet::new));
+    Map<String, TreeSet<String>> bySeparater =
+        Map.of("/", entriesWithSlash, "\\", entriesWithBackslash);
+
+    for (var sep : bySeparater.keySet()) {
+      var entries = bySeparater.get(sep);
+      assertEquals(
+          pathListOf(sep, "random/os/Process$Fork.class", "random/os/Process.class"),
+          findClassAndChildren(entries, "random.os.Process", sep, ".class"));
+      assertEquals(
+          pathListOf(
+              sep,
+              "random/time/Calendar$Month.class",
+              "random/time/Calendar.class",
+              "random/time/Clock.class"),
+          findClassAndChildren(entries, "random.time", sep, ".class"));
+      assertEquals(
+          pathListOf(
+              sep,
+              "random/widget/Dialog$Button$Color.class",
+              "random/widget/Dialog$Button.class",
+              "random/widget/Dialog.class"),
+          findClassAndChildren(entries, "random.widget.Dialog", sep, ".class"));
+      assertEquals(
+          pathListOf(sep, "random/os/App.class"),
+          findClassAndChildren(entries, "random.os.App", sep, ".class"));
+      assertEquals(
+          pathListOf(sep, "random/os/App.class"),
+          findClassAndChildren(entries, "random.os.App", sep, ".class"));
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/JSONComparisonTest.java b/pkgs/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/JSONComparisonTest.java
new file mode 100644
index 0000000..b02ee15
--- /dev/null
+++ b/pkgs/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/JSONComparisonTest.java
@@ -0,0 +1,53 @@
+package com.github.dart_lang.jnigen.apisummarizer;
+
+import com.github.dart_lang.jnigen.apisummarizer.util.Log;
+import java.io.File;
+import java.io.IOException;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JSONComparisonTest {
+  static final File exampleClassJsonOutput =
+      new File("src/test/resources/exampleClassSummary.json");
+
+  @SuppressWarnings("SameParameterValue")
+  private int gitDiff(File a, File b) throws IOException, InterruptedException {
+    return gitDiff(a.getPath(), b.getPath());
+  }
+
+  private int gitDiff(String a, String b) throws IOException, InterruptedException {
+    String colorSupport = "--color=never";
+    if (System.console() != null && System.getenv().get("TERM") != null) {
+      colorSupport = "--color=always";
+    }
+    String[] compareCommand = {
+      "git", "diff", colorSupport, "--no-index", a, b,
+    };
+    var compare = Runtime.getRuntime().exec(compareCommand);
+    compare.getErrorStream().transferTo(System.err);
+    compare.getInputStream().transferTo(System.out);
+    return compare.waitFor();
+  }
+
+  @Test
+  public void testExampleSummary() throws IOException, InterruptedException {
+    var tempFile = File.createTempFile("summarizer_test", ".json");
+    Log.info("Temporary file: %s", tempFile.getPath());
+    Main.main(
+        new String[] {
+          "-s", "src/test/resources", "com.example.Example", "-o", tempFile.getPath(),
+        });
+    int comparison = gitDiff(exampleClassJsonOutput, tempFile);
+    if (comparison != 0) {
+      Log.warning("New output (%s) is different than reference output.", tempFile.getPath());
+    }
+
+    // Fail test if git diff exited with 1
+    Assert.assertEquals(0, comparison);
+
+    var deleted = tempFile.delete();
+    if (!deleted) {
+      Log.warning("Cannot delete temp file %s", tempFile.getPath());
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/test/resources/com/example/Example.java b/pkgs/jnigen/java/src/test/resources/com/example/Example.java
new file mode 100644
index 0000000..b5856a2
--- /dev/null
+++ b/pkgs/jnigen/java/src/test/resources/com/example/Example.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.example;
+
+public class Example {
+  public static final boolean staticFinalField = true;
+
+  public Example(int instanceField) {
+    this.instanceField = instanceField;
+  }
+
+  public static String staticField = "hello";
+
+  public static String getStaticField() {
+    return staticField;
+  }
+
+  public int instanceField;
+
+  public int getInstanceField() {
+    return instanceField;
+  }
+
+  protected int overrideableMethod(int x, int y) {}
+
+  int defaultAccessNotVisible();
+
+  private int privateAccessNotVisible();
+
+  public static class Aux extends Example {
+    public static int nothing = 0;
+
+    public static Example getAnExample() {
+      return new Example();
+    }
+  }
+}
diff --git a/pkgs/jnigen/java/src/test/resources/exampleClassSummary.json b/pkgs/jnigen/java/src/test/resources/exampleClassSummary.json
new file mode 100644
index 0000000..6786a26
--- /dev/null
+++ b/pkgs/jnigen/java/src/test/resources/exampleClassSummary.json
@@ -0,0 +1,185 @@
+[ {
+  "declKind" : "CLASS",
+  "modifiers" : [ "public" ],
+  "binaryName" : "com.example.Example",
+  "methods" : [ {
+    "modifiers" : [ "public" ],
+    "name" : "<init>",
+    "params" : [ {
+      "name" : "instanceField",
+      "type" : {
+        "shorthand" : "int",
+        "kind" : "PRIMITIVE",
+        "type" : {
+          "name" : "int"
+        }
+      }
+    } ],
+    "returnType" : {
+      "shorthand" : "void",
+      "kind" : "PRIMITIVE",
+      "type" : {
+        "name" : "void"
+      }
+    }
+  }, {
+    "modifiers" : [ "static", "public" ],
+    "name" : "getStaticField",
+    "returnType" : {
+      "shorthand" : "java.lang.String",
+      "kind" : "DECLARED",
+      "type" : {
+        "binaryName" : "java.lang.String",
+        "simpleName" : "String"
+      }
+    }
+  }, {
+    "modifiers" : [ "public" ],
+    "name" : "getInstanceField",
+    "returnType" : {
+      "shorthand" : "int",
+      "kind" : "PRIMITIVE",
+      "type" : {
+        "name" : "int"
+      }
+    }
+  }, {
+    "modifiers" : [ "protected" ],
+    "name" : "overrideableMethod",
+    "params" : [ {
+      "name" : "x",
+      "type" : {
+        "shorthand" : "int",
+        "kind" : "PRIMITIVE",
+        "type" : {
+          "name" : "int"
+        }
+      }
+    }, {
+      "name" : "y",
+      "type" : {
+        "shorthand" : "int",
+        "kind" : "PRIMITIVE",
+        "type" : {
+          "name" : "int"
+        }
+      }
+    } ],
+    "returnType" : {
+      "shorthand" : "int",
+      "kind" : "PRIMITIVE",
+      "type" : {
+        "name" : "int"
+      }
+    }
+  }, {
+    "name" : "defaultAccessNotVisible",
+    "returnType" : {
+      "shorthand" : "int",
+      "kind" : "PRIMITIVE",
+      "type" : {
+        "name" : "int"
+      }
+    }
+  }, {
+    "modifiers" : [ "private" ],
+    "name" : "privateAccessNotVisible",
+    "returnType" : {
+      "shorthand" : "int",
+      "kind" : "PRIMITIVE",
+      "type" : {
+        "name" : "int"
+      }
+    }
+  } ],
+  "fields" : [ {
+    "modifiers" : [ "static", "public", "final" ],
+    "name" : "staticFinalField",
+    "type" : {
+      "shorthand" : "boolean",
+      "kind" : "PRIMITIVE",
+      "type" : {
+        "name" : "boolean"
+      }
+    },
+    "defaultValue" : true
+  }, {
+    "modifiers" : [ "static", "public" ],
+    "name" : "staticField",
+    "type" : {
+      "shorthand" : "java.lang.String",
+      "kind" : "DECLARED",
+      "type" : {
+        "binaryName" : "java.lang.String",
+        "simpleName" : "String"
+      }
+    }
+  }, {
+    "modifiers" : [ "public" ],
+    "name" : "instanceField",
+    "type" : {
+      "shorthand" : "int",
+      "kind" : "PRIMITIVE",
+      "type" : {
+        "name" : "int"
+      }
+    }
+  } ],
+  "superclass" : {
+    "shorthand" : "java.lang.Object",
+    "kind" : "DECLARED",
+    "type" : {
+      "binaryName" : "java.lang.Object",
+      "simpleName" : "Object"
+    }
+  },
+  "hasStaticInit" : false,
+  "hasInstanceInit" : false
+}, {
+  "declKind" : "CLASS",
+  "modifiers" : [ "static", "public" ],
+  "binaryName" : "com.example.Example$Aux",
+  "methods" : [ {
+    "modifiers" : [ "public" ],
+    "name" : "<init>",
+    "returnType" : {
+      "shorthand" : "void",
+      "kind" : "PRIMITIVE",
+      "type" : {
+        "name" : "void"
+      }
+    }
+  }, {
+    "modifiers" : [ "static", "public" ],
+    "name" : "getAnExample",
+    "returnType" : {
+      "shorthand" : "com.example.Example",
+      "kind" : "DECLARED",
+      "type" : {
+        "binaryName" : "com.example.Example",
+        "simpleName" : "Example"
+      }
+    }
+  } ],
+  "fields" : [ {
+    "modifiers" : [ "static", "public" ],
+    "name" : "nothing",
+    "type" : {
+      "shorthand" : "int",
+      "kind" : "PRIMITIVE",
+      "type" : {
+        "name" : "int"
+      }
+    }
+  } ],
+  "superclass" : {
+    "shorthand" : "com.example.Example",
+    "kind" : "DECLARED",
+    "type" : {
+      "binaryName" : "com.example.Example",
+      "simpleName" : "Example"
+    }
+  },
+  "hasStaticInit" : false,
+  "hasInstanceInit" : false
+} ]
\ No newline at end of file
diff --git a/pkgs/jnigen/lib/jnigen.dart b/pkgs/jnigen/lib/jnigen.dart
new file mode 100644
index 0000000..8e2ebe0
--- /dev/null
+++ b/pkgs/jnigen/lib/jnigen.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This library exports a high level programmatic API to jnigen, the entry
+/// point of which is runJniGenTask function, which takes run configuration as
+/// a JniGenTask.
+library jnigen;
+
+export 'src/elements/elements.dart';
+export 'src/config/config.dart';
+export 'src/config/filters.dart';
+export 'src/generate_bindings.dart';
diff --git a/pkgs/jnigen/lib/src/bindings/c_generator.dart b/pkgs/jnigen/lib/src/bindings/c_generator.dart
new file mode 100644
index 0000000..79b07c8
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/c_generator.dart
@@ -0,0 +1,383 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import '../config/config.dart';
+import '../elements/elements.dart';
+import '../logging/logging.dart';
+import '../util/find_package.dart';
+import '../util/string_util.dart';
+import 'visitor.dart';
+
+class CFieldName extends Visitor<Field, String> {
+  const CFieldName();
+
+  @override
+  String visit(Field node) {
+    final className = node.classDecl.uniqueName;
+    final fieldName = node.finalName;
+    return '${className}__$fieldName';
+  }
+}
+
+class CMethodName extends Visitor<Method, String> {
+  const CMethodName();
+
+  @override
+  String visit(Method node) {
+    final className = node.classDecl.uniqueName;
+    final methodName = node.finalName;
+    return '${className}__$methodName';
+  }
+}
+
+class CGenerator extends Visitor<Classes, Future<void>> {
+  static const _prelude = '''// Autogenerated by jnigen. DO NOT EDIT!
+
+#include <stdint.h>
+#include "jni.h"
+#include "dartjni.h"
+
+thread_local JNIEnv *jniEnv;
+JniContext *jni;
+
+JniContext *(*context_getter)(void);
+JNIEnv *(*env_getter)(void);
+
+void setJniGetters(JniContext *(*cg)(void),
+        JNIEnv *(*eg)(void)) {
+    context_getter = cg;
+    env_getter = eg;
+}
+
+''';
+
+  final Config config;
+
+  CGenerator(this.config);
+
+  Future<void> _copyFileFromPackage(String package, String relPath, Uri target,
+      {String Function(String)? transform}) async {
+    final packagePath = await findPackageRoot(package);
+    if (packagePath != null) {
+      final sourceFile = File.fromUri(packagePath.resolve(relPath));
+      final targetFile = await File.fromUri(target).create(recursive: true);
+      var source = await sourceFile.readAsString();
+      if (transform != null) {
+        source = transform(source);
+      }
+      await targetFile.writeAsString(source);
+    } else {
+      log.warning('package $package not found! '
+          'skipped copying ${target.toFilePath()}');
+    }
+  }
+
+  @override
+  Future<void> visit(Classes node) async {
+    // Write C file and init file.
+    final cConfig = config.outputConfig.cConfig!;
+    final cRoot = cConfig.path;
+    final preamble = config.preamble;
+    log.info("Using c root = $cRoot");
+    final libraryName = cConfig.libraryName;
+    log.info('Creating dart init file ...');
+    // Create C file.
+    final subdir = cConfig.subdir ?? '.';
+    final cFileRelativePath = '$subdir/$libraryName.c';
+    final cFile = await File.fromUri(cRoot.resolve(cFileRelativePath))
+        .create(recursive: true);
+    final cFileStream = cFile.openWrite();
+    // Write C Bindings.
+    if (preamble != null) {
+      cFileStream.writeln(preamble);
+    }
+    cFileStream.write(_prelude);
+    final classGenerator = _CClassGenerator(config, cFileStream);
+    for (final classDecl in node.decls.values) {
+      classDecl.accept(classGenerator);
+    }
+    await cFileStream.close();
+    log.info('Copying auxiliary files...');
+    for (final file in ['dartjni.h', '.clang-format']) {
+      await _copyFileFromPackage(
+          'jni', 'src/$file', cRoot.resolve('$subdir/$file'));
+    }
+    await _copyFileFromPackage(
+        'jnigen', 'cmake/CMakeLists.txt.tmpl', cRoot.resolve('CMakeLists.txt'),
+        transform: (s) {
+      return s
+          .replaceAll('{{LIBRARY_NAME}}', libraryName)
+          .replaceAll('{{SUBDIR}}', subdir);
+    });
+    log.info('Running clang-format on C bindings');
+    try {
+      final clangFormat = Process.runSync('clang-format', ['-i', cFile.path]);
+      if (clangFormat.exitCode != 0) {
+        printError(clangFormat.stderr);
+        log.warning('clang-format exited with ${clangFormat.exitCode}');
+      }
+    } on ProcessException catch (e) {
+      log.warning('cannot run clang-format: $e');
+    }
+  }
+}
+
+const _classVarPrefix = '_c_';
+const _jniResultType = 'JniResult';
+const _loadEnvCall = 'load_env();';
+const _ifError =
+    '(JniResult){.value = {.j = 0}, .exception = check_exception()}';
+
+class _CClassGenerator extends Visitor<ClassDecl, void> {
+  final Config config;
+  final StringSink s;
+
+  _CClassGenerator(this.config, this.s);
+
+  @override
+  void visit(ClassDecl node) {
+    final classNameInC = node.uniqueName;
+    final classVar = '$_classVarPrefix$classNameInC';
+    // Global variable in C that holds the reference to class.
+    s.write('''// ${node.binaryName}
+jclass $classVar = NULL;
+
+''');
+
+    final methodGenerator = _CMethodGenerator(config, s);
+    for (final method in node.methods) {
+      method.accept(methodGenerator);
+    }
+
+    final fieldGenerator = _CFieldGenerator(config, s);
+    for (final field in node.fields) {
+      field.accept(fieldGenerator);
+    }
+  }
+}
+
+class _CLoadClassGenerator extends Visitor<ClassDecl, String> {
+  _CLoadClassGenerator();
+
+  @override
+  String visit(ClassDecl node) {
+    final classVar = '$_classVarPrefix${node.uniqueName}';
+    return '''    load_class_global_ref(&$classVar, "${node.internalName}");
+    if ($classVar == NULL) return $_ifError;''';
+  }
+}
+
+class _CMethodGenerator extends Visitor<Method, void> {
+  static const _methodVarPrefix = '_m_';
+
+  final Config config;
+  final StringSink s;
+
+  _CMethodGenerator(this.config, this.s);
+
+  @override
+  void visit(Method node) {
+    final classNameInC = node.classDecl.uniqueName;
+
+    final cMethodName = node.accept(const CMethodName());
+    final classRef = '$_classVarPrefix$classNameInC';
+    final methodId = '$_methodVarPrefix$cMethodName';
+    final cMethodParams = [
+      if (!node.isCtor && !node.isStatic) 'jobject self_',
+      ...node.params.accept(const _CParamGenerator(addReturnType: true)),
+    ].join(',');
+    final jniSignature = node.descriptor;
+    final ifStaticMethodID = node.isStatic ? 'static_' : '';
+
+    var javaReturnType = node.returnType.type;
+    if (node.isCtor) {
+      javaReturnType = DeclaredType(
+        binaryName: node.classDecl.binaryName,
+      );
+    }
+    final callType = node.returnType.accept(const _CTypeCallSite());
+    final callArgs = [
+      'jniEnv',
+      if (!node.isCtor && !node.isStatic) 'self_' else classRef,
+      methodId,
+      ...node.params.accept(const _CParamGenerator(addReturnType: false))
+    ].join(', ');
+
+    var ifAssignResult = '';
+    if (javaReturnType.name != 'void') {
+      ifAssignResult =
+          '${javaReturnType.accept(const _CReturnType())} _result = ';
+    }
+
+    final ifStaticCall = node.isStatic ? 'Static' : '';
+    final envMethod =
+        node.isCtor ? 'NewObject' : 'Call$ifStaticCall${callType}Method';
+    final returnResultIfAny = javaReturnType.accept(const _CResult());
+    s.write('''
+jmethodID $methodId = NULL;
+FFI_PLUGIN_EXPORT
+$_jniResultType $cMethodName($cMethodParams) {
+    $_loadEnvCall
+    ${node.classDecl.accept(_CLoadClassGenerator())}
+    load_${ifStaticMethodID}method($classRef,
+      &$methodId, "${node.name}", "$jniSignature");
+    if ($methodId == NULL) return $_ifError;
+    $ifAssignResult(*jniEnv)->$envMethod($callArgs);
+    $returnResultIfAny
+}
+
+''');
+  }
+}
+
+class _CFieldGenerator extends Visitor<Field, void> {
+  static const _fieldVarPrefix = '_f_';
+
+  final Config config;
+  final StringSink s;
+
+  _CFieldGenerator(this.config, this.s);
+
+  @override
+  void visit(Field node) {
+    final cClassName = node.classDecl.uniqueName;
+
+    final fieldName = node.finalName;
+    final fieldNameInC = node.accept(const CFieldName());
+    final fieldVar = "$_fieldVarPrefix$fieldNameInC";
+
+    // If the field is final and default is assigned, then no need to wrap
+    // this field. It should then be a constant in dart code.
+    if (node.isStatic && node.isFinal && node.defaultValue != null) {
+      return;
+    }
+
+    s.write('jfieldID $fieldVar = NULL;\n');
+
+    final classVar = '$_classVarPrefix$cClassName';
+    void writeAccessor({bool isSetter = false}) {
+      const cReturnType = _jniResultType;
+      final cMethodPrefix = isSetter ? 'set' : 'get';
+      final formalArgs = [
+        if (!node.isStatic) 'jobject self_',
+        if (isSetter) '${node.type.accept(const _CReturnType())} value',
+      ].join(', ');
+      final ifStaticField = node.isStatic ? 'static_' : '';
+      final ifStaticCall = node.isStatic ? 'Static' : '';
+      final callType = node.type.accept(const _CTypeCallSite());
+      final objectArgument = node.isStatic ? classVar : 'self_';
+
+      String accessorStatements;
+      if (isSetter) {
+        accessorStatements =
+            '    (*jniEnv)->Set$ifStaticCall${callType}Field(jniEnv, '
+            '$objectArgument, $fieldVar, value);\n'
+            '    return $_ifError;';
+      } else {
+        final getterExpr =
+            '(*jniEnv)->Get$ifStaticCall${callType}Field(jniEnv, '
+            '$objectArgument, $fieldVar)';
+        final cResultType = node.type.accept(const _CReturnType());
+        final result = node.type.accept(const _CResult());
+        accessorStatements = '''    $cResultType _result = $getterExpr;
+    $result''';
+      }
+      s.write('''
+FFI_PLUGIN_EXPORT
+$cReturnType ${cMethodPrefix}_$fieldNameInC($formalArgs) {
+    $_loadEnvCall
+    ${node.classDecl.accept(_CLoadClassGenerator())}
+    load_${ifStaticField}field($classVar, &$fieldVar, "$fieldName",
+      "${node.type.descriptor}");
+$accessorStatements
+}
+
+''');
+    }
+
+    writeAccessor(isSetter: false);
+    if (node.isFinal) {
+      return;
+    }
+    writeAccessor(isSetter: true);
+  }
+}
+
+class _CParamGenerator extends Visitor<Param, String> {
+  /// These should be avoided in parameter names.
+  static const _cTypeKeywords = {
+    'short',
+    'char',
+    'int',
+    'long',
+    'float',
+    'double',
+  };
+
+  const _CParamGenerator({required this.addReturnType});
+
+  final bool addReturnType;
+
+  @override
+  String visit(Param node) {
+    final paramName =
+        (_cTypeKeywords.contains(node.name) ? '${node.name}0' : node.name)
+            .replaceAll('\$', '_');
+    if (addReturnType) {
+      final type = node.type.accept(const _CReturnType());
+      return '$type $paramName';
+    }
+    return paramName;
+  }
+}
+
+class _CReturnType extends TypeVisitor<String> {
+  const _CReturnType();
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return 'jobject';
+  }
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    return node.cType;
+  }
+}
+
+class _CTypeCallSite extends TypeVisitor<String> {
+  const _CTypeCallSite();
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return 'Object';
+  }
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    return node.name.capitalize();
+  }
+}
+
+class _CResult extends TypeVisitor<String> {
+  const _CResult();
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return 'return to_global_ref_result(_result);';
+  }
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    if (node.name == 'void') {
+      return 'return $_ifError;';
+    }
+    // The union field is the same as the type's signature, but in lowercase.
+    final unionField = node.signature.toLowerCase();
+    return 'return (JniResult){.value = {.$unionField = _result}, '
+        '.exception = check_exception()};';
+  }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
new file mode 100644
index 0000000..b202293
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
@@ -0,0 +1,1732 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:meta/meta.dart';
+
+import '../config/config.dart';
+import '../config/experiments.dart';
+import '../elements/elements.dart';
+import '../logging/logging.dart';
+import '../util/string_util.dart';
+import 'c_generator.dart';
+import 'resolver.dart';
+import 'visitor.dart';
+
+// Import prefixes.
+const _jni = 'jni';
+const _ffi = 'ffi';
+
+// package:jni types.
+const _jType = '$_jni.JObjType';
+const _jPointer = '$_jni.JObjectPtr';
+const _jArray = '$_jni.JArray';
+const _jObject = '$_jni.JObject';
+const _jResult = '$_jni.JniResult';
+const _jCallType = '$_jni.JniCallType';
+
+// package:ffi types.
+const _voidPointer = '$_ffi.Pointer<$_ffi.Void>';
+
+// Prefixes and suffixes.
+const _typeParamPrefix = '\$';
+
+// Misc.
+const _protectedExtension = 'ProtectedJniExtensions';
+const _classRef = '_class.reference';
+const _env = '$_jni.Jni.env';
+const _accessors = '$_jni.Jni.accessors';
+const _lookup = 'jniLookup';
+const _selfPointer = 'reference';
+
+// Docs.
+const _releaseInstruction =
+    '  /// The returned object must be released after use, '
+    'by calling the [release] method.';
+
+extension on Iterable<String> {
+  /// Similar to [join] but adds the [separator] to the end as well.
+  String delimited([String separator = '']) {
+    return map((e) => '$e$separator').join();
+  }
+}
+
+/// Encloses [inside] in the middle of [open] and [close]
+/// if [inside] is not empty.
+String _encloseIfNotEmpty(String open, String inside, String close) {
+  if (inside == '') return '';
+  return '$open$inside$close';
+}
+
+String _newLine({int depth = 0}) {
+  return '\n${'  ' * depth}';
+}
+
+/// Merges two maps. For the same keys, their value lists will be concatenated.
+///
+/// ** After calling this, the original maps might get modified! **
+Map<K, List<V>> _mergeMapValues<K, V>(Map<K, List<V>> a, Map<K, List<V>> b) {
+  final merged = <K, List<V>>{};
+  for (final key in {...a.keys, ...b.keys}) {
+    if (!a.containsKey(key)) {
+      merged[key] = b[key]!;
+      continue;
+    }
+    if (!b.containsKey(key)) {
+      merged[key] = a[key]!;
+      continue;
+    }
+
+    // Merging the smaller one to the bigger one
+    if (a[key]!.length > b[key]!.length) {
+      merged[key] = a[key]!;
+      merged[key]!.addAll(b[key]!);
+    } else {
+      merged[key] = b[key]!;
+      merged[key]!.addAll(a[key]!);
+    }
+  }
+  return merged;
+}
+
+/// **Naming Convention**
+///
+/// Let's take the following code as an example:
+///
+/// ```dart
+/// Method definition
+/// void f<T extends num, U>(JType<T> $T, JType<U> $U, T t, U u) {
+///   // ...
+/// }
+/// f<int, String>($T, $U, t, u); // Calling the Method
+/// ```
+///
+/// Here `f` will be replaced according to the place of usage.
+///
+/// * `fArgsDef` refers to `T t, U u` – the arguments in the method
+///   definition.
+/// * `fArgsCall` refer to `t, u` – the arguments passed to call the method.
+/// * `fTypeParamsDef` refers to `<T extends num, U>` – the type parameters
+///   of the method at the point of definition.
+/// * `fTypeParamsCall` refers to `<int, String>` – the type parameters when
+///   calling, or whenever we don't want to have the `extends` keyword.
+/// * `fTypeClassesDef` refers to `JType<T> $T, JType<U> $U`.
+/// * `fTypeClassesCall` refers to `$T, $U` when calling the method.
+class DartGenerator extends Visitor<Classes, Future<void>> {
+  final Config config;
+
+  DartGenerator(this.config);
+
+  static const cInitImport = 'import "dart:ffi" as ffi;\n'
+      'import "package:jni/internal_helpers_for_jnigen.dart";\n';
+
+  /// Initialization code for C based bindings.
+  ///
+  /// Should be called once in a package. In package-structured bindings
+  /// this is placed in _init.dart in package root.
+  String get cInitCode => '''
+// Auto-generated initialization code.
+
+final $_ffi.Pointer<T> Function<T extends $_ffi.NativeType>(String sym) $_lookup =
+    $_protectedExtension.initGeneratedLibrary("${config.outputConfig.cConfig!.libraryName}");
+
+
+''';
+
+  static const autoGeneratedNotice = '// Autogenerated by jnigen. '
+      'DO NOT EDIT!\n\n';
+  static const defaultImports = '''
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+''';
+
+  // Sort alphabetically.
+  static const defaultLintSuppressions = '''
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+''';
+  static const preImportBoilerplate =
+      autoGeneratedNotice + defaultLintSuppressions + defaultImports;
+
+  /// Run dart format command on [path].
+  Future<void> _runDartFormat(String path) async {
+    log.info('Running dart format...');
+    final formatRes = await Process.run('dart', ['format', path]);
+    // if negative exit code, likely due to an interrupt.
+    if (formatRes.exitCode > 0) {
+      log.fatal('Dart format completed with exit code ${formatRes.exitCode} '
+          'This usually means there\'s a syntax error in bindings.\n'
+          'Please look at the generated files and report a bug: \n'
+          'https://github.com/dart-lang/jnigen/issues/new\n');
+    }
+  }
+
+  @override
+  Future<void> visit(Classes node) async {
+    final cBased = config.outputConfig.bindingsType == BindingsType.cBased;
+    final root = config.outputConfig.dartConfig.path;
+    final preamble = config.preamble ?? '';
+    if (config.outputConfig.dartConfig.structure ==
+        OutputStructure.singleFile) {
+      final file = File.fromUri(root);
+      await file.create(recursive: true);
+      log.info("Generating ${cBased ? "C + Dart" : "Pure Dart"} Bindings");
+      final s = file.openWrite();
+      s.writeln(preamble);
+      s.writeln(autoGeneratedNotice);
+      s.writeln(defaultLintSuppressions);
+      s.writeln(defaultImports);
+      if (cBased) {
+        s.writeln(cInitCode);
+      }
+      final resolver = Resolver(
+        importedClasses: config.importedClasses,
+        currentClass: null, // Single file mode.
+        inputClassNames: node.decls.keys.toSet(),
+      );
+      final classGenerator = _ClassGenerator(config, s, resolver);
+      for (final classDecl in node.decls.values) {
+        classDecl.accept(classGenerator);
+      }
+      await s.close();
+      await _runDartFormat(file.path);
+      return;
+    }
+    final files = <String, List<ClassDecl>>{};
+    final packages = <String, Set<String>>{};
+    for (final classDecl in node.decls.values) {
+      final fileClass = Resolver.getFileClassName(classDecl.binaryName);
+
+      files.putIfAbsent(fileClass, () => <ClassDecl>[]);
+      files[fileClass]!.add(classDecl);
+
+      packages.putIfAbsent(classDecl.packageName, () => {});
+      packages[classDecl.packageName]!.add(fileClass.split('.').last);
+    }
+
+    log.info("Using dart root = $root");
+    const initFileName = '_init.dart';
+    if (cBased) {
+      final initFileUri = root.resolve(initFileName);
+      final initFile = File.fromUri(initFileUri);
+      await initFile.create(recursive: true);
+      final initStream = initFile.openWrite();
+      initStream.writeln(preamble);
+      initStream.writeln(cInitImport);
+      initStream.writeln(cInitCode);
+      await initStream.close();
+    }
+    for (var fileClassName in files.keys) {
+      final relativeFileName = '${fileClassName.replaceAll('.', '/')}.dart';
+      final dartFileUri = root.resolve(relativeFileName);
+      final dartFile = await File.fromUri(dartFileUri).create(recursive: true);
+      log.fine('$fileClassName -> ${dartFile.path}');
+
+      final classesInFile = files[fileClassName]!;
+      final dartFileStream = dartFile.openWrite();
+      dartFileStream.writeln(preamble);
+      dartFileStream.writeln(autoGeneratedNotice);
+      dartFileStream.writeln(defaultLintSuppressions);
+      dartFileStream.writeln(defaultImports);
+      final s = StringBuffer();
+      if (cBased) {
+        final initFilePath = ('../' *
+                relativeFileName.codeUnits
+                    .where((cu) => cu == '/'.codeUnitAt(0))
+                    .length) +
+            initFileName;
+        s.write('import "$initFilePath";');
+      }
+      final resolver = Resolver(
+        importedClasses: config.importedClasses,
+        currentClass: fileClassName,
+        inputClassNames: node.decls.keys.toSet(),
+      );
+      final classGenerator = _ClassGenerator(config, s, resolver);
+      for (final classDecl in classesInFile) {
+        classDecl.accept(classGenerator);
+      }
+      dartFileStream.writeAll(resolver.getImportStrings(), '\n');
+      dartFileStream.writeln(s.toString());
+      await dartFileStream.close();
+    }
+
+    // write _package.dart export files
+    for (var package in packages.keys) {
+      final dirUri = root.resolve('${package.replaceAll('.', '/')}/');
+      final exportFileUri = dirUri.resolve("_package.dart");
+      final exportFile = File.fromUri(exportFileUri);
+      exportFile.createSync(recursive: true);
+      final exports =
+          packages[package]!.map((cls) => 'export "$cls.dart";').join('\n');
+      exportFile.writeAsStringSync(exports);
+    }
+    await _runDartFormat(root.toFilePath());
+    log.info('Completed.');
+  }
+}
+
+/// Generates the Dart class definition, type class, and the array extension.
+class _ClassGenerator extends Visitor<ClassDecl, void> {
+  final Config config;
+  final StringSink s;
+  final Resolver resolver;
+
+  _ClassGenerator(
+    this.config,
+    this.s,
+    this.resolver,
+  );
+
+  static const staticTypeGetter = 'type';
+  static const instanceTypeGetter = '\$$staticTypeGetter';
+
+  @override
+  void visit(ClassDecl node) {
+    final isDartOnly =
+        config.outputConfig.bindingsType == BindingsType.dartOnly;
+
+    // Docs.
+    s.write('/// from: ${node.binaryName}\n');
+    node.javadoc?.accept(_DocGenerator(s, depth: 0));
+
+    // Class definition.
+    final name = node.finalName;
+    final superName = node.superclass!.accept(_TypeGenerator(resolver));
+    final implClassName = '\$${name}Impl';
+    final typeParamsDef = _encloseIfNotEmpty(
+      '<',
+      node.allTypeParams
+          .accept(const _TypeParamGenerator(withExtends: true))
+          .join(', '),
+      '>',
+    );
+    final typeParams = node.allTypeParams
+        .accept(const _TypeParamGenerator(withExtends: false));
+    final typeParamsCall = _encloseIfNotEmpty(
+      '<',
+      typeParams.map((typeParam) => '$_typeParamPrefix$typeParam').join(', '),
+      '>',
+    );
+    final staticTypeGetterCallArgs = _encloseIfNotEmpty(
+      '(',
+      typeParams.join(', '),
+      ')',
+    );
+    final typeClassesDef = typeParams
+        .map((typeParam) =>
+            'final $_jType<$_typeParamPrefix$typeParam> $typeParam;')
+        .join(_newLine(depth: 1));
+    final ctorTypeClassesDef = typeParams
+        .map((typeParam) => 'this.$typeParam,')
+        .join(_newLine(depth: 2));
+    final superClass = (node.classDecl.superclass!.type as DeclaredType);
+    final superTypeClassesCall = superClass.classDecl.isObject
+        ? ''
+        : superClass.params
+            .accept(_TypeClassGenerator(resolver))
+            .map((typeClass) => '${typeClass.name},')
+            .join(_newLine(depth: 2));
+    s.write('''
+class $name$typeParamsDef extends $superName {
+  @override
+  late final $_jType<$name$typeParamsCall> $instanceTypeGetter = $staticTypeGetter$staticTypeGetterCallArgs;
+
+  $typeClassesDef
+
+  $name.fromRef(
+    $ctorTypeClassesDef
+    $_jPointer ref,
+  ): super.fromRef(
+    $superTypeClassesCall
+    ref
+  );
+
+''');
+
+    if (isDartOnly) {
+      final internalName = node.internalName;
+      s.write('''
+  static final _class = $_jni.Jni.findJClass(r"$internalName");
+
+''');
+    }
+
+    // Static TypeClass getter.
+    s.writeln(
+        '  /// The type which includes information such as the signature of this class.');
+    final typeClassName = node.typeClassName;
+    if (typeParams.isEmpty) {
+      s.writeln('static const $staticTypeGetter = $typeClassName();');
+    } else {
+      final staticTypeGetterTypeClassesDef = typeParams
+          .map(
+              (typeParam) => '$_jType<$_typeParamPrefix$typeParam> $typeParam,')
+          .join(_newLine(depth: 2));
+      final typeClassesCall =
+          typeParams.map((typeParam) => '$typeParam,').join(_newLine(depth: 3));
+      s.write('''
+  static $typeClassName$typeParamsCall $staticTypeGetter$typeParamsDef(
+    $staticTypeGetterTypeClassesDef
+  ) {
+    return $typeClassName(
+      $typeClassesCall
+    );
+  }
+
+''');
+    }
+
+    // Fields and Methods
+    final fieldGenerator = _FieldGenerator(config, resolver, s);
+    for (final field in node.fields) {
+      field.accept(fieldGenerator);
+    }
+    final methodGenerator = _MethodGenerator(config, resolver, s);
+    for (final method in node.methods) {
+      method.accept(methodGenerator);
+    }
+
+    // Experimental: Interface implementation.
+    if (node.declKind == DeclKind.interfaceKind &&
+        (config.experiments?.contains(Experiment.interfaceImplementation) ??
+            false)) {
+      s.write('''
+  /// Maps a specific port to the implemented interface.
+  static final Map<int, $implClassName> _\$impls = {};
+''');
+      s.write(r'''
+  ReceivePort? _$p;
+
+  static jni.JObjectPtr _$invoke(
+    int port,
+    jni.JObjectPtr descriptor,
+    jni.JObjectPtr args,
+  ) {
+    return _$invokeMethod(
+      port,
+      $MethodInvocation.fromAddresses(
+        0,
+        descriptor.address,
+        args.address,
+      ),
+    );
+  }
+
+  static final ffi.Pointer<
+          ffi.NativeFunction<
+              jni.JObjectPtr Function(
+                  ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>>
+      _$invokePointer = ffi.Pointer.fromFunction(_$invoke);
+
+  static ffi.Pointer<ffi.Void> _$invokeMethod(
+    int $p,
+    $MethodInvocation $i,
+  ) {
+    try {
+      final $d = $i.methodDescriptor.toDartString(releaseOriginal: true);
+      final $a = $i.args;
+    ''');
+      final proxyMethodIf = _InterfaceMethodIf(resolver, s);
+      for (final method in node.methods) {
+        method.accept(proxyMethodIf);
+      }
+      s.write('''
+    } catch (e) {
+      return $_protectedExtension.newDartException(e.toString());
+    }
+    return jni.nullptr;
+  }
+
+  factory $name.implement(
+    $implClassName$typeParamsCall \$impl,
+  ) {
+''');
+      final typeClassesCall = typeParams
+          .map((typeParam) => '\$impl.$typeParam,')
+          .join(_newLine(depth: 3));
+      s.write('''
+    final \$p = ReceivePort();
+    final \$x = $name.fromRef(
+      $typeClassesCall
+      $_protectedExtension.newPortProxy(
+        r"${node.binaryName}",
+        \$p,
+        _\$invokePointer,
+      ),
+    ).._\$p = \$p;
+    final \$a = \$p.sendPort.nativePort; 
+    _\$impls[\$a] = \$impl;
+    \$p.listen((\$m) {
+      if (\$m == null) {
+        _\$impls.remove(\$p.sendPort.nativePort);
+        \$p.close();
+        return;
+      }
+      final \$i = \$MethodInvocation.fromMessage(\$m as List<dynamic>);
+      final \$r = _\$invokeMethod(\$p.sendPort.nativePort, \$i);
+      $_protectedExtension.returnResult(\$i.result, \$r);
+    });
+    return \$x;
+  }
+  ''');
+    }
+
+    // Writing any custom code provided for this class.
+    if (config.customClassBody?.containsKey(node.binaryName) ?? false) {
+      s.writeln(config.customClassBody![node.binaryName]);
+    }
+
+    // End of Class definition.
+    s.writeln('}');
+
+    // Experimental: Interface implementation
+    // Abstract and concrete Impl class definition.
+    // Used for interface implementation.
+    if (node.declKind == DeclKind.interfaceKind &&
+        (config.experiments?.contains(Experiment.interfaceImplementation) ??
+            false)) {
+      // Abstract Impl class.
+      final typeClassGetters = typeParams
+          .map((typeParam) =>
+              '$_jType<$_typeParamPrefix$typeParam> get $typeParam;')
+          .join(_newLine(depth: 1));
+      final abstractFactoryArgs = _encloseIfNotEmpty(
+        '{',
+        [
+          ...typeParams
+              .map((typeParam) => 'required $_jType<\$$typeParam> $typeParam,'),
+          ...node.methods.accept(_ConcreteImplClosureCtorArg(resolver)),
+        ].join(_newLine(depth: 2)),
+        '}',
+      );
+      s.write('''
+abstract interface class $implClassName$typeParamsDef {
+  factory $implClassName(
+    $abstractFactoryArgs
+  ) = _$implClassName;
+
+  $typeClassGetters
+
+''');
+      final abstractImplMethod = _AbstractImplMethod(resolver, s);
+      for (final method in node.methods) {
+        method.accept(abstractImplMethod);
+      }
+      s.writeln('}');
+
+      // Concrete Impl class.
+      // This is for passing closures instead of implementing the class.
+      final concreteCtorArgs = _encloseIfNotEmpty(
+        '{',
+        [
+          ...typeParams.map((typeParam) => 'required this.$typeParam,'),
+          ...node.methods.accept(_ConcreteImplClosureCtorArg(resolver)),
+        ].join(_newLine(depth: 2)),
+        '}',
+      );
+      final setClosures = _encloseIfNotEmpty(
+        ' :  ',
+        node.methods
+            .map((method) => '_${method.finalName} = ${method.finalName}')
+            .join(', '),
+        '',
+      );
+      final typeClassesDef = typeParams.map((typeParam) => '''
+@override
+final $_jType<\$$typeParam> $typeParam;
+''').join(_newLine(depth: 1));
+      s.write('''
+
+class _$implClassName$typeParamsDef implements $implClassName$typeParamsCall {
+  _$implClassName(
+    $concreteCtorArgs
+  )$setClosures;
+
+  $typeClassesDef
+
+''');
+      final concreteClosureDef = _ConcreteImplClosureDef(resolver, s);
+      for (final method in node.methods) {
+        method.accept(concreteClosureDef);
+      }
+      s.writeln();
+      final concreteMethodDef = _ConcreteImplMethod(resolver, s);
+      for (final method in node.methods) {
+        method.accept(concreteMethodDef);
+      }
+      s.writeln('}');
+    }
+    // TypeClass definition.
+    final typeClassesCall =
+        typeParams.map((typeParam) => '$typeParam,').join(_newLine(depth: 2));
+    final signature = node.signature;
+    final superTypeClass = superClass.accept(_TypeClassGenerator(resolver));
+    final hashCodeTypeClasses = typeParams.join(', ');
+    final equalityTypeClasses = typeParams
+        .map((typeParam) => ' &&\n        $typeParam == other.$typeParam')
+        .join();
+    final hashCode = typeParams.isEmpty
+        ? '($typeClassName).hashCode'
+        : 'Object.hash($typeClassName, $hashCodeTypeClasses)';
+    s.write('''
+final class $typeClassName$typeParamsDef extends $_jType<$name$typeParamsCall> {
+  $typeClassesDef
+
+  const $typeClassName(
+    $ctorTypeClassesDef
+  );
+
+  @override
+  String get signature => r"$signature";
+
+  @override
+  $name$typeParamsCall fromRef($_jPointer ref) => $name.fromRef(
+    $typeClassesCall
+    ref
+  );
+
+  @override
+  $_jType get superType => ${superTypeClass.name};
+
+  @override
+  final superCount = ${node.superCount};
+
+  @override
+  int get hashCode => $hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($typeClassName$typeParamsCall) &&
+        other is $typeClassName$typeParamsCall$equalityTypeClasses;
+  }
+}
+
+''');
+    log.finest('Generated bindings for class ${node.binaryName}');
+  }
+}
+
+/// Generates the JavaDoc comments.
+class _DocGenerator extends Visitor<JavaDocComment, void> {
+  final StringSink s;
+  final int depth;
+
+  const _DocGenerator(this.s, {required this.depth});
+
+  @override
+  void visit(JavaDocComment node) {
+    final link = RegExp('{@link ([^{}]+)}');
+    final indent = '  ' * depth;
+    final comments = node.comment
+        .replaceAllMapped(link, (match) => match.group(1) ?? '')
+        .replaceAll('#', '\\#')
+        .replaceAll('<p>', '')
+        .replaceAll('</p>', '\n')
+        .replaceAll('<b>', '__')
+        .replaceAll('</b>', '__')
+        .replaceAll('<em>', '_')
+        .replaceAll('</em>', '_')
+        .split('\n')
+        .join('\n$indent///');
+    s.write('''
+$indent///
+$indent/// $comments
+''');
+  }
+}
+
+/// Generates the user-facing Dart type.
+class _TypeGenerator extends TypeVisitor<String> {
+  final Resolver? resolver;
+
+  const _TypeGenerator(this.resolver);
+
+  @override
+  String visitArrayType(ArrayType node) {
+    final innerType = node.type;
+    if (innerType.kind == Kind.primitive) {
+      return '$_jArray<$_jni.j${(innerType.type as PrimitiveType).name}>';
+    }
+    return '$_jArray<${innerType.accept(this)}>';
+  }
+
+  @override
+  String visitDeclaredType(DeclaredType node) {
+    if (node.classDecl.binaryName == 'java.lang.Object' ||
+        node.classDecl.binaryName == 'java.lang.String') {
+      return '$_jni.${node.classDecl.finalName}';
+    }
+
+    // All type parameters of this type
+    final allTypeParams = node.classDecl.allTypeParams
+        .accept(const _TypeParamGenerator(withExtends: false))
+        .toList();
+    // The ones that are declared.
+    final definedTypeParams = node.params.accept(this).toList();
+
+    // Replacing the declared ones. They come at the end.
+    // The rest will be JObject.
+    if (allTypeParams.length >= node.params.length) {
+      allTypeParams.replaceRange(
+        0,
+        allTypeParams.length - node.params.length,
+        List.filled(
+          allTypeParams.length - node.params.length,
+          _jObject,
+        ),
+      );
+      allTypeParams.replaceRange(
+        allTypeParams.length - node.params.length,
+        allTypeParams.length,
+        definedTypeParams,
+      );
+    }
+
+    final typeParams = _encloseIfNotEmpty('<', allTypeParams.join(', '), '>');
+    final prefix = resolver?.resolvePrefix(node.classDecl) ?? '';
+    return '$prefix${node.classDecl.finalName}$typeParams';
+  }
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    return node.dartType;
+  }
+
+  @override
+  String visitTypeVar(TypeVar node) {
+    return '$_typeParamPrefix${node.name}';
+  }
+
+  @override
+  String visitWildcard(Wildcard node) {
+    // TODO(#141): Support wildcards
+    return super.visitWildcard(node);
+  }
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return _jObject;
+  }
+}
+
+class _TypeClass {
+  final String name;
+  final bool canBeConst;
+
+  const _TypeClass(this.name, this.canBeConst);
+}
+
+/// Generates the type class.
+class _TypeClassGenerator extends TypeVisitor<_TypeClass> {
+  final bool isConst;
+
+  /// Whether or not to return the equivalent boxed type class for primitives.
+  /// Only for interface implemetation.
+  final bool boxPrimitives;
+
+  /// Whether or not to find the correct type variable from the static map.
+  /// Only for interface implemetation.
+  final bool typeVarFromMap;
+
+  final Resolver resolver;
+
+  _TypeClassGenerator(
+    this.resolver, {
+    this.isConst = true,
+    this.boxPrimitives = false,
+    this.typeVarFromMap = false,
+  });
+
+  @override
+  _TypeClass visitArrayType(ArrayType node) {
+    final innerTypeClass = node.type.accept(_TypeClassGenerator(
+      resolver,
+      isConst: false,
+      boxPrimitives: false,
+      typeVarFromMap: typeVarFromMap,
+    ));
+    final ifConst = innerTypeClass.canBeConst && isConst ? 'const ' : '';
+    return _TypeClass(
+      '$ifConst${_jArray}Type(${innerTypeClass.name})',
+      innerTypeClass.canBeConst,
+    );
+  }
+
+  @override
+  _TypeClass visitDeclaredType(DeclaredType node) {
+    final allTypeParams = node.classDecl.allTypeParams
+        .accept(const _TypeParamGenerator(withExtends: false))
+        .toList();
+
+    // The ones that are declared.
+    final definedTypeClasses = node.params.accept(_TypeClassGenerator(
+      resolver,
+      isConst: false,
+      boxPrimitives: false,
+      typeVarFromMap: typeVarFromMap,
+    ));
+
+    // Can be const if all the type parameters are defined and each of them are
+    // also const.
+    final canBeConst =
+        allTypeParams.isEmpty || definedTypeClasses.every((e) => e.canBeConst);
+
+    // Replacing the declared ones. They come at the end.
+    // The rest will be `JObjectType`.
+    if (allTypeParams.length >= node.params.length) {
+      allTypeParams.replaceRange(
+        0,
+        allTypeParams.length - node.params.length,
+        List.filled(
+          allTypeParams.length - node.params.length,
+          // Adding const to subexpressions if the entire expression is not const.
+          '${canBeConst ? '' : 'const '}${_jObject}Type()',
+        ),
+      );
+      allTypeParams.replaceRange(
+        allTypeParams.length - node.params.length,
+        allTypeParams.length,
+        // Adding const to subexpressions if the entire expression is not const.
+        definedTypeClasses.map((param) =>
+            '${param.canBeConst && !canBeConst ? 'const ' : ''}${param.name}'),
+      );
+    }
+
+    final args = allTypeParams.join(', ');
+    final ifConst = isConst && canBeConst ? 'const ' : '';
+    final prefix = resolver.resolvePrefix(node.classDecl);
+    return _TypeClass(
+      '$ifConst$prefix${node.classDecl.typeClassName}($args)',
+      canBeConst,
+    );
+  }
+
+  @override
+  _TypeClass visitPrimitiveType(PrimitiveType node) {
+    final ifConst = isConst ? 'const ' : '';
+    final name = boxPrimitives ? 'J${node.boxedName}' : 'j${node.name}';
+    return _TypeClass('$ifConst$_jni.${name}Type()', true);
+  }
+
+  @override
+  _TypeClass visitTypeVar(TypeVar node) {
+    if (typeVarFromMap) {
+      return _TypeClass('_\$impls[\$p]!.${node.name}', false);
+    }
+    return _TypeClass(node.name, false);
+  }
+
+  @override
+  _TypeClass visitWildcard(Wildcard node) {
+    // TODO(#141): Support wildcards
+    return super.visitWildcard(node);
+  }
+
+  @override
+  _TypeClass visitNonPrimitiveType(ReferredType node) {
+    final ifConst = isConst ? 'const ' : '';
+    return _TypeClass('$ifConst${_jObject}Type()', true);
+  }
+}
+
+class _TypeParamGenerator extends Visitor<TypeParam, String> {
+  final bool withExtends;
+
+  const _TypeParamGenerator({required this.withExtends});
+
+  @override
+  String visit(TypeParam node) {
+    if (!withExtends) {
+      return node.name;
+    }
+    // TODO(#144): resolve the actual type being extended, if any.
+    return '$_typeParamPrefix${node.name} extends $_jObject';
+  }
+}
+
+class _JniResultGetter extends TypeVisitor<String> {
+  const _JniResultGetter();
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    if (node.name == 'void') return 'check()';
+    if (node.name == 'double') return 'doubleFloat';
+    if (node.name == 'int') return 'integer';
+    return node.name;
+  }
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return 'object';
+  }
+}
+
+/// Type signature for C-based bindings.
+///
+/// When `isFFi` is `true`, it generates the ffi type signature and when it's
+/// false, it generates the dart type signature.
+///
+/// For example `ffi.Int32` is an ffi type signature while `int` is a Dart one:
+/// ```dart
+/// jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Int32, ffi.Int32)>>(
+///       "sum")
+///   .asFunction<jni.JniResult Function(int, int)>();
+/// ```
+class _TypeSig extends TypeVisitor<String> {
+  final bool isFfi;
+
+  const _TypeSig({required this.isFfi});
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    if (isFfi) return '$_ffi.${node.ffiType}';
+    if (node.name == 'boolean') return 'int';
+    return node.dartType;
+  }
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return _voidPointer;
+  }
+}
+
+class _TypeName extends TypeVisitor<String> {
+  const _TypeName();
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    return node.name;
+  }
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return 'object';
+  }
+}
+
+class _CallType extends TypeVisitor<String> {
+  const _CallType();
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    return '$_jCallType.${node.name}Type';
+  }
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return '$_jCallType.objectType';
+  }
+}
+
+class _ToNativeSuffix extends TypeVisitor<String> {
+  const _ToNativeSuffix();
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    if (node.name == 'boolean') {
+      return ' ? 1 : 0';
+    }
+    return '';
+  }
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return '.$_selfPointer';
+  }
+}
+
+class _FromNative extends TypeVisitor<String> {
+  final Resolver resolver;
+  final String value;
+
+  const _FromNative(this.resolver, this.value);
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    return value;
+  }
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    final typeClass = node.accept(_TypeClassGenerator(resolver)).name;
+    return '$typeClass.fromRef($value)';
+  }
+}
+
+class _FieldGenerator extends Visitor<Field, void> {
+  final Config config;
+  final Resolver resolver;
+  final StringSink s;
+
+  const _FieldGenerator(this.config, this.resolver, this.s);
+
+  void writeCAccessor(Field node) {
+    final name = node.finalName;
+    final cName = node.accept(const CFieldName());
+    final ifRef = node.isStatic ? '' : '$_jPointer, ';
+
+    s.write('''
+  static final _get_$name =
+      $_lookup<$_ffi.NativeFunction<$_jResult Function($ifRef)>>(
+              "get_$cName")
+          .asFunction<$_jResult Function($ifRef)>();
+
+''');
+
+    if (!node.isFinal) {
+      final ffiSig = node.type.accept(const _TypeSig(isFfi: true));
+      final dartSig = node.type.accept(const _TypeSig(isFfi: false));
+      s.write('''
+  static final _set_$name =
+      $_lookup<$_ffi.NativeFunction<$_jResult Function($ifRef$ffiSig)>>(
+              "set_$cName")
+          .asFunction<$_jResult Function($ifRef$dartSig)>();
+
+''');
+    }
+  }
+
+  String cGetter(Field node) {
+    final name = node.finalName;
+    final getter = node.type.accept(const _JniResultGetter());
+    final self = node.isStatic ? '' : _selfPointer;
+    return '_get_$name($self).$getter';
+  }
+
+  String cSetter(Field node) {
+    final name = node.finalName;
+    final self = node.isStatic ? '' : '$_selfPointer, ';
+    final toNativeSuffix = node.type.accept(const _ToNativeSuffix());
+    return '_set_$name(${self}value$toNativeSuffix).check()';
+  }
+
+  void writeDartOnlyAccessor(Field node) {
+    final name = node.finalName;
+    final ifStatic = node.isStatic ? 'Static' : '';
+    final descriptor = node.type.descriptor;
+    s.write('''
+  static final _id_$name = 
+      $_accessors.get${ifStatic}FieldIDOf(
+        $_classRef, 
+        r"${node.name}",
+        r"$descriptor",
+      );
+''');
+  }
+
+  String dartOnlyGetter(Field node) {
+    final name = node.finalName;
+    final self = node.isStatic ? _classRef : _selfPointer;
+    final ifStatic = node.isStatic ? 'Static' : '';
+    final callType = node.type.accept(const _CallType());
+    final resultGetter = node.type.accept(const _JniResultGetter());
+    return '$_accessors.get${ifStatic}Field($self, _id_$name, $callType)'
+        '.$resultGetter';
+  }
+
+  String dartOnlySetter(Field node) {
+    final name = node.finalName;
+    final self = node.isStatic ? _classRef : _selfPointer;
+    final ifStatic = node.isStatic ? 'Static' : '';
+    final fieldType = node.type.accept(const _TypeName()).capitalize();
+    final toNativeSuffix = node.type.accept(const _ToNativeSuffix());
+    return '$_env.Set$ifStatic${fieldType}Field($self, _id_$name, value$toNativeSuffix)';
+  }
+
+  void writeDocs(Field node, {required bool writeReleaseInstructions}) {
+    final originalDecl = '${node.type.shorthand} ${node.name}';
+    s.writeln('  /// from: ${node.modifiers.join(' ')} $originalDecl');
+    if (node.type.kind != Kind.primitive && writeReleaseInstructions) {
+      s.writeln(_releaseInstruction);
+    }
+    node.javadoc?.accept(_DocGenerator(s, depth: 1));
+  }
+
+  @override
+  void visit(Field node) {
+    final isCBased = config.outputConfig.bindingsType == BindingsType.cBased;
+
+    // Check if it should be a `static const` getter.
+    if (node.isFinal && node.isStatic && node.defaultValue != null) {
+      final name = node.finalName;
+      final value = node.defaultValue!;
+      // TODO(#31): Should we leave String as a normal getter instead?
+      if (value is String || value is num || value is bool) {
+        writeDocs(node, writeReleaseInstructions: false);
+        s.write('  static const $name = ');
+        if (value is String) {
+          s.write('r"""$value"""');
+        } else {
+          s.write(value);
+        }
+        s.writeln(';\n');
+        return;
+      }
+    }
+
+    // Accessors.
+    (isCBased ? writeCAccessor : writeDartOnlyAccessor)(node);
+
+    // Getter docs.
+    writeDocs(node, writeReleaseInstructions: true);
+
+    final name = node.finalName;
+    final ifStatic = node.isStatic ? 'static ' : '';
+    final type = node.type.accept(_TypeGenerator(resolver));
+    s.write('$ifStatic$type get $name => ');
+    s.write(node.type.accept(
+      _FromNative(resolver, (isCBased ? cGetter : dartOnlyGetter)(node)),
+    ));
+    s.writeln(';\n');
+    if (!node.isFinal) {
+      // Setter docs.
+      writeDocs(node, writeReleaseInstructions: true);
+
+      s.write('${ifStatic}set $name($type value) => ');
+      s.write((isCBased ? cSetter : dartOnlySetter)(node));
+      s.writeln(';\n');
+    }
+  }
+}
+
+class _MethodTypeSig extends Visitor<Method, String> {
+  final bool isFfi;
+
+  const _MethodTypeSig({required this.isFfi});
+
+  @override
+  String visit(Method node) {
+    final args = [
+      if (!node.isCtor && !node.isStatic) _voidPointer,
+      ...node.params.map((param) => param.type).accept(
+            _TypeSig(isFfi: isFfi),
+          )
+    ].join(', ');
+    return '$_jResult Function($args)';
+  }
+}
+
+/// Generates Dart bindings for Java methods.
+class _MethodGenerator extends Visitor<Method, void> {
+  final Config config;
+  final Resolver resolver;
+  final StringSink s;
+
+  const _MethodGenerator(this.config, this.resolver, this.s);
+
+  void writeCAccessor(Method node) {
+    final name = node.finalName;
+    final cName = node.accept(const CMethodName());
+    final ffiSig = node.accept(const _MethodTypeSig(isFfi: true));
+    final dartSig = node.accept(const _MethodTypeSig(isFfi: false));
+    s.write('''
+  static final _$name =
+          $_lookup<$_ffi.NativeFunction<$ffiSig>>("$cName")
+      .asFunction<$dartSig>();
+
+''');
+  }
+
+  void writeDartOnlyAccessor(Method node) {
+    final name = node.finalName;
+    final ifStatic = node.isStatic ? 'Static' : '';
+    final descriptor = node.descriptor;
+    s.write('''
+  static final _id_$name = $_accessors.get${ifStatic}MethodIDOf(
+          $_classRef, r"${node.name}", r"$descriptor");
+
+''');
+  }
+
+  bool isSuspendFun(Method node) {
+    return node.asyncReturnType != null;
+  }
+
+  String cCtor(Method node) {
+    final name = node.finalName;
+    final params =
+        node.params.accept(const _ParamCall(isCBased: true)).join(', ');
+    return '_$name($params)';
+  }
+
+  String dartOnlyCtor(Method node) {
+    final name = node.finalName;
+    final params =
+        node.params.accept(const _ParamCall(isCBased: false)).join(', ');
+    return '$_accessors.newObjectWithArgs($_classRef, _id_$name, [$params])';
+  }
+
+  String cMethodCall(Method node) {
+    final name = node.finalName;
+    final params = [
+      if (!node.isStatic) _selfPointer,
+      ...node.params.accept(const _ParamCall(isCBased: true)),
+    ].join(', ');
+    final resultGetter = node.returnType.accept(const _JniResultGetter());
+    return '_$name($params).$resultGetter';
+  }
+
+  String dartOnlyMethodCall(Method node) {
+    final name = node.finalName;
+    final ifStatic = node.isStatic ? 'Static' : '';
+    final self = node.isStatic ? _classRef : _selfPointer;
+    final callType = node.returnType.accept(const _CallType());
+    final params =
+        node.params.accept(const _ParamCall(isCBased: false)).join(', ');
+    final resultGetter = node.returnType.accept(const _JniResultGetter());
+    return '$_accessors.call${ifStatic}MethodWithArgs'
+        '($self, _id_$name, $callType, [$params]).$resultGetter';
+  }
+
+  @override
+  void visit(Method node) {
+    final isCBased = config.outputConfig.bindingsType == BindingsType.cBased;
+
+    // Accessors
+    (isCBased ? writeCAccessor : writeDartOnlyAccessor)(node);
+
+    // Docs
+    s.write('  /// from: ');
+    s.writeAll(node.modifiers.map((m) => '$m '));
+    s.write('${node.returnType.shorthand} ${node.name}(');
+    s.writeAll(node.params.map((p) => '${p.type.shorthand} ${p.name}'), ', ');
+    s.writeln(')');
+    if (node.returnType.kind != Kind.primitive || node.isCtor) {
+      s.writeln(_releaseInstruction);
+    }
+    node.javadoc?.accept(_DocGenerator(s, depth: 1));
+
+    // Used for inferring the type parameter from the given parameters.
+    final typeLocators = node.params
+        .accept(_ParamTypeLocator(resolver: resolver))
+        .fold(<String, List<String>>{}, _mergeMapValues).map(
+            (key, value) => MapEntry(
+                  key,
+                  _encloseIfNotEmpty(
+                    '[',
+                    value.delimited(', '),
+                    ']',
+                  ),
+                ));
+
+    bool isRequired(TypeParam typeParam) {
+      return (typeLocators[typeParam.name] ?? '').isEmpty;
+    }
+
+    final typeInference =
+        (node.isCtor ? node.classDecl.allTypeParams : node.typeParams)
+            .where((tp) => !isRequired(tp))
+            .map((tp) => tp.name)
+            .map((tp) => '$tp ??= $_jni.lowestCommonSuperType'
+                '(${typeLocators[tp]}) as $_jType<$_typeParamPrefix$tp>;')
+            .join(_newLine(depth: 2));
+
+    if (node.isCtor) {
+      final className = node.classDecl.finalName;
+      final name = node.finalName;
+      final ctorName = name == 'new0' ? className : '$className.$name';
+      final paramsDef = node.params.accept(_ParamDef(resolver)).delimited(', ');
+      final typeClassDef = _encloseIfNotEmpty(
+        '{',
+        node.classDecl.allTypeParams
+            .map((typeParam) => typeParam
+                .accept(_CtorTypeClassDef(isRequired: isRequired(typeParam))))
+            .delimited(', '),
+        '}',
+      );
+      final typeClassCall = node.classDecl.allTypeParams
+          .map((typeParam) =>
+              typeParam.accept(const _TypeParamGenerator(withExtends: false)))
+          .delimited(', ');
+
+      final ctorExpr = (isCBased ? cCtor : dartOnlyCtor)(node);
+      s.write('''
+  factory $ctorName($paramsDef$typeClassDef) {
+    $typeInference
+    return ${node.classDecl.finalName}.fromRef(
+      $typeClassCall
+      $ctorExpr.object
+    );
+  }
+
+''');
+      return;
+    }
+
+    final name = node.finalName;
+    final returnType = isSuspendFun(node)
+        ? 'Future<${node.asyncReturnType!.accept(_TypeGenerator(resolver))}>'
+        : node.returnType.accept(_TypeGenerator(resolver));
+    final returnTypeClass = (node.asyncReturnType ?? node.returnType)
+        .accept(_TypeClassGenerator(resolver))
+        .name;
+    final ifStatic = node.isStatic ? 'static ' : '';
+    final defArgs = node.params.accept(_ParamDef(resolver)).toList();
+    final typeClassDef = _encloseIfNotEmpty(
+      '{',
+      node.typeParams
+          .map((typeParam) => typeParam.accept(_MethodTypeClassDef(
+                isRequired: isRequired(typeParam),
+              )))
+          .delimited(', '),
+      '}',
+    );
+    final typeParamsDef = _encloseIfNotEmpty(
+      '<',
+      node.typeParams
+          .accept(const _TypeParamGenerator(withExtends: true))
+          .join(', '),
+      '>',
+    );
+    if (isSuspendFun(node)) {
+      defArgs.removeLast();
+    }
+    final params = defArgs.delimited(', ');
+    s.write('  $ifStatic$returnType $name$typeParamsDef($params$typeClassDef)');
+    final callExpr = (isCBased ? cMethodCall : dartOnlyMethodCall)(node);
+    if (isSuspendFun(node)) {
+      final returning =
+          node.asyncReturnType!.accept(_FromNative(resolver, '\$o'));
+      s.write('''async {
+    $typeInference
+    final \$p = ReceivePort();
+    final \$c = $_jObject.fromRef($_protectedExtension.newPortContinuation(\$p));
+    $callExpr;
+    final \$o = $_jPointer.fromAddress(await \$p.first);
+    final \$k = $returnTypeClass.getClass().reference;
+    if (!$_jni.Jni.env.IsInstanceOf(\$o, \$k)) {
+      throw "Failed";
+    }
+    return $returning;
+  }
+
+''');
+    } else {
+      final returning = node.returnType.accept(_FromNative(resolver, callExpr));
+      s.writeln('''{
+    $typeInference
+    return $returning;
+  }
+''');
+    }
+  }
+}
+
+/// Generates the method type param definition.
+///
+/// For example `required JObjType<T> $T` in:
+/// ```dart
+/// void bar(..., {required JObjType<T> $T}) => ...
+/// ```
+class _MethodTypeClassDef extends Visitor<TypeParam, String> {
+  final bool isRequired;
+
+  const _MethodTypeClassDef({required this.isRequired});
+
+  @override
+  String visit(TypeParam node) {
+    return '${isRequired ? 'required ' : ''}$_jType'
+        '<$_typeParamPrefix${node.name}>${isRequired ? '' : '?'} ${node.name}';
+  }
+}
+
+/// Generates the class type param definition. Used only in constructors.
+///
+/// For example `required this.$T` in:
+/// ```dart
+/// class Foo {
+///   final JObjType<T> $T;
+///   Foo(..., {required this.$T}) => ...
+/// }
+/// ```
+class _CtorTypeClassDef extends Visitor<TypeParam, String> {
+  final bool isRequired;
+
+  const _CtorTypeClassDef({required this.isRequired});
+
+  @override
+  String visit(TypeParam node) {
+    return '${isRequired ? 'required ' : ''} $_jType'
+        '<$_typeParamPrefix${node.name}>${isRequired ? '' : '?'} ${node.name}';
+  }
+}
+
+/// Method parameter's definition.
+///
+/// For example `Foo foo` in:
+/// ```dart
+/// void bar(Foo foo) => ...
+/// ```
+class _ParamDef extends Visitor<Param, String> {
+  final Resolver resolver;
+
+  const _ParamDef(this.resolver);
+
+  @override
+  String visit(Param node) {
+    final type = node.type.accept(_TypeGenerator(resolver));
+    return '$type ${node.finalName}';
+  }
+}
+
+/// Method parameter used in calling the native method.
+///
+/// For example `foo.reference` in:
+/// ```dart
+/// void bar(Foo foo) => _bar(foo.reference);
+/// ```
+class _ParamCall extends Visitor<Param, String> {
+  final bool isCBased;
+
+  const _ParamCall({required this.isCBased});
+
+  @override
+  String visit(Param node) {
+    final nativeSuffix = node.type.accept(const _ToNativeSuffix());
+    final paramCall = '${node.finalName}$nativeSuffix';
+    if (!isCBased) {
+      // We need to wrap [paramCall] in the appropriate wrapper class.
+      return node.type.accept(_JValueWrapper(paramCall));
+    }
+    return paramCall;
+  }
+}
+
+/// Wraps the parameter in the appropriate JValue wrapper class.
+///
+/// For instance, `int` in Dart can be mapped to `long` or `int` or ... in Java.
+/// The wrapper class is how we identify the type in pure dart bindings.
+class _JValueWrapper extends TypeVisitor<String> {
+  final String param;
+
+  _JValueWrapper(this.param);
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return param;
+  }
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    if (node.name == 'long' ||
+        node.name == 'double' ||
+        node.name == 'boolean') {
+      return param;
+    }
+    return '$_jni.JValue${node.name.capitalize()}($param)';
+  }
+}
+
+/// A pair of [StringBuffer]s that can create an expression from the outer layer
+/// inwards.
+///
+/// For example:
+/// ```dart
+/// final buffer = OutsideInBuffer(); // asterisk (*) is used to show the middle
+/// buffer.appendLeft('f('); // f(*
+/// buffer.prependRight('x)'); // f(*x)
+/// buffer.appendLeft('g('); // f(g(*x)
+/// buffer.prependRight('y) + '); // f(g(*y) + x)
+/// buffer.toString(); // f(g(y) + x)
+/// ```
+@visibleForTesting
+class OutsideInBuffer {
+  final StringBuffer _leftBuffer;
+  final StringBuffer _rightBuffer;
+
+  OutsideInBuffer()
+      : _leftBuffer = StringBuffer(),
+        _rightBuffer = StringBuffer();
+
+  void prependRight(Object? object) {
+    final s = object.toString();
+    for (var i = 0; i < s.length; ++i) {
+      _rightBuffer.write(s[s.length - i - 1]);
+    }
+  }
+
+  void appendLeft(Object? object) {
+    _leftBuffer.write(object);
+  }
+
+  @override
+  String toString() {
+    return _leftBuffer.toString() + _rightBuffer.toString().reversed;
+  }
+}
+
+/// The ways to locate each type parameter.
+///
+/// For example in `JArray<JMap<$T, $T>> a`, `T` can be retreived using
+/// ```dart
+/// ((((a.$type as jni.JArrayType).elementType) as $JMapType).K) as jni.JObjType<$T>
+/// ```
+/// and
+/// ```dart
+/// ((((a.$type as jni.JArrayType).elementType) as $JMapType).V) as jni.JObjType<$T>
+/// ```
+class _ParamTypeLocator extends Visitor<Param, Map<String, List<String>>> {
+  final Resolver resolver;
+
+  _ParamTypeLocator({required this.resolver});
+
+  @override
+  Map<String, List<String>> visit(Param node) {
+    return node.type.accept(_TypeVarLocator(resolver: resolver)).map(
+          (key, value) => MapEntry(
+            key,
+            value
+                .map((e) =>
+                    (e..appendLeft('${node.finalName}.\$type')).toString())
+                .toList(),
+          ),
+        );
+  }
+}
+
+class _TypeVarLocator extends TypeVisitor<Map<String, List<OutsideInBuffer>>> {
+  final Resolver resolver;
+
+  _TypeVarLocator({required this.resolver});
+
+  @override
+  Map<String, List<OutsideInBuffer>> visitNonPrimitiveType(ReferredType node) {
+    return {};
+  }
+
+  @override
+  Map<String, List<OutsideInBuffer>> visitWildcard(Wildcard node) {
+    // TODO(#141): Support wildcards
+    return super.visitWildcard(node);
+  }
+
+  @override
+  Map<String, List<OutsideInBuffer>> visitTypeVar(TypeVar node) {
+    return {
+      node.name: [
+        OutsideInBuffer(),
+      ],
+    };
+  }
+
+  @override
+  Map<String, List<OutsideInBuffer>> visitDeclaredType(DeclaredType node) {
+    if (node.classDecl.isObject) {
+      return {};
+    }
+    final offset = node.classDecl.allTypeParams.length - node.params.length;
+    final result = <String, List<OutsideInBuffer>>{};
+    final prefix = resolver.resolvePrefix(node.classDecl);
+    final typeClass = '$prefix${node.classDecl.typeClassName}';
+    for (var i = 0; i < node.params.length; ++i) {
+      final typeParam = node.classDecl.allTypeParams[i + offset].name;
+      final exprs = node.params[i].accept(this);
+      for (final expr in exprs.entries) {
+        for (final buffer in expr.value) {
+          buffer.appendLeft('(');
+          buffer.prependRight(' as $typeClass).$typeParam');
+          result[expr.key] = (result[expr.key] ?? [])..add(buffer);
+        }
+      }
+    }
+    return result;
+  }
+
+  @override
+  Map<String, List<OutsideInBuffer>> visitArrayType(ArrayType node) {
+    final exprs = node.type.accept(this);
+    for (final e in exprs.values.expand((i) => i)) {
+      e.appendLeft('((');
+      e.prependRight(' as ${_jArray}Type).elementType as $_jType)');
+    }
+    return exprs;
+  }
+
+  @override
+  Map<String, List<OutsideInBuffer>> visitPrimitiveType(PrimitiveType node) {
+    return {};
+  }
+}
+
+/// Method defintion for Impl abstract class used for interface implementation.
+class _AbstractImplMethod extends Visitor<Method, void> {
+  final Resolver resolver;
+  final StringSink s;
+
+  _AbstractImplMethod(this.resolver, this.s);
+
+  @override
+  void visit(Method node) {
+    final returnType = node.returnType.accept(_TypeGenerator(resolver));
+    final name = node.finalName;
+    final args = node.params.accept(_ParamDef(resolver)).join(', ');
+    s.writeln('  $returnType $name($args);');
+  }
+}
+
+/// Closure defintion for concrete Impl class used for interface implementation.
+class _ConcreteImplClosureDef extends Visitor<Method, void> {
+  final Resolver resolver;
+  final StringSink s;
+
+  _ConcreteImplClosureDef(this.resolver, this.s);
+
+  @override
+  void visit(Method node) {
+    final returnType = node.returnType.accept(_TypeGenerator(resolver));
+    final name = node.finalName;
+    final args = node.params.accept(_ParamDef(resolver)).join(', ');
+    s.writeln('  final $returnType Function($args) _$name;');
+  }
+}
+
+/// Closure argument for concrete Impl class constructor.
+/// Used for interface implementation.
+class _ConcreteImplClosureCtorArg extends Visitor<Method, String> {
+  final Resolver resolver;
+
+  _ConcreteImplClosureCtorArg(this.resolver);
+
+  @override
+  String visit(Method node) {
+    final returnType = node.returnType.accept(_TypeGenerator(resolver));
+    final name = node.finalName;
+    final args = node.params.accept(_ParamDef(resolver)).join(', ');
+    return 'required $returnType Function($args) $name,';
+  }
+}
+
+/// Method defintion for concrete Impl class used for interface implementation.
+class _ConcreteImplMethod extends Visitor<Method, void> {
+  final Resolver resolver;
+  final StringSink s;
+
+  _ConcreteImplMethod(this.resolver, this.s);
+
+  @override
+  void visit(Method node) {
+    final returnType = node.returnType.accept(_TypeGenerator(resolver));
+    final name = node.finalName;
+    final argsDef = node.params.accept(_ParamDef(resolver)).join(', ');
+    final argsCall = node.params.map((param) => param.finalName).join(', ');
+    s.write('''
+  $returnType $name($argsDef) {
+    return _$name($argsCall);
+  }''');
+  }
+}
+
+/// The if statement to check which method has been called from the proxy class.
+class _InterfaceMethodIf extends Visitor<Method, void> {
+  final Resolver resolver;
+  final StringSink s;
+
+  _InterfaceMethodIf(this.resolver, this.s);
+
+  @override
+  void visit(Method node) {
+    final isVoid = node.returnType.name == 'void';
+    final signature = node.javaSig;
+    final saveResult = isVoid ? '' : 'final \$r = ';
+    final name = node.finalName;
+    s.write('''
+        if (\$d == r"$signature") {
+          ${saveResult}_\$impls[\$p]!.$name(
+''');
+    for (var i = 0; i < node.params.length; ++i) {
+      node.params[i].accept(_InterfaceParamCast(resolver, s, paramIndex: i));
+    }
+    const returnBox = _InterfaceReturnBox();
+    s.write('''
+          );
+          return ${node.returnType.accept(returnBox)};
+        }
+''');
+  }
+}
+
+/// Generates casting to the correct parameter type from the list of JObject
+/// arguments received from the call to the proxy class.
+class _InterfaceParamCast extends Visitor<Param, void> {
+  final Resolver resolver;
+  final StringSink s;
+  final int paramIndex;
+
+  _InterfaceParamCast(
+    this.resolver,
+    this.s, {
+    required this.paramIndex,
+  });
+
+  @override
+  void visit(Param node) {
+    final typeClass = node.type
+        .accept(_TypeClassGenerator(
+          resolver,
+          boxPrimitives: true,
+          typeVarFromMap: true,
+        ))
+        .name;
+    s.write('\$a[$paramIndex].castTo($typeClass, releaseOriginal: true)');
+    if (node.type.kind == Kind.primitive) {
+      // Convert to Dart type.
+      final name = (node.type.type as PrimitiveType).name;
+      s.write('.${name}Value(releaseOriginal: true)');
+    }
+    s.writeln(',');
+  }
+}
+
+/// Boxes the returned primitive value into the correct Boxed type.
+/// Only returns the reference for non primitive types.
+/// Returns null for void.
+///
+/// Since Dart doesn't know that this global reference is still used, it might
+/// garbage collect it via [NativeFinalizer] thus making it invalid.
+/// This passes the ownership to Java using [setAsReleased].
+///
+/// `toPointer` detaches the object from the [NativeFinalizer] and Java
+/// will clean up the global reference afterwards.
+///
+/// For example `$r.toJInteger().toPointer()` when the return
+/// type is `integer`.
+class _InterfaceReturnBox extends TypeVisitor<String> {
+  const _InterfaceReturnBox();
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    // Casting is done to create a new global reference. The user might
+    // use the original reference elsewhere and so the original object
+    // should not be [setAsReleased].
+    return '(\$r as $_jObject).castTo(const ${_jObject}Type()).toPointer()';
+  }
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    if (node.name == 'void') {
+      return '$_jni.nullptr';
+    }
+    return '$_jni.J${node.boxedName}(\$r).toPointer()';
+  }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/descriptor.dart b/pkgs/jnigen/lib/src/bindings/descriptor.dart
new file mode 100644
index 0000000..f7d774a
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/descriptor.dart
@@ -0,0 +1,115 @@
+import '../elements/elements.dart';
+import 'visitor.dart';
+
+/// Adds the type and method descriptor to all methods in all of the classes.
+///
+/// ASM already fills the descriptor field for methods but doclet does not.
+class Descriptor extends Visitor<Classes, void> {
+  const Descriptor();
+
+  @override
+  void visit(Classes node) {
+    for (final classDecl in node.decls.values) {
+      classDecl.accept(const _ClassDescriptor());
+    }
+  }
+}
+
+class _ClassDescriptor extends Visitor<ClassDecl, void> {
+  const _ClassDescriptor();
+
+  @override
+  void visit(ClassDecl node) {
+    final methodDescriptor = MethodDescriptor(node.allTypeParams);
+    for (final method in node.methods) {
+      method.descriptor ??= method.accept(methodDescriptor);
+    }
+    final typeDescriptor = TypeDescriptor(node.allTypeParams);
+    for (final field in node.fields) {
+      field.type.descriptor = field.type.accept(typeDescriptor);
+    }
+    for (final interface in node.interfaces) {
+      interface.descriptor = interface.accept(typeDescriptor);
+    }
+  }
+}
+
+/// Generates JNI Method descriptor.
+///
+/// https://docs.oracle.com/en/java/javase/18/docs/specs/jni/types.html#type-signatures
+/// Also see: [TypeDescriptor]
+class MethodDescriptor extends Visitor<Method, String> {
+  final List<TypeParam> typeParams;
+
+  MethodDescriptor(this.typeParams);
+
+  @override
+  String visit(Method node) {
+    final s = StringBuffer();
+    final typeParamsIncludingThis = [...typeParams, ...node.typeParams];
+    final typeDescriptor = TypeDescriptor(typeParamsIncludingThis);
+    s.write('(');
+    for (final param in node.params) {
+      final desc = param.type.accept(typeDescriptor);
+      param.type.descriptor = desc;
+      s.write(desc);
+    }
+    s.write(')');
+    final returnTypeDesc =
+        node.returnType.accept(TypeDescriptor(typeParamsIncludingThis));
+    node.returnType.descriptor = returnTypeDesc;
+    s.write(returnTypeDesc);
+    return s.toString();
+  }
+}
+
+/// JVM representation of type signatures.
+///
+/// https://docs.oracle.com/en/java/javase/18/docs/specs/jni/types.html#type-signatures
+class TypeDescriptor extends TypeVisitor<String> {
+  final List<TypeParam> typeParams;
+
+  TypeDescriptor(this.typeParams);
+
+  @override
+  String visitArrayType(ArrayType node) {
+    final inner = node.type.accept(this);
+    return '[$inner';
+  }
+
+  @override
+  String visitDeclaredType(DeclaredType node) {
+    final internalName = node.binaryName.replaceAll('.', '/');
+    return 'L$internalName;';
+  }
+
+  @override
+  String visitPrimitiveType(PrimitiveType node) {
+    return node.signature;
+  }
+
+  @override
+  String visitTypeVar(TypeVar node) {
+    final typeParam = typeParams.lastWhere(
+      (typeParam) => typeParam.name == node.name,
+      orElse: () {
+        print('${node.name} was not found!');
+        throw 'error';
+      },
+    );
+    return typeParam.bounds.isEmpty
+        ? super.visitTypeVar(node)
+        : typeParam.bounds.first.accept(this);
+  }
+
+  @override
+  String visitWildcard(Wildcard node) {
+    final extendsBound = node.extendsBound?.accept(this);
+    return extendsBound ?? super.visitWildcard(node);
+  }
+
+  @override
+  String visitNonPrimitiveType(ReferredType node) {
+    return 'Ljava/lang/Object;';
+  }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/excluder.dart b/pkgs/jnigen/lib/src/bindings/excluder.dart
new file mode 100644
index 0000000..1568bd3
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/excluder.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../config/config.dart';
+import '../elements/elements.dart';
+import '../logging/logging.dart';
+import 'visitor.dart';
+
+bool _isPrivate(ClassMember classMember) =>
+    !classMember.isPublic && !classMember.isProtected;
+
+class Excluder extends Visitor<Classes, void> {
+  final Config config;
+
+  const Excluder(this.config);
+
+  @override
+  void visit(Classes node) {
+    node.decls.removeWhere((_, classDecl) {
+      final excluded = _isPrivate(classDecl) ||
+          !(config.exclude?.classes?.included(classDecl) ?? true);
+      if (excluded) {
+        log.fine('Excluded class ${classDecl.binaryName}');
+      }
+      return excluded;
+    });
+    final classExcluder = _ClassExcluder(config);
+    for (final classDecl in node.decls.values) {
+      classDecl.accept(classExcluder);
+    }
+  }
+}
+
+class _ClassExcluder extends Visitor<ClassDecl, void> {
+  final Config config;
+
+  _ClassExcluder(this.config);
+
+  @override
+  void visit(ClassDecl node) {
+    node.methods = node.methods.where((method) {
+      final included = !_isPrivate(method) &&
+          !method.name.startsWith('_') &&
+          (config.exclude?.methods?.included(node, method) ?? true);
+      if (!included) {
+        log.fine('Excluded method ${node.binaryName}#${method.name}');
+      }
+      return included;
+    }).toList();
+    node.fields = node.fields.where((field) {
+      final included = !_isPrivate(field) &&
+          !field.name.startsWith('_') &&
+          (config.exclude?.fields?.included(node, field) ?? true);
+      if (!included) {
+        log.fine('Excluded field ${node.binaryName}#${field.name}');
+      }
+      return included;
+    }).toList();
+  }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart b/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart
new file mode 100644
index 0000000..4a48aa8
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/kotlin_processor.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../elements/elements.dart';
+import 'visitor.dart';
+
+/// A [Visitor] that adds the the information from Kotlin's metadata to the Java
+/// classes and methods.
+class KotlinProcessor extends Visitor<Classes, void> {
+  @override
+  void visit(Classes node) {
+    final classProcessor = _KotlinClassProcessor();
+    for (final classDecl in node.decls.values) {
+      classDecl.accept(classProcessor);
+    }
+  }
+}
+
+class _KotlinClassProcessor extends Visitor<ClassDecl, void> {
+  @override
+  void visit(ClassDecl node) {
+    if (node.kotlinClass == null) {
+      return;
+    }
+    // This [ClassDecl] is actually a Kotlin class.
+    // Matching methods and functions from the metadata.
+    final functions = <String, KotlinFunction>{};
+    for (final function in node.kotlinClass!.functions) {
+      final signature = function.name + function.descriptor;
+      functions[signature] = function;
+    }
+    for (final method in node.methods) {
+      final signature = method.name + method.descriptor!;
+      if (functions.containsKey(signature)) {
+        method.accept(_KotlinMethodProcessor(functions[signature]!));
+      }
+    }
+  }
+}
+
+class _KotlinMethodProcessor extends Visitor<Method, void> {
+  final KotlinFunction function;
+
+  _KotlinMethodProcessor(this.function);
+
+  @override
+  void visit(Method node) {
+    if (function.isSuspend) {
+      const kotlinContinutationType = 'kotlin.coroutines.Continuation';
+      assert(node.params.isNotEmpty &&
+          node.params.last.type.kind == Kind.declared &&
+          node.params.last.type.name == kotlinContinutationType);
+      final continuationType = node.params.last.type.type as DeclaredType;
+      node.asyncReturnType = continuationType.params.isEmpty
+          ? TypeUsage.object
+          : continuationType.params.first;
+    }
+  }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/linker.dart b/pkgs/jnigen/lib/src/bindings/linker.dart
new file mode 100644
index 0000000..8e90910
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/linker.dart
@@ -0,0 +1,203 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../config/config.dart';
+import '../elements/elements.dart';
+import '../logging/logging.dart';
+import 'visitor.dart';
+
+typedef _Resolver = ClassDecl Function(String? binaryName);
+
+/// A [Visitor] that adds the correct [ClassDecl] references from the
+/// string binary names.
+///
+/// It adds the following references:
+/// * Links [ClassDecl] objects from imported dependencies.
+/// * Adds references from child elements back to their parent elements.
+/// * Resolves Kotlin specific `asyncReturnType` for methods.
+class Linker extends Visitor<Classes, Future<void>> {
+  Linker(this.config);
+
+  final Config config;
+
+  @override
+  Future<void> visit(Classes node) async {
+    // Specify paths for this package's classes.
+    final root = config.outputConfig.dartConfig.path;
+    if (config.outputConfig.dartConfig.structure ==
+        OutputStructure.singleFile) {
+      // Connect all to the root if the output is in single file mode.
+      final path = root.toFilePath();
+      for (final decl in node.decls.values) {
+        decl.path = path;
+      }
+    } else {
+      for (final decl in node.decls.values) {
+        final dollarSign = decl.binaryName.indexOf('\$');
+        final className = dollarSign != -1
+            ? decl.binaryName.substring(0, dollarSign)
+            : decl.binaryName;
+        final path = className.replaceAll('.', '/');
+        decl.path = root.resolve(path).toFilePath();
+      }
+    }
+
+    // Find all the imported classes.
+    await config.importClasses();
+
+    if (config.importedClasses.keys
+        .toSet()
+        .intersection(node.decls.keys.toSet())
+        .isNotEmpty) {
+      log.fatal(
+        'Trying to re-import the generated classes.\n'
+        'Try hiding the class(es) in import.',
+      );
+    }
+
+    for (final className in config.importedClasses.keys) {
+      log.finest('Imported $className successfully.');
+    }
+
+    ClassDecl resolve(String? binaryName) {
+      return config.importedClasses[binaryName] ??
+          node.decls[binaryName] ??
+          resolve(TypeUsage.object.name);
+    }
+
+    final classLinker = _ClassLinker(
+      config,
+      resolve,
+    );
+    for (final classDecl in node.decls.values) {
+      classDecl.accept(classLinker);
+    }
+  }
+}
+
+class _ClassLinker extends Visitor<ClassDecl, void> {
+  final Config config;
+  final _Resolver resolve;
+  final Set<ClassDecl> _linked;
+
+  _ClassLinker(
+    this.config,
+    this.resolve,
+  ) : _linked = {...config.importedClasses.values};
+
+  @override
+  void visit(ClassDecl node) {
+    if (_linked.contains(node)) return;
+    log.finest('Linking ${node.binaryName}.');
+    _linked.add(node);
+
+    node.parent = node.parentName == null ? null : resolve(node.parentName);
+
+    final typeLinker = _TypeLinker(resolve);
+    node.superclass ??= TypeUsage.object;
+    node.superclass!.type.accept(typeLinker);
+    final superclass = (node.superclass!.type as DeclaredType).classDecl;
+    superclass.accept(this);
+
+    node.superCount = superclass.superCount + 1;
+
+    final fieldLinker = _FieldLinker(typeLinker);
+    for (final field in node.fields) {
+      field.classDecl = node;
+      field.accept(fieldLinker);
+    }
+    final methodLinker = _MethodLinker(config, typeLinker);
+    for (final method in node.methods) {
+      method.classDecl = node;
+      method.accept(methodLinker);
+    }
+    node.interfaces.accept(typeLinker).toList();
+    node.typeParams.accept(_TypeParamLinker(typeLinker)).toList();
+  }
+}
+
+class _MethodLinker extends Visitor<Method, void> {
+  _MethodLinker(this.config, this.typeVisitor);
+
+  final Config config;
+  final TypeVisitor<void> typeVisitor;
+
+  @override
+  void visit(Method node) {
+    node.returnType.accept(typeVisitor);
+    final typeParamLinker = _TypeParamLinker(typeVisitor);
+    final paramLinker = _ParamLinker(typeVisitor);
+    node.typeParams.accept(typeParamLinker).toList();
+    node.params.accept(paramLinker).toList();
+    node.asyncReturnType?.accept(typeVisitor);
+  }
+}
+
+class _TypeLinker extends TypeVisitor<void> {
+  const _TypeLinker(this.resolve);
+
+  final _Resolver resolve;
+
+  @override
+  void visitDeclaredType(DeclaredType node) {
+    node.params.accept(this).toList();
+    node.classDecl = resolve(node.binaryName);
+  }
+
+  @override
+  void visitWildcard(Wildcard node) {
+    node.superBound?.type.accept(this);
+    node.extendsBound?.type.accept(this);
+  }
+
+  @override
+  void visitArrayType(ArrayType node) {
+    node.type.accept(this);
+  }
+
+  @override
+  void visitPrimitiveType(PrimitiveType node) {
+    // Do nothing
+  }
+
+  @override
+  void visitNonPrimitiveType(ReferredType node) {
+    // Do nothing
+  }
+}
+
+class _FieldLinker extends Visitor<Field, void> {
+  _FieldLinker(this.typeVisitor);
+
+  final TypeVisitor<void> typeVisitor;
+
+  @override
+  void visit(Field node) {
+    node.type.accept(typeVisitor);
+  }
+}
+
+class _TypeParamLinker extends Visitor<TypeParam, void> {
+  _TypeParamLinker(this.typeVisitor);
+
+  final TypeVisitor<void> typeVisitor;
+
+  @override
+  void visit(TypeParam node) {
+    for (final bound in node.bounds) {
+      bound.accept(typeVisitor);
+    }
+  }
+}
+
+class _ParamLinker extends Visitor<Param, void> {
+  _ParamLinker(this.typeVisitor);
+
+  final TypeVisitor<void> typeVisitor;
+
+  @override
+  void visit(Param node) {
+    node.type.accept(typeVisitor);
+  }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/renamer.dart b/pkgs/jnigen/lib/src/bindings/renamer.dart
new file mode 100644
index 0000000..56fea0a
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/renamer.dart
@@ -0,0 +1,261 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../config/config.dart';
+import '../elements/elements.dart';
+import '../logging/logging.dart';
+import 'visitor.dart';
+
+const Set<String> _keywords = {
+  'abstract',
+  'as',
+  'assert',
+  'async',
+  'await',
+  'break',
+  'case',
+  'catch',
+  'class',
+  'const',
+  'continue',
+  'covariant',
+  'default',
+  'deferred',
+  'do',
+  'dynamic',
+  'else',
+  'enum',
+  'export',
+  'extends',
+  'extension',
+  'external',
+  'factory',
+  'false',
+  'final',
+  'finally',
+  'for',
+  'Function',
+  'get',
+  'hide',
+  'if',
+  'implements',
+  'import',
+  'in',
+  'interface',
+  'is',
+  'late',
+  'library',
+  'mixin',
+  'new',
+  'null',
+  'on',
+  'operator',
+  'part',
+  'required',
+  'rethrow',
+  'return',
+  'set',
+  'show',
+  'static',
+  'super',
+  'switch',
+  'sync',
+  'this',
+  'throw',
+  'true',
+  'try',
+  'type', // Is used for type classes.
+  'typedef',
+  'var',
+  'void',
+  'while',
+  'with',
+  'yield',
+};
+
+/// Methods & properties already defined by dart JObject base class.
+const Map<String, int> _definedSyms = {
+  'equals': 1,
+  'toString': 1,
+  'hashCode': 1,
+  'runtimeType': 1,
+  'noSuchMethod': 1,
+  'reference': 1,
+  'isReleased': 1,
+  'isNull': 1,
+  'use': 1,
+  'release': 1,
+  'releasedBy': 1,
+  'getFieldID': 1,
+  'getStaticFieldID': 1,
+  'getMethodID': 1,
+  'getStaticMethodID': 1,
+  'getField': 1,
+  'getFieldByName': 1,
+  'getStaticField': 1,
+  'getStaticFieldByName': 1,
+  'callMethod': 1,
+  'callMethodByName': 1,
+  'callStaticMethod': 1,
+  'callStaticMethodByName': 1,
+};
+
+/// Appends 0 to [name] if [name] is a keyword.
+///
+/// Examples:
+/// * `int` -> `int0`
+/// * `i` -> `i`
+String _keywordRename(String name) =>
+    _keywords.contains(name) ? '${name}0' : name;
+
+String _renameConflict(Map<String, int> counts, String name) {
+  if (counts.containsKey(name)) {
+    final count = counts[name]!;
+    final renamed = '$name$count';
+    counts[name] = count + 1;
+    return renamed;
+  }
+  counts[name] = 1;
+  return _keywordRename(name);
+}
+
+class Renamer implements Visitor<Classes, void> {
+  final Config config;
+
+  Renamer(this.config);
+
+  @override
+  void visit(Classes node) {
+    final classRenamer = _ClassRenamer(config);
+
+    for (final classDecl in node.decls.values) {
+      classDecl.accept(classRenamer);
+    }
+  }
+}
+
+class _ClassRenamer implements Visitor<ClassDecl, void> {
+  final Config config;
+  final Set<ClassDecl> renamed;
+  final Map<String, int> classNameCounts = {};
+  final Map<ClassDecl, Map<String, int>> nameCounts = {};
+
+  _ClassRenamer(
+    this.config,
+  ) : renamed = {...config.importedClasses.values};
+
+  /// Returns class name as useful in dart.
+  ///
+  /// Eg -> a.b.X.Y -> X_Y
+  static String _getSimplifiedClassName(String binaryName) =>
+      binaryName.split('.').last.replaceAll('\$', '_');
+
+  @override
+  void visit(ClassDecl node) {
+    if (renamed.contains(node)) return;
+    log.finest('Renaming ${node.binaryName}.');
+    renamed.add(node);
+
+    nameCounts[node] = {..._definedSyms};
+    node.methodNumsAfterRenaming = {};
+
+    final className = _getSimplifiedClassName(node.binaryName);
+    node.uniqueName = _renameConflict(classNameCounts, className);
+
+    // When generating all the classes in a single file
+    // the names need to be unique.
+    final uniquifyName =
+        config.outputConfig.dartConfig.structure == OutputStructure.singleFile;
+    node.finalName = uniquifyName ? node.uniqueName : className;
+    // TODO(#143): $ at the beginning is a temporary fix for the name collision.
+    node.typeClassName = '\$${node.finalName}Type';
+    log.fine('Class ${node.binaryName} is named ${node.finalName}');
+
+    final superClass = (node.superclass!.type as DeclaredType).classDecl;
+    superClass.accept(this);
+    nameCounts[node]!.addAll(nameCounts[superClass] ?? {});
+    final methodRenamer = _MethodRenamer(
+      config,
+      nameCounts[node]!,
+    );
+    for (final method in node.methods) {
+      method.accept(methodRenamer);
+    }
+
+    final fieldRenamer = _FieldRenamer(config, nameCounts[node]!);
+    for (final field in node.fields) {
+      field.accept(fieldRenamer);
+    }
+  }
+}
+
+class _MethodRenamer implements Visitor<Method, void> {
+  _MethodRenamer(this.config, this.nameCounts);
+
+  final Config config;
+  final Map<String, int> nameCounts;
+
+  @override
+  void visit(Method node) {
+    final name = node.name == '<init>' ? 'new' : node.name;
+    final sig = node.javaSig;
+    // If node is in super class, assign its number, overriding it.
+    final superClass =
+        (node.classDecl.superclass!.type as DeclaredType).classDecl;
+    final superNum = superClass.methodNumsAfterRenaming[sig];
+    if (superNum != null) {
+      // Don't rename if superNum == 0
+      // Unless the node name is a keyword.
+      final superNumText = superNum == 0 ? '' : '$superNum';
+      final methodName = superNum == 0 ? _keywordRename(name) : name;
+      node.finalName = '$methodName$superNumText';
+      node.classDecl.methodNumsAfterRenaming[sig] = superNum;
+    } else {
+      node.finalName = _renameConflict(nameCounts, name);
+      node.classDecl.methodNumsAfterRenaming[sig] = nameCounts[name]! - 1;
+    }
+    log.fine('Method ${node.classDecl.binaryName}#${node.name}'
+        ' is named ${node.finalName}');
+
+    final paramRenamer = _ParamRenamer(config);
+    for (final param in node.params) {
+      param.accept(paramRenamer);
+    }
+
+    // Kotlin specific
+    if (node.asyncReturnType != null) {
+      // It's a suspend fun so the continuation parameter
+      // should be named $c instead
+      node.params.last.finalName = '\$c';
+    }
+  }
+}
+
+class _FieldRenamer implements Visitor<Field, void> {
+  _FieldRenamer(this.config, this.nameCounts);
+
+  final Config config;
+  final Map<String, int> nameCounts;
+
+  @override
+  void visit(Field node) {
+    node.finalName = _renameConflict(
+      nameCounts,
+      node.name,
+    );
+    log.fine('Field ${node.classDecl.binaryName}#${node.name}'
+        ' is named ${node.finalName}');
+  }
+}
+
+class _ParamRenamer implements Visitor<Param, void> {
+  _ParamRenamer(this.config);
+
+  final Config config;
+
+  @override
+  void visit(Param node) {
+    node.finalName = _keywordRename(node.name);
+  }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/resolver.dart b/pkgs/jnigen/lib/src/bindings/resolver.dart
new file mode 100644
index 0000000..68b250b
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/resolver.dart
@@ -0,0 +1,153 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:math';
+
+import '../elements/elements.dart';
+import '../logging/logging.dart';
+
+class Resolver {
+  /// Class corresponding to currently writing file.
+  ///
+  /// Is [null] when in single file mode.
+  final String? currentClass;
+
+  /// Explicit import mappings.
+  final Map<String, ClassDecl> importedClasses;
+
+  /// Names of all classes in input.
+  final Set<String> inputClassNames;
+
+  final List<String> importStrings = [];
+
+  final Set<String> _relativeImportedClasses = {};
+  final Map<String, String> _importedNameToClass = {};
+  final Map<String, String> _classToImportedName = {};
+
+  Resolver({
+    required this.importedClasses,
+    required this.currentClass,
+    this.inputClassNames = const {},
+  });
+
+  static String getFileClassName(String binaryName) {
+    final dollarSign = binaryName.indexOf('\$');
+    if (dollarSign != -1) {
+      return binaryName.substring(0, dollarSign);
+    }
+    return binaryName;
+  }
+
+  /// splits [str] into 2 from last occurence of [sep]
+  static List<String> cutFromLast(String str, String sep) {
+    final li = str.lastIndexOf(sep);
+    if (li == -1) {
+      return ['', str];
+    }
+    return [str.substring(0, li), str.substring(li + 1)];
+  }
+
+  /// Get the prefix for the class
+  String resolvePrefix(ClassDecl classDecl) {
+    if (classDecl.path == 'package:jni/jni.dart') {
+      // For package:jni we don't use a leading underscore.
+      return 'jni.';
+    }
+    final binaryName = classDecl.binaryName;
+    final target = getFileClassName(binaryName);
+
+    // For classes we generate (inside [inputClassNames]) no import
+    // (and therefore no prefix) is necessary when:
+    // * Never necessary in single file mode
+    // * In multi file mode if the target is the same as the current class
+    if ((currentClass == null || target == currentClass) &&
+        inputClassNames.contains(binaryName)) {
+      return '';
+    }
+
+    if (_classToImportedName.containsKey(target)) {
+      // This package was already resolved
+      // but we still need to check if it was a relative import, in which case
+      // the class not in inputClassNames cannot be mapped here.
+      if (!_relativeImportedClasses.contains(target) ||
+          inputClassNames.contains(binaryName)) {
+        final importedName = _classToImportedName[target];
+        return '$importedName.';
+      }
+    }
+
+    final classImport = getImport(target, binaryName);
+    log.finest('$target resolved to $classImport for $binaryName');
+    if (classImport == null) {
+      return '';
+    }
+
+    final pkgName = cutFromLast(target, '.')[1].toLowerCase();
+    if (pkgName.isEmpty) {
+      throw UnsupportedError('No package could be deduced from '
+          'qualified binaryName');
+    }
+
+    // We always name imports with an underscore suffix, so that they can be
+    // never shadowed by a parameter or local variable.
+    var importedName = '${pkgName}_';
+    int suffix = 0;
+    while (_importedNameToClass.containsKey(importedName)) {
+      suffix++;
+      importedName = '$pkgName${suffix}_';
+    }
+
+    _importedNameToClass[importedName] = target;
+    _classToImportedName[target] = importedName;
+    importStrings.add('import "$classImport" as $importedName;\n');
+    return '$importedName.';
+  }
+
+  /// Returns import string for [classToResolve], or `null` if the class is not
+  /// found.
+  ///
+  /// [binaryName] is the class name trying to be resolved. This parameter is
+  /// requested so that classes included in current bindings can be resolved
+  /// using relative path.
+  String? getImport(String classToResolve, String binaryName) {
+    final prefix = classToResolve;
+
+    // short circuit if the requested class is specified directly in import map.
+    if (importedClasses.containsKey(binaryName)) {
+      return importedClasses[binaryName]!.path;
+    }
+
+    if (prefix.isEmpty) {
+      throw UnsupportedError('unexpected: empty package name.');
+    }
+
+    final dest = classToResolve.split('.');
+    final src = currentClass!.split('.');
+    // Use relative import when the required class is included in current set
+    // of bindings.
+    if (inputClassNames.contains(binaryName)) {
+      int common = 0;
+      // find the common prefix path directory of current package, and directory
+      // of target package
+      // src.length - 1 simply corresponds to directory of the package.
+      for (int i = 0; i < src.length - 1 && i < dest.length - 1; i++) {
+        if (src[i] == dest[i]) {
+          common++;
+        } else {
+          break;
+        }
+      }
+      final pathToCommon = '../' * ((src.length - 1) - common);
+      final pathToClass = dest.sublist(max(common, 0)).join('/');
+      _relativeImportedClasses.add(classToResolve);
+      return '$pathToCommon$pathToClass.dart';
+    }
+
+    return null;
+  }
+
+  List<String> getImportStrings() {
+    return importStrings;
+  }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/unnester.dart b/pkgs/jnigen/lib/src/bindings/unnester.dart
new file mode 100644
index 0000000..d9323df
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/unnester.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../elements/elements.dart';
+import 'visitor.dart';
+
+/// A [Visitor] that processes nested classes.
+///
+/// Nested classes are not supported in Dart. So this "unnests" them into
+/// separate classes.
+class Unnester extends Visitor<Classes, void> {
+  const Unnester();
+
+  @override
+  void visit(node) {
+    final classProcessor = _ClassUnnester();
+    for (final classDecl in node.decls.values) {
+      classDecl.accept(classProcessor);
+    }
+  }
+}
+
+class _ClassUnnester extends Visitor<ClassDecl, void> {
+  final processed = <ClassDecl>{};
+
+  @override
+  void visit(ClassDecl node) {
+    if (processed.contains(node)) return;
+    processed.add(node);
+    // We need to visit the ancestors first.
+    node.parent?.accept(this);
+
+    // Add type params of outer classes to the nested classes.
+    final allTypeParams = <TypeParam>[];
+    if (!node.isStatic) {
+      allTypeParams.addAll(node.parent?.allTypeParams ?? []);
+    }
+    allTypeParams.addAll(node.typeParams);
+    node.allTypeParams = allTypeParams;
+
+    if (node.isNested && !node.isStatic) {
+      const methodProcessor = _MethodUnnester();
+      for (final method in node.methods) {
+        method.accept(methodProcessor);
+      }
+    }
+  }
+}
+
+class _MethodUnnester extends Visitor<Method, void> {
+  const _MethodUnnester();
+
+  @override
+  void visit(Method node) {
+    assert(!node.classDecl.isStatic);
+    assert(node.classDecl.isNested);
+    // TODO(#319): Unnest the methods in APISummarizer itself.
+    // For now the nullity of [node.descriptor] identifies if the doclet
+    // backend was used and the method would potentially need "unnesting".
+    if ((node.isCtor || node.isStatic) && node.descriptor == null) {
+      // Non-static nested classes take an instance of their outer class as the
+      // first parameter.
+      //
+      // This is not accounted for by the **doclet** summarizer, so we
+      // manually add it as the first parameter.
+      final parentTypeParamCount = node.classDecl.allTypeParams.length -
+          node.classDecl.typeParams.length;
+      final parentTypeParams = [
+        for (final typeParam
+            in node.classDecl.allTypeParams.take(parentTypeParamCount)) ...[
+          TypeUsage(
+              shorthand: typeParam.name, kind: Kind.typeVariable, typeJson: {})
+            ..type = TypeVar(name: typeParam.name),
+        ]
+      ];
+      final parentType = DeclaredType(
+        binaryName: node.classDecl.parent!.binaryName,
+        params: parentTypeParams,
+      )..classDecl = node.classDecl.parent!;
+      final parentTypeUsage = TypeUsage(
+          shorthand: parentType.binaryName, kind: Kind.declared, typeJson: {})
+        ..type = parentType;
+      final param = Param(name: '\$parent', type: parentTypeUsage);
+      // Make the list modifiable.
+      if (node.params.isEmpty) node.params = [];
+      node.params.insert(0, param);
+    }
+  }
+}
diff --git a/pkgs/jnigen/lib/src/bindings/visitor.dart b/pkgs/jnigen/lib/src/bindings/visitor.dart
new file mode 100644
index 0000000..417180c
--- /dev/null
+++ b/pkgs/jnigen/lib/src/bindings/visitor.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../elements/elements.dart';
+
+abstract class Visitor<T extends Element<T>, R> {
+  const Visitor();
+
+  R visit(T node);
+}
+
+abstract class TypeVisitor<R> {
+  const TypeVisitor();
+
+  R visitNonPrimitiveType(ReferredType node);
+  R visitPrimitiveType(PrimitiveType node);
+  R visitArrayType(ArrayType node) => visitNonPrimitiveType(node);
+  R visitDeclaredType(DeclaredType node) => visitNonPrimitiveType(node);
+  R visitTypeVar(TypeVar node) => visitNonPrimitiveType(node);
+  R visitWildcard(Wildcard node) => visitNonPrimitiveType(node);
+}
+
+extension MultiVisitor<T extends Element<T>> on Iterable<Element<T>> {
+  /// Accepts all lazily. Remember to call `.toList()` or similar methods!
+  Iterable<R> accept<R>(Visitor<T, R> v) {
+    return map((e) => e.accept(v));
+  }
+}
+
+extension MultiTypeUsageVisitor on Iterable<TypeUsage> {
+  /// Accepts all lazily. Remember to call `.toList()` or similar methods!
+  Iterable<R> accept<R>(TypeVisitor<R> v) {
+    return map((e) => e.type.accept(v));
+  }
+}
diff --git a/pkgs/jnigen/lib/src/config/config.dart b/pkgs/jnigen/lib/src/config/config.dart
new file mode 100644
index 0000000..c4168d8
--- /dev/null
+++ b/pkgs/jnigen/lib/src/config/config.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'config_types.dart';
+export 'config_exception.dart';
diff --git a/pkgs/jnigen/lib/src/config/config_exception.dart b/pkgs/jnigen/lib/src/config/config_exception.dart
new file mode 100644
index 0000000..d3518cc
--- /dev/null
+++ b/pkgs/jnigen/lib/src/config/config_exception.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Exception thrown when a configuration value is invalid.
+class ConfigException implements Exception {
+  ConfigException(this.message);
+  String message;
+
+  @override
+  String toString() => 'Error parsing configuration: $message';
+}
diff --git a/pkgs/jnigen/lib/src/config/config_types.dart b/pkgs/jnigen/lib/src/config/config_types.dart
new file mode 100644
index 0000000..02dcbc2
--- /dev/null
+++ b/pkgs/jnigen/lib/src/config/config_types.dart
@@ -0,0 +1,703 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+import 'package:pub_semver/pub_semver.dart';
+import 'package:yaml/yaml.dart';
+
+import '../elements/elements.dart';
+import '../logging/logging.dart';
+import '../util/find_package.dart';
+import 'config_exception.dart';
+import 'experiments.dart';
+import 'filters.dart';
+import 'yaml_reader.dart';
+
+/// Modify this when symbols file format changes according to pub_semver.
+final _currentVersion = Version(1, 0, 0);
+
+/// Configuration for dependencies to be downloaded using maven.
+///
+/// Dependency names should be listed in groupId:artifactId:version format.
+/// For [sourceDeps], sources will be unpacked to [sourceDir] root and JAR files
+/// will also be downloaded. For the packages in jarOnlyDeps, only JAR files
+/// will be downloaded.
+///
+/// When passed as a parameter to [Config], the downloaded sources and
+/// JAR files will be automatically added to source path and class path
+/// respectively.
+class MavenDownloads {
+  static const defaultMavenSourceDir = 'mvn_java';
+  static const defaultMavenJarDir = 'mvn_jar';
+
+  MavenDownloads({
+    this.sourceDeps = const [],
+    // ASK: Should this be changed to a gitignore'd directory like build ?
+    this.sourceDir = defaultMavenSourceDir,
+    this.jarOnlyDeps = const [],
+    this.jarDir = defaultMavenJarDir,
+  });
+  List<String> sourceDeps;
+  String sourceDir;
+  List<String> jarOnlyDeps;
+  String jarDir;
+}
+
+/// Configuration for Android SDK sources and stub JAR files.
+///
+/// The SDK directories for platform stub JARs and sources are searched in the
+/// same order in which [versions] are specified.
+///
+/// If [addGradleDeps] is true, a gradle stub is run in order to collect the
+/// actual compile classpath of the `android/` subproject.
+/// This will fail if there was no previous build of the project, or if a
+/// `clean` task was run either through flutter or gradle wrapper. In such case,
+/// it's required to run `flutter build apk` & retry running `jnigen`.
+///
+/// A configuration is invalid if [versions] is unspecified or empty, and gradle
+/// options are also false. If [sdkRoot] is not specified but versions is
+/// specified, an attempt is made to find out SDK installation directory using
+/// environment variable `ANDROID_SDK_ROOT` if it's defined, else an error
+/// will be thrown.
+class AndroidSdkConfig {
+  AndroidSdkConfig({
+    this.versions,
+    this.sdkRoot,
+    this.addGradleDeps = false,
+    this.addGradleSources = false,
+    this.androidExample,
+  }) {
+    if (versions != null && sdkRoot == null) {
+      throw ConfigException("No SDK Root specified for finding Android SDK "
+          "from version priority list $versions");
+    }
+    if (versions == null && !addGradleDeps && !addGradleSources) {
+      throw ConfigException('Neither any SDK versions nor `addGradleDeps` '
+          'is specified. Unable to find Android libraries.');
+    }
+  }
+
+  /// Versions of android SDK to search for, in decreasing order of preference.
+  List<int>? versions;
+
+  /// Root of Android SDK installation, this should be normally given on
+  /// command line or by setting `ANDROID_SDK_ROOT`, since this varies from
+  /// system to system.
+  String? sdkRoot;
+
+  /// Attempt to determine exact compile time dependencies by running a gradle
+  /// stub in android subproject of this project.
+  ///
+  /// An Android build must have happened before we are able to obtain classpath
+  /// of Gradle dependencies. Run `flutter build apk` before running a jnigen
+  /// script with this option.
+  ///
+  /// For the same reason, if the flutter project is a plugin instead of
+  /// application, it's not possible to determine the build classpath directly.
+  /// Please provide [androidExample] pointing to an example application in
+  /// that case.
+  bool addGradleDeps;
+
+  /// Similar to [addGradleDeps], runs a stub to obtain source dependencies of
+  /// the Android project.
+  ///
+  /// This may cause additional source JAR artifacts to be downloaded. Like the
+  /// [addGradleDeps] option, plugins cannot be built so an example should be
+  /// specified.
+  bool addGradleSources;
+
+  /// Relative path to example application which will be used to determine
+  /// compile time classpath using a gradle stub. For most Android plugin
+  /// packages, 'example' will be the name of example application created inside
+  /// the package. This example should be built once before using this option,
+  /// so that gradle would have resolved all the dependencies.
+  String? androidExample;
+}
+
+extension on String {
+  /// Converts the enum name from camelCase to snake_case.
+  String toSnakeCase() {
+    return splitMapJoin(
+      RegExp('[A-Z]'),
+      onMatch: (p) => '_${p[0]!.toLowerCase()}',
+    );
+  }
+}
+
+extension<T extends Enum> on Iterable<T> {
+  Map<String, T> valuesMap() {
+    return Map.fromEntries(map((e) => MapEntry(e.name.toSnakeCase(), e)));
+  }
+}
+
+T _getEnumValueFromString<T>(
+    Map<String, T> values, String? name, T defaultVal) {
+  if (name == null) return defaultVal;
+  final value = values[name];
+  if (value == null) {
+    throw ConfigException('Got: $name, allowed: ${values.keys}');
+  }
+  return value;
+}
+
+/// Additional options to pass to the summary generator component.
+class SummarizerOptions {
+  SummarizerOptions(
+      {this.extraArgs = const [], this.workingDirectory, this.backend});
+  List<String> extraArgs;
+  Uri? workingDirectory;
+  SummarizerBackend? backend;
+}
+
+/// Backend for reading summary of Java libraries
+enum SummarizerBackend {
+  /// Generate Java API summaries using JARs in provided `classPath`s.
+  asm,
+
+  /// Generate Java API summaries using source files in provided `sourcePath`s.
+  doclet,
+}
+
+SummarizerBackend? getSummarizerBackend(
+  String? name,
+  SummarizerBackend? defaultVal,
+) {
+  return _getEnumValueFromString(
+    SummarizerBackend.values.valuesMap(),
+    name,
+    defaultVal,
+  );
+}
+
+void _ensureIsDirectory(String name, Uri path) {
+  if (!path.toFilePath().endsWith(Platform.pathSeparator)) {
+    throw ConfigException('$name must be a directory path. If using YAML '
+        'config, please ensure the path ends with a slash (/).');
+  }
+}
+
+enum OutputStructure { packageStructure, singleFile }
+
+OutputStructure getOutputStructure(String? name, OutputStructure defaultVal) {
+  return _getEnumValueFromString(
+    OutputStructure.values.valuesMap(),
+    name,
+    defaultVal,
+  );
+}
+
+enum BindingsType { cBased, dartOnly }
+
+extension GetConfigString on BindingsType {
+  String getConfigString() {
+    return name.toSnakeCase();
+  }
+}
+
+BindingsType getBindingsType(String? name, BindingsType defaultVal) {
+  return _getEnumValueFromString(
+    BindingsType.values.valuesMap(),
+    name,
+    defaultVal,
+  );
+}
+
+class CCodeOutputConfig {
+  CCodeOutputConfig({
+    required this.path,
+    required this.libraryName,
+    this.subdir,
+  }) {
+    _ensureIsDirectory('C output path', path);
+    if (subdir != null) {
+      _ensureIsDirectory('C subdirectory', path.resolve(subdir!));
+    }
+  }
+
+  /// Directory to write JNI C Bindings, in C+Dart mode.
+  ///
+  /// Strictly speaking, this is the root to place the `CMakeLists.txt` file
+  /// for the generated C bindings. It may be desirable to use the [subdir]
+  /// options to write C files to a subdirectory of [path]. For instance,
+  /// when generated code is required to be in `third_party` directory.
+  Uri path;
+
+  /// Name of generated library in CMakeLists.txt configuration.
+  ///
+  /// This will also determine the name of shared object file.
+  String libraryName;
+
+  /// Subfolder relative to [path] to write generated C code.
+  String? subdir;
+}
+
+class DartCodeOutputConfig {
+  DartCodeOutputConfig({
+    required this.path,
+    this.structure = OutputStructure.packageStructure,
+  }) {
+    if (structure == OutputStructure.singleFile) {
+      if (p.extension(path.toFilePath()) != '.dart') {
+        throw ConfigException(
+            'Dart\'s output path must end with ".dart" in single file mode.');
+      }
+    } else {
+      _ensureIsDirectory('Dart output path', path);
+    }
+  }
+
+  /// Path to write generated Dart bindings.
+  Uri path;
+
+  /// File structure of the generated Dart bindings.
+  OutputStructure structure;
+}
+
+class SymbolsOutputConfig {
+  /// Path to write generated Dart bindings.
+  final Uri path;
+
+  SymbolsOutputConfig(this.path) {
+    if (p.extension(path.toFilePath()) != '.yaml') {
+      throw ConfigException('Symbol\'s output path must end with ".yaml".');
+    }
+  }
+}
+
+class OutputConfig {
+  OutputConfig({
+    required this.dartConfig,
+    this.bindingsType = BindingsType.cBased,
+    this.cConfig,
+    this.symbolsConfig,
+  }) {
+    if (bindingsType == BindingsType.cBased && cConfig == null) {
+      throw ConfigException('C output config must be provided!');
+    }
+  }
+  BindingsType bindingsType;
+  DartCodeOutputConfig dartConfig;
+  CCodeOutputConfig? cConfig;
+  SymbolsOutputConfig? symbolsConfig;
+}
+
+class BindingExclusions {
+  BindingExclusions({this.methods, this.fields, this.classes});
+  MethodFilter? methods;
+  FieldFilter? fields;
+  ClassFilter? classes;
+}
+
+bool _isCapitalized(String s) {
+  final firstLetter = s.substring(0, 1);
+  return firstLetter == firstLetter.toUpperCase();
+}
+
+void _validateClassName(String className) {
+  final parts = className.split('.');
+  assert(parts.isNotEmpty);
+  const nestedClassesInfo =
+      "Nested classes cannot be specified separately. Specifying the "
+      "parent class will pull the nested classes.";
+  if (parts.length > 1 && _isCapitalized(parts[parts.length - 2])) {
+    // Try to detect possible nested classes specified using dot notation eg:
+    // `com.package.Class.NestedClass` and emit a warning.
+    log.warning("It appears a nested class $className is specified in the "
+        "config. $nestedClassesInfo");
+  }
+  if (className.contains('\$')) {
+    throw ConfigException(
+        "Nested class $className not allowed. $nestedClassesInfo");
+  }
+}
+
+/// Configuration for jnigen binding generation.
+class Config {
+  Config({
+    required this.outputConfig,
+    required this.classes,
+    this.experiments,
+    this.exclude,
+    this.sourcePath,
+    this.classPath,
+    this.preamble,
+    this.customClassBody,
+    this.androidSdkConfig,
+    this.mavenDownloads,
+    this.summarizerOptions,
+    this.logLevel = Level.INFO,
+    this.dumpJsonTo,
+    this.imports,
+  }) {
+    for (final className in classes) {
+      _validateClassName(className);
+    }
+  }
+
+  /// Output configuration for generated bindings
+  OutputConfig outputConfig;
+
+  /// List of classes or packages for which bindings have to be generated.
+  ///
+  /// The names must be fully qualified, and it's assumed that the directory
+  /// structure corresponds to package naming. For example, com.abc.MyClass
+  /// should be resolvable as `com/abc/MyClass.java` from one of the provided
+  /// source paths. Same applies if ASM backend is used, except that the file
+  /// name suffix is `.class`.
+  List<String> classes;
+
+  Set<Experiment?>? experiments;
+
+  /// Methods and fields to be excluded from generated bindings.
+  final BindingExclusions? exclude;
+
+  /// Paths to search for java source files.
+  ///
+  /// If a source package is downloaded through [mavenDownloads] option,
+  /// the corresponding source folder is automatically added and does not
+  /// need to be explicitly specified.
+  List<Uri>? sourcePath;
+
+  /// class path for scanning java libraries. If [backend] is `asm`, the
+  /// specified classpath is used to search for [classes], otherwise it's
+  /// merely used by the doclet API to find transitively referenced classes,
+  /// but not the specified classes / packages themselves.
+  List<Uri>? classPath;
+
+  /// Common text to be pasted on top of generated C and Dart files.
+  final String? preamble;
+
+  /// Configuration to search for Android SDK libraries (Experimental).
+  final AndroidSdkConfig? androidSdkConfig;
+
+  /// Configuration for auto-downloading JAR / source packages using maven,
+  /// along with their transitive dependencies.
+  final MavenDownloads? mavenDownloads;
+
+  /// Additional options for the summarizer component.
+  SummarizerOptions? summarizerOptions;
+
+  /// List of dependencies.
+  final List<Uri>? imports;
+
+  /// Call [importClasses] before using this.
+  late final Map<String, ClassDecl> importedClasses;
+
+  /// Custom code that is added to the end of the class body with the specified
+  /// binary name.
+  ///
+  /// Used for testing package:jnigen.
+  final Map<String, String>? customClassBody;
+
+  Future<void> importClasses() async {
+    importedClasses = {};
+    for (final import in [
+      // Implicitly importing package:jni symbols.
+      Uri.parse('package:jni/jni_symbols.yaml'),
+      ...?imports,
+    ]) {
+      // Getting the actual uri in case of package uris.
+      final Uri yamlUri;
+      final String importPath;
+      if (import.scheme == 'package') {
+        final packageName = import.pathSegments.first;
+        final packageRoot = await findPackageRoot(packageName);
+        if (packageRoot == null) {
+          log.fatal('package:$packageName was not found.');
+        }
+        yamlUri = packageRoot
+            .resolve('lib/')
+            .resolve(import.pathSegments.sublist(1).join('/'));
+        importPath = 'package:$packageName';
+      } else {
+        yamlUri = import;
+        importPath = ([...import.pathSegments]..removeLast()).join('/');
+      }
+      log.finest('Parsing yaml file in url $yamlUri.');
+      final YamlMap yaml;
+      try {
+        final symbolsFile = File.fromUri(yamlUri);
+        final content = symbolsFile.readAsStringSync();
+        yaml = loadYaml(content, sourceUrl: yamlUri);
+      } on UnsupportedError catch (_) {
+        log.fatal('Could not reference "$import".');
+      } catch (e, s) {
+        log.warning(e);
+        log.warning(s);
+        log.fatal('Error while parsing yaml file "$import".');
+      }
+      final version = Version.parse(yaml['version'] as String);
+      if (!VersionConstraint.compatibleWith(_currentVersion).allows(version)) {
+        log.fatal('"$import" is version "$version" which is not compatible with'
+            'the current JNIgen symbols version $_currentVersion');
+      }
+      final files = yaml['files'] as YamlMap;
+      for (final entry in files.entries) {
+        final filePath = entry.key as String;
+        final classes = entry.value as YamlMap;
+        for (final classEntry in classes.entries) {
+          final binaryName = classEntry.key as String;
+          final decl = classEntry.value as YamlMap;
+          if (importedClasses.containsKey(binaryName)) {
+            log.fatal(
+              'Re-importing "$binaryName" in "$import".\n'
+              'Try hiding the class in import.',
+            );
+          }
+          final classDecl = ClassDecl(
+            declKind: DeclKind.classKind,
+            binaryName: binaryName,
+          )
+            ..path = '$importPath/$filePath'
+            ..finalName = decl['name']
+            ..typeClassName = decl['type_class']
+            ..superCount = decl['super_count']
+            ..allTypeParams = []
+            ..parent = null;
+          for (final typeParamEntry
+              in ((decl['type_params'] as YamlMap?)?.entries) ??
+                  <MapEntry<dynamic, dynamic>>[]) {
+            final typeParamName = typeParamEntry.key as String;
+            final bounds = (typeParamEntry.value as YamlMap).entries.map((e) {
+              final boundName = e.key as String;
+              // Can only be DECLARED or TYPE_VARIABLE
+              if (!['DECLARED', 'TYPE_VARIABLE'].contains(e.value)) {
+                log.fatal(
+                  'Unsupported bound kind "${e.value}" for bound "$boundName" '
+                  'in type parameter "$typeParamName" '
+                  'of "$binaryName".',
+                );
+              }
+              final boundKind = (e.value as String) == 'DECLARED'
+                  ? Kind.declared
+                  : Kind.typeVariable;
+              final ReferredType type;
+              if (boundKind == Kind.declared) {
+                type = DeclaredType(binaryName: boundName);
+              } else {
+                type = TypeVar(name: boundName);
+              }
+              return TypeUsage(
+                shorthand: binaryName,
+                kind: boundKind,
+                typeJson: {},
+              )..type = type;
+            }).toList();
+            classDecl.allTypeParams.add(
+              TypeParam(name: typeParamName, bounds: bounds),
+            );
+          }
+          classDecl.methodNumsAfterRenaming =
+              (decl['methods'] as YamlMap?)?.cast() ?? {};
+          importedClasses[binaryName] = classDecl;
+        }
+      }
+    }
+  }
+
+  /// Directory containing the YAML configuration file, if any.
+  Uri? get configRoot => _configRoot;
+  Uri? _configRoot;
+
+  /// Log verbosity. The possible values in decreasing order of verbosity
+  /// are verbose > debug > info > warning > error. Defaults to [LogLevel.info]
+  Level logLevel = Level.INFO;
+
+  /// File to which JSON summary is written before binding generation.
+  final String? dumpJsonTo;
+
+  static final _levels = Map.fromEntries(
+      Level.LEVELS.map((l) => MapEntry(l.name.toLowerCase(), l)));
+
+  static Config parseArgs(List<String> args) {
+    final prov = YamlReader.parseArgs(args);
+
+    final List<String> missingValues = [];
+
+    T must<T>(T? Function(String) f, T ifNull, String property) {
+      final res = f(property);
+      if (res == null) {
+        missingValues.add(property);
+        return ifNull;
+      }
+      return res;
+    }
+
+    MemberFilter<T>? regexFilter<T extends ClassMember>(String property) {
+      final exclusions = prov.getStringList(property);
+      if (exclusions == null) return null;
+      final List<MemberFilter<T>> filters = [];
+      for (var exclusion in exclusions) {
+        final split = exclusion.split('#');
+        if (split.length != 2) {
+          throw ConfigException('Error parsing exclusion: "$exclusion": '
+              'expected to be in binaryName#member format.');
+        }
+        filters.add(MemberNameFilter<T>.exclude(
+          RegExp(split[0]),
+          RegExp(split[1]),
+        ));
+      }
+      return CombinedMemberFilter<T>(filters);
+    }
+
+    String? getSdkRoot() {
+      final root = prov.getString(_Props.androidSdkRoot) ??
+          Platform.environment['ANDROID_SDK_ROOT'];
+      return root;
+    }
+
+    Level logLevelFromString(String? levelName) {
+      if (levelName == null) return Level.INFO;
+      final level = _levels[levelName.toLowerCase()];
+      if (level == null) {
+        throw ConfigException('Not a valid logging level: $levelName');
+      }
+      return level;
+    }
+
+    final configRoot = prov.getConfigRoot();
+    String resolveFromConfigRoot(String reference) =>
+        configRoot?.resolve(reference).toFilePath() ?? reference;
+
+    final config = Config(
+      sourcePath: prov.getPathList(_Props.sourcePath),
+      classPath: prov.getPathList(_Props.classPath),
+      classes: must(prov.getStringList, [], _Props.classes),
+      summarizerOptions: SummarizerOptions(
+        extraArgs: prov.getStringList(_Props.summarizerArgs) ?? const [],
+        backend: getSummarizerBackend(prov.getString(_Props.backend), null),
+        workingDirectory: prov.getPath(_Props.summarizerWorkingDir),
+      ),
+      exclude: BindingExclusions(
+        methods: regexFilter<Method>(_Props.excludeMethods),
+        fields: regexFilter<Field>(_Props.excludeFields),
+      ),
+      outputConfig: OutputConfig(
+        bindingsType: getBindingsType(
+          prov.getString(_Props.bindingsType),
+          BindingsType.cBased,
+        ),
+        cConfig: prov.hasValue(_Props.cCodeOutputConfig)
+            ? CCodeOutputConfig(
+                libraryName: must(prov.getString, '', _Props.libraryName),
+                path: must(prov.getPath, Uri.parse('.'), _Props.cRoot),
+                subdir: prov.getString(_Props.cSubdir),
+              )
+            : null,
+        dartConfig: DartCodeOutputConfig(
+          path: must(prov.getPath, Uri.parse('.'), _Props.dartRoot),
+          structure: getOutputStructure(
+            prov.getString(_Props.outputStructure),
+            OutputStructure.packageStructure,
+          ),
+        ),
+        symbolsConfig: prov.hasValue(_Props.symbolsOutputConfig)
+            ? SymbolsOutputConfig(
+                must(prov.getPath, Uri.parse('.'), _Props.symbolsOutputConfig),
+              )
+            : null,
+      ),
+      preamble: prov.getString(_Props.preamble),
+      experiments: prov
+          .getStringList(_Props.experiments)
+          ?.map(
+            (e) => Experiment.fromString(e),
+          )
+          .toSet(),
+      imports: prov.getPathList(_Props.import),
+      mavenDownloads: prov.hasValue(_Props.mavenDownloads)
+          ? MavenDownloads(
+              sourceDeps: prov.getStringList(_Props.sourceDeps) ?? const [],
+              sourceDir: prov.getPath(_Props.mavenSourceDir)?.toFilePath() ??
+                  resolveFromConfigRoot(MavenDownloads.defaultMavenSourceDir),
+              jarOnlyDeps: prov.getStringList(_Props.jarOnlyDeps) ?? const [],
+              jarDir: prov.getPath(_Props.mavenJarDir)?.toFilePath() ??
+                  resolveFromConfigRoot(MavenDownloads.defaultMavenJarDir),
+            )
+          : null,
+      androidSdkConfig: prov.hasValue(_Props.androidSdkConfig)
+          ? AndroidSdkConfig(
+              versions: prov
+                  .getStringList(_Props.androidSdkVersions)
+                  ?.map(int.parse)
+                  .toList(),
+              sdkRoot: getSdkRoot(),
+              addGradleDeps: prov.getBool(_Props.addGradleDeps) ?? false,
+              addGradleSources: prov.getBool(_Props.addGradleSources) ?? false,
+              // Leaving this as getString instead of getPath, because
+              // it's resolved later in android_sdk_tools.
+              androidExample: prov.getString(_Props.androidExample),
+            )
+          : null,
+      logLevel: logLevelFromString(
+        prov.getOneOf(
+          _Props.logLevel,
+          _levels.keys.toSet(),
+        ),
+      ),
+    );
+    if (missingValues.isNotEmpty) {
+      stderr.write('Following config values are required but not provided\n'
+          'Please provide these properties through YAML '
+          'or use the command line switch -D<property_name>=<value>.\n');
+      for (var missing in missingValues) {
+        stderr.writeln('* $missing');
+      }
+      if (missingValues.contains(_Props.androidSdkRoot)) {
+        stderr.writeln('Please specify ${_Props.androidSdkRoot} through '
+            'command line or ensure that the ANDROID_SDK_ROOT environment '
+            'variable is set.');
+      }
+      exit(1);
+    }
+    config._configRoot = configRoot;
+    return config;
+  }
+}
+
+class _Props {
+  static const summarizer = 'summarizer';
+  static const summarizerArgs = '$summarizer.extra_args';
+  static const summarizerWorkingDir = '$summarizer.working_dir';
+  static const backend = '$summarizer.backend';
+
+  static const sourcePath = 'source_path';
+  static const classPath = 'class_path';
+  static const classes = 'classes';
+  static const exclude = 'exclude';
+  static const excludeMethods = '$exclude.methods';
+  static const excludeFields = '$exclude.fields';
+
+  static const experiments = 'enable_experiment';
+  static const import = 'import';
+  static const outputConfig = 'output';
+  static const bindingsType = '$outputConfig.bindings_type';
+  static const cCodeOutputConfig = '$outputConfig.c';
+  static const dartCodeOutputConfig = '$outputConfig.dart';
+  static const symbolsOutputConfig = '$outputConfig.symbols';
+  static const cRoot = '$cCodeOutputConfig.path';
+  static const cSubdir = '$cCodeOutputConfig.subdir';
+  static const dartRoot = '$dartCodeOutputConfig.path';
+  static const outputStructure = '$dartCodeOutputConfig.structure';
+  static const libraryName = '$cCodeOutputConfig.library_name';
+  static const preamble = 'preamble';
+  static const logLevel = 'log_level';
+
+  static const mavenDownloads = 'maven_downloads';
+  static const sourceDeps = '$mavenDownloads.source_deps';
+  static const mavenSourceDir = '$mavenDownloads.source_dir';
+  static const jarOnlyDeps = '$mavenDownloads.jar_only_deps';
+  static const mavenJarDir = '$mavenDownloads.jar_dir';
+
+  static const androidSdkConfig = 'android_sdk_config';
+  static const androidSdkRoot = '$androidSdkConfig.sdk_root';
+  static const androidSdkVersions = '$androidSdkConfig.versions';
+  static const addGradleDeps = '$androidSdkConfig.add_gradle_deps';
+  static const addGradleSources = '$androidSdkConfig.add_gradle_sources';
+  static const androidExample = '$androidSdkConfig.android_example';
+}
diff --git a/pkgs/jnigen/lib/src/config/experiments.dart b/pkgs/jnigen/lib/src/config/experiments.dart
new file mode 100644
index 0000000..6bb65c1
--- /dev/null
+++ b/pkgs/jnigen/lib/src/config/experiments.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class Experiment {
+  static const available = [
+    interfaceImplementation,
+  ];
+
+  static const interfaceImplementation = Experiment(
+    name: 'interface_implementation',
+    description: 'Enables generation of machinery for '
+        'implementing Java interfaces in Dart.',
+    isExpired: false,
+  );
+
+  final String name;
+  final String description;
+  final bool isExpired;
+
+  const Experiment({
+    required this.name,
+    required this.description,
+    required this.isExpired,
+  });
+
+  factory Experiment.fromString(String s) {
+    final search = available.where((element) => element.name == s);
+    if (search.isEmpty) {
+      throw 'The experiment $s is not available in this version.';
+    }
+    final result = search.single;
+    if (result.isExpired) {
+      throw 'The experiment $s can no longer be used in this version.';
+    }
+    return result;
+  }
+}
diff --git a/pkgs/jnigen/lib/src/config/filters.dart b/pkgs/jnigen/lib/src/config/filters.dart
new file mode 100644
index 0000000..1d0e1a5
--- /dev/null
+++ b/pkgs/jnigen/lib/src/config/filters.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../elements/elements.dart';
+
+bool _matchesCompletely(String string, Pattern pattern) {
+  final match = pattern.matchAsPrefix(string);
+  return match != null && match.group(0) == string;
+}
+
+/// A filter which tells if bindings for given [ClassDecl] are generated.
+abstract class ClassFilter {
+  bool included(ClassDecl decl);
+}
+
+/// This filter includes the declarations for which [predicate] returns true.
+class CustomClassFilter implements ClassFilter {
+  CustomClassFilter(this.predicate);
+  final bool Function(ClassDecl) predicate;
+  @override
+  bool included(ClassDecl decl) {
+    return predicate(decl);
+  }
+}
+
+/// Filter to include / exclude classes by matching on the binary name.
+/// A binary name is like qualified name but with a `$` used to indicate nested
+/// class instead of `.`, guaranteeing a unique name.
+class ClassNameFilter implements ClassFilter {
+  ClassNameFilter.include(this.pattern) : onMatch = true;
+  ClassNameFilter.exclude(this.pattern) : onMatch = false;
+  final bool onMatch;
+  final Pattern pattern;
+  @override
+  bool included(ClassDecl decl) {
+    if (_matchesCompletely(decl.binaryName, pattern)) {
+      return onMatch;
+    }
+    return !onMatch;
+  }
+}
+
+abstract class MemberFilter<T extends ClassMember> {
+  bool included(ClassDecl classDecl, T member);
+}
+
+/// Filter that excludes or includes members based on class and member name.
+class MemberNameFilter<T extends ClassMember> implements MemberFilter<T> {
+  MemberNameFilter.include(this.classPattern, this.namePattern)
+      : onMatch = true;
+  MemberNameFilter.exclude(this.classPattern, this.namePattern)
+      : onMatch = false;
+  final bool onMatch;
+  final Pattern classPattern, namePattern;
+  @override
+  bool included(ClassDecl classDecl, T member) {
+    final matches = _matchesCompletely(classDecl.binaryName, classPattern) &&
+        _matchesCompletely(member.name, namePattern);
+    return matches ? onMatch : !onMatch;
+  }
+}
+
+/// Filter that includes or excludes a member based on a custom callback.
+class CustomMemberFilter<T extends ClassMember> implements MemberFilter<T> {
+  CustomMemberFilter(this.predicate);
+  bool Function(ClassDecl, T) predicate;
+  @override
+  bool included(ClassDecl classDecl, T member) => predicate(classDecl, member);
+}
+
+/// Filter which excludes classes excluded by any one filter in [filters].
+class CombinedClassFilter implements ClassFilter {
+  CombinedClassFilter.all(this.filters);
+  final List<ClassFilter> filters;
+  @override
+  bool included(ClassDecl decl) => filters.every((f) => f.included(decl));
+}
+
+/// Filter which excludes members excluded by any one filter in [filters].
+class CombinedMemberFilter<T extends ClassMember> implements MemberFilter<T> {
+  CombinedMemberFilter(this.filters);
+
+  final List<MemberFilter<T>> filters;
+
+  @override
+  bool included(ClassDecl decl, T member) {
+    return filters.every((f) => f.included(decl, member));
+  }
+}
+
+typedef FieldFilter = MemberFilter<Field>;
+typedef MethodFilter = MemberFilter<Method>;
+
+/// Filter using binary name of the class and name of the field.
+typedef FieldNameFilter = MemberNameFilter<Field>;
+
+/// Filter using binary name of the class and name of the method.
+typedef MethodNameFilter = MemberNameFilter<Method>;
+
+/// Predicate based filter for field, which can access class declaration
+/// and the field.
+typedef CustomFieldFilter = CustomMemberFilter<Field>;
+
+/// Predicate based filter for method, which can access class declaration
+/// and the method.
+typedef CustomMethodFilter = CustomMemberFilter<Method>;
+
+/// This filter excludes fields if any one of sub-filters returns false.
+typedef CombinedFieldFilter = CombinedMemberFilter<Field>;
+
+/// This filter excludes methods if any one of sub-filters returns false.
+typedef CombinedMethodFilter = CombinedMemberFilter<Method>;
+
+MemberFilter<T> excludeAll<T extends ClassMember>(List<List<Pattern>> names) {
+  return CombinedMemberFilter<T>(
+      names.map((p) => MemberNameFilter<T>.exclude(p[0], p[1])).toList());
+}
diff --git a/pkgs/jnigen/lib/src/config/yaml_reader.dart b/pkgs/jnigen/lib/src/config/yaml_reader.dart
new file mode 100644
index 0000000..1cb1f46
--- /dev/null
+++ b/pkgs/jnigen/lib/src/config/yaml_reader.dart
@@ -0,0 +1,115 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:yaml/yaml.dart';
+import 'package:cli_config/cli_config.dart' as cli_config;
+
+import 'config_exception.dart';
+
+/// YAML Reader which enables to override specific values from command line.
+class YamlReader {
+  final cli_config.Config _config;
+
+  final Uri? _configRoot;
+
+  YamlReader.of(this._config, this._configRoot);
+
+  /// Parses the provided command line arguments and returns a [YamlReader].
+  ///
+  /// This is a utility function which does all things a program would do when
+  /// parsing command line arguments, including exiting from the program when
+  /// arguments are invalid.
+  static YamlReader parseArgs(List<String> args,
+      {bool allowYamlConfig = true}) {
+    final parser = ArgParser();
+    parser.addFlag('help', abbr: 'h', help: 'Show this help.');
+
+    // Sometimes it's required to change a config value for a single invocation,
+    // then this option can be used. Conventionally in -D switch is used in
+    // C to set preprocessor variable & in java to override a config property.
+
+    parser.addMultiOption('override',
+        abbr: 'D',
+        help: 'Override or assign a config property from command line.');
+    if (allowYamlConfig) {
+      parser.addOption('config', abbr: 'c', help: 'Path to YAML config.');
+    }
+
+    final results = parser.parse(args);
+    if (results['help']) {
+      stderr.writeln(parser.usage);
+      exit(1);
+    }
+    final configFilePath = results['config'] as String?;
+    String? configFileContents;
+    Uri? configFileUri;
+    if (configFilePath != null) {
+      try {
+        configFileContents = File(configFilePath).readAsStringSync();
+        configFileUri = File(configFilePath).uri;
+      } on Exception catch (e) {
+        stderr.writeln('Cannot read $configFilePath: $e.');
+      }
+    }
+    final regex = RegExp('([a-z-_.]+)=(.+)');
+    final properties = <String, String>{};
+    for (var prop in results['override']) {
+      final match = regex.matchAsPrefix(prop as String);
+      if (match != null && match.group(0) == prop) {
+        final propertyName = match.group(1);
+        final propertyValue = match.group(2);
+        properties[propertyName!] = propertyValue!;
+      } else {
+        throw ConfigException('override does not match expected pattern');
+      }
+    }
+    final config = cli_config.Config.fromConfigFileContents(
+      commandLineDefines: results['override'],
+      workingDirectory: Directory.current.uri,
+      environment: Platform.environment,
+      fileContents: configFileContents,
+      fileSourceUri: configFileUri,
+    );
+    return YamlReader.of(
+      config,
+      configFileUri?.resolve('.'),
+    );
+  }
+
+  bool? getBool(String property) => _config.optionalBool(property);
+
+  String? getString(String property) => _config.optionalString(property);
+
+  /// Same as [getString] but path is resolved relative to YAML config if it's
+  /// from YAML config.
+  Uri? getPath(String property) => _config.optionalPath(property);
+
+  List<String>? getStringList(String property) => _config.optionalStringList(
+        property,
+        splitCliPattern: ';',
+        combineAllConfigs: false,
+      );
+
+  List<Uri>? getPathList(String property) => _config.optionalPathList(
+        property,
+        combineAllConfigs: false,
+        splitCliPattern: ';',
+      );
+
+  String? getOneOf(String property, Set<String> values) =>
+      _config.optionalString(property, validValues: values);
+
+  Map<String, String>? getStringMap(String property) {
+    final value = _config.valueOf<YamlMap?>(property);
+    return value?.cast<String, String>();
+  }
+
+  bool hasValue(String property) => _config.valueOf<dynamic>(property) != null;
+
+  /// Returns URI of the directory containing YAML config.
+  Uri? getConfigRoot() => _configRoot;
+}
diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart
new file mode 100644
index 0000000..020f05d
--- /dev/null
+++ b/pkgs/jnigen/lib/src/elements/elements.dart
@@ -0,0 +1,681 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:json_annotation/json_annotation.dart';
+
+// Types to describe java API elements
+
+import '../bindings/visitor.dart';
+
+part 'elements.g.dart';
+
+abstract class Element<T extends Element<T>> {
+  const Element();
+
+  R accept<R>(Visitor<T, R> v);
+}
+
+@JsonEnum()
+
+/// A kind describes the type of a declaration.
+enum DeclKind {
+  @JsonValue('CLASS')
+  classKind,
+  @JsonValue('INTERFACE')
+  interfaceKind,
+  @JsonValue('ENUM')
+  enumKind,
+}
+
+class Classes implements Element<Classes> {
+  const Classes(this.decls);
+
+  final Map<String, ClassDecl> decls;
+
+  factory Classes.fromJson(List<dynamic> json) {
+    final decls = <String, ClassDecl>{};
+    for (final declJson in json) {
+      final classDecl = ClassDecl.fromJson(declJson);
+      decls[classDecl.binaryName] = classDecl;
+    }
+    return Classes(decls);
+  }
+
+  @override
+  R accept<R>(Visitor<Classes, R> v) {
+    return v.visit(this);
+  }
+}
+
+// Note: We give default values in constructor, if the field is nullable in
+// JSON. this allows us to reduce JSON size by providing Include.NON_NULL
+// option in java.
+
+@JsonSerializable(createToJson: false)
+class ClassDecl extends ClassMember implements Element<ClassDecl> {
+  ClassDecl({
+    this.annotations = const [],
+    this.javadoc,
+    required this.declKind,
+    this.modifiers = const {},
+    required this.binaryName,
+    this.typeParams = const [],
+    this.methods = const [],
+    this.fields = const [],
+    this.superclass,
+    this.interfaces = const [],
+    this.hasStaticInit = false,
+    this.hasInstanceInit = false,
+    this.values,
+    this.kotlinClass,
+  });
+
+  @override
+  final Set<String> modifiers;
+
+  final List<Annotation> annotations;
+  final KotlinClass? kotlinClass;
+  final JavaDocComment? javadoc;
+  final DeclKind declKind;
+  final String binaryName;
+  List<TypeParam> typeParams;
+  List<Method> methods;
+  List<Field> fields;
+  final List<TypeUsage> interfaces;
+  final bool hasStaticInit;
+  final bool hasInstanceInit;
+
+  /// Will default to java.lang.Object if null by [Linker].
+  TypeUsage? superclass;
+
+  /// Contains enum constant names if class is an enum,
+  /// as obtained by `.values()` method in Java.
+  final List<String>? values;
+
+  String get internalName => binaryName.replaceAll(".", "/");
+
+  String get packageName => (binaryName.split('.')..removeLast()).join('.');
+
+  /// The number of super classes this type has.
+  ///
+  /// Populated by [Linker].
+  @JsonKey(includeFromJson: false)
+  late final int superCount;
+
+  /// Parent's [ClassDecl] obtained from [parentName].
+  ///
+  /// Populated by [Linker].
+  @JsonKey(includeFromJson: false)
+  late final ClassDecl? parent;
+
+  /// Final name of this class.
+  ///
+  /// Populated by [Renamer].
+  @JsonKey(includeFromJson: false)
+  late final String finalName;
+
+  /// Name of the type class.
+  ///
+  /// Populated by [Renamer].
+  @JsonKey(includeFromJson: false)
+  late final String typeClassName;
+
+  /// Unique name obtained by renaming conflicting names with a number.
+  ///
+  /// This is used by C bindings instead of fully qualified name to reduce
+  /// the verbosity of generated bindings.
+  ///
+  /// Populated by [Renamer].
+  @JsonKey(includeFromJson: false)
+  late final String uniqueName;
+
+  /// Type parameters including the ones from its ancestors
+  ///
+  /// Populated by [Linker].
+  @JsonKey(includeFromJson: false)
+  List<TypeParam> allTypeParams = const [];
+
+  /// The path which this class is generated in.
+  ///
+  /// Populated by [Linker].
+  @JsonKey(includeFromJson: false)
+  late final String path;
+
+  /// The numeric suffix of the methods.
+  ///
+  /// Populated by [Renamer].
+  @JsonKey(includeFromJson: false)
+  late final Map<String, int> methodNumsAfterRenaming;
+
+  @override
+  String toString() {
+    return 'Java class declaration for $binaryName';
+  }
+
+  String get signature => 'L$internalName;';
+
+  factory ClassDecl.fromJson(Map<String, dynamic> json) =>
+      _$ClassDeclFromJson(json);
+
+  @override
+  R accept<R>(Visitor<ClassDecl, R> v) {
+    return v.visit(this);
+  }
+
+  @override
+  ClassDecl get classDecl => this;
+
+  @override
+  String get name => finalName;
+
+  bool get isObject => superCount == 0;
+
+  @JsonKey(includeFromJson: false)
+  late final String? parentName = binaryName.contains(r'$')
+      ? binaryName.splitMapJoin(RegExp(r'\$[^$]+$'), onMatch: (_) => '')
+      : null;
+
+  @JsonKey(includeFromJson: false)
+  late final isNested = parentName != null;
+}
+
+@JsonEnum()
+enum Kind {
+  @JsonValue('PRIMITIVE')
+  primitive,
+  @JsonValue('TYPE_VARIABLE')
+  typeVariable,
+  @JsonValue('WILDCARD')
+  wildcard,
+  @JsonValue('DECLARED')
+  declared,
+  @JsonValue('ARRAY')
+  array,
+}
+
+@JsonSerializable(createToJson: false)
+class TypeUsage {
+  TypeUsage({
+    required this.shorthand,
+    required this.kind,
+    required this.typeJson,
+  });
+
+  static TypeUsage object = TypeUsage(
+      kind: Kind.declared, shorthand: 'java.lang.Object', typeJson: {})
+    ..type = DeclaredType(binaryName: 'java.lang.Object');
+
+  final String shorthand;
+  final Kind kind;
+
+  @JsonKey(name: "type")
+  final Map<String, dynamic> typeJson;
+
+  /// Populated by [TypeUsage.fromJson].
+  @JsonKey(includeFromJson: false)
+  late final ReferredType type;
+
+  /// Populated by [Descriptor].
+  @JsonKey(includeFromJson: false)
+  late String descriptor;
+
+  String get name => type.name;
+
+  // Since json_serializable doesn't directly support union types,
+  // we have to temporarily store `type` in a JSON map, and switch on the
+  // enum value received.
+  factory TypeUsage.fromJson(Map<String, dynamic> json) {
+    final t = _$TypeUsageFromJson(json);
+    switch (t.kind) {
+      case Kind.primitive:
+        t.type = PrimitiveType.fromJson(t.typeJson);
+        break;
+      case Kind.typeVariable:
+        t.type = TypeVar.fromJson(t.typeJson);
+        break;
+      case Kind.wildcard:
+        t.type = Wildcard.fromJson(t.typeJson);
+        break;
+      case Kind.declared:
+        t.type = DeclaredType.fromJson(t.typeJson);
+        break;
+      case Kind.array:
+        t.type = ArrayType.fromJson(t.typeJson);
+        break;
+    }
+    return t;
+  }
+
+  R accept<R>(TypeVisitor<R> v) {
+    return type.accept(v);
+  }
+}
+
+abstract class ReferredType {
+  const ReferredType();
+  String get name;
+
+  R accept<R>(TypeVisitor<R> v);
+}
+
+class PrimitiveType extends ReferredType {
+  static const _primitives = {
+    'byte': PrimitiveType._(
+      name: 'byte',
+      signature: 'B',
+      dartType: 'int',
+      boxedName: 'Byte',
+      cType: 'int8_t',
+      ffiType: 'Int8',
+    ),
+    'short': PrimitiveType._(
+      name: 'short',
+      signature: 'S',
+      dartType: 'int',
+      boxedName: 'Short',
+      cType: 'int16_t',
+      ffiType: 'Int16',
+    ),
+    'char': PrimitiveType._(
+      name: 'char',
+      signature: 'C',
+      dartType: 'int',
+      boxedName: 'Character',
+      cType: 'uint16_t',
+      ffiType: 'Uint16',
+    ),
+    'int': PrimitiveType._(
+      name: 'int',
+      signature: 'I',
+      dartType: 'int',
+      boxedName: 'Integer',
+      cType: 'int32_t',
+      ffiType: 'Int32',
+    ),
+    'long': PrimitiveType._(
+      name: 'long',
+      signature: 'J',
+      dartType: 'int',
+      boxedName: 'Long',
+      cType: 'int64_t',
+      ffiType: 'Int64',
+    ),
+    'float': PrimitiveType._(
+      name: 'float',
+      signature: 'F',
+      dartType: 'double',
+      boxedName: 'Float',
+      cType: 'float',
+      ffiType: 'Float',
+    ),
+    'double': PrimitiveType._(
+      name: 'double',
+      signature: 'D',
+      dartType: 'double',
+      boxedName: 'Double',
+      cType: 'double',
+      ffiType: 'Double',
+    ),
+    'boolean': PrimitiveType._(
+      name: 'boolean',
+      signature: 'Z',
+      dartType: 'bool',
+      boxedName: 'Boolean',
+      cType: 'uint8_t',
+      ffiType: 'Uint8',
+    ),
+    'void': PrimitiveType._(
+      name: 'void',
+      signature: 'V',
+      dartType: 'void',
+      boxedName: 'Void', // Not used.
+      cType: 'void',
+      ffiType: 'Void',
+    ),
+  };
+
+  const PrimitiveType._({
+    required this.name,
+    required this.signature,
+    required this.dartType,
+    required this.boxedName,
+    required this.cType,
+    required this.ffiType,
+  });
+
+  @override
+  final String name;
+
+  final String signature;
+  final String dartType;
+  final String boxedName;
+  final String cType;
+  final String ffiType;
+
+  factory PrimitiveType.fromJson(Map<String, dynamic> json) {
+    return _primitives[json['name']]!;
+  }
+
+  @override
+  R accept<R>(TypeVisitor<R> v) {
+    return v.visitPrimitiveType(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class DeclaredType extends ReferredType {
+  DeclaredType({
+    required this.binaryName,
+    this.params = const [],
+  });
+
+  final String binaryName;
+  final List<TypeUsage> params;
+
+  @JsonKey(includeFromJson: false)
+  late ClassDecl classDecl;
+
+  @override
+  String get name => binaryName;
+
+  factory DeclaredType.fromJson(Map<String, dynamic> json) =>
+      _$DeclaredTypeFromJson(json);
+
+  @override
+  R accept<R>(TypeVisitor<R> v) {
+    return v.visitDeclaredType(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class TypeVar extends ReferredType {
+  TypeVar({required this.name});
+
+  @override
+  String name;
+
+  factory TypeVar.fromJson(Map<String, dynamic> json) =>
+      _$TypeVarFromJson(json);
+
+  @override
+  R accept<R>(TypeVisitor<R> v) {
+    return v.visitTypeVar(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class Wildcard extends ReferredType {
+  Wildcard({this.extendsBound, this.superBound});
+  TypeUsage? extendsBound, superBound;
+
+  @override
+  String get name => "?";
+
+  factory Wildcard.fromJson(Map<String, dynamic> json) =>
+      _$WildcardFromJson(json);
+
+  @override
+  R accept<R>(TypeVisitor<R> v) {
+    return v.visitWildcard(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class ArrayType extends ReferredType {
+  ArrayType({required this.type});
+  TypeUsage type;
+
+  @override
+  String get name => "[${type.name}";
+
+  factory ArrayType.fromJson(Map<String, dynamic> json) =>
+      _$ArrayTypeFromJson(json);
+
+  @override
+  R accept<R>(TypeVisitor<R> v) {
+    return v.visitArrayType(this);
+  }
+}
+
+abstract class ClassMember {
+  String get name;
+  ClassDecl get classDecl;
+  Set<String> get modifiers;
+
+  bool get isStatic => modifiers.contains('static');
+  bool get isFinal => modifiers.contains('final');
+  bool get isPublic => modifiers.contains('public');
+  bool get isProtected => modifiers.contains('protected');
+}
+
+@JsonSerializable(createToJson: false)
+class Method extends ClassMember implements Element<Method> {
+  Method({
+    this.annotations = const [],
+    this.javadoc,
+    this.modifiers = const {},
+    required this.name,
+    this.descriptor,
+    this.typeParams = const [],
+    this.params = const [],
+    required this.returnType,
+  });
+
+  @override
+  final String name;
+  @override
+  final Set<String> modifiers;
+
+  final List<Annotation> annotations;
+  final JavaDocComment? javadoc;
+  final List<TypeParam> typeParams;
+  List<Param> params;
+  final TypeUsage returnType;
+
+  /// Can be used to match with [KotlinFunction]'s descriptor.
+  ///
+  /// Can create a unique signature in combination with [name].
+  /// Populated either by the ASM backend or [Descriptor].
+  String? descriptor;
+
+  /// The [ClassDecl] where this method is defined.
+  ///
+  /// Populated by [Linker].
+  @JsonKey(includeFromJson: false)
+  @override
+  late ClassDecl classDecl;
+
+  /// Populated by [Renamer].
+  @JsonKey(includeFromJson: false)
+  late String finalName;
+
+  @JsonKey(includeFromJson: false)
+  late bool isOverridden;
+
+  /// The actual return type when the method is a Kotlin's suspend fun.
+  ///
+  /// Populated by [KotlinProcessor].
+  @JsonKey(includeFromJson: false)
+  TypeUsage? asyncReturnType;
+
+  @JsonKey(includeFromJson: false)
+  late final String javaSig = '$name$descriptor';
+
+  bool get isCtor => name == '<init>';
+
+  factory Method.fromJson(Map<String, dynamic> json) => _$MethodFromJson(json);
+
+  @override
+  R accept<R>(Visitor<Method, R> v) {
+    return v.visit(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class Param implements Element<Param> {
+  Param({
+    this.annotations = const [],
+    this.javadoc,
+    required this.name,
+    required this.type,
+  });
+
+  final List<Annotation> annotations;
+  final JavaDocComment? javadoc;
+  final String name;
+  final TypeUsage type;
+
+  /// Populated by [Renamer].
+  @JsonKey(includeFromJson: false)
+  late String finalName;
+
+  factory Param.fromJson(Map<String, dynamic> json) => _$ParamFromJson(json);
+
+  @override
+  R accept<R>(Visitor<Param, R> v) {
+    return v.visit(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class Field extends ClassMember implements Element<Field> {
+  Field({
+    this.annotations = const [],
+    this.javadoc,
+    this.modifiers = const {},
+    required this.name,
+    required this.type,
+    this.defaultValue,
+  });
+
+  @override
+  final String name;
+  @override
+  final Set<String> modifiers;
+
+  final List<Annotation> annotations;
+  final JavaDocComment? javadoc;
+  final TypeUsage type;
+  final Object? defaultValue;
+
+  /// The [ClassDecl] where this field is defined.
+  ///
+  /// Populated by [Linker].
+  @JsonKey(includeFromJson: false)
+  @override
+  late final ClassDecl classDecl;
+
+  /// Populated by [Renamer].
+  @JsonKey(includeFromJson: false)
+  late final String finalName;
+
+  factory Field.fromJson(Map<String, dynamic> json) => _$FieldFromJson(json);
+
+  @override
+  R accept<R>(Visitor<Field, R> v) {
+    return v.visit(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class TypeParam implements Element<TypeParam> {
+  TypeParam({required this.name, this.bounds = const []});
+
+  final String name;
+  final List<TypeUsage> bounds;
+
+  @JsonKey(includeFromJson: false)
+  late final String erasure;
+
+  factory TypeParam.fromJson(Map<String, dynamic> json) =>
+      _$TypeParamFromJson(json);
+
+  @override
+  R accept<R>(Visitor<TypeParam, R> v) {
+    return v.visit(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class JavaDocComment implements Element<JavaDocComment> {
+  JavaDocComment({this.comment = ''});
+
+  final String comment;
+
+  @JsonKey(includeFromJson: false)
+  late final String dartDoc;
+
+  factory JavaDocComment.fromJson(Map<String, dynamic> json) =>
+      _$JavaDocCommentFromJson(json);
+
+  @override
+  R accept<R>(Visitor<JavaDocComment, R> v) {
+    return v.visit(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class Annotation implements Element<Annotation> {
+  Annotation({
+    required this.binaryName,
+    this.properties = const {},
+  });
+
+  final String binaryName;
+  final Map<String, Object> properties;
+
+  factory Annotation.fromJson(Map<String, dynamic> json) =>
+      _$AnnotationFromJson(json);
+
+  @override
+  R accept<R>(Visitor<Annotation, R> v) {
+    return v.visit(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class KotlinClass implements Element<KotlinClass> {
+  KotlinClass({
+    required this.name,
+    this.functions = const [],
+  });
+
+  final String name;
+  final List<KotlinFunction> functions;
+
+  factory KotlinClass.fromJson(Map<String, dynamic> json) =>
+      _$KotlinClassFromJson(json);
+
+  @override
+  R accept<R>(Visitor<KotlinClass, R> v) {
+    return v.visit(this);
+  }
+}
+
+@JsonSerializable(createToJson: false)
+class KotlinFunction implements Element<KotlinFunction> {
+  KotlinFunction({
+    required this.name,
+    required this.descriptor,
+    required this.kotlinName,
+    required this.isSuspend,
+  });
+
+  final String name;
+
+  /// Used to match with [Method]'s descriptor.
+  ///
+  /// Creates a unique signature in combination with [name].
+  final String descriptor;
+  final String kotlinName;
+  final bool isSuspend;
+
+  factory KotlinFunction.fromJson(Map<String, dynamic> json) =>
+      _$KotlinFunctionFromJson(json);
+
+  @override
+  R accept<R>(Visitor<KotlinFunction, R> v) {
+    return v.visit(this);
+  }
+}
diff --git a/pkgs/jnigen/lib/src/elements/elements.g.dart b/pkgs/jnigen/lib/src/elements/elements.g.dart
new file mode 100644
index 0000000..dcd35dd
--- /dev/null
+++ b/pkgs/jnigen/lib/src/elements/elements.g.dart
@@ -0,0 +1,186 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'elements.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+ClassDecl _$ClassDeclFromJson(Map<String, dynamic> json) => ClassDecl(
+      annotations: (json['annotations'] as List<dynamic>?)
+              ?.map((e) => Annotation.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      javadoc: json['javadoc'] == null
+          ? null
+          : JavaDocComment.fromJson(json['javadoc'] as Map<String, dynamic>),
+      declKind: $enumDecode(_$DeclKindEnumMap, json['declKind']),
+      modifiers: (json['modifiers'] as List<dynamic>?)
+              ?.map((e) => e as String)
+              .toSet() ??
+          const {},
+      binaryName: json['binaryName'] as String,
+      typeParams: (json['typeParams'] as List<dynamic>?)
+              ?.map((e) => TypeParam.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      methods: (json['methods'] as List<dynamic>?)
+              ?.map((e) => Method.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      fields: (json['fields'] as List<dynamic>?)
+              ?.map((e) => Field.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      superclass: json['superclass'] == null
+          ? null
+          : TypeUsage.fromJson(json['superclass'] as Map<String, dynamic>),
+      interfaces: (json['interfaces'] as List<dynamic>?)
+              ?.map((e) => TypeUsage.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      hasStaticInit: json['hasStaticInit'] as bool? ?? false,
+      hasInstanceInit: json['hasInstanceInit'] as bool? ?? false,
+      values:
+          (json['values'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      kotlinClass: json['kotlinClass'] == null
+          ? null
+          : KotlinClass.fromJson(json['kotlinClass'] as Map<String, dynamic>),
+    );
+
+const _$DeclKindEnumMap = {
+  DeclKind.classKind: 'CLASS',
+  DeclKind.interfaceKind: 'INTERFACE',
+  DeclKind.enumKind: 'ENUM',
+};
+
+TypeUsage _$TypeUsageFromJson(Map<String, dynamic> json) => TypeUsage(
+      shorthand: json['shorthand'] as String,
+      kind: $enumDecode(_$KindEnumMap, json['kind']),
+      typeJson: json['type'] as Map<String, dynamic>,
+    );
+
+const _$KindEnumMap = {
+  Kind.primitive: 'PRIMITIVE',
+  Kind.typeVariable: 'TYPE_VARIABLE',
+  Kind.wildcard: 'WILDCARD',
+  Kind.declared: 'DECLARED',
+  Kind.array: 'ARRAY',
+};
+
+DeclaredType _$DeclaredTypeFromJson(Map<String, dynamic> json) => DeclaredType(
+      binaryName: json['binaryName'] as String,
+      params: (json['params'] as List<dynamic>?)
+              ?.map((e) => TypeUsage.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+    );
+
+TypeVar _$TypeVarFromJson(Map<String, dynamic> json) => TypeVar(
+      name: json['name'] as String,
+    );
+
+Wildcard _$WildcardFromJson(Map<String, dynamic> json) => Wildcard(
+      extendsBound: json['extendsBound'] == null
+          ? null
+          : TypeUsage.fromJson(json['extendsBound'] as Map<String, dynamic>),
+      superBound: json['superBound'] == null
+          ? null
+          : TypeUsage.fromJson(json['superBound'] as Map<String, dynamic>),
+    );
+
+ArrayType _$ArrayTypeFromJson(Map<String, dynamic> json) => ArrayType(
+      type: TypeUsage.fromJson(json['type'] as Map<String, dynamic>),
+    );
+
+Method _$MethodFromJson(Map<String, dynamic> json) => Method(
+      annotations: (json['annotations'] as List<dynamic>?)
+              ?.map((e) => Annotation.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      javadoc: json['javadoc'] == null
+          ? null
+          : JavaDocComment.fromJson(json['javadoc'] as Map<String, dynamic>),
+      modifiers: (json['modifiers'] as List<dynamic>?)
+              ?.map((e) => e as String)
+              .toSet() ??
+          const {},
+      name: json['name'] as String,
+      descriptor: json['descriptor'] as String?,
+      typeParams: (json['typeParams'] as List<dynamic>?)
+              ?.map((e) => TypeParam.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      params: (json['params'] as List<dynamic>?)
+              ?.map((e) => Param.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      returnType:
+          TypeUsage.fromJson(json['returnType'] as Map<String, dynamic>),
+    );
+
+Param _$ParamFromJson(Map<String, dynamic> json) => Param(
+      annotations: (json['annotations'] as List<dynamic>?)
+              ?.map((e) => Annotation.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      javadoc: json['javadoc'] == null
+          ? null
+          : JavaDocComment.fromJson(json['javadoc'] as Map<String, dynamic>),
+      name: json['name'] as String,
+      type: TypeUsage.fromJson(json['type'] as Map<String, dynamic>),
+    );
+
+Field _$FieldFromJson(Map<String, dynamic> json) => Field(
+      annotations: (json['annotations'] as List<dynamic>?)
+              ?.map((e) => Annotation.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+      javadoc: json['javadoc'] == null
+          ? null
+          : JavaDocComment.fromJson(json['javadoc'] as Map<String, dynamic>),
+      modifiers: (json['modifiers'] as List<dynamic>?)
+              ?.map((e) => e as String)
+              .toSet() ??
+          const {},
+      name: json['name'] as String,
+      type: TypeUsage.fromJson(json['type'] as Map<String, dynamic>),
+      defaultValue: json['defaultValue'],
+    );
+
+TypeParam _$TypeParamFromJson(Map<String, dynamic> json) => TypeParam(
+      name: json['name'] as String,
+      bounds: (json['bounds'] as List<dynamic>?)
+              ?.map((e) => TypeUsage.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+    );
+
+JavaDocComment _$JavaDocCommentFromJson(Map<String, dynamic> json) =>
+    JavaDocComment(
+      comment: json['comment'] as String? ?? '',
+    );
+
+Annotation _$AnnotationFromJson(Map<String, dynamic> json) => Annotation(
+      binaryName: json['binaryName'] as String,
+      properties: (json['properties'] as Map<String, dynamic>?)?.map(
+            (k, e) => MapEntry(k, e as Object),
+          ) ??
+          const {},
+    );
+
+KotlinClass _$KotlinClassFromJson(Map<String, dynamic> json) => KotlinClass(
+      name: json['name'] as String,
+      functions: (json['functions'] as List<dynamic>?)
+              ?.map((e) => KotlinFunction.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          const [],
+    );
+
+KotlinFunction _$KotlinFunctionFromJson(Map<String, dynamic> json) =>
+    KotlinFunction(
+      name: json['name'] as String,
+      descriptor: json['descriptor'] as String,
+      kotlinName: json['kotlinName'] as String,
+      isSuspend: json['isSuspend'] as bool,
+    );
diff --git a/pkgs/jnigen/lib/src/generate_bindings.dart b/pkgs/jnigen/lib/src/generate_bindings.dart
new file mode 100644
index 0000000..883ca85
--- /dev/null
+++ b/pkgs/jnigen/lib/src/generate_bindings.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'dart:convert';
+
+import 'bindings/c_generator.dart';
+import 'bindings/dart_generator.dart';
+import 'bindings/descriptor.dart';
+import 'bindings/excluder.dart';
+import 'bindings/kotlin_processor.dart';
+import 'bindings/linker.dart';
+import 'bindings/unnester.dart';
+import 'bindings/renamer.dart';
+import 'elements/elements.dart';
+import 'summary/summary.dart';
+import 'config/config.dart';
+import 'tools/tools.dart';
+import 'logging/logging.dart';
+
+void collectOutputStream(Stream<List<int>> stream, StringBuffer buffer) =>
+    stream.transform(const Utf8Decoder()).forEach(buffer.write);
+Future<void> generateJniBindings(Config config) async {
+  setLoggingLevel(config.logLevel);
+
+  await buildSummarizerIfNotExists();
+
+  final Classes classes;
+
+  try {
+    classes = await getSummary(config);
+  } on SummaryParseException catch (e) {
+    if (e.stderr != null) {
+      printError(e.stderr);
+    }
+    log.fatal(e.message);
+  }
+
+  classes.accept(Excluder(config));
+  classes.accept(KotlinProcessor());
+  await classes.accept(Linker(config));
+  classes.accept(const Unnester());
+  classes.accept(const Descriptor());
+  classes.accept(Renamer(config));
+
+  final cBased = config.outputConfig.bindingsType == BindingsType.cBased;
+  if (cBased) {
+    await classes.accept(CGenerator(config));
+  }
+
+  try {
+    await classes.accept(DartGenerator(config));
+    log.info('Completed');
+  } on Exception catch (e, trace) {
+    stderr.writeln(trace);
+    log.fatal('Error while writing bindings: $e');
+  }
+}
diff --git a/pkgs/jnigen/lib/src/logging/logging.dart b/pkgs/jnigen/lib/src/logging/logging.dart
new file mode 100644
index 0000000..71db217
--- /dev/null
+++ b/pkgs/jnigen/lib/src/logging/logging.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// coverage:ignore-file
+
+import 'dart:io';
+import 'package:logging/logging.dart';
+
+const _ansiRed = '\x1b[31m';
+const _ansiYellow = '\x1b[33m';
+const _ansiDefault = '\x1b[39;49m';
+
+String _colorize(String message, String colorCode) {
+  if (stderr.supportsAnsiEscapes) {
+    return '$colorCode$message$_ansiDefault';
+  }
+  return message;
+}
+
+/// Format [DateTime] for use in filename
+String _formatTime(DateTime now) {
+  return '${now.year}-${now.month}-${now.day}-'
+      '${now.hour}.${now.minute}.${now.second}';
+}
+
+// We need to respect logging level for console but log everything to file.
+// Hierarchical logging is convoluted. I'm just keeping track of log level.
+var _logLevel = Level.INFO;
+
+final _logDirUri = Directory.current.uri.resolve(".dart_tool/jnigen/logs/");
+
+final _logDir = () {
+  final dir = Directory.fromUri(_logDirUri);
+  dir.createSync(recursive: true);
+  return dir;
+}();
+
+Uri _getDefaultLogFileUri() =>
+    _logDir.uri.resolve("jnigen-${_formatTime(DateTime.now())}.log");
+
+IOSink? _logStream;
+
+/// Enable saving the logs to a file.
+///
+/// This is only meant to be called from an application entry point such as
+/// `main`.
+void enableLoggingToFile() {
+  _deleteOldLogFiles();
+  if (_logStream != null) {
+    throw StateError('Log file is already set');
+  }
+  _logStream = File.fromUri(_getDefaultLogFileUri()).openWrite();
+}
+
+// Maximum number of log files to keep.
+const _maxLogFiles = 5;
+
+/// Delete log files except most recent [_maxLogFiles] files.
+void _deleteOldLogFiles() {
+  final logFiles = _logDir.listSync().map((f) => File(f.path)).toList();
+  // sort in descending order of last modified time.
+  logFiles
+      .sort((f1, f2) => f2.lastModifiedSync().compareTo(f1.lastModifiedSync()));
+  final toDelete = logFiles.length < _maxLogFiles
+      ? const <File>[]
+      : logFiles.sublist(_maxLogFiles - 1);
+  for (final oldLogFile in toDelete) {
+    oldLogFile.deleteSync();
+  }
+}
+
+Logger log = () {
+  // initialize the logger.
+  final jnigenLogger = Logger('jnigen');
+  Logger.root.level = Level.ALL;
+  Logger.root.onRecord.listen((r) {
+    // Write to file regardless of level.
+    _logStream?.writeln('${r.level} ${r.time}: ${r.message}');
+    // write to console only if level is above configured level.
+    if (r.level < _logLevel) {
+      return;
+    }
+    var message = '(${r.loggerName}) ${r.level.name}: ${r.message}';
+    if ((r.level == Level.SHOUT || r.level == Level.SEVERE)) {
+      message = _colorize(message, _ansiRed);
+    } else if (r.level == Level.WARNING) {
+      message = _colorize(message, _ansiYellow);
+    }
+    stderr.writeln(message);
+  });
+  return jnigenLogger;
+}();
+
+/// Set logging level to [level].
+void setLoggingLevel(Level level) {
+  log.fine('Set log level: $level');
+  _logLevel = level;
+}
+
+/// Prints [message] without logging information.
+///
+/// Primarily used in printing output of failed commands.
+void printError(Object? message) {
+  stderr.writeln(_colorize(message.toString(), _ansiRed));
+}
+
+extension FatalErrors on Logger {
+  Never fatal(Object? message, {int exitCode = 1}) {
+    message = _colorize('Fatal: $message', _ansiRed);
+    stderr.writeln(message);
+    return exit(exitCode);
+  }
+}
+
+extension WriteToFile on Logger {
+  void writeToFile(Object? data) {
+    _logStream?.writeln(data);
+  }
+
+  void writeSectionToFile(String? sectionName, Object? data) {
+    _logStream?.writeln("==== Begin $sectionName ====");
+    _logStream?.writeln(data);
+    _logStream?.writeln("==== End $sectionName ====");
+  }
+}
diff --git a/pkgs/jnigen/lib/src/summary/summary.dart b/pkgs/jnigen/lib/src/summary/summary.dart
new file mode 100644
index 0000000..1706275
--- /dev/null
+++ b/pkgs/jnigen/lib/src/summary/summary.dart
@@ -0,0 +1,195 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import '../../tools.dart';
+import '../config/config.dart';
+import '../elements/elements.dart';
+import '../generate_bindings.dart';
+import '../logging/logging.dart';
+
+class SummaryParseException implements Exception {
+  final String? stderr;
+  final String message;
+  SummaryParseException(this.message) : stderr = null;
+  SummaryParseException.withStderr(this.stderr, this.message);
+
+  @override
+  String toString() => message;
+}
+
+/// A command based summary source which calls the ApiSummarizer command.
+/// [sourcePaths] and [classPaths] can be provided for the summarizer to find
+/// required dependencies. The [classes] argument specifies the fully qualified
+/// names of classes or packages included in the generated summary. when a
+/// package is specified, its contents are included recursively.
+///
+/// When the default summarizer scans the [sourcePaths], it assumes that
+/// the directory names reflect actual package paths. For example, a class name
+/// com.example.pkg.Cls will be mapped to com/example/pkg/Cls.java.
+///
+/// The default summarizer needs to be built with `jnigen:setup`
+/// script before this API is used.
+class SummarizerCommand {
+  SummarizerCommand({
+    this.command = "java -jar .dart_tool/jnigen/ApiSummarizer.jar",
+    List<Uri>? sourcePath,
+    List<Uri>? classPath,
+    this.extraArgs = const [],
+    required this.classes,
+    this.workingDirectory,
+    this.backend,
+  })  : sourcePaths = sourcePath ?? [],
+        classPaths = classPath ?? [];
+
+  static const sourcePathsOption = '-s';
+  static const classPathsOption = '-c';
+
+  String command;
+  List<Uri> sourcePaths, classPaths;
+
+  List<String> extraArgs;
+  List<String> classes;
+
+  Uri? workingDirectory;
+  SummarizerBackend? backend;
+
+  void addSourcePaths(List<Uri> paths) {
+    sourcePaths.addAll(paths);
+  }
+
+  void addClassPaths(List<Uri> paths) {
+    classPaths.addAll(paths);
+  }
+
+  void _addPathParam(List<String> args, String option, List<Uri> paths) {
+    if (paths.isNotEmpty) {
+      final joined = paths
+          .map((uri) => uri.toFilePath())
+          .join(Platform.isWindows ? ';' : ':');
+      if (option.endsWith("=")) {
+        args.add(option + joined);
+      } else {
+        args.addAll([option, joined]);
+      }
+    }
+  }
+
+  Future<Process> runProcess() async {
+    final commandSplit = command.split(" ");
+    final exec = commandSplit[0];
+    final List<String> args = commandSplit.sublist(1);
+
+    _addPathParam(args, sourcePathsOption, sourcePaths);
+    _addPathParam(args, classPathsOption, classPaths);
+    if (backend != null) {
+      args.addAll(['--backend', backend!.name]);
+    }
+    args.addAll(extraArgs);
+    args.addAll(classes);
+    log.info('execute $exec ${args.join(' ')}');
+    final proc = await Process.start(exec, args,
+        workingDirectory: workingDirectory?.toFilePath() ?? '.');
+    return proc;
+  }
+}
+
+Future<Classes> getSummary(Config config) async {
+  // This function is a potential entry point in tests, which set log level to
+  // warning.
+  setLoggingLevel(config.logLevel);
+  final summarizer = SummarizerCommand(
+    sourcePath: config.sourcePath,
+    classPath: config.classPath,
+    classes: config.classes,
+    workingDirectory: config.summarizerOptions?.workingDirectory,
+    extraArgs: config.summarizerOptions?.extraArgs ?? const [],
+    backend: config.summarizerOptions?.backend,
+  );
+
+  // Additional sources added using maven downloads and gradle trickery.
+  final extraSources = <Uri>[];
+  final extraJars = <Uri>[];
+  final mavenDl = config.mavenDownloads;
+  if (mavenDl != null) {
+    final sourcePath = mavenDl.sourceDir;
+    await Directory(sourcePath).create(recursive: true);
+    await MavenTools.downloadMavenSources(
+        MavenTools.deps(mavenDl.sourceDeps), sourcePath);
+    extraSources.add(Uri.directory(sourcePath));
+    final jarPath = mavenDl.jarDir;
+    await Directory(jarPath).create(recursive: true);
+    await MavenTools.downloadMavenJars(
+        MavenTools.deps(mavenDl.sourceDeps + mavenDl.jarOnlyDeps), jarPath);
+    extraJars.addAll(await Directory(jarPath)
+        .list()
+        .where((entry) => entry.path.endsWith('.jar'))
+        .map((entry) => entry.uri)
+        .toList());
+  }
+  final androidConfig = config.androidSdkConfig;
+  if (androidConfig != null && androidConfig.addGradleDeps) {
+    final deps = AndroidSdkTools.getGradleClasspaths(
+      configRoot: config.configRoot,
+      androidProject: androidConfig.androidExample ?? '.',
+    );
+    extraJars.addAll(deps.map(Uri.file));
+  }
+  if (androidConfig != null && androidConfig.addGradleSources) {
+    final deps = AndroidSdkTools.getGradleSources(
+      configRoot: config.configRoot,
+      androidProject: androidConfig.androidExample ?? '.',
+    );
+    extraSources.addAll(deps.map(Uri.file));
+  }
+  if (androidConfig != null && androidConfig.versions != null) {
+    final versions = androidConfig.versions!;
+    final androidSdkRoot =
+        androidConfig.sdkRoot ?? AndroidSdkTools.getAndroidSdkRoot();
+    final androidJar = await AndroidSdkTools.getAndroidJarPath(
+        sdkRoot: androidSdkRoot, versionOrder: versions);
+    if (androidJar != null) {
+      extraJars.add(Uri.directory(androidJar));
+    }
+  }
+
+  summarizer.addSourcePaths(extraSources);
+  summarizer.addClassPaths(extraJars);
+
+  Process process;
+  Stream<List<int>> input;
+  final stopwatch = Stopwatch()..start();
+  try {
+    process = await summarizer.runProcess();
+    input = process.stdout;
+  } on Exception catch (e) {
+    throw SummaryParseException('Cannot generate API summary: $e');
+  }
+  final stderrBuffer = StringBuffer();
+  collectOutputStream(process.stderr, stderrBuffer);
+  final stream = const JsonDecoder().bind(const Utf8Decoder().bind(input));
+  dynamic json;
+  try {
+    json = await stream.single;
+    stopwatch.stop();
+    log.info('Parsing inputs took ${stopwatch.elapsedMilliseconds} ms');
+  } on Exception catch (e) {
+    await process.exitCode;
+    throw SummaryParseException.withStderr(
+      stderrBuffer.toString(),
+      'Cannot generate summary: $e',
+    );
+  } finally {
+    log.writeSectionToFile("summarizer logs", stderrBuffer.toString());
+  }
+  if (json == null) {
+    throw SummaryParseException('Expected JSON element from summarizer.');
+  }
+  final list = json as List;
+  final classes = Classes.fromJson(list);
+  return classes;
+}
diff --git a/pkgs/jnigen/lib/src/tools/android_sdk_tools.dart b/pkgs/jnigen/lib/src/tools/android_sdk_tools.dart
new file mode 100644
index 0000000..3de5575
--- /dev/null
+++ b/pkgs/jnigen/lib/src/tools/android_sdk_tools.dart
@@ -0,0 +1,226 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:path/path.dart';
+
+import '../logging/logging.dart';
+
+class _AndroidToolsException implements Exception {
+  _AndroidToolsException(this.message);
+  String message;
+  @override
+  String toString() => message;
+}
+
+class SdkNotFoundException extends _AndroidToolsException {
+  SdkNotFoundException(String message) : super(message);
+}
+
+class GradleException extends _AndroidToolsException {
+  GradleException(String message) : super(message);
+}
+
+class AndroidSdkTools {
+  static String getAndroidSdkRoot() {
+    final envVar = Platform.environment['ANDROID_SDK_ROOT'];
+    if (envVar == null) {
+      throw SdkNotFoundException('Android SDK not found. Please set '
+          'ANDROID_SDK_ROOT environment variable or specify through command '
+          'line override.');
+    }
+    return envVar;
+  }
+
+  static Future<String?> _getVersionDir(
+      String relative, String sdkRoot, List<int> versionOrder) async {
+    final parent = join(sdkRoot, relative);
+    for (var version in versionOrder) {
+      final dir = Directory(join(parent, 'android-$version'));
+      if (await dir.exists()) {
+        return dir.path;
+      }
+    }
+    return null;
+  }
+
+  static Future<String?> _getFile(String sdkRoot, String relative,
+      List<int> versionOrder, String file) async {
+    final platform = await _getVersionDir(relative, sdkRoot, versionOrder);
+    if (platform == null) return null;
+    final filePath = join(platform, file);
+    if (await File(filePath).exists()) {
+      log.info('Found $filePath');
+      return filePath;
+    }
+    return null;
+  }
+
+  static const _gradleCannotFindJars = 'Gradle stub cannot find JAR libraries. '
+      'This might be because no APK build has happened yet.';
+
+  static const _leftOverStubWarning = 'If you are seeing this error in '
+      '`flutter build` output, it is likely that `jnigen` left some stubs in '
+      'the build.gradle file. Please restore that file from your version '
+      'control system or manually remove the stub functions named '
+      '$_gradleGetClasspathTaskName and / or $_gradleGetSourcesTaskName.';
+
+  static Future<String?> getAndroidJarPath(
+          {required String sdkRoot, required List<int> versionOrder}) async =>
+      await _getFile(sdkRoot, 'platforms', versionOrder, 'android.jar');
+
+  static const _gradleGetClasspathTaskName = 'getReleaseCompileClasspath';
+
+  static const _gradleGetClasspathStub = '''
+// Gradle stub for listing dependencies in jnigen. If found in
+// android/build.gradle, please delete the following function.
+task $_gradleGetClasspathTaskName(type: Copy) {
+  project.afterEvaluate {
+    try {
+      def app = project(':app')
+      def android = app.android
+      def cp = [android.getBootClasspath()[0]]
+      android.applicationVariants.each { variant ->
+        if (variant.name.equals('release')) {
+          cp += variant.javaCompile.classpath.getFiles()
+        }
+      }
+      cp.each { println it }
+    } catch (Exception e) {
+      System.err.println("$_gradleCannotFindJars")
+      System.err.println("$_leftOverStubWarning")
+      throw e
+    }
+  }
+}
+''';
+
+  static const _gradleGetSourcesTaskName = 'getSources';
+  // adapted from https://stackoverflow.com/questions/39975780/how-can-i-use-gradle-to-download-dependencies-and-their-source-files-and-place-t/39981143#39981143
+  // Although it appears we can use this same code for getting JAR artifacts,
+  // there is no JAR equivalent for `org.gradle.language.base.Artifact`.
+  // So it appears different methods should be used for JAR artifacts.
+  static const _gradleGetSourcesStub = '''
+// Gradle stub for fetching source dependencies in jnigen. If found in
+// android/build.gradle, please delete the following function.
+task $_gradleGetSourcesTaskName(type: Copy) {
+  project.afterEvaluate {
+    def componentIds = project(':app').configurations.releaseCompileClasspath.incoming
+      .resolutionResult.allDependencies.collect { it.selected.id }
+
+    ArtifactResolutionResult result = dependencies.createArtifactResolutionQuery()
+      .forComponents(componentIds)
+      .withArtifacts(JvmLibrary, SourcesArtifact)
+      .execute()
+
+    def sourceArtifacts = []
+
+    result.resolvedComponents.each { ComponentArtifactsResult component ->
+      Set<ArtifactResult> sources = component.getArtifacts(SourcesArtifact)
+      sources.each { ArtifactResult ar ->
+        if (ar instanceof ResolvedArtifactResult) {
+          sourceArtifacts << ar.file
+        }
+      }
+    }
+    sourceArtifacts.forEach { println it }
+  }
+  System.err.println("$_leftOverStubWarning")
+}
+''';
+
+  /// Get release compile classpath used by Gradle for android build.
+  ///
+  /// This function temporarily overwrites the build.gradle file by a stub with
+  /// function to list all dependency paths for release variant.
+  /// This function fails if no gradle build is attempted before.
+  ///
+  /// If current project is not directly buildable by gradle, eg: a plugin,
+  /// a relative path to other project can be specified using [androidProject].
+  static List<String> getGradleClasspaths(
+          {Uri? configRoot, String androidProject = '.'}) =>
+      _runGradleStub(
+        stubName: _gradleGetClasspathTaskName,
+        stubCode: _gradleGetClasspathStub,
+        androidProject: androidProject,
+        configRoot: configRoot,
+      );
+
+  /// Get source paths for all gradle dependencies.
+  ///
+  /// This function temporarily overwrites the build.gradle file by a stub with
+  /// function to list all dependency paths for release variant.
+  /// This function fails if no gradle build is attempted before.
+  static List<String> getGradleSources(
+      {Uri? configRoot, String androidProject = '.'}) {
+    return _runGradleStub(
+      stubName: _gradleGetSourcesTaskName,
+      stubCode: _gradleGetSourcesStub,
+      androidProject: androidProject,
+      configRoot: configRoot,
+    );
+  }
+
+  static String _appendStub(String script, String stub) {
+    return script.contains(stub) ? script : script + stub;
+  }
+
+  static List<String> _runGradleStub({
+    required String stubName,
+    required String stubCode,
+    Uri? configRoot,
+    String androidProject = '.',
+  }) {
+    log.info('trying to obtain gradle dependencies [$stubName]...');
+    if (configRoot != null) {
+      androidProject = configRoot.resolve(androidProject).toFilePath();
+    }
+    final android = join(androidProject, 'android');
+    final buildGradle = join(android, 'build.gradle');
+    final buildGradleOld = join(android, 'build.gradle.old');
+    final origBuild = File(buildGradle);
+    final script = origBuild.readAsStringSync();
+    origBuild.renameSync(buildGradleOld);
+    origBuild.createSync();
+    log.finer('Writing temporary gradle script with stub "$stubName"...');
+    origBuild.writeAsStringSync(_appendStub(script, stubCode));
+    log.finer('Running gradle wrapper...');
+    final gradleCommand = Platform.isWindows ? '.\\gradlew.bat' : './gradlew';
+    ProcessResult procRes;
+    try {
+      procRes = Process.runSync(gradleCommand, ['-q', stubName],
+          workingDirectory: android, runInShell: true);
+    } finally {
+      log.info('Restoring build scripts');
+      origBuild.writeAsStringSync(
+        script
+            .replaceAll(_gradleGetClasspathStub, "")
+            .replaceAll(_gradleGetSourcesStub, ""),
+      );
+      File(buildGradleOld).deleteSync();
+    }
+    if (procRes.exitCode != 0) {
+      final inAndroidProject =
+          (androidProject == '.') ? '' : ' in $androidProject';
+      throw GradleException('\n\ngradle exited with status '
+          '${procRes.exitCode}\n. This can be because the Android build is not '
+          'yet cached. Please run `flutter build apk`$inAndroidProject and try '
+          'again\n');
+    }
+    // Record both stdout and stderr of gradle.
+    log.writeSectionToFile("Gradle logs ($stubName)", procRes.stderr);
+    log.writeSectionToFile("Gradle output ($stubName)", procRes.stdout);
+    final output = procRes.stdout as String;
+    if (output.isEmpty) {
+      printError(procRes.stderr);
+      throw Exception("Gradle stub exited without output.");
+    }
+    final paths = (procRes.stdout as String)
+        .trim()
+        .split(Platform.isWindows ? '\r\n' : '\n');
+    log.fine('Found ${paths.length} entries');
+    return paths;
+  }
+}
diff --git a/pkgs/jnigen/lib/src/tools/build_summarizer.dart b/pkgs/jnigen/lib/src/tools/build_summarizer.dart
new file mode 100644
index 0000000..1e0ec06
--- /dev/null
+++ b/pkgs/jnigen/lib/src/tools/build_summarizer.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// TODO(#43): Address concurrently building summarizer.
+//
+// In the current state summarizer has to be built before tests, which run
+// concurrently. Ignoring coverage for this file until that issue is addressed.
+//
+// coverage:ignore-file
+
+import 'dart:io';
+
+import 'package:path/path.dart';
+
+import '../logging/logging.dart';
+import '../util/find_package.dart';
+
+final toolPath = join('.', '.dart_tool', 'jnigen');
+final mvnTargetDir = join(toolPath, 'target');
+final jarFile = join(toolPath, 'ApiSummarizer.jar');
+final targetJarFile = join(mvnTargetDir, 'ApiSummarizer.jar');
+
+Future<void> buildApiSummarizer() async {
+  final pkg = await findPackageRoot('jnigen');
+  if (pkg == null) {
+    log.fatal('package jnigen not found!');
+  }
+  final pom = pkg.resolve('java/pom.xml');
+  await Directory(toolPath).create(recursive: true);
+  final mvnArgs = [
+    'compile',
+    '--batch-mode',
+    '--update-snapshots',
+    '-f',
+    pom.toFilePath(),
+  ];
+  log.info('execute mvn ${mvnArgs.join(" ")}');
+  try {
+    final mvnProc = await Process.run('mvn', mvnArgs,
+        workingDirectory: toolPath, runInShell: true);
+    final exitCode = mvnProc.exitCode;
+    if (exitCode == 0) {
+      await File(targetJarFile).rename(jarFile);
+    } else {
+      printError(mvnProc.stdout);
+      printError(mvnProc.stderr);
+      printError("maven exited with $exitCode");
+    }
+  } finally {
+    await Directory(mvnTargetDir).delete(recursive: true);
+  }
+}
+
+Future<void> buildSummarizerIfNotExists({bool force = false}) async {
+  // TODO(#43): This function cannot be invoked concurrently because 2 processes
+  // will start building summarizer at once. Introduce a locking mechnanism so
+  // that when one process is building summarizer JAR, other process waits using
+  // exponential backoff.
+  final jarExists = await File(jarFile).exists();
+  final isJarStale = jarExists &&
+      await isPackageModifiedAfter(
+          'jnigen', await File(jarFile).lastModified(), 'java/');
+  if (isJarStale) {
+    log.info('Rebuilding ApiSummarizer component since sources '
+        'have changed. This might take some time.');
+  }
+  if (!jarExists) {
+    log.info('Building ApiSummarizer component. '
+        'This might take some time. '
+        'The build will be cached for subsequent runs.');
+  }
+  if (!jarExists || isJarStale || force) {
+    await buildApiSummarizer();
+  } else {
+    log.info('ApiSummarizer.jar exists. Skipping build..');
+  }
+}
diff --git a/pkgs/jnigen/lib/src/tools/maven_tools.dart b/pkgs/jnigen/lib/src/tools/maven_tools.dart
new file mode 100644
index 0000000..74cb3a1
--- /dev/null
+++ b/pkgs/jnigen/lib/src/tools/maven_tools.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:path/path.dart';
+
+import '../logging/logging.dart';
+
+/// This class provides some utility methods to download a sources / jars
+/// using maven along with transitive dependencies.
+class MavenTools {
+  static final currentDir = Directory(".");
+
+  /// Helper method since we can't pass inheritStdio option to [Process.run].
+  static Future<int> _runCmd(String exec, List<String> args,
+      [String? workingDirectory]) async {
+    log.info('execute $exec ${args.join(" ")}');
+    final proc = await Process.start(exec, args,
+        workingDirectory: workingDirectory,
+        runInShell: true,
+        mode: ProcessStartMode.inheritStdio);
+    return proc.exitCode;
+  }
+
+  static Future<void> _runMavenCommand(
+    List<MavenDependency> deps,
+    List<String> mvnArgs,
+    Directory tempDir,
+  ) async {
+    final pom = _getStubPom(deps);
+    final tempPom = join(tempDir.path, "temp_pom.xml");
+    final tempTarget = join(tempDir.path, "target");
+    log.finer('using POM stub:\n$pom');
+    await File(tempPom).writeAsString(pom);
+    await Directory(tempTarget).create();
+    await _runCmd('mvn', ['-q', '-f', tempPom, ...mvnArgs]);
+    await File(tempPom).delete();
+    await Directory(tempTarget).delete(recursive: true);
+  }
+
+  /// Create a list of [MavenDependency] objects from maven coordinates in string form.
+  static List<MavenDependency> deps(List<String> depNames) =>
+      depNames.map(MavenDependency.fromString).toList();
+
+  /// Downloads and unpacks source files of [deps] into [targetDir].
+  static Future<void> downloadMavenSources(
+      List<MavenDependency> deps, String targetDir) async {
+    final tempDir = await currentDir.createTemp("maven_temp_");
+    await _runMavenCommand(
+      deps,
+      [
+        'dependency:unpack-dependencies',
+        '-DexcludeTransitive=true',
+        '-DoutputDirectory=../$targetDir',
+        '-Dclassifier=sources',
+      ],
+      tempDir,
+    );
+    await tempDir.delete(recursive: true);
+  }
+
+  /// Downloads JAR files of all [deps] transitively into [targetDir].
+  static Future<void> downloadMavenJars(
+      List<MavenDependency> deps, String targetDir) async {
+    final tempDir = await currentDir.createTemp("maven_temp_");
+    await _runMavenCommand(
+      deps,
+      [
+        'dependency:copy-dependencies',
+        '-DoutputDirectory=../$targetDir',
+      ],
+      tempDir,
+    );
+    await tempDir.delete(recursive: true);
+  }
+
+  static String _getStubPom(List<MavenDependency> deps,
+      {String javaVersion = '11'}) {
+    final depDecls = <String>[];
+    for (var dep in deps) {
+      final otherTags = StringBuffer();
+      for (var entry in dep.otherTags.entries) {
+        otherTags.write('''
+      <${entry.key}>
+        ${entry.value}
+      </${entry.key}>
+      ''');
+      }
+      depDecls.add('''
+      <dependency>
+        <groupId>${dep.groupID}</groupId>
+        <artifactId>${dep.artifactID}</artifactId>
+        <version>${dep.version}</version>
+        ${otherTags.toString()}
+      </dependency>''');
+    }
+    return '''
+<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">
+    <repositories>
+      <repository>
+        <id>google-maven-repo</id>
+        <name>Google Maven Repository</name>
+        <url>https://maven.google.com</url>
+      </repository>
+    </repositories>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.mycompany.app</groupId>
+    <artifactId>jnigen_maven_stub</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <properties>
+      <maven.compiler.source>$javaVersion</maven.compiler.source>
+      <maven.compiler.target>$javaVersion</maven.compiler.target>
+    </properties>
+    <dependencies>
+${depDecls.join("\n")}
+    </dependencies>
+    <build>
+      <directory>\${project.basedir}/target</directory>
+    </build>
+</project>''';
+  }
+}
+
+/// Maven dependency with group ID, artifact ID, and version.
+class MavenDependency {
+  MavenDependency(this.groupID, this.artifactID, this.version,
+      {this.otherTags = const {}});
+  factory MavenDependency.fromString(String fullName) {
+    final components = fullName.split(':');
+    if (components.length != 3) {
+      throw ArgumentError('invalid name for maven dependency: $fullName');
+    }
+    return MavenDependency(components[0], components[1], components[2]);
+  }
+  String groupID, artifactID, version;
+  Map<String, String> otherTags;
+}
diff --git a/pkgs/jnigen/lib/src/tools/tools.dart b/pkgs/jnigen/lib/src/tools/tools.dart
new file mode 100644
index 0000000..9677a3e
--- /dev/null
+++ b/pkgs/jnigen/lib/src/tools/tools.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'android_sdk_tools.dart';
+export 'maven_tools.dart';
+export 'build_summarizer.dart';
diff --git a/pkgs/jnigen/lib/src/util/find_package.dart b/pkgs/jnigen/lib/src/util/find_package.dart
new file mode 100644
index 0000000..89d95dd
--- /dev/null
+++ b/pkgs/jnigen/lib/src/util/find_package.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:package_config/package_config.dart';
+
+Future<Package?> findPackage(String packageName) async {
+  final packageConfig = await findPackageConfig(Directory.current);
+  if (packageConfig == null) {
+    return null;
+  }
+  return packageConfig[packageName];
+}
+
+Future<Uri?> findPackageRoot(String packageName) async {
+  return (await findPackage(packageName))?.root;
+}
+
+Future<bool> isPackageModifiedAfter(String packageName, DateTime time,
+    [String? subDir]) async {
+  final root = await findPackageRoot(packageName);
+  if (root == null) {
+    throw UnsupportedError('package $packageName does not exist');
+  }
+  var checkRoot = root;
+  if (subDir != null) {
+    checkRoot = root.resolve(subDir);
+  }
+  final dir = Directory.fromUri(checkRoot);
+  if (!await dir.exists()) {
+    throw UnsupportedError('can not resolve $subDir in $packageName');
+  }
+  // A directory's modification time is not helpful because one of
+  // internal files may be modified later.
+  // In case of git / pub package we might be able to check pubspec, but no
+  // such technique applies for path packages.
+  await for (final entry in dir.list(recursive: true)) {
+    final stat = await entry.stat();
+    if (stat.modified.isAfter(time)) {
+      return true;
+    }
+  }
+  return false;
+}
diff --git a/pkgs/jnigen/lib/src/util/string_util.dart b/pkgs/jnigen/lib/src/util/string_util.dart
new file mode 100644
index 0000000..5a65bfb
--- /dev/null
+++ b/pkgs/jnigen/lib/src/util/string_util.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+extension StringUtil on String {
+  /// Makes the first letter uppercase.
+  String capitalize() {
+    return '${this[0].toUpperCase()}${substring(1)}';
+  }
+
+  /// Reverses an ASCII string.
+  String get reversed => split('').reversed.join();
+}
diff --git a/pkgs/jnigen/lib/tools.dart b/pkgs/jnigen/lib/tools.dart
new file mode 100644
index 0000000..252e4b7
--- /dev/null
+++ b/pkgs/jnigen/lib/tools.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library jnigen_tools;
+
+export 'src/tools/tools.dart';
diff --git a/pkgs/jnigen/pubspec.yaml b/pkgs/jnigen/pubspec.yaml
new file mode 100644
index 0000000..35e01bc
--- /dev/null
+++ b/pkgs/jnigen/pubspec.yaml
@@ -0,0 +1,37 @@
+# Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+name: jnigen
+description: A Dart bindings generator for Java and Kotlin that uses JNI under the hood to interop with Java virtual machine.
+version: 0.8.0-wip
+repository: https://github.com/dart-lang/jnigen/tree/main/jnigen
+
+environment:
+  sdk: ">=3.1.0 <4.0.0"
+
+topics:
+  - interop
+  - ffi
+  - codegen
+  - java
+  - jni
+
+dependencies:
+  json_annotation: ^4.8.0
+  package_config: ^2.1.0
+  path: ^1.8.0
+  pub_semver: ^2.1.4
+  args: ^2.3.0
+  yaml: ^3.1.0
+  logging: ^1.0.2
+  meta: ^1.8.0
+  cli_config: ^0.1.0
+
+dev_dependencies:
+  lints: ^2.0.0
+  jni:
+    path: ../jni
+  test: ^1.24.1
+  build_runner: ^2.2.0
+  json_serializable: ^6.6.0
diff --git a/pkgs/jnigen/test/.gitignore b/pkgs/jnigen/test/.gitignore
new file mode 100644
index 0000000..36f1ec5
--- /dev/null
+++ b/pkgs/jnigen/test/.gitignore
@@ -0,0 +1,4 @@
+# TODO(#166): Remove this.
+!jni.jar
+runtime_test_registrant_dartonly_generated.dart
+generated_runtime_test.dart
\ No newline at end of file
diff --git a/pkgs/jnigen/test/README.md b/pkgs/jnigen/test/README.md
new file mode 100644
index 0000000..e3ada83
--- /dev/null
+++ b/pkgs/jnigen/test/README.md
@@ -0,0 +1,13 @@
+## How to run tests?
+#### One-time setup:
+```
+dart run jnigen:setup
+```
+
+#### Running tests
+```sh
+dart run tool/generate_runtime_tests.dart ## Regenerates runtime test files
+dart test
+```
+
+Note: Tests fail if summarizer is not previously built and 2 tests try to build it concurrently. We have to address it using a lock file and exponential backoff (#43). Temporarily, run `dart run jnigen:setup` before running tests for the first time.
diff --git a/pkgs/jnigen/test/config_test.dart b/pkgs/jnigen/test/config_test.dart
new file mode 100644
index 0000000..ae2f987
--- /dev/null
+++ b/pkgs/jnigen/test/config_test.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jnigen/src/config/config.dart';
+import 'package:test/test.dart';
+import 'package:path/path.dart' hide equals;
+import 'package:path/path.dart' as path show equals;
+
+import 'jackson_core_test/generate.dart';
+import 'test_util/test_util.dart';
+
+const packageTests = 'test';
+final jacksonCoreTests = absolute(packageTests, 'jackson_core_test');
+final thirdParty = absolute(jacksonCoreTests, 'third_party');
+final lib = absolute(thirdParty, 'c_based', 'dart_bindings');
+final src = absolute(thirdParty, 'c_based', 'c_bindings');
+final testLib = absolute(thirdParty, 'test_', 'c_based', 'dart_bindings');
+final testSrc = absolute(thirdParty, 'test_', 'c_based', 'c_bindings');
+
+/// Compares 2 [Config] objects using [expect] to give useful errors when
+/// two fields are not equal.
+void expectConfigsAreEqual(Config a, Config b) {
+  expect(a.classes, equals(b.classes), reason: "classes");
+  expect(a.outputConfig.cConfig?.libraryName,
+      equals(b.outputConfig.cConfig?.libraryName),
+      reason: "libraryName");
+  expect(a.outputConfig.cConfig?.path, equals(b.outputConfig.cConfig?.path),
+      reason: "cRoot");
+  expect(a.outputConfig.dartConfig.path, equals(b.outputConfig.dartConfig.path),
+      reason: "dartRoot");
+  expect(a.outputConfig.symbolsConfig?.path,
+      equals(b.outputConfig.symbolsConfig?.path),
+      reason: "symbolsRoot");
+  expect(a.sourcePath, equals(b.sourcePath), reason: "sourcePath");
+  expect(a.experiments, equals(b.experiments), reason: "experiments");
+  expect(a.classPath, equals(b.classPath), reason: "classPath");
+  expect(a.preamble, equals(b.preamble), reason: "preamble");
+  final am = a.mavenDownloads;
+  final bm = b.mavenDownloads;
+  if (am != null) {
+    expect(bm, isNotNull);
+    expect(am.sourceDeps, bm!.sourceDeps, reason: "mavenDownloads.sourceDeps");
+    expect(path.equals(am.sourceDir, bm.sourceDir), isTrue,
+        reason: "mavenDownloads.sourceDir");
+    expect(am.jarOnlyDeps, bm.jarOnlyDeps,
+        reason: "mavenDownloads.jarOnlyDeps");
+    expect(path.equals(am.jarDir, bm.jarDir), isTrue,
+        reason: "mavenDownloads.jarDir");
+  } else {
+    expect(bm, isNull, reason: "mavenDownloads");
+  }
+  final aa = a.androidSdkConfig;
+  final ba = b.androidSdkConfig;
+  if (aa != null) {
+    expect(ba, isNotNull, reason: "androidSdkConfig");
+    expect(aa.versions, ba!.versions, reason: "androidSdkConfig.versions");
+    expect(aa.sdkRoot, ba.sdkRoot, reason: "androidSdkConfig.sdkRoot");
+  } else {
+    expect(ba, isNull, reason: "androidSdkConfig");
+  }
+  final aso = a.summarizerOptions;
+  final bso = b.summarizerOptions;
+  if (aso != null) {
+    expect(bso, isNotNull, reason: "summarizerOptions");
+    expect(aso.extraArgs, bso!.extraArgs,
+        reason: "summarizerOptions.extraArgs");
+    expect(aso.workingDirectory, bso.workingDirectory,
+        reason: "summarizerOptions.workingDirectory");
+    expect(aso.backend, bso.backend, reason: "summarizerOptions.backend");
+  } else {
+    expect(bso, isNull, reason: "summarizerOptions");
+  }
+}
+
+final jnigenYaml = join(jacksonCoreTests, 'jnigen.yaml');
+
+Config parseYamlConfig({List<String> overrides = const []}) =>
+    Config.parseArgs(['--config', jnigenYaml, ...overrides]);
+
+void testForErrorChecking<T extends Exception>(
+    {required String name,
+    required List<String> overrides,
+    dynamic Function(Config)? function}) {
+  test(name, () {
+    expect(
+      () {
+        final config = parseYamlConfig(overrides: overrides);
+        if (function != null) {
+          function(config);
+        }
+      },
+      throwsA(isA<T>()),
+    );
+  });
+}
+
+void main() async {
+  await checkLocallyBuiltDependencies();
+  final config = Config.parseArgs([
+    '--config',
+    jnigenYaml,
+    '-Doutput.c.path=$testSrc${Platform.pathSeparator}',
+    '-Doutput.dart.path=$testLib${Platform.pathSeparator}',
+  ]);
+
+  test('compare configuration values', () {
+    expectConfigsAreEqual(
+      config,
+      getConfig(
+        root: join(thirdParty, 'test_'),
+        bindingsType: BindingsType.cBased,
+      ),
+    );
+  });
+
+  group('Test for config error checking', () {
+    testForErrorChecking<ConfigException>(
+      name: 'Invalid bindings type',
+      overrides: ['-Doutput.bindings_type=c_base'],
+    );
+    testForErrorChecking<ConfigException>(
+      name: 'Invalid output structure',
+      overrides: ['-Doutput.dart.structure=singl_file'],
+    );
+    testForErrorChecking<ConfigException>(
+      name: 'Dart path not ending with /',
+      overrides: ['-Doutput.dart.path=lib'],
+    );
+    testForErrorChecking<FormatException>(
+      name: 'Invalid log level',
+      overrides: ['-Dlog_level=inf'],
+    );
+    testForErrorChecking<ConfigException>(
+      name: 'Nested class specified',
+      overrides: ['-Dclasses=com.android.Clock\$Clock'],
+    );
+  });
+}
diff --git a/pkgs/jnigen/test/dart_generator_test.dart b/pkgs/jnigen/test/dart_generator_test.dart
new file mode 100644
index 0000000..e9bbb76
--- /dev/null
+++ b/pkgs/jnigen/test/dart_generator_test.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:jnigen/src/bindings/dart_generator.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() async {
+  await checkLocallyBuiltDependencies();
+
+  test('OutsideInBuffer', () {
+    final buffer = OutsideInBuffer();
+    buffer.appendLeft('f(');
+    buffer.prependRight('x)');
+    buffer.appendLeft('g(');
+    buffer.prependRight('y) + ');
+    expect(buffer.toString(), 'f(g(y) + x)');
+  });
+}
diff --git a/pkgs/jnigen/test/descriptor_test.dart b/pkgs/jnigen/test/descriptor_test.dart
new file mode 100644
index 0000000..f1b9060
--- /dev/null
+++ b/pkgs/jnigen/test/descriptor_test.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:jnigen/src/bindings/descriptor.dart';
+import 'package:jnigen/src/bindings/linker.dart';
+import 'package:jnigen/src/bindings/unnester.dart';
+import 'package:jnigen/src/config/config_types.dart';
+import 'package:jnigen/src/summary/summary.dart';
+import 'package:test/test.dart';
+
+import 'simple_package_test/generate.dart' as simple_package_test;
+import 'kotlin_test/generate.dart' as kotlin_test;
+import 'jackson_core_test/generate.dart' as jackson_core_test;
+import 'test_util/test_util.dart';
+
+void main() {
+  checkLocallyBuiltDependencies();
+  for (final (name, getConfig) in [
+    ('simple_package', simple_package_test.getConfig),
+    ('kotlin', kotlin_test.getConfig),
+    ('jackson_core', jackson_core_test.getConfig),
+  ]) {
+    test('Method descriptor generation for $name',
+        timeout: const Timeout.factor(3), () async {
+      final config = getConfig();
+      config.summarizerOptions =
+          SummarizerOptions(backend: SummarizerBackend.asm);
+      final classes = await getSummary(config);
+      await classes.accept(Linker(config));
+      classes.accept(const Unnester());
+      for (final decl in classes.decls.values) {
+        // Checking if the descriptor from ASM matches the one generated by
+        // [MethodDescriptor].
+        final methodDescriptor = MethodDescriptor(decl.allTypeParams);
+        for (final method in decl.methods) {
+          expect(method.descriptor, method.accept(methodDescriptor));
+        }
+      }
+    });
+  }
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/.gitignore b/pkgs/jnigen/test/jackson_core_test/.gitignore
new file mode 100644
index 0000000..0215812
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/.gitignore
@@ -0,0 +1,5 @@
+*.jar
+third_party/jar/**
+third_party/java/**
+third_party/test_lib/
+third_party/test_src/
diff --git a/pkgs/jnigen/test/jackson_core_test/generate.dart b/pkgs/jnigen/test/jackson_core_test/generate.dart
new file mode 100644
index 0000000..796a760
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/generate.dart
@@ -0,0 +1,93 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:jnigen/jnigen.dart';
+import 'package:jnigen/src/config/experiments.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' hide equals;
+
+const jacksonPreamble = '// Generated from jackson-core which is licensed under'
+    ' the Apache License 2.0.\n'
+    '// The following copyright from the original authors applies.\n'
+    '// See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE\n'
+    '//\n'
+    '// Copyright (c) 2007 - The Jackson Project Authors\n'
+    '// Licensed under the Apache License, Version 2.0 (the "License")\n'
+    '// you may not use this file except in compliance with the License.\n'
+    '// You may obtain a copy of the License at\n'
+    '//\n'
+    '//     http://www.apache.org/licenses/LICENSE-2.0\n'
+    '//\n'
+    '// Unless required by applicable law or agreed to in writing, software\n'
+    '// distributed under the License is distributed on an "AS IS" BASIS,\n'
+    '// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n'
+    '// See the License for the specific language governing permissions and\n'
+    '// limitations under the License.\n';
+
+const testName = 'jackson_core_test';
+final thirdPartyDir = join('test', testName, 'third_party');
+const deps = ['com.fasterxml.jackson.core:jackson-core:2.13.4'];
+
+Config getConfig({
+  String? root,
+  bool generateFullVersion = false,
+  bool useAsm = false,
+  BindingsType bindingsType = BindingsType.dartOnly,
+}) {
+  final rootDir = root ?? thirdPartyDir;
+  final bindingTypeDir = bindingsType.getConfigString();
+  final config = Config(
+    mavenDownloads: MavenDownloads(
+      sourceDeps: deps,
+      sourceDir: join(thirdPartyDir, 'java'),
+      jarDir: join(thirdPartyDir, 'jar'),
+    ),
+    summarizerOptions: SummarizerOptions(
+      backend: useAsm ? SummarizerBackend.asm : null,
+    ),
+    preamble: jacksonPreamble,
+    outputConfig: OutputConfig(
+      bindingsType: bindingsType,
+      // Have to be judicious here, and ensure null when bindings type is
+      // dart-only, because config-test is also using this.
+      cConfig: bindingsType == BindingsType.cBased
+          ? CCodeOutputConfig(
+              libraryName: 'jackson_core',
+              path: Uri.directory(join(rootDir, bindingTypeDir, 'c_bindings')),
+            )
+          : null,
+      dartConfig: DartCodeOutputConfig(
+        path: Uri.directory(join(rootDir, bindingTypeDir, 'dart_bindings')),
+      ),
+    ),
+    classes: (generateFullVersion)
+        ? ['com.fasterxml.jackson.core']
+        : [
+            'com.fasterxml.jackson.core.JsonFactory',
+            'com.fasterxml.jackson.core.JsonParser',
+            'com.fasterxml.jackson.core.JsonToken',
+          ],
+    logLevel: Level.INFO,
+    exclude: BindingExclusions(
+      // TODO(#31): Remove field exclusions.
+      fields: excludeAll<Field>([
+        ['com.fasterxml.jackson.core.JsonFactory', 'DEFAULT_QUOTE_CHAR'],
+        ['com.fasterxml.jackson.core.Base64Variant', 'PADDING_CHAR_NONE'],
+        ['com.fasterxml.jackson.core.base.ParserMinimalBase', 'CHAR_NULL'],
+        ['com.fasterxml.jackson.core.io.UTF32Reader', 'NC'],
+      ]),
+      // TODO(#159): Remove class exclusions.
+      classes: ClassNameFilter.exclude(
+        'com.fasterxml.jackson.core.JsonFactoryBuilder',
+      ),
+    ),
+    experiments: {Experiment.interfaceImplementation},
+  );
+  return config;
+}
+
+void main() async {
+  await generateJniBindings(getConfig(bindingsType: BindingsType.cBased));
+  await generateJniBindings(getConfig(bindingsType: BindingsType.dartOnly));
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/generated_files_test.dart b/pkgs/jnigen/test/jackson_core_test/generated_files_test.dart
new file mode 100644
index 0000000..818e5d0
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/generated_files_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'package:jnigen/jnigen.dart';
+
+import '../test_util/test_util.dart';
+import 'generate.dart';
+
+void main() async {
+  await checkLocallyBuiltDependencies();
+
+  generateAndCompareBothModes(
+    'Generate and compare bindings for jackson_core library',
+    getConfig(bindingsType: BindingsType.cBased),
+    getConfig(bindingsType: BindingsType.dartOnly),
+  );
+  test(
+      'generate and analyze bindings for complete library, '
+      'not just required classes', () async {
+    final config = getConfig(generateFullVersion: true);
+    await generateAndAnalyzeBindings(config);
+  }, timeout: const Timeout(Duration(minutes: 2)), tags: largeTestTag);
+
+  test('generate and analyze bindings using ASM', () async {
+    final config = getConfig(generateFullVersion: true, useAsm: true);
+    await generateAndAnalyzeBindings(config);
+  }, timeout: const Timeout(Duration(minutes: 2)), tags: largeTestTag);
+
+  test('Generate and analyze pure dart bindings', () async {
+    final config = getConfig(generateFullVersion: true);
+    config.outputConfig.bindingsType = BindingsType.dartOnly;
+    await generateAndAnalyzeBindings(config);
+  }, timeout: const Timeout.factor(2), tags: largeTestTag);
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/jnigen.yaml b/pkgs/jnigen/test/jackson_core_test/jnigen.yaml
new file mode 100644
index 0000000..cf3b4db
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/jnigen.yaml
@@ -0,0 +1,48 @@
+maven_downloads:
+  source_deps:
+    - 'com.fasterxml.jackson.core:jackson-core:2.13.4'
+  source_dir: third_party/java/
+  jar_dir: third_party/jar/
+
+output:
+  bindings_type: c_based
+  c:
+    library_name: jackson_core
+    path: third_party/c_based/c_bindings/
+  dart:
+    path: third_party/c_based/dart_bindings/
+
+classes:
+  - 'com.fasterxml.jackson.core.JsonFactory'
+  - 'com.fasterxml.jackson.core.JsonParser'
+  - 'com.fasterxml.jackson.core.JsonToken'
+
+exclude:
+  fields:
+    - 'com.fasterxml.jackson.core.JsonFactory#DEFAULT_QUOTE_CHAR'
+    - 'com.fasterxml.jackson.core.Base64Variant#PADDING_CHAR_NONE'
+    - 'com.fasterxml.jackson.core.base.ParserMinimalBase#CHAR_NULL'
+    - 'com.fasterxml.jackson.core.io.UTF32Reader#NC'
+
+preamble: |
+  // Generated from jackson-core which is licensed under the Apache License 2.0.
+  // The following copyright from the original authors applies.
+  // See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+  //
+  // Copyright (c) 2007 - The Jackson Project Authors
+  // 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.
+
+log_level: warning
+
+enable_experiment:
+  - interface_implementation
diff --git a/pkgs/jnigen/test/jackson_core_test/runtime_test_registrant.dart b/pkgs/jnigen/test/jackson_core_test/runtime_test_registrant.dart
new file mode 100644
index 0000000..28e6c2b
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/runtime_test_registrant.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+import 'package:jni/jni.dart';
+
+import '../test_util/callback_types.dart';
+
+import 'third_party/c_based/dart_bindings/com/fasterxml/jackson/core/_package.dart';
+
+// This file doesn't define main, because only one JVM has to be spawned with
+// all classpaths, it's managed at a different file which calls these tests.
+
+void registerTests(String groupName, TestRunnerCallback test) {
+  group(groupName, () {
+    test('simple json parsing test', () {
+      final json = JString.fromString('[1, true, false, 2, 4]');
+      JsonFactory factory;
+      factory = JsonFactory();
+      final parser = factory.createParser6(json);
+      final values = <bool>[];
+      while (!parser.isClosed()) {
+        final next = parser.nextToken();
+        if (next.isNull) continue;
+        values.add(next.isNumeric());
+        next.release();
+      }
+      expect(values, equals([false, true, false, false, true, true, false]));
+      for (final obj in [factory, parser, json]) {
+        obj.release();
+      }
+    });
+    test("parsing invalid JSON throws JniException", () {
+      using((arena) {
+        final factory = JsonFactory()..releasedBy(arena);
+        final erroneous = factory
+            .createParser6("<html>".toJString()..releasedBy(arena))
+          ..releasedBy(arena);
+        expect(() => erroneous.nextToken(), throwsA(isA<JniException>()));
+      });
+    });
+  });
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/.clang-format b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/.clang-format
new file mode 100644
index 0000000..a256c2f
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/.clang-format
@@ -0,0 +1,15 @@
+# From dart SDK: https://github.com/dart-lang/sdk/blob/main/.clang-format
+
+# Defines the Chromium style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
+
+# clang-format doesn't seem to do a good job of this for longer comments.
+ReflowComments: 'false'
+
+# We have lots of these. Though we need to put them all in curly braces,
+# clang-format can't do that.
+AllowShortIfStatementsOnASingleLine: 'true'
+
+# Put escaped newlines into the rightmost column.
+AlignEscapedNewlinesLeft: false
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/CMakeLists.txt b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/CMakeLists.txt
new file mode 100644
index 0000000..db216f5
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/CMakeLists.txt
@@ -0,0 +1,32 @@
+# jni_native_build (Build with jni:setup. Do not delete this line.)
+
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project(jackson_core VERSION 0.0.1 LANGUAGES C)
+
+add_library(jackson_core SHARED
+  "./jackson_core.c"
+)
+
+set_target_properties(jackson_core PROPERTIES
+  OUTPUT_NAME "jackson_core"
+)
+
+target_compile_definitions(jackson_core PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries(jackson_core log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries(jackson_core ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/dartjni.h b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/dartjni.h
new file mode 100644
index 0000000..8f1dc74
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/dartjni.h
@@ -0,0 +1,424 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h.
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
+#if _WIN32
+#define FFI_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FFI_PLUGIN_EXPORT
+#endif
+
+#if defined _WIN32
+#define thread_local __declspec(thread)
+#else
+#define thread_local __thread
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#ifdef __ANDROID__
+#define __ENVP_CAST (JNIEnv**)
+#else
+#define __ENVP_CAST (void**)
+#endif
+
+/// Locking functions for windows and pthread.
+
+#if defined _WIN32
+#include <windows.h>
+
+typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  InitializeCriticalSection(lock);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  EnterCriticalSection(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  LeaveCriticalSection(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  DeleteCriticalSection(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  // Not available.
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ ||         \
+    defined __GNUC__
+#include <pthread.h>
+
+typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  pthread_mutex_init(lock, NULL);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  pthread_mutex_lock(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  pthread_mutex_unlock(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  pthread_mutex_destroy(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  pthread_cond_destroy(cond);
+}
+
+#else
+
+#error "No locking/condition variable support; Possibly unsupported platform"
+
+#endif
+
+typedef struct CallbackResult {
+  MutexLock lock;
+  ConditionVariable cond;
+  int ready;
+  jobject object;
+} CallbackResult;
+
+typedef struct JniLocks {
+  MutexLock classLoadingLock;
+  MutexLock methodLoadingLock;
+  MutexLock fieldLoadingLock;
+} JniLocks;
+
+/// Represents the error when dart-jni layer has already spawned singleton VM.
+#define DART_JNI_SINGLETON_EXISTS (-99);
+
+/// Stores the global state of the JNI.
+typedef struct JniContext {
+  JavaVM* jvm;
+  jobject classLoader;
+  jmethodID loadClassMethod;
+  jobject currentActivity;
+  jobject appContext;
+  JniLocks locks;
+} JniContext;
+
+// jniEnv for this thread, used by inline functions in this header,
+// therefore declared as extern.
+extern thread_local JNIEnv* jniEnv;
+
+extern JniContext* jni;
+
+/// Types used by JNI API to distinguish between primitive types.
+enum JniType {
+  booleanType = 0,
+  byteType = 1,
+  shortType = 2,
+  charType = 3,
+  intType = 4,
+  longType = 5,
+  floatType = 6,
+  doubleType = 7,
+  objectType = 8,
+  voidType = 9,
+};
+
+/// Result type for use by JNI.
+///
+/// If [exception] is null, it means the result is valid.
+/// It's assumed that the caller knows the expected type in [result].
+typedef struct JniResult {
+  jvalue value;
+  jthrowable exception;
+} JniResult;
+
+/// Similar to [JniResult] but for class lookups.
+typedef struct JniClassLookupResult {
+  jclass value;
+  jthrowable exception;
+} JniClassLookupResult;
+
+/// Similar to [JniResult] but for method/field ID lookups.
+typedef struct JniPointerResult {
+  const void* value;
+  jthrowable exception;
+} JniPointerResult;
+
+/// JniExceptionDetails holds 2 jstring objects, one is the result of
+/// calling `toString` on exception object, other is stack trace;
+typedef struct JniExceptionDetails {
+  jstring message;
+  jstring stacktrace;
+} JniExceptionDetails;
+
+/// This struct contains functions which wrap method call / field access conveniently along with
+/// exception checking.
+///
+/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us
+/// to check for and clear the exception before returning to dart code, which requires these functions
+/// to return result types.
+typedef struct JniAccessorsStruct {
+  JniClassLookupResult (*getClass)(char* internalName);
+  JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature);
+  JniPointerResult (*getStaticFieldID)(jclass cls,
+                                       char* fieldName,
+                                       char* signature);
+  JniPointerResult (*getMethodID)(jclass cls,
+                                  char* methodName,
+                                  char* signature);
+  JniPointerResult (*getStaticMethodID)(jclass cls,
+                                        char* methodName,
+                                        char* signature);
+  JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args);
+  JniResult (*newPrimitiveArray)(jsize length, int type);
+  JniResult (*newObjectArray)(jsize length,
+                              jclass elementClass,
+                              jobject initialElement);
+  JniResult (*getArrayElement)(jarray array, int index, int type);
+  JniResult (*callMethod)(jobject obj,
+                          jmethodID methodID,
+                          int callType,
+                          jvalue* args);
+  JniResult (*callStaticMethod)(jclass cls,
+                                jmethodID methodID,
+                                int callType,
+                                jvalue* args);
+  JniResult (*getField)(jobject obj, jfieldID fieldID, int callType);
+  JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType);
+  JniExceptionDetails (*getExceptionDetails)(jthrowable exception);
+} JniAccessorsStruct;
+
+FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors();
+
+FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void);
+
+FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void);
+
+/// Spawn a JVM with given arguments.
+///
+/// Returns JNI_OK on success, and one of the documented JNI error codes on
+/// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple
+/// JVMs is made, even if the underlying API potentially supports multiple VMs.
+FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args);
+
+/// Load class through platform-specific mechanism.
+///
+/// Currently uses application classloader on android,
+/// and JNIEnv->FindClass on other platforms.
+FFI_PLUGIN_EXPORT jclass FindClass(const char* name);
+
+/// Returns Application classLoader (on Android),
+/// which can be used to load application and platform classes.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetClassLoader(void);
+
+/// Returns application context on Android.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetApplicationContext(void);
+
+/// Returns current activity of the app on Android.
+FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void);
+
+static inline void attach_thread() {
+  if (jniEnv == NULL) {
+    (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL);
+  }
+}
+
+/// Load class into [cls] using platform specific mechanism
+static inline void load_class_platform(jclass* cls, const char* name) {
+#ifdef __ANDROID__
+  jstring className = (*jniEnv)->NewStringUTF(jniEnv, name);
+  *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader,
+                                     jni->loadClassMethod, className);
+  (*jniEnv)->DeleteLocalRef(jniEnv, className);
+#else
+  *cls = (*jniEnv)->FindClass(jniEnv, name);
+#endif
+}
+
+static inline void load_class_local_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(cls, name);
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_class_global_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    jclass tmp = NULL;
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(&tmp, name);
+      if (!(*jniEnv)->ExceptionCheck(jniEnv)) {
+        *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp);
+        (*jniEnv)->DeleteLocalRef(jniEnv, tmp);
+      }
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_method(jclass cls,
+                               jmethodID* res,
+                               const char* name,
+                               const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_static_method(jclass cls,
+                                      jmethodID* res,
+                                      const char* name,
+                                      const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_field(jclass cls,
+                              jfieldID* res,
+                              const char* name,
+                              const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline void load_static_field(jclass cls,
+                                     jfieldID* res,
+                                     const char* name,
+                                     const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline jobject to_global_ref(jobject ref) {
+  jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref);
+  (*jniEnv)->DeleteLocalRef(jniEnv, ref);
+  return g;
+}
+
+// These functions are useful for C+Dart bindings, and not required for pure dart bindings.
+
+FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr();
+
+/// For use by jni_gen's generated code
+/// don't use these.
+
+// these 2 fn ptr vars will be defined by generated code library
+extern JniContext* (*context_getter)(void);
+extern JNIEnv* (*env_getter)(void);
+
+// this function will be exported by generated code library
+// it will set above 2 variables.
+FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void),
+                                     JNIEnv* (*eg)(void));
+
+static inline void load_env() {
+  if (jniEnv == NULL) {
+    jni = context_getter();
+    jniEnv = env_getter();
+  }
+}
+
+static inline jthrowable check_exception() {
+  jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv);
+  if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv);
+  if (exception == NULL) return NULL;
+  return to_global_ref(exception);
+}
+
+static inline JniResult to_global_ref_result(jobject ref) {
+  JniResult result;
+  result.exception = check_exception();
+  if (result.exception == NULL) {
+    result.value.l = to_global_ref(ref);
+  }
+  return result;
+}
+
+FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+
+FFI_PLUGIN_EXPORT
+JniResult DartException__ctor(jstring message);
+
+FFI_PLUGIN_EXPORT
+JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+                                 int64_t port,
+                                 int64_t functionPtr);
+
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c
new file mode 100644
index 0000000..3203612
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c
@@ -0,0 +1,3729 @@
+// Generated from jackson-core which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+// See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+//
+// Copyright (c) 2007 - The Jackson Project Authors
+// 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+#include <stdint.h>
+#include "dartjni.h"
+#include "jni.h"
+
+thread_local JNIEnv* jniEnv;
+JniContext* jni;
+
+JniContext* (*context_getter)(void);
+JNIEnv* (*env_getter)(void);
+
+void setJniGetters(JniContext* (*cg)(void), JNIEnv* (*eg)(void)) {
+  context_getter = cg;
+  env_getter = eg;
+}
+
+// com.fasterxml.jackson.core.JsonFactory
+jclass _c_JsonFactory = NULL;
+
+jmethodID _m_JsonFactory__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__new0() {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__new0, "<init>", "()V");
+  if (_m_JsonFactory__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_JsonFactory, _m_JsonFactory__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__new1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__new1(jobject oc) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__new1, "<init>",
+              "(Lcom/fasterxml/jackson/core/ObjectCodec;)V");
+  if (_m_JsonFactory__new1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_JsonFactory, _m_JsonFactory__new1, oc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__new2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__new2(jobject src, jobject codec) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__new2, "<init>",
+              "(Lcom/fasterxml/jackson/core/JsonFactory;Lcom/fasterxml/jackson/"
+              "core/ObjectCodec;)V");
+  if (_m_JsonFactory__new2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_JsonFactory,
+                                         _m_JsonFactory__new2, src, codec);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__new3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__new3(jobject b) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__new3, "<init>",
+              "(Lcom/fasterxml/jackson/core/JsonFactoryBuilder;)V");
+  if (_m_JsonFactory__new3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_JsonFactory, _m_JsonFactory__new3, b);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__new4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__new4(jobject b, uint8_t bogus) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__new4, "<init>",
+              "(Lcom/fasterxml/jackson/core/TSFBuilder;Z)V");
+  if (_m_JsonFactory__new4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_JsonFactory,
+                                         _m_JsonFactory__new4, b, bogus);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__rebuild = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__rebuild(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__rebuild, "rebuild",
+              "()Lcom/fasterxml/jackson/core/TSFBuilder;");
+  if (_m_JsonFactory__rebuild == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__rebuild);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__builder = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__builder() {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_JsonFactory, &_m_JsonFactory__builder, "builder",
+                     "()Lcom/fasterxml/jackson/core/TSFBuilder;");
+  if (_m_JsonFactory__builder == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_JsonFactory,
+                                                      _m_JsonFactory__builder);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__copy = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__copy(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__copy, "copy",
+              "()Lcom/fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__copy == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__copy);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__readResolve = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__readResolve(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__readResolve, "readResolve",
+              "()Ljava/lang/Object;");
+  if (_m_JsonFactory__readResolve == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__readResolve);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__requiresPropertyOrdering = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__requiresPropertyOrdering(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__requiresPropertyOrdering,
+              "requiresPropertyOrdering", "()Z");
+  if (_m_JsonFactory__requiresPropertyOrdering == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonFactory__requiresPropertyOrdering);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__canHandleBinaryNatively = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__canHandleBinaryNatively(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__canHandleBinaryNatively,
+              "canHandleBinaryNatively", "()Z");
+  if (_m_JsonFactory__canHandleBinaryNatively == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonFactory__canHandleBinaryNatively);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__canUseCharArrays = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__canUseCharArrays(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__canUseCharArrays,
+              "canUseCharArrays", "()Z");
+  if (_m_JsonFactory__canUseCharArrays == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonFactory__canUseCharArrays);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__canParseAsync = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__canParseAsync(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__canParseAsync, "canParseAsync",
+              "()Z");
+  if (_m_JsonFactory__canParseAsync == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(jniEnv, self_,
+                                                 _m_JsonFactory__canParseAsync);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__getFormatReadFeatureType = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getFormatReadFeatureType(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getFormatReadFeatureType,
+              "getFormatReadFeatureType", "()Ljava/lang/Class;");
+  if (_m_JsonFactory__getFormatReadFeatureType == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__getFormatReadFeatureType);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__getFormatWriteFeatureType = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getFormatWriteFeatureType(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getFormatWriteFeatureType,
+              "getFormatWriteFeatureType", "()Ljava/lang/Class;");
+  if (_m_JsonFactory__getFormatWriteFeatureType == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__getFormatWriteFeatureType);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__canUseSchema = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__canUseSchema(jobject self_, jobject schema) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__canUseSchema, "canUseSchema",
+              "(Lcom/fasterxml/jackson/core/FormatSchema;)Z");
+  if (_m_JsonFactory__canUseSchema == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonFactory__canUseSchema, schema);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__getFormatName = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getFormatName(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getFormatName, "getFormatName",
+              "()Ljava/lang/String;");
+  if (_m_JsonFactory__getFormatName == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__getFormatName);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__hasFormat = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__hasFormat(jobject self_, jobject acc) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__hasFormat, "hasFormat",
+              "(Lcom/fasterxml/jackson/core/format/InputAccessor;)Lcom/"
+              "fasterxml/jackson/core/format/MatchStrength;");
+  if (_m_JsonFactory__hasFormat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+                                                _m_JsonFactory__hasFormat, acc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__requiresCustomCodec = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__requiresCustomCodec(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__requiresCustomCodec,
+              "requiresCustomCodec", "()Z");
+  if (_m_JsonFactory__requiresCustomCodec == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonFactory__requiresCustomCodec);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__hasJSONFormat = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__hasJSONFormat(jobject self_, jobject acc) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__hasJSONFormat, "hasJSONFormat",
+              "(Lcom/fasterxml/jackson/core/format/InputAccessor;)Lcom/"
+              "fasterxml/jackson/core/format/MatchStrength;");
+  if (_m_JsonFactory__hasJSONFormat == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__hasJSONFormat, acc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__version = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__version(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__version, "version",
+              "()Lcom/fasterxml/jackson/core/Version;");
+  if (_m_JsonFactory__version == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__version);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__configure = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__configure(jobject self_, jobject f, uint8_t state) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__configure, "configure",
+              "(Lcom/fasterxml/jackson/core/JsonFactory$Feature;Z)Lcom/"
+              "fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__configure == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__configure, f, state);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__enable = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__enable(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__enable, "enable",
+              "(Lcom/fasterxml/jackson/core/JsonFactory$Feature;)Lcom/"
+              "fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__enable == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__enable, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__disable = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__disable(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__disable, "disable",
+              "(Lcom/fasterxml/jackson/core/JsonFactory$Feature;)Lcom/"
+              "fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__disable == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__disable, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__isEnabled = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__isEnabled(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__isEnabled, "isEnabled",
+              "(Lcom/fasterxml/jackson/core/JsonFactory$Feature;)Z");
+  if (_m_JsonFactory__isEnabled == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonFactory__isEnabled, f);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__getParserFeatures = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getParserFeatures(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getParserFeatures,
+              "getParserFeatures", "()I");
+  if (_m_JsonFactory__getParserFeatures == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(jniEnv, self_,
+                                             _m_JsonFactory__getParserFeatures);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__getGeneratorFeatures = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getGeneratorFeatures(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getGeneratorFeatures,
+              "getGeneratorFeatures", "()I");
+  if (_m_JsonFactory__getGeneratorFeatures == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_JsonFactory__getGeneratorFeatures);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__getFormatParserFeatures = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getFormatParserFeatures(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getFormatParserFeatures,
+              "getFormatParserFeatures", "()I");
+  if (_m_JsonFactory__getFormatParserFeatures == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_JsonFactory__getFormatParserFeatures);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__getFormatGeneratorFeatures = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getFormatGeneratorFeatures(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getFormatGeneratorFeatures,
+              "getFormatGeneratorFeatures", "()I");
+  if (_m_JsonFactory__getFormatGeneratorFeatures == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_JsonFactory__getFormatGeneratorFeatures);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__configure1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__configure1(jobject self_, jobject f, uint8_t state) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__configure1, "configure",
+              "(Lcom/fasterxml/jackson/core/JsonParser$Feature;Z)Lcom/"
+              "fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__configure1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__configure1, f, state);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__enable1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__enable1(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__enable1, "enable",
+              "(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Lcom/fasterxml/"
+              "jackson/core/JsonFactory;");
+  if (_m_JsonFactory__enable1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__enable1, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__disable1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__disable1(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__disable1, "disable",
+              "(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Lcom/fasterxml/"
+              "jackson/core/JsonFactory;");
+  if (_m_JsonFactory__disable1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__disable1, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__isEnabled1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__isEnabled1(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__isEnabled1, "isEnabled",
+              "(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Z");
+  if (_m_JsonFactory__isEnabled1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(jniEnv, self_,
+                                                 _m_JsonFactory__isEnabled1, f);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__isEnabled2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__isEnabled2(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__isEnabled2, "isEnabled",
+              "(Lcom/fasterxml/jackson/core/StreamReadFeature;)Z");
+  if (_m_JsonFactory__isEnabled2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(jniEnv, self_,
+                                                 _m_JsonFactory__isEnabled2, f);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__getInputDecorator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getInputDecorator(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getInputDecorator,
+              "getInputDecorator",
+              "()Lcom/fasterxml/jackson/core/io/InputDecorator;");
+  if (_m_JsonFactory__getInputDecorator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__getInputDecorator);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__setInputDecorator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__setInputDecorator(jobject self_, jobject d) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__setInputDecorator,
+              "setInputDecorator",
+              "(Lcom/fasterxml/jackson/core/io/InputDecorator;)Lcom/fasterxml/"
+              "jackson/core/JsonFactory;");
+  if (_m_JsonFactory__setInputDecorator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__setInputDecorator, d);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__configure2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__configure2(jobject self_, jobject f, uint8_t state) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__configure2, "configure",
+              "(Lcom/fasterxml/jackson/core/JsonGenerator$Feature;Z)Lcom/"
+              "fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__configure2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__configure2, f, state);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__enable2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__enable2(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__enable2, "enable",
+              "(Lcom/fasterxml/jackson/core/JsonGenerator$Feature;)Lcom/"
+              "fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__enable2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__enable2, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__disable2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__disable2(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__disable2, "disable",
+              "(Lcom/fasterxml/jackson/core/JsonGenerator$Feature;)Lcom/"
+              "fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__disable2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__disable2, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__isEnabled3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__isEnabled3(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__isEnabled3, "isEnabled",
+              "(Lcom/fasterxml/jackson/core/JsonGenerator$Feature;)Z");
+  if (_m_JsonFactory__isEnabled3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(jniEnv, self_,
+                                                 _m_JsonFactory__isEnabled3, f);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__isEnabled4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__isEnabled4(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__isEnabled4, "isEnabled",
+              "(Lcom/fasterxml/jackson/core/StreamWriteFeature;)Z");
+  if (_m_JsonFactory__isEnabled4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(jniEnv, self_,
+                                                 _m_JsonFactory__isEnabled4, f);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory__getCharacterEscapes = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getCharacterEscapes(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getCharacterEscapes,
+              "getCharacterEscapes",
+              "()Lcom/fasterxml/jackson/core/io/CharacterEscapes;");
+  if (_m_JsonFactory__getCharacterEscapes == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__getCharacterEscapes);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__setCharacterEscapes = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__setCharacterEscapes(jobject self_, jobject esc) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__setCharacterEscapes,
+              "setCharacterEscapes",
+              "(Lcom/fasterxml/jackson/core/io/CharacterEscapes;)Lcom/"
+              "fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__setCharacterEscapes == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__setCharacterEscapes, esc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__getOutputDecorator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getOutputDecorator(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getOutputDecorator,
+              "getOutputDecorator",
+              "()Lcom/fasterxml/jackson/core/io/OutputDecorator;");
+  if (_m_JsonFactory__getOutputDecorator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__getOutputDecorator);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__setOutputDecorator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__setOutputDecorator(jobject self_, jobject d) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__setOutputDecorator,
+              "setOutputDecorator",
+              "(Lcom/fasterxml/jackson/core/io/OutputDecorator;)Lcom/fasterxml/"
+              "jackson/core/JsonFactory;");
+  if (_m_JsonFactory__setOutputDecorator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__setOutputDecorator, d);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__setRootValueSeparator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__setRootValueSeparator(jobject self_, jobject sep) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__setRootValueSeparator,
+              "setRootValueSeparator",
+              "(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonFactory;");
+  if (_m_JsonFactory__setRootValueSeparator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__setRootValueSeparator, sep);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__getRootValueSeparator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getRootValueSeparator(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getRootValueSeparator,
+              "getRootValueSeparator", "()Ljava/lang/String;");
+  if (_m_JsonFactory__getRootValueSeparator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__getRootValueSeparator);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__setCodec = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__setCodec(jobject self_, jobject oc) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__setCodec, "setCodec",
+              "(Lcom/fasterxml/jackson/core/ObjectCodec;)Lcom/fasterxml/"
+              "jackson/core/JsonFactory;");
+  if (_m_JsonFactory__setCodec == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__setCodec, oc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__getCodec = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__getCodec(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__getCodec, "getCodec",
+              "()Lcom/fasterxml/jackson/core/ObjectCodec;");
+  if (_m_JsonFactory__getCodec == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonFactory__getCodec);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser, "createParser",
+              "(Ljava/io/File;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser1(jobject self_, jobject url) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser1, "createParser",
+              "(Ljava/net/URL;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser1, url);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser2(jobject self_, jobject in) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser2, "createParser",
+              "(Ljava/io/InputStream;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser2, in);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser3(jobject self_, jobject r) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser3, "createParser",
+              "(Ljava/io/Reader;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser3, r);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser4(jobject self_, jobject data) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser4, "createParser",
+              "([B)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser4, data);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser5 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser5(jobject self_,
+                                     jobject data,
+                                     int32_t offset,
+                                     int32_t len) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser5, "createParser",
+              "([BII)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser5 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser5, data, offset, len);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser6 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser6(jobject self_, jobject content) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser6, "createParser",
+              "(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser6 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser6, content);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser7 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser7(jobject self_, jobject content) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser7, "createParser",
+              "([C)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser7 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser7, content);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser8 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser8(jobject self_,
+                                     jobject content,
+                                     int32_t offset,
+                                     int32_t len) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser8, "createParser",
+              "([CII)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser8 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser8, content, offset, len);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createParser9 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createParser9(jobject self_, jobject in) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createParser9, "createParser",
+              "(Ljava/io/DataInput;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createParser9 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createParser9, in);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createNonBlockingByteArrayParser = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createNonBlockingByteArrayParser(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createNonBlockingByteArrayParser,
+              "createNonBlockingByteArrayParser",
+              "()Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createNonBlockingByteArrayParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createNonBlockingByteArrayParser);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createGenerator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createGenerator(jobject self_,
+                                       jobject out,
+                                       jobject enc) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createGenerator,
+              "createGenerator",
+              "(Ljava/io/OutputStream;Lcom/fasterxml/jackson/core/"
+              "JsonEncoding;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+  if (_m_JsonFactory__createGenerator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createGenerator, out, enc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createGenerator1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createGenerator1(jobject self_, jobject out) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_JsonFactory, &_m_JsonFactory__createGenerator1, "createGenerator",
+      "(Ljava/io/OutputStream;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+  if (_m_JsonFactory__createGenerator1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createGenerator1, out);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createGenerator2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createGenerator2(jobject self_, jobject w) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createGenerator2,
+              "createGenerator",
+              "(Ljava/io/Writer;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+  if (_m_JsonFactory__createGenerator2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createGenerator2, w);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createGenerator3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createGenerator3(jobject self_, jobject f, jobject enc) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createGenerator3,
+              "createGenerator",
+              "(Ljava/io/File;Lcom/fasterxml/jackson/core/JsonEncoding;)Lcom/"
+              "fasterxml/jackson/core/JsonGenerator;");
+  if (_m_JsonFactory__createGenerator3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createGenerator3, f, enc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createGenerator4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createGenerator4(jobject self_,
+                                        jobject out,
+                                        jobject enc) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createGenerator4,
+              "createGenerator",
+              "(Ljava/io/DataOutput;Lcom/fasterxml/jackson/core/"
+              "JsonEncoding;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+  if (_m_JsonFactory__createGenerator4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createGenerator4, out, enc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createGenerator5 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createGenerator5(jobject self_, jobject out) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_JsonFactory, &_m_JsonFactory__createGenerator5, "createGenerator",
+      "(Ljava/io/DataOutput;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+  if (_m_JsonFactory__createGenerator5 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createGenerator5, out);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonParser = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonParser(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createJsonParser,
+              "createJsonParser",
+              "(Ljava/io/File;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createJsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonParser, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonParser1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonParser1(jobject self_, jobject url) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createJsonParser1,
+              "createJsonParser",
+              "(Ljava/net/URL;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createJsonParser1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonParser1, url);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonParser2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonParser2(jobject self_, jobject in) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createJsonParser2,
+              "createJsonParser",
+              "(Ljava/io/InputStream;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createJsonParser2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonParser2, in);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonParser3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonParser3(jobject self_, jobject r) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createJsonParser3,
+              "createJsonParser",
+              "(Ljava/io/Reader;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createJsonParser3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonParser3, r);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonParser4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonParser4(jobject self_, jobject data) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createJsonParser4,
+              "createJsonParser",
+              "([B)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createJsonParser4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonParser4, data);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonParser5 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonParser5(jobject self_,
+                                         jobject data,
+                                         int32_t offset,
+                                         int32_t len) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createJsonParser5,
+              "createJsonParser",
+              "([BII)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createJsonParser5 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonParser5, data, offset, len);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonParser6 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonParser6(jobject self_, jobject content) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createJsonParser6,
+              "createJsonParser",
+              "(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonFactory__createJsonParser6 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonParser6, content);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonGenerator = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonGenerator(jobject self_,
+                                           jobject out,
+                                           jobject enc) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createJsonGenerator,
+              "createJsonGenerator",
+              "(Ljava/io/OutputStream;Lcom/fasterxml/jackson/core/"
+              "JsonEncoding;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+  if (_m_JsonFactory__createJsonGenerator == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonGenerator, out, enc);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonGenerator1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonGenerator1(jobject self_, jobject out) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory, &_m_JsonFactory__createJsonGenerator1,
+              "createJsonGenerator",
+              "(Ljava/io/Writer;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+  if (_m_JsonFactory__createJsonGenerator1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonGenerator1, out);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory__createJsonGenerator2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory__createJsonGenerator2(jobject self_, jobject out) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_JsonFactory, &_m_JsonFactory__createJsonGenerator2,
+      "createJsonGenerator",
+      "(Ljava/io/OutputStream;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+  if (_m_JsonFactory__createJsonGenerator2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonFactory__createJsonGenerator2, out);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_JsonFactory__DEFAULT_FACTORY_FEATURE_FLAGS = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_JsonFactory__DEFAULT_FACTORY_FEATURE_FLAGS() {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_JsonFactory,
+                    &_f_JsonFactory__DEFAULT_FACTORY_FEATURE_FLAGS,
+                    "DEFAULT_FACTORY_FEATURE_FLAGS", "I");
+  int32_t _result = (*jniEnv)->GetStaticIntField(
+      jniEnv, _c_JsonFactory, _f_JsonFactory__DEFAULT_FACTORY_FEATURE_FLAGS);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jfieldID _f_JsonFactory__DEFAULT_PARSER_FEATURE_FLAGS = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_JsonFactory__DEFAULT_PARSER_FEATURE_FLAGS() {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_JsonFactory,
+                    &_f_JsonFactory__DEFAULT_PARSER_FEATURE_FLAGS,
+                    "DEFAULT_PARSER_FEATURE_FLAGS", "I");
+  int32_t _result = (*jniEnv)->GetStaticIntField(
+      jniEnv, _c_JsonFactory, _f_JsonFactory__DEFAULT_PARSER_FEATURE_FLAGS);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jfieldID _f_JsonFactory__DEFAULT_GENERATOR_FEATURE_FLAGS = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_JsonFactory__DEFAULT_GENERATOR_FEATURE_FLAGS() {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_JsonFactory,
+                    &_f_JsonFactory__DEFAULT_GENERATOR_FEATURE_FLAGS,
+                    "DEFAULT_GENERATOR_FEATURE_FLAGS", "I");
+  int32_t _result = (*jniEnv)->GetStaticIntField(
+      jniEnv, _c_JsonFactory, _f_JsonFactory__DEFAULT_GENERATOR_FEATURE_FLAGS);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jfieldID _f_JsonFactory__DEFAULT_ROOT_VALUE_SEPARATOR = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_JsonFactory__DEFAULT_ROOT_VALUE_SEPARATOR() {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory,
+                        "com/fasterxml/jackson/core/JsonFactory");
+  if (_c_JsonFactory == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_JsonFactory,
+                    &_f_JsonFactory__DEFAULT_ROOT_VALUE_SEPARATOR,
+                    "DEFAULT_ROOT_VALUE_SEPARATOR",
+                    "Lcom/fasterxml/jackson/core/SerializableString;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(
+      jniEnv, _c_JsonFactory, _f_JsonFactory__DEFAULT_ROOT_VALUE_SEPARATOR);
+  return to_global_ref_result(_result);
+}
+
+// com.fasterxml.jackson.core.JsonFactory$Feature
+jclass _c_JsonFactory_Feature = NULL;
+
+jmethodID _m_JsonFactory_Feature__values = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory_Feature__values() {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory_Feature,
+                        "com/fasterxml/jackson/core/JsonFactory$Feature");
+  if (_c_JsonFactory_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_JsonFactory_Feature, &_m_JsonFactory_Feature__values,
+                     "values",
+                     "()[Lcom/fasterxml/jackson/core/JsonFactory$Feature;");
+  if (_m_JsonFactory_Feature__values == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_JsonFactory_Feature, _m_JsonFactory_Feature__values);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory_Feature__valueOf = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory_Feature__valueOf(jobject name) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory_Feature,
+                        "com/fasterxml/jackson/core/JsonFactory$Feature");
+  if (_c_JsonFactory_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_JsonFactory_Feature, &_m_JsonFactory_Feature__valueOf, "valueOf",
+      "(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonFactory$Feature;");
+  if (_m_JsonFactory_Feature__valueOf == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_JsonFactory_Feature, _m_JsonFactory_Feature__valueOf, name);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonFactory_Feature__collectDefaults = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory_Feature__collectDefaults() {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory_Feature,
+                        "com/fasterxml/jackson/core/JsonFactory$Feature");
+  if (_c_JsonFactory_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_JsonFactory_Feature,
+                     &_m_JsonFactory_Feature__collectDefaults,
+                     "collectDefaults", "()I");
+  if (_m_JsonFactory_Feature__collectDefaults == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallStaticIntMethod(
+      jniEnv, _c_JsonFactory_Feature, _m_JsonFactory_Feature__collectDefaults);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory_Feature__enabledByDefault = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory_Feature__enabledByDefault(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory_Feature,
+                        "com/fasterxml/jackson/core/JsonFactory$Feature");
+  if (_c_JsonFactory_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory_Feature, &_m_JsonFactory_Feature__enabledByDefault,
+              "enabledByDefault", "()Z");
+  if (_m_JsonFactory_Feature__enabledByDefault == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonFactory_Feature__enabledByDefault);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory_Feature__enabledIn = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory_Feature__enabledIn(jobject self_, int32_t flags) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory_Feature,
+                        "com/fasterxml/jackson/core/JsonFactory$Feature");
+  if (_c_JsonFactory_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory_Feature, &_m_JsonFactory_Feature__enabledIn,
+              "enabledIn", "(I)Z");
+  if (_m_JsonFactory_Feature__enabledIn == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonFactory_Feature__enabledIn, flags);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonFactory_Feature__getMask = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonFactory_Feature__getMask(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonFactory_Feature,
+                        "com/fasterxml/jackson/core/JsonFactory$Feature");
+  if (_c_JsonFactory_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonFactory_Feature, &_m_JsonFactory_Feature__getMask,
+              "getMask", "()I");
+  if (_m_JsonFactory_Feature__getMask == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonFactory_Feature__getMask);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+// com.fasterxml.jackson.core.JsonParser
+jclass _c_JsonParser = NULL;
+
+jmethodID _m_JsonParser__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__new0() {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__new0, "<init>", "()V");
+  if (_m_JsonParser__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_JsonParser, _m_JsonParser__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__new1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__new1(int32_t features) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__new1, "<init>", "(I)V");
+  if (_m_JsonParser__new1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_JsonParser,
+                                         _m_JsonParser__new1, features);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getCodec = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getCodec(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getCodec, "getCodec",
+              "()Lcom/fasterxml/jackson/core/ObjectCodec;");
+  if (_m_JsonParser__getCodec == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__getCodec);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__setCodec = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__setCodec(jobject self_, jobject oc) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__setCodec, "setCodec",
+              "(Lcom/fasterxml/jackson/core/ObjectCodec;)V");
+  if (_m_JsonParser__setCodec == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_JsonParser__setCodec, oc);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getInputSource = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getInputSource(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getInputSource, "getInputSource",
+              "()Ljava/lang/Object;");
+  if (_m_JsonParser__getInputSource == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__getInputSource);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__setRequestPayloadOnError = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__setRequestPayloadOnError(jobject self_, jobject payload) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__setRequestPayloadOnError,
+              "setRequestPayloadOnError",
+              "(Lcom/fasterxml/jackson/core/util/RequestPayload;)V");
+  if (_m_JsonParser__setRequestPayloadOnError == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_JsonParser__setRequestPayloadOnError, payload);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__setRequestPayloadOnError1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__setRequestPayloadOnError1(jobject self_,
+                                                jobject payload,
+                                                jobject charset) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__setRequestPayloadOnError1,
+              "setRequestPayloadOnError", "([BLjava/lang/String;)V");
+  if (_m_JsonParser__setRequestPayloadOnError1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_JsonParser__setRequestPayloadOnError1, payload,
+                            charset);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__setRequestPayloadOnError2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__setRequestPayloadOnError2(jobject self_,
+                                                jobject payload) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__setRequestPayloadOnError2,
+              "setRequestPayloadOnError", "(Ljava/lang/String;)V");
+  if (_m_JsonParser__setRequestPayloadOnError2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_JsonParser__setRequestPayloadOnError2, payload);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__setSchema = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__setSchema(jobject self_, jobject schema) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__setSchema, "setSchema",
+              "(Lcom/fasterxml/jackson/core/FormatSchema;)V");
+  if (_m_JsonParser__setSchema == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_JsonParser__setSchema, schema);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getSchema = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getSchema(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getSchema, "getSchema",
+              "()Lcom/fasterxml/jackson/core/FormatSchema;");
+  if (_m_JsonParser__getSchema == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__getSchema);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__canUseSchema = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__canUseSchema(jobject self_, jobject schema) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__canUseSchema, "canUseSchema",
+              "(Lcom/fasterxml/jackson/core/FormatSchema;)Z");
+  if (_m_JsonParser__canUseSchema == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__canUseSchema, schema);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__requiresCustomCodec = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__requiresCustomCodec(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__requiresCustomCodec,
+              "requiresCustomCodec", "()Z");
+  if (_m_JsonParser__requiresCustomCodec == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__requiresCustomCodec);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__canParseAsync = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__canParseAsync(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__canParseAsync, "canParseAsync",
+              "()Z");
+  if (_m_JsonParser__canParseAsync == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonParser__canParseAsync);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getNonBlockingInputFeeder = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getNonBlockingInputFeeder(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getNonBlockingInputFeeder,
+              "getNonBlockingInputFeeder",
+              "()Lcom/fasterxml/jackson/core/async/NonBlockingInputFeeder;");
+  if (_m_JsonParser__getNonBlockingInputFeeder == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getNonBlockingInputFeeder);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getReadCapabilities = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getReadCapabilities(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getReadCapabilities,
+              "getReadCapabilities",
+              "()Lcom/fasterxml/jackson/core/util/JacksonFeatureSet;");
+  if (_m_JsonParser__getReadCapabilities == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getReadCapabilities);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__version = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__version(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__version, "version",
+              "()Lcom/fasterxml/jackson/core/Version;");
+  if (_m_JsonParser__version == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__version);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__close = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__close(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__close, "close", "()V");
+  if (_m_JsonParser__close == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_JsonParser__close);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__isClosed = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__isClosed(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__isClosed, "isClosed", "()Z");
+  if (_m_JsonParser__isClosed == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonParser__isClosed);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getParsingContext = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getParsingContext(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getParsingContext,
+              "getParsingContext",
+              "()Lcom/fasterxml/jackson/core/JsonStreamContext;");
+  if (_m_JsonParser__getParsingContext == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getParsingContext);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__currentLocation = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__currentLocation(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__currentLocation, "currentLocation",
+              "()Lcom/fasterxml/jackson/core/JsonLocation;");
+  if (_m_JsonParser__currentLocation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+                                                _m_JsonParser__currentLocation);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__currentTokenLocation = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__currentTokenLocation(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__currentTokenLocation,
+              "currentTokenLocation",
+              "()Lcom/fasterxml/jackson/core/JsonLocation;");
+  if (_m_JsonParser__currentTokenLocation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__currentTokenLocation);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getCurrentLocation = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getCurrentLocation(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getCurrentLocation,
+              "getCurrentLocation",
+              "()Lcom/fasterxml/jackson/core/JsonLocation;");
+  if (_m_JsonParser__getCurrentLocation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getCurrentLocation);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getTokenLocation = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getTokenLocation(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getTokenLocation,
+              "getTokenLocation",
+              "()Lcom/fasterxml/jackson/core/JsonLocation;");
+  if (_m_JsonParser__getTokenLocation == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getTokenLocation);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__currentValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__currentValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__currentValue, "currentValue",
+              "()Ljava/lang/Object;");
+  if (_m_JsonParser__currentValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__currentValue);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__assignCurrentValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__assignCurrentValue(jobject self_, jobject v) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__assignCurrentValue,
+              "assignCurrentValue", "(Ljava/lang/Object;)V");
+  if (_m_JsonParser__assignCurrentValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_JsonParser__assignCurrentValue,
+                            v);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getCurrentValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getCurrentValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getCurrentValue, "getCurrentValue",
+              "()Ljava/lang/Object;");
+  if (_m_JsonParser__getCurrentValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+                                                _m_JsonParser__getCurrentValue);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__setCurrentValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__setCurrentValue(jobject self_, jobject v) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__setCurrentValue, "setCurrentValue",
+              "(Ljava/lang/Object;)V");
+  if (_m_JsonParser__setCurrentValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_JsonParser__setCurrentValue, v);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__releaseBuffered = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__releaseBuffered(jobject self_, jobject out) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__releaseBuffered, "releaseBuffered",
+              "(Ljava/io/OutputStream;)I");
+  if (_m_JsonParser__releaseBuffered == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_JsonParser__releaseBuffered, out);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__releaseBuffered1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__releaseBuffered1(jobject self_, jobject w) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__releaseBuffered1,
+              "releaseBuffered", "(Ljava/io/Writer;)I");
+  if (_m_JsonParser__releaseBuffered1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_JsonParser__releaseBuffered1, w);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__enable = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__enable(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__enable, "enable",
+              "(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Lcom/fasterxml/"
+              "jackson/core/JsonParser;");
+  if (_m_JsonParser__enable == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__enable, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__disable = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__disable(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__disable, "disable",
+              "(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Lcom/fasterxml/"
+              "jackson/core/JsonParser;");
+  if (_m_JsonParser__disable == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__disable, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__configure = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__configure(jobject self_, jobject f, uint8_t state) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__configure, "configure",
+              "(Lcom/fasterxml/jackson/core/JsonParser$Feature;Z)Lcom/"
+              "fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonParser__configure == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__configure, f, state);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__isEnabled = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__isEnabled(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__isEnabled, "isEnabled",
+              "(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Z");
+  if (_m_JsonParser__isEnabled == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonParser__isEnabled, f);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__isEnabled1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__isEnabled1(jobject self_, jobject f) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__isEnabled1, "isEnabled",
+              "(Lcom/fasterxml/jackson/core/StreamReadFeature;)Z");
+  if (_m_JsonParser__isEnabled1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonParser__isEnabled1, f);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getFeatureMask = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getFeatureMask(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getFeatureMask, "getFeatureMask",
+              "()I");
+  if (_m_JsonParser__getFeatureMask == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser__getFeatureMask);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__setFeatureMask = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__setFeatureMask(jobject self_, int32_t mask) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__setFeatureMask, "setFeatureMask",
+              "(I)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonParser__setFeatureMask == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__setFeatureMask, mask);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__overrideStdFeatures = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__overrideStdFeatures(jobject self_,
+                                          int32_t values,
+                                          int32_t mask) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__overrideStdFeatures,
+              "overrideStdFeatures",
+              "(II)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonParser__overrideStdFeatures == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__overrideStdFeatures, values, mask);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getFormatFeatures = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getFormatFeatures(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getFormatFeatures,
+              "getFormatFeatures", "()I");
+  if (_m_JsonParser__getFormatFeatures == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser__getFormatFeatures);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__overrideFormatFeatures = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__overrideFormatFeatures(jobject self_,
+                                             int32_t values,
+                                             int32_t mask) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__overrideFormatFeatures,
+              "overrideFormatFeatures",
+              "(II)Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonParser__overrideFormatFeatures == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__overrideFormatFeatures, values, mask);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__nextToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__nextToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__nextToken, "nextToken",
+              "()Lcom/fasterxml/jackson/core/JsonToken;");
+  if (_m_JsonParser__nextToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__nextToken);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__nextValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__nextValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__nextValue, "nextValue",
+              "()Lcom/fasterxml/jackson/core/JsonToken;");
+  if (_m_JsonParser__nextValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__nextValue);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__nextFieldName = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__nextFieldName(jobject self_, jobject str) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__nextFieldName, "nextFieldName",
+              "(Lcom/fasterxml/jackson/core/SerializableString;)Z");
+  if (_m_JsonParser__nextFieldName == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__nextFieldName, str);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__nextFieldName1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__nextFieldName1(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__nextFieldName1, "nextFieldName",
+              "()Ljava/lang/String;");
+  if (_m_JsonParser__nextFieldName1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__nextFieldName1);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__nextTextValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__nextTextValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__nextTextValue, "nextTextValue",
+              "()Ljava/lang/String;");
+  if (_m_JsonParser__nextTextValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__nextTextValue);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__nextIntValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__nextIntValue(jobject self_, int32_t defaultValue) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__nextIntValue, "nextIntValue",
+              "(I)I");
+  if (_m_JsonParser__nextIntValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_JsonParser__nextIntValue, defaultValue);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__nextLongValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__nextLongValue(jobject self_, int64_t defaultValue) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__nextLongValue, "nextLongValue",
+              "(J)J");
+  if (_m_JsonParser__nextLongValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int64_t _result = (*jniEnv)->CallLongMethod(
+      jniEnv, self_, _m_JsonParser__nextLongValue, defaultValue);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__nextBooleanValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__nextBooleanValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__nextBooleanValue,
+              "nextBooleanValue", "()Ljava/lang/Boolean;");
+  if (_m_JsonParser__nextBooleanValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__nextBooleanValue);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__skipChildren = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__skipChildren(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__skipChildren, "skipChildren",
+              "()Lcom/fasterxml/jackson/core/JsonParser;");
+  if (_m_JsonParser__skipChildren == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__skipChildren);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__finishToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__finishToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__finishToken, "finishToken", "()V");
+  if (_m_JsonParser__finishToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_JsonParser__finishToken);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__currentToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__currentToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__currentToken, "currentToken",
+              "()Lcom/fasterxml/jackson/core/JsonToken;");
+  if (_m_JsonParser__currentToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__currentToken);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__currentTokenId = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__currentTokenId(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__currentTokenId, "currentTokenId",
+              "()I");
+  if (_m_JsonParser__currentTokenId == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser__currentTokenId);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getCurrentToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getCurrentToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getCurrentToken, "getCurrentToken",
+              "()Lcom/fasterxml/jackson/core/JsonToken;");
+  if (_m_JsonParser__getCurrentToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+                                                _m_JsonParser__getCurrentToken);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getCurrentTokenId = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getCurrentTokenId(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getCurrentTokenId,
+              "getCurrentTokenId", "()I");
+  if (_m_JsonParser__getCurrentTokenId == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser__getCurrentTokenId);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__hasCurrentToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__hasCurrentToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__hasCurrentToken, "hasCurrentToken",
+              "()Z");
+  if (_m_JsonParser__hasCurrentToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__hasCurrentToken);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__hasTokenId = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__hasTokenId(jobject self_, int32_t id) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__hasTokenId, "hasTokenId", "(I)Z");
+  if (_m_JsonParser__hasTokenId == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(jniEnv, self_,
+                                                 _m_JsonParser__hasTokenId, id);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__hasToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__hasToken(jobject self_, jobject t) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__hasToken, "hasToken",
+              "(Lcom/fasterxml/jackson/core/JsonToken;)Z");
+  if (_m_JsonParser__hasToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonParser__hasToken, t);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__isExpectedStartArrayToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__isExpectedStartArrayToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__isExpectedStartArrayToken,
+              "isExpectedStartArrayToken", "()Z");
+  if (_m_JsonParser__isExpectedStartArrayToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__isExpectedStartArrayToken);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__isExpectedStartObjectToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__isExpectedStartObjectToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__isExpectedStartObjectToken,
+              "isExpectedStartObjectToken", "()Z");
+  if (_m_JsonParser__isExpectedStartObjectToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__isExpectedStartObjectToken);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__isExpectedNumberIntToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__isExpectedNumberIntToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__isExpectedNumberIntToken,
+              "isExpectedNumberIntToken", "()Z");
+  if (_m_JsonParser__isExpectedNumberIntToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__isExpectedNumberIntToken);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__isNaN = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__isNaN(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__isNaN, "isNaN", "()Z");
+  if (_m_JsonParser__isNaN == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonParser__isNaN);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__clearCurrentToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__clearCurrentToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__clearCurrentToken,
+              "clearCurrentToken", "()V");
+  if (_m_JsonParser__clearCurrentToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_JsonParser__clearCurrentToken);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getLastClearedToken = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getLastClearedToken(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getLastClearedToken,
+              "getLastClearedToken",
+              "()Lcom/fasterxml/jackson/core/JsonToken;");
+  if (_m_JsonParser__getLastClearedToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getLastClearedToken);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__overrideCurrentName = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__overrideCurrentName(jobject self_, jobject name) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__overrideCurrentName,
+              "overrideCurrentName", "(Ljava/lang/String;)V");
+  if (_m_JsonParser__overrideCurrentName == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_JsonParser__overrideCurrentName,
+                            name);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getCurrentName = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getCurrentName(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getCurrentName, "getCurrentName",
+              "()Ljava/lang/String;");
+  if (_m_JsonParser__getCurrentName == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__getCurrentName);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__currentName = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__currentName(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__currentName, "currentName",
+              "()Ljava/lang/String;");
+  if (_m_JsonParser__currentName == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__currentName);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getText = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getText(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getText, "getText",
+              "()Ljava/lang/String;");
+  if (_m_JsonParser__getText == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__getText);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getText1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getText1(jobject self_, jobject writer) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getText1, "getText",
+              "(Ljava/io/Writer;)I");
+  if (_m_JsonParser__getText1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser__getText1, writer);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getTextCharacters = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getTextCharacters(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getTextCharacters,
+              "getTextCharacters", "()[C");
+  if (_m_JsonParser__getTextCharacters == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getTextCharacters);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getTextLength = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getTextLength(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getTextLength, "getTextLength",
+              "()I");
+  if (_m_JsonParser__getTextLength == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser__getTextLength);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getTextOffset = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getTextOffset(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getTextOffset, "getTextOffset",
+              "()I");
+  if (_m_JsonParser__getTextOffset == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser__getTextOffset);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__hasTextCharacters = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__hasTextCharacters(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__hasTextCharacters,
+              "hasTextCharacters", "()Z");
+  if (_m_JsonParser__hasTextCharacters == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__hasTextCharacters);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getNumberValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getNumberValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getNumberValue, "getNumberValue",
+              "()Ljava/lang/Number;");
+  if (_m_JsonParser__getNumberValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__getNumberValue);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getNumberValueExact = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getNumberValueExact(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getNumberValueExact,
+              "getNumberValueExact", "()Ljava/lang/Number;");
+  if (_m_JsonParser__getNumberValueExact == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getNumberValueExact);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getNumberType = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getNumberType(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getNumberType, "getNumberType",
+              "()Lcom/fasterxml/jackson/core/JsonParser$NumberType;");
+  if (_m_JsonParser__getNumberType == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__getNumberType);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getByteValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getByteValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getByteValue, "getByteValue",
+              "()B");
+  if (_m_JsonParser__getByteValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int8_t _result =
+      (*jniEnv)->CallByteMethod(jniEnv, self_, _m_JsonParser__getByteValue);
+  return (JniResult){.value = {.b = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getShortValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getShortValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getShortValue, "getShortValue",
+              "()S");
+  if (_m_JsonParser__getShortValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int16_t _result =
+      (*jniEnv)->CallShortMethod(jniEnv, self_, _m_JsonParser__getShortValue);
+  return (JniResult){.value = {.s = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getIntValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getIntValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getIntValue, "getIntValue", "()I");
+  if (_m_JsonParser__getIntValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser__getIntValue);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getLongValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getLongValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getLongValue, "getLongValue",
+              "()J");
+  if (_m_JsonParser__getLongValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int64_t _result =
+      (*jniEnv)->CallLongMethod(jniEnv, self_, _m_JsonParser__getLongValue);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getBigIntegerValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getBigIntegerValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getBigIntegerValue,
+              "getBigIntegerValue", "()Ljava/math/BigInteger;");
+  if (_m_JsonParser__getBigIntegerValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getBigIntegerValue);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getFloatValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getFloatValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getFloatValue, "getFloatValue",
+              "()F");
+  if (_m_JsonParser__getFloatValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  float _result =
+      (*jniEnv)->CallFloatMethod(jniEnv, self_, _m_JsonParser__getFloatValue);
+  return (JniResult){.value = {.f = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getDoubleValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getDoubleValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getDoubleValue, "getDoubleValue",
+              "()D");
+  if (_m_JsonParser__getDoubleValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  double _result =
+      (*jniEnv)->CallDoubleMethod(jniEnv, self_, _m_JsonParser__getDoubleValue);
+  return (JniResult){.value = {.d = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getDecimalValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getDecimalValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getDecimalValue, "getDecimalValue",
+              "()Ljava/math/BigDecimal;");
+  if (_m_JsonParser__getDecimalValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+                                                _m_JsonParser__getDecimalValue);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getBooleanValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getBooleanValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getBooleanValue, "getBooleanValue",
+              "()Z");
+  if (_m_JsonParser__getBooleanValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__getBooleanValue);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getEmbeddedObject = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getEmbeddedObject(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getEmbeddedObject,
+              "getEmbeddedObject", "()Ljava/lang/Object;");
+  if (_m_JsonParser__getEmbeddedObject == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getEmbeddedObject);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getBinaryValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getBinaryValue(jobject self_, jobject bv) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getBinaryValue, "getBinaryValue",
+              "(Lcom/fasterxml/jackson/core/Base64Variant;)[B");
+  if (_m_JsonParser__getBinaryValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getBinaryValue, bv);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getBinaryValue1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getBinaryValue1(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getBinaryValue1, "getBinaryValue",
+              "()[B");
+  if (_m_JsonParser__getBinaryValue1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+                                                _m_JsonParser__getBinaryValue1);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__readBinaryValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__readBinaryValue(jobject self_, jobject out) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__readBinaryValue, "readBinaryValue",
+              "(Ljava/io/OutputStream;)I");
+  if (_m_JsonParser__readBinaryValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_JsonParser__readBinaryValue, out);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__readBinaryValue1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__readBinaryValue1(jobject self_, jobject bv, jobject out) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_JsonParser, &_m_JsonParser__readBinaryValue1, "readBinaryValue",
+      "(Lcom/fasterxml/jackson/core/Base64Variant;Ljava/io/OutputStream;)I");
+  if (_m_JsonParser__readBinaryValue1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_JsonParser__readBinaryValue1, bv, out);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getValueAsInt = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsInt(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsInt, "getValueAsInt",
+              "()I");
+  if (_m_JsonParser__getValueAsInt == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser__getValueAsInt);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getValueAsInt1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsInt1(jobject self_, int32_t def) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsInt1, "getValueAsInt",
+              "(I)I");
+  if (_m_JsonParser__getValueAsInt1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_JsonParser__getValueAsInt1, def);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getValueAsLong = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsLong(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsLong, "getValueAsLong",
+              "()J");
+  if (_m_JsonParser__getValueAsLong == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int64_t _result =
+      (*jniEnv)->CallLongMethod(jniEnv, self_, _m_JsonParser__getValueAsLong);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getValueAsLong1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsLong1(jobject self_, int64_t def) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsLong1, "getValueAsLong",
+              "(J)J");
+  if (_m_JsonParser__getValueAsLong1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int64_t _result = (*jniEnv)->CallLongMethod(
+      jniEnv, self_, _m_JsonParser__getValueAsLong1, def);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getValueAsDouble = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsDouble(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsDouble,
+              "getValueAsDouble", "()D");
+  if (_m_JsonParser__getValueAsDouble == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  double _result = (*jniEnv)->CallDoubleMethod(jniEnv, self_,
+                                               _m_JsonParser__getValueAsDouble);
+  return (JniResult){.value = {.d = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getValueAsDouble1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsDouble1(jobject self_, double def) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsDouble1,
+              "getValueAsDouble", "(D)D");
+  if (_m_JsonParser__getValueAsDouble1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  double _result = (*jniEnv)->CallDoubleMethod(
+      jniEnv, self_, _m_JsonParser__getValueAsDouble1, def);
+  return (JniResult){.value = {.d = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getValueAsBoolean = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsBoolean(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsBoolean,
+              "getValueAsBoolean", "()Z");
+  if (_m_JsonParser__getValueAsBoolean == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__getValueAsBoolean);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getValueAsBoolean1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsBoolean1(jobject self_, uint8_t def) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsBoolean1,
+              "getValueAsBoolean", "(Z)Z");
+  if (_m_JsonParser__getValueAsBoolean1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__getValueAsBoolean1, def);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getValueAsString = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsString(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsString,
+              "getValueAsString", "()Ljava/lang/String;");
+  if (_m_JsonParser__getValueAsString == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getValueAsString);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getValueAsString1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getValueAsString1(jobject self_, jobject def) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getValueAsString1,
+              "getValueAsString", "(Ljava/lang/String;)Ljava/lang/String;");
+  if (_m_JsonParser__getValueAsString1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__getValueAsString1, def);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__canReadObjectId = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__canReadObjectId(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__canReadObjectId, "canReadObjectId",
+              "()Z");
+  if (_m_JsonParser__canReadObjectId == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser__canReadObjectId);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__canReadTypeId = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__canReadTypeId(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__canReadTypeId, "canReadTypeId",
+              "()Z");
+  if (_m_JsonParser__canReadTypeId == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonParser__canReadTypeId);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser__getObjectId = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getObjectId(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getObjectId, "getObjectId",
+              "()Ljava/lang/Object;");
+  if (_m_JsonParser__getObjectId == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__getObjectId);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__getTypeId = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__getTypeId(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__getTypeId, "getTypeId",
+              "()Ljava/lang/Object;");
+  if (_m_JsonParser__getTypeId == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonParser__getTypeId);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__readValueAs = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__readValueAs(jobject self_, jobject valueType) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__readValueAs, "readValueAs",
+              "(Ljava/lang/Class;)Ljava/lang/Object;");
+  if (_m_JsonParser__readValueAs == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__readValueAs, valueType);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__readValueAs1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__readValueAs1(jobject self_, jobject valueTypeRef) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_JsonParser, &_m_JsonParser__readValueAs1, "readValueAs",
+      "(Lcom/fasterxml/jackson/core/type/TypeReference;)Ljava/lang/Object;");
+  if (_m_JsonParser__readValueAs1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__readValueAs1, valueTypeRef);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__readValuesAs = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__readValuesAs(jobject self_, jobject valueType) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__readValuesAs, "readValuesAs",
+              "(Ljava/lang/Class;)Ljava/util/Iterator;");
+  if (_m_JsonParser__readValuesAs == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__readValuesAs, valueType);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__readValuesAs1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__readValuesAs1(jobject self_, jobject valueTypeRef) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_JsonParser, &_m_JsonParser__readValuesAs1, "readValuesAs",
+      "(Lcom/fasterxml/jackson/core/type/TypeReference;)Ljava/util/Iterator;");
+  if (_m_JsonParser__readValuesAs1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_JsonParser__readValuesAs1, valueTypeRef);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser__readValueAsTree = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser__readValueAsTree(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser, &_m_JsonParser__readValueAsTree, "readValueAsTree",
+              "()Lcom/fasterxml/jackson/core/TreeNode;");
+  if (_m_JsonParser__readValueAsTree == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+                                                _m_JsonParser__readValueAsTree);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_JsonParser__DEFAULT_READ_CAPABILITIES = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_JsonParser__DEFAULT_READ_CAPABILITIES() {
+  load_env();
+  load_class_global_ref(&_c_JsonParser,
+                        "com/fasterxml/jackson/core/JsonParser");
+  if (_c_JsonParser == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_JsonParser, &_f_JsonParser__DEFAULT_READ_CAPABILITIES,
+                    "DEFAULT_READ_CAPABILITIES",
+                    "Lcom/fasterxml/jackson/core/util/JacksonFeatureSet;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(
+      jniEnv, _c_JsonParser, _f_JsonParser__DEFAULT_READ_CAPABILITIES);
+  return to_global_ref_result(_result);
+}
+
+// com.fasterxml.jackson.core.JsonParser$Feature
+jclass _c_JsonParser_Feature = NULL;
+
+jmethodID _m_JsonParser_Feature__values = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser_Feature__values() {
+  load_env();
+  load_class_global_ref(&_c_JsonParser_Feature,
+                        "com/fasterxml/jackson/core/JsonParser$Feature");
+  if (_c_JsonParser_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_JsonParser_Feature, &_m_JsonParser_Feature__values,
+                     "values",
+                     "()[Lcom/fasterxml/jackson/core/JsonParser$Feature;");
+  if (_m_JsonParser_Feature__values == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_JsonParser_Feature, _m_JsonParser_Feature__values);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser_Feature__valueOf = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser_Feature__valueOf(jobject name) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser_Feature,
+                        "com/fasterxml/jackson/core/JsonParser$Feature");
+  if (_c_JsonParser_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_JsonParser_Feature, &_m_JsonParser_Feature__valueOf, "valueOf",
+      "(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonParser$Feature;");
+  if (_m_JsonParser_Feature__valueOf == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_JsonParser_Feature, _m_JsonParser_Feature__valueOf, name);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser_Feature__collectDefaults = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser_Feature__collectDefaults() {
+  load_env();
+  load_class_global_ref(&_c_JsonParser_Feature,
+                        "com/fasterxml/jackson/core/JsonParser$Feature");
+  if (_c_JsonParser_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_JsonParser_Feature,
+                     &_m_JsonParser_Feature__collectDefaults, "collectDefaults",
+                     "()I");
+  if (_m_JsonParser_Feature__collectDefaults == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallStaticIntMethod(
+      jniEnv, _c_JsonParser_Feature, _m_JsonParser_Feature__collectDefaults);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser_Feature__enabledByDefault = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser_Feature__enabledByDefault(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser_Feature,
+                        "com/fasterxml/jackson/core/JsonParser$Feature");
+  if (_c_JsonParser_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser_Feature, &_m_JsonParser_Feature__enabledByDefault,
+              "enabledByDefault", "()Z");
+  if (_m_JsonParser_Feature__enabledByDefault == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser_Feature__enabledByDefault);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser_Feature__enabledIn = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser_Feature__enabledIn(jobject self_, int32_t flags) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser_Feature,
+                        "com/fasterxml/jackson/core/JsonParser$Feature");
+  if (_c_JsonParser_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser_Feature, &_m_JsonParser_Feature__enabledIn,
+              "enabledIn", "(I)Z");
+  if (_m_JsonParser_Feature__enabledIn == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result = (*jniEnv)->CallBooleanMethod(
+      jniEnv, self_, _m_JsonParser_Feature__enabledIn, flags);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonParser_Feature__getMask = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser_Feature__getMask(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser_Feature,
+                        "com/fasterxml/jackson/core/JsonParser$Feature");
+  if (_c_JsonParser_Feature == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonParser_Feature, &_m_JsonParser_Feature__getMask, "getMask",
+              "()I");
+  if (_m_JsonParser_Feature__getMask == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonParser_Feature__getMask);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+// com.fasterxml.jackson.core.JsonParser$NumberType
+jclass _c_JsonParser_NumberType = NULL;
+
+jmethodID _m_JsonParser_NumberType__values = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser_NumberType__values() {
+  load_env();
+  load_class_global_ref(&_c_JsonParser_NumberType,
+                        "com/fasterxml/jackson/core/JsonParser$NumberType");
+  if (_c_JsonParser_NumberType == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_JsonParser_NumberType,
+                     &_m_JsonParser_NumberType__values, "values",
+                     "()[Lcom/fasterxml/jackson/core/JsonParser$NumberType;");
+  if (_m_JsonParser_NumberType__values == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_JsonParser_NumberType, _m_JsonParser_NumberType__values);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonParser_NumberType__valueOf = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonParser_NumberType__valueOf(jobject name) {
+  load_env();
+  load_class_global_ref(&_c_JsonParser_NumberType,
+                        "com/fasterxml/jackson/core/JsonParser$NumberType");
+  if (_c_JsonParser_NumberType == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_JsonParser_NumberType, &_m_JsonParser_NumberType__valueOf, "valueOf",
+      "(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonParser$NumberType;");
+  if (_m_JsonParser_NumberType__valueOf == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_JsonParser_NumberType, _m_JsonParser_NumberType__valueOf,
+      name);
+  return to_global_ref_result(_result);
+}
+
+// com.fasterxml.jackson.core.JsonToken
+jclass _c_JsonToken = NULL;
+
+jmethodID _m_JsonToken__values = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__values() {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_JsonToken, &_m_JsonToken__values, "values",
+                     "()[Lcom/fasterxml/jackson/core/JsonToken;");
+  if (_m_JsonToken__values == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_JsonToken,
+                                                      _m_JsonToken__values);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonToken__valueOf = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__valueOf(jobject name) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_JsonToken, &_m_JsonToken__valueOf, "valueOf",
+      "(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonToken;");
+  if (_m_JsonToken__valueOf == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_JsonToken, _m_JsonToken__valueOf, name);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonToken__id = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__id(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonToken, &_m_JsonToken__id, "id", "()I");
+  if (_m_JsonToken__id == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(jniEnv, self_, _m_JsonToken__id);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonToken__asString = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__asString(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonToken, &_m_JsonToken__asString, "asString",
+              "()Ljava/lang/String;");
+  if (_m_JsonToken__asString == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonToken__asString);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonToken__asCharArray = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__asCharArray(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonToken, &_m_JsonToken__asCharArray, "asCharArray", "()[C");
+  if (_m_JsonToken__asCharArray == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonToken__asCharArray);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonToken__asByteArray = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__asByteArray(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonToken, &_m_JsonToken__asByteArray, "asByteArray", "()[B");
+  if (_m_JsonToken__asByteArray == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_JsonToken__asByteArray);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonToken__isNumeric = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__isNumeric(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonToken, &_m_JsonToken__isNumeric, "isNumeric", "()Z");
+  if (_m_JsonToken__isNumeric == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonToken__isNumeric);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonToken__isStructStart = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__isStructStart(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonToken, &_m_JsonToken__isStructStart, "isStructStart",
+              "()Z");
+  if (_m_JsonToken__isStructStart == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonToken__isStructStart);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonToken__isStructEnd = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__isStructEnd(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonToken, &_m_JsonToken__isStructEnd, "isStructEnd", "()Z");
+  if (_m_JsonToken__isStructEnd == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonToken__isStructEnd);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonToken__isScalarValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__isScalarValue(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonToken, &_m_JsonToken__isScalarValue, "isScalarValue",
+              "()Z");
+  if (_m_JsonToken__isScalarValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonToken__isScalarValue);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_JsonToken__isBoolean = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonToken__isBoolean(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_JsonToken, "com/fasterxml/jackson/core/JsonToken");
+  if (_c_JsonToken == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_JsonToken, &_m_JsonToken__isBoolean, "isBoolean", "()Z");
+  if (_m_JsonToken__isBoolean == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_JsonToken__isBoolean);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/_init.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/_init.dart
new file mode 100644
index 0000000..8018e7d
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/_init.dart
@@ -0,0 +1,24 @@
+// Generated from jackson-core which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+// See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+//
+// Copyright (c) 2007 - The Jackson Project Authors
+// 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.
+
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+
+// Auto-generated initialization code.
+
+final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
+    ProtectedJniExtensions.initGeneratedLibrary("jackson_core");
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
new file mode 100644
index 0000000..822a052
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
@@ -0,0 +1,1997 @@
+// Generated from jackson-core which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+// See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+//
+// Copyright (c) 2007 - The Jackson Project Authors
+// 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+import "JsonParser.dart" as jsonparser_;
+import "../../../../_init.dart";
+
+/// from: com.fasterxml.jackson.core.JsonFactory
+///
+/// The main factory class of Jackson package, used to configure and
+/// construct reader (aka parser, JsonParser)
+/// and writer (aka generator, JsonGenerator)
+/// instances.
+///
+/// Factory instances are thread-safe and reusable after configuration
+/// (if any). Typically applications and services use only a single
+/// globally shared factory instance, unless they need differently
+/// configured factories. Factory reuse is important if efficiency matters;
+/// most recycling of expensive construct is done on per-factory basis.
+///
+/// Creation of a factory instance is a light-weight operation,
+/// and since there is no need for pluggable alternative implementations
+/// (as there is no "standard" JSON processor API to implement),
+/// the default constructor is used for constructing factory
+/// instances.
+///@author Tatu Saloranta
+class JsonFactory extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonFactory> $type = type;
+
+  JsonFactory.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonFactoryType();
+
+  /// from: static public final java.lang.String FORMAT_NAME_JSON
+  ///
+  /// Name used to identify JSON format
+  /// (and returned by \#getFormatName()
+  static const FORMAT_NAME_JSON = r"""JSON""";
+
+  static final _get_DEFAULT_FACTORY_FEATURE_FLAGS =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_JsonFactory__DEFAULT_FACTORY_FEATURE_FLAGS")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static protected final int DEFAULT_FACTORY_FEATURE_FLAGS
+  ///
+  /// Bitfield (set of flags) of all factory features that are enabled by default.
+  static int get DEFAULT_FACTORY_FEATURE_FLAGS =>
+      _get_DEFAULT_FACTORY_FEATURE_FLAGS().integer;
+
+  static final _get_DEFAULT_PARSER_FEATURE_FLAGS =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_JsonFactory__DEFAULT_PARSER_FEATURE_FLAGS")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static protected final int DEFAULT_PARSER_FEATURE_FLAGS
+  ///
+  /// Bitfield (set of flags) of all parser features that are enabled
+  /// by default.
+  static int get DEFAULT_PARSER_FEATURE_FLAGS =>
+      _get_DEFAULT_PARSER_FEATURE_FLAGS().integer;
+
+  static final _get_DEFAULT_GENERATOR_FEATURE_FLAGS =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_JsonFactory__DEFAULT_GENERATOR_FEATURE_FLAGS")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static protected final int DEFAULT_GENERATOR_FEATURE_FLAGS
+  ///
+  /// Bitfield (set of flags) of all generator features that are enabled
+  /// by default.
+  static int get DEFAULT_GENERATOR_FEATURE_FLAGS =>
+      _get_DEFAULT_GENERATOR_FEATURE_FLAGS().integer;
+
+  static final _get_DEFAULT_ROOT_VALUE_SEPARATOR =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_JsonFactory__DEFAULT_ROOT_VALUE_SEPARATOR")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final com.fasterxml.jackson.core.SerializableString DEFAULT_ROOT_VALUE_SEPARATOR
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JObject get DEFAULT_ROOT_VALUE_SEPARATOR => const jni.JObjectType()
+      .fromRef(_get_DEFAULT_ROOT_VALUE_SEPARATOR().object);
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "JsonFactory__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Default constructor used to create factory instances.
+  /// Creation of a factory instance is a light-weight operation,
+  /// but it is still a good idea to reuse limited number of
+  /// factory instances (and quite often just a single instance):
+  /// factories are used as context for storing some reused
+  /// processing objects (such as symbol tables parsers use)
+  /// and this reuse only works within context of a single
+  /// factory instance.
+  factory JsonFactory() {
+    return JsonFactory.fromRef(_new0().object);
+  }
+
+  static final _new1 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__new1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(com.fasterxml.jackson.core.ObjectCodec oc)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory JsonFactory.new1(
+    jni.JObject oc,
+  ) {
+    return JsonFactory.fromRef(_new1(oc.reference).object);
+  }
+
+  static final _new2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__new2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void <init>(com.fasterxml.jackson.core.JsonFactory src, com.fasterxml.jackson.core.ObjectCodec codec)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor used when copy()ing a factory instance.
+  ///@param src Original factory to copy settings from
+  ///@param codec Databinding-level codec to use, if any
+  ///@since 2.2.1
+  factory JsonFactory.new2(
+    JsonFactory src,
+    jni.JObject codec,
+  ) {
+    return JsonFactory.fromRef(_new2(src.reference, codec.reference).object);
+  }
+
+  static final _new3 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__new3")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(com.fasterxml.jackson.core.JsonFactoryBuilder b)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor used by JsonFactoryBuilder for instantiation.
+  ///@param b Builder that contains settings to use
+  ///@since 2.10
+  factory JsonFactory.new3(
+    jni.JObject b,
+  ) {
+    return JsonFactory.fromRef(_new3(b.reference).object);
+  }
+
+  static final _new4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>, ffi.Uint8)>>("JsonFactory__new4")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: protected void <init>(com.fasterxml.jackson.core.TSFBuilder<?,?> b, boolean bogus)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor for subtypes; needed to work around the fact that before 3.0,
+  /// this factory has cumbersome dual role as generic type as well as actual
+  /// implementation for json.
+  ///@param b Builder that contains settings to use
+  ///@param bogus Argument only needed to separate constructor signature; ignored
+  factory JsonFactory.new4(
+    jni.JObject b,
+    bool bogus,
+  ) {
+    return JsonFactory.fromRef(_new4(b.reference, bogus ? 1 : 0).object);
+  }
+
+  static final _rebuild = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__rebuild")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.TSFBuilder<?,?> rebuild()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that allows construction of differently configured factory, starting
+  /// with settings of this factory.
+  ///@return Builder instance to use
+  ///@since 2.10
+  jni.JObject rebuild() {
+    return const jni.JObjectType().fromRef(_rebuild(reference).object);
+  }
+
+  static final _builder =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "JsonFactory__builder")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.fasterxml.jackson.core.TSFBuilder<?,?> builder()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Main factory method to use for constructing JsonFactory instances with
+  /// different configuration: creates and returns a builder for collecting configuration
+  /// settings; instance created by calling {@code build()} after all configuration
+  /// set.
+  ///
+  /// NOTE: signature unfortunately does not expose true implementation type; this
+  /// will be fixed in 3.0.
+  ///@return Builder instance to use
+  static jni.JObject builder() {
+    return const jni.JObjectType().fromRef(_builder().object);
+  }
+
+  static final _copy = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__copy")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory copy()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing a new JsonFactory that has
+  /// the same settings as this instance, but is otherwise
+  /// independent (i.e. nothing is actually shared, symbol tables
+  /// are separate).
+  /// Note that ObjectCodec reference is not copied but is
+  /// set to null; caller typically needs to set it after calling
+  /// this method. Reason for this is that the codec is used for
+  /// callbacks, and assumption is that there is strict 1-to-1
+  /// mapping between codec, factory. Caller has to, then, explicitly
+  /// set codec after making the copy.
+  ///@return Copy of this factory instance
+  ///@since 2.1
+  JsonFactory copy() {
+    return const $JsonFactoryType().fromRef(_copy(reference).object);
+  }
+
+  static final _readResolve = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__readResolve")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected java.lang.Object readResolve()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that we need to override to actually make restoration go
+  /// through constructors etc: needed to allow JDK serializability of
+  /// factory instances.
+  ///
+  /// Note: must be overridden by sub-classes as well.
+  ///@return Newly constructed instance
+  jni.JObject readResolve() {
+    return const jni.JObjectType().fromRef(_readResolve(reference).object);
+  }
+
+  static final _requiresPropertyOrdering = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__requiresPropertyOrdering")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean requiresPropertyOrdering()
+  ///
+  /// Introspection method that higher-level functionality may call
+  /// to see whether underlying data format requires a stable ordering
+  /// of object properties or not.
+  /// This is usually used for determining
+  /// whether to force a stable ordering (like alphabetic ordering by name)
+  /// if no ordering if explicitly specified.
+  ///
+  /// Default implementation returns <code>false</code> as JSON does NOT
+  /// require stable ordering. Formats that require ordering include positional
+  /// textual formats like <code>CSV</code>, and schema-based binary formats
+  /// like <code>Avro</code>.
+  ///@return Whether format supported by this factory
+  ///   requires Object properties to be ordered.
+  ///@since 2.3
+  bool requiresPropertyOrdering() {
+    return _requiresPropertyOrdering(reference).boolean;
+  }
+
+  static final _canHandleBinaryNatively = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__canHandleBinaryNatively")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean canHandleBinaryNatively()
+  ///
+  /// Introspection method that higher-level functionality may call
+  /// to see whether underlying data format can read and write binary
+  /// data natively; that is, embeded it as-is without using encodings
+  /// such as Base64.
+  ///
+  /// Default implementation returns <code>false</code> as JSON does not
+  /// support native access: all binary content must use Base64 encoding.
+  /// Most binary formats (like Smile and Avro) support native binary content.
+  ///@return Whether format supported by this factory
+  ///    supports native binary content
+  ///@since 2.3
+  bool canHandleBinaryNatively() {
+    return _canHandleBinaryNatively(reference).boolean;
+  }
+
+  static final _canUseCharArrays = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__canUseCharArrays")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean canUseCharArrays()
+  ///
+  /// Introspection method that can be used by base factory to check
+  /// whether access using <code>char[]</code> is something that actual
+  /// parser implementations can take advantage of, over having to
+  /// use java.io.Reader. Sub-types are expected to override
+  /// definition; default implementation (suitable for JSON) alleges
+  /// that optimization are possible; and thereby is likely to try
+  /// to access java.lang.String content by first copying it into
+  /// recyclable intermediate buffer.
+  ///@return Whether access to decoded textual content can be efficiently
+  ///   accessed using parser method {@code getTextCharacters()}.
+  ///@since 2.4
+  bool canUseCharArrays() {
+    return _canUseCharArrays(reference).boolean;
+  }
+
+  static final _canParseAsync = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__canParseAsync")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean canParseAsync()
+  ///
+  /// Introspection method that can be used to check whether this
+  /// factory can create non-blocking parsers: parsers that do not
+  /// use blocking I/O abstractions but instead use a
+  /// com.fasterxml.jackson.core.async.NonBlockingInputFeeder.
+  ///@return Whether this factory supports non-blocking ("async") parsing or
+  ///    not (and consequently whether {@code createNonBlockingXxx()} method(s) work)
+  ///@since 2.9
+  bool canParseAsync() {
+    return _canParseAsync(reference).boolean;
+  }
+
+  static final _getFormatReadFeatureType = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getFormatReadFeatureType")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Class<? extends com.fasterxml.jackson.core.FormatFeature> getFormatReadFeatureType()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject getFormatReadFeatureType() {
+    return const jni.JObjectType()
+        .fromRef(_getFormatReadFeatureType(reference).object);
+  }
+
+  static final _getFormatWriteFeatureType = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getFormatWriteFeatureType")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Class<? extends com.fasterxml.jackson.core.FormatFeature> getFormatWriteFeatureType()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject getFormatWriteFeatureType() {
+    return const jni.JObjectType()
+        .fromRef(_getFormatWriteFeatureType(reference).object);
+  }
+
+  static final _canUseSchema = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__canUseSchema")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean canUseSchema(com.fasterxml.jackson.core.FormatSchema schema)
+  ///
+  /// Method that can be used to quickly check whether given schema
+  /// is something that parsers and/or generators constructed by this
+  /// factory could use. Note that this means possible use, at the level
+  /// of data format (i.e. schema is for same data format as parsers and
+  /// generators this factory constructs); individual schema instances
+  /// may have further usage restrictions.
+  ///@param schema Schema instance to check
+  ///@return Whether parsers and generators constructed by this factory
+  ///   can use specified format schema instance
+  bool canUseSchema(
+    jni.JObject schema,
+  ) {
+    return _canUseSchema(reference, schema.reference).boolean;
+  }
+
+  static final _getFormatName = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getFormatName")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getFormatName()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that returns short textual id identifying format
+  /// this factory supports.
+  ///
+  /// Note: sub-classes should override this method; default
+  /// implementation will return null for all sub-classes
+  ///@return Name of the format handled by parsers, generators this factory creates
+  jni.JString getFormatName() {
+    return const jni.JStringType().fromRef(_getFormatName(reference).object);
+  }
+
+  static final _hasFormat = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__hasFormat")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.format.MatchStrength hasFormat(com.fasterxml.jackson.core.format.InputAccessor acc)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject hasFormat(
+    jni.JObject acc,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_hasFormat(reference, acc.reference).object);
+  }
+
+  static final _requiresCustomCodec = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__requiresCustomCodec")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean requiresCustomCodec()
+  ///
+  /// Method that can be called to determine if a custom
+  /// ObjectCodec is needed for binding data parsed
+  /// using JsonParser constructed by this factory
+  /// (which typically also implies the same for serialization
+  /// with JsonGenerator).
+  ///@return True if custom codec is needed with parsers and
+  ///   generators created by this factory; false if a general
+  ///   ObjectCodec is enough
+  ///@since 2.1
+  bool requiresCustomCodec() {
+    return _requiresCustomCodec(reference).boolean;
+  }
+
+  static final _hasJSONFormat = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__hasJSONFormat")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected com.fasterxml.jackson.core.format.MatchStrength hasJSONFormat(com.fasterxml.jackson.core.format.InputAccessor acc)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject hasJSONFormat(
+    jni.JObject acc,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_hasJSONFormat(reference, acc.reference).object);
+  }
+
+  static final _version = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__version")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.Version version()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject version() {
+    return const jni.JObjectType().fromRef(_version(reference).object);
+  }
+
+  static final _configure = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>, ffi.Uint8)>>("JsonFactory__configure")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public final com.fasterxml.jackson.core.JsonFactory configure(com.fasterxml.jackson.core.JsonFactory.Feature f, boolean state)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling or disabling specified parser feature
+  /// (check JsonParser.Feature for list of features)
+  ///@param f Feature to enable/disable
+  ///@param state Whether to enable or disable the feature
+  ///@return This factory instance (to allow call chaining)
+  ///@deprecated since 2.10 use JsonFactoryBuilder\#configure(JsonFactory.Feature, boolean) instead
+  JsonFactory configure(
+    JsonFactory_Feature f,
+    bool state,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_configure(reference, f.reference, state ? 1 : 0).object);
+  }
+
+  static final _enable = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__enable")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory enable(com.fasterxml.jackson.core.JsonFactory.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling specified parser feature
+  /// (check JsonFactory.Feature for list of features)
+  ///@param f Feature to enable
+  ///@return This factory instance (to allow call chaining)
+  ///@deprecated since 2.10 use JsonFactoryBuilder\#configure(JsonFactory.Feature, boolean) instead
+  JsonFactory enable(
+    JsonFactory_Feature f,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_enable(reference, f.reference).object);
+  }
+
+  static final _disable = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__disable")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory disable(com.fasterxml.jackson.core.JsonFactory.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for disabling specified parser features
+  /// (check JsonFactory.Feature for list of features)
+  ///@param f Feature to disable
+  ///@return This factory instance (to allow call chaining)
+  ///@deprecated since 2.10 use JsonFactoryBuilder\#configure(JsonFactory.Feature, boolean) instead
+  JsonFactory disable(
+    JsonFactory_Feature f,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_disable(reference, f.reference).object);
+  }
+
+  static final _isEnabled = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__isEnabled")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.JsonFactory.Feature f)
+  ///
+  /// Checked whether specified parser feature is enabled.
+  ///@param f Feature to check
+  ///@return True if the specified feature is enabled
+  bool isEnabled(
+    JsonFactory_Feature f,
+  ) {
+    return _isEnabled(reference, f.reference).boolean;
+  }
+
+  static final _getParserFeatures = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getParserFeatures")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final int getParserFeatures()
+  int getParserFeatures() {
+    return _getParserFeatures(reference).integer;
+  }
+
+  static final _getGeneratorFeatures = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getGeneratorFeatures")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final int getGeneratorFeatures()
+  int getGeneratorFeatures() {
+    return _getGeneratorFeatures(reference).integer;
+  }
+
+  static final _getFormatParserFeatures = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getFormatParserFeatures")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getFormatParserFeatures()
+  int getFormatParserFeatures() {
+    return _getFormatParserFeatures(reference).integer;
+  }
+
+  static final _getFormatGeneratorFeatures = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getFormatGeneratorFeatures")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getFormatGeneratorFeatures()
+  int getFormatGeneratorFeatures() {
+    return _getFormatGeneratorFeatures(reference).integer;
+  }
+
+  static final _configure1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>, ffi.Uint8)>>("JsonFactory__configure1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public final com.fasterxml.jackson.core.JsonFactory configure(com.fasterxml.jackson.core.JsonParser.Feature f, boolean state)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling or disabling specified parser feature
+  /// (check JsonParser.Feature for list of features)
+  ///@param f Feature to enable/disable
+  ///@param state Whether to enable or disable the feature
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory configure1(
+    jsonparser_.JsonParser_Feature f,
+    bool state,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_configure1(reference, f.reference, state ? 1 : 0).object);
+  }
+
+  static final _enable1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__enable1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory enable(com.fasterxml.jackson.core.JsonParser.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling specified parser feature
+  /// (check JsonParser.Feature for list of features)
+  ///@param f Feature to enable
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory enable1(
+    jsonparser_.JsonParser_Feature f,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_enable1(reference, f.reference).object);
+  }
+
+  static final _disable1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__disable1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory disable(com.fasterxml.jackson.core.JsonParser.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for disabling specified parser features
+  /// (check JsonParser.Feature for list of features)
+  ///@param f Feature to disable
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory disable1(
+    jsonparser_.JsonParser_Feature f,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_disable1(reference, f.reference).object);
+  }
+
+  static final _isEnabled1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__isEnabled1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.JsonParser.Feature f)
+  ///
+  /// Method for checking if the specified parser feature is enabled.
+  ///@param f Feature to check
+  ///@return True if specified feature is enabled
+  bool isEnabled1(
+    jsonparser_.JsonParser_Feature f,
+  ) {
+    return _isEnabled1(reference, f.reference).boolean;
+  }
+
+  static final _isEnabled2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__isEnabled2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.StreamReadFeature f)
+  ///
+  /// Method for checking if the specified stream read feature is enabled.
+  ///@param f Feature to check
+  ///@return True if specified feature is enabled
+  ///@since 2.10
+  bool isEnabled2(
+    jni.JObject f,
+  ) {
+    return _isEnabled2(reference, f.reference).boolean;
+  }
+
+  static final _getInputDecorator = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getInputDecorator")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.io.InputDecorator getInputDecorator()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for getting currently configured input decorator (if any;
+  /// there is no default decorator).
+  ///@return InputDecorator configured, if any
+  jni.JObject getInputDecorator() {
+    return const jni.JObjectType()
+        .fromRef(_getInputDecorator(reference).object);
+  }
+
+  static final _setInputDecorator = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__setInputDecorator")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setInputDecorator(com.fasterxml.jackson.core.io.InputDecorator d)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for overriding currently configured input decorator
+  ///@param d Decorator to configure for this factory, if any ({@code null} if none)
+  ///@return This factory instance (to allow call chaining)
+  ///@deprecated Since 2.10 use JsonFactoryBuilder\#inputDecorator(InputDecorator) instead
+  JsonFactory setInputDecorator(
+    jni.JObject d,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_setInputDecorator(reference, d.reference).object);
+  }
+
+  static final _configure2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>, ffi.Uint8)>>("JsonFactory__configure2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public final com.fasterxml.jackson.core.JsonFactory configure(com.fasterxml.jackson.core.JsonGenerator.Feature f, boolean state)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling or disabling specified generator feature
+  /// (check JsonGenerator.Feature for list of features)
+  ///@param f Feature to enable/disable
+  ///@param state Whether to enable or disable the feature
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory configure2(
+    jni.JObject f,
+    bool state,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_configure2(reference, f.reference, state ? 1 : 0).object);
+  }
+
+  static final _enable2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__enable2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory enable(com.fasterxml.jackson.core.JsonGenerator.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling specified generator features
+  /// (check JsonGenerator.Feature for list of features)
+  ///@param f Feature to enable
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory enable2(
+    jni.JObject f,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_enable2(reference, f.reference).object);
+  }
+
+  static final _disable2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__disable2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory disable(com.fasterxml.jackson.core.JsonGenerator.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for disabling specified generator feature
+  /// (check JsonGenerator.Feature for list of features)
+  ///@param f Feature to disable
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory disable2(
+    jni.JObject f,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_disable2(reference, f.reference).object);
+  }
+
+  static final _isEnabled3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__isEnabled3")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.JsonGenerator.Feature f)
+  ///
+  /// Check whether specified generator feature is enabled.
+  ///@param f Feature to check
+  ///@return Whether specified feature is enabled
+  bool isEnabled3(
+    jni.JObject f,
+  ) {
+    return _isEnabled3(reference, f.reference).boolean;
+  }
+
+  static final _isEnabled4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__isEnabled4")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.StreamWriteFeature f)
+  ///
+  /// Check whether specified stream write feature is enabled.
+  ///@param f Feature to check
+  ///@return Whether specified feature is enabled
+  ///@since 2.10
+  bool isEnabled4(
+    jni.JObject f,
+  ) {
+    return _isEnabled4(reference, f.reference).boolean;
+  }
+
+  static final _getCharacterEscapes = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getCharacterEscapes")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.io.CharacterEscapes getCharacterEscapes()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for accessing custom escapes factory uses for JsonGenerators
+  /// it creates.
+  ///@return Configured {@code CharacterEscapes}, if any; {@code null} if none
+  jni.JObject getCharacterEscapes() {
+    return const jni.JObjectType()
+        .fromRef(_getCharacterEscapes(reference).object);
+  }
+
+  static final _setCharacterEscapes = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__setCharacterEscapes")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setCharacterEscapes(com.fasterxml.jackson.core.io.CharacterEscapes esc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for defining custom escapes factory uses for JsonGenerators
+  /// it creates.
+  ///@param esc CharaterEscapes to set (or {@code null} for "none")
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory setCharacterEscapes(
+    jni.JObject esc,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_setCharacterEscapes(reference, esc.reference).object);
+  }
+
+  static final _getOutputDecorator = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getOutputDecorator")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.io.OutputDecorator getOutputDecorator()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for getting currently configured output decorator (if any;
+  /// there is no default decorator).
+  ///@return OutputDecorator configured for generators factory creates, if any;
+  ///    {@code null} if none.
+  jni.JObject getOutputDecorator() {
+    return const jni.JObjectType()
+        .fromRef(_getOutputDecorator(reference).object);
+  }
+
+  static final _setOutputDecorator = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__setOutputDecorator")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setOutputDecorator(com.fasterxml.jackson.core.io.OutputDecorator d)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for overriding currently configured output decorator
+  ///@return This factory instance (to allow call chaining)
+  ///@param d Output decorator to use, if any
+  ///@deprecated Since 2.10 use JsonFactoryBuilder\#outputDecorator(OutputDecorator) instead
+  JsonFactory setOutputDecorator(
+    jni.JObject d,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_setOutputDecorator(reference, d.reference).object);
+  }
+
+  static final _setRootValueSeparator = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__setRootValueSeparator")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setRootValueSeparator(java.lang.String sep)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that allows overriding String used for separating root-level
+  /// JSON values (default is single space character)
+  ///@param sep Separator to use, if any; null means that no separator is
+  ///   automatically added
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory setRootValueSeparator(
+    jni.JString sep,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_setRootValueSeparator(reference, sep.reference).object);
+  }
+
+  static final _getRootValueSeparator = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getRootValueSeparator")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getRootValueSeparator()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// @return Root value separator configured, if any
+  jni.JString getRootValueSeparator() {
+    return const jni.JStringType()
+        .fromRef(_getRootValueSeparator(reference).object);
+  }
+
+  static final _setCodec = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__setCodec")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setCodec(com.fasterxml.jackson.core.ObjectCodec oc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for associating a ObjectCodec (typically
+  /// a <code>com.fasterxml.jackson.databind.ObjectMapper</code>)
+  /// with this factory (and more importantly, parsers and generators
+  /// it constructs). This is needed to use data-binding methods
+  /// of JsonParser and JsonGenerator instances.
+  ///@param oc Codec to use
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory setCodec(
+    jni.JObject oc,
+  ) {
+    return const $JsonFactoryType()
+        .fromRef(_setCodec(reference, oc.reference).object);
+  }
+
+  static final _getCodec = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__getCodec")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.ObjectCodec getCodec()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject getCodec() {
+    return const jni.JObjectType().fromRef(_getCodec(reference).object);
+  }
+
+  static final _createParser = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createParser")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.io.File f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// contents of specified file.
+  ///
+  ///
+  /// Encoding is auto-detected from contents according to JSON
+  /// specification recommended mechanism. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///
+  ///
+  /// Underlying input stream (needed for reading contents)
+  /// will be __owned__ (and managed, i.e. closed as need be) by
+  /// the parser, since caller has no access to it.
+  ///@param f File that contains JSON content to parse
+  ///@since 2.1
+  jsonparser_.JsonParser createParser(
+    jni.JObject f,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createParser(reference, f.reference).object);
+  }
+
+  static final _createParser1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createParser1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.net.URL url)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// contents of resource reference by given URL.
+  ///
+  /// Encoding is auto-detected from contents according to JSON
+  /// specification recommended mechanism. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///
+  /// Underlying input stream (needed for reading contents)
+  /// will be __owned__ (and managed, i.e. closed as need be) by
+  /// the parser, since caller has no access to it.
+  ///@param url URL pointing to resource that contains JSON content to parse
+  ///@since 2.1
+  jsonparser_.JsonParser createParser1(
+    jni.JObject url,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createParser1(reference, url.reference).object);
+  }
+
+  static final _createParser2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createParser2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.io.InputStream in)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// the contents accessed via specified input stream.
+  ///
+  /// The input stream will __not be owned__ by
+  /// the parser, it will still be managed (i.e. closed if
+  /// end-of-stream is reacher, or parser close method called)
+  /// if (and only if) com.fasterxml.jackson.core.StreamReadFeature\#AUTO_CLOSE_SOURCE
+  /// is enabled.
+  ///
+  ///
+  /// Note: no encoding argument is taken since it can always be
+  /// auto-detected as suggested by JSON RFC. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///@param in InputStream to use for reading JSON content to parse
+  ///@since 2.1
+  jsonparser_.JsonParser createParser2(
+    jni.JObject in0,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createParser2(reference, in0.reference).object);
+  }
+
+  static final _createParser3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createParser3")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.io.Reader r)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents accessed via specified Reader.
+  ///
+  /// The read stream will __not be owned__ by
+  /// the parser, it will still be managed (i.e. closed if
+  /// end-of-stream is reacher, or parser close method called)
+  /// if (and only if) com.fasterxml.jackson.core.StreamReadFeature\#AUTO_CLOSE_SOURCE
+  /// is enabled.
+  ///@param r Reader to use for reading JSON content to parse
+  ///@since 2.1
+  jsonparser_.JsonParser createParser3(
+    jni.JObject r,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createParser3(reference, r.reference).object);
+  }
+
+  static final _createParser4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createParser4")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(byte[] data)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents of given byte array.
+  ///@since 2.1
+  jsonparser_.JsonParser createParser4(
+    jni.JArray<jni.jbyte> data,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createParser4(reference, data.reference).object);
+  }
+
+  static final _createParser5 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Int32)>>("JsonFactory__createParser5")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int, int)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(byte[] data, int offset, int len)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents of given byte array.
+  ///@param data Buffer that contains data to parse
+  ///@param offset Offset of the first data byte within buffer
+  ///@param len Length of contents to parse within buffer
+  ///@since 2.1
+  jsonparser_.JsonParser createParser5(
+    jni.JArray<jni.jbyte> data,
+    int offset,
+    int len,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createParser5(reference, data.reference, offset, len).object);
+  }
+
+  static final _createParser6 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createParser6")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.lang.String content)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// contents of given String.
+  ///@since 2.1
+  jsonparser_.JsonParser createParser6(
+    jni.JString content,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createParser6(reference, content.reference).object);
+  }
+
+  static final _createParser7 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createParser7")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(char[] content)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// contents of given char array.
+  ///@since 2.4
+  jsonparser_.JsonParser createParser7(
+    jni.JArray<jni.jchar> content,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createParser7(reference, content.reference).object);
+  }
+
+  static final _createParser8 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Int32)>>("JsonFactory__createParser8")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int, int)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(char[] content, int offset, int len)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing contents of given char array.
+  ///@since 2.4
+  jsonparser_.JsonParser createParser8(
+    jni.JArray<jni.jchar> content,
+    int offset,
+    int len,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(
+        _createParser8(reference, content.reference, offset, len).object);
+  }
+
+  static final _createParser9 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createParser9")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.io.DataInput in)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Optional method for constructing parser for reading contents from specified DataInput
+  /// instance.
+  ///
+  /// If this factory does not support DataInput as source,
+  /// will throw UnsupportedOperationException
+  ///@since 2.8
+  jsonparser_.JsonParser createParser9(
+    jni.JObject in0,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createParser9(reference, in0.reference).object);
+  }
+
+  static final _createNonBlockingByteArrayParser = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory__createNonBlockingByteArrayParser")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createNonBlockingByteArrayParser()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Optional method for constructing parser for non-blocking parsing
+  /// via com.fasterxml.jackson.core.async.ByteArrayFeeder
+  /// interface (accessed using JsonParser\#getNonBlockingInputFeeder()
+  /// from constructed instance).
+  ///
+  /// If this factory does not support non-blocking parsing (either at all,
+  /// or from byte array),
+  /// will throw UnsupportedOperationException.
+  ///
+  /// Note that JSON-backed factory only supports parsing of UTF-8 encoded JSON content
+  /// (and US-ASCII since it is proper subset); other encodings are not supported
+  /// at this point.
+  ///@since 2.9
+  jsonparser_.JsonParser createNonBlockingByteArrayParser() {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createNonBlockingByteArrayParser(reference).object);
+  }
+
+  static final _createGenerator = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createGenerator")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.OutputStream out, com.fasterxml.jackson.core.JsonEncoding enc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// using specified output stream.
+  /// Encoding to use must be specified, and needs to be one of available
+  /// types (as per JSON specification).
+  ///
+  /// Underlying stream __is NOT owned__ by the generator constructed,
+  /// so that generator will NOT close the output stream when
+  /// JsonGenerator\#close is called (unless auto-closing
+  /// feature,
+  /// com.fasterxml.jackson.core.JsonGenerator.Feature\#AUTO_CLOSE_TARGET
+  /// is enabled).
+  /// Using application needs to close it explicitly if this is the case.
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats)
+  /// and that ignore passed in encoding.
+  ///@param out OutputStream to use for writing JSON content
+  ///@param enc Character encoding to use
+  ///@since 2.1
+  jni.JObject createGenerator(
+    jni.JObject out,
+    jni.JObject enc,
+  ) {
+    return const jni.JObjectType().fromRef(
+        _createGenerator(reference, out.reference, enc.reference).object);
+  }
+
+  static final _createGenerator1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createGenerator1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.OutputStream out)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Convenience method for constructing generator that uses default
+  /// encoding of the format (UTF-8 for JSON and most other data formats).
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats).
+  ///@since 2.1
+  jni.JObject createGenerator1(
+    jni.JObject out,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_createGenerator1(reference, out.reference).object);
+  }
+
+  static final _createGenerator2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createGenerator2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.Writer w)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// using specified Writer.
+  ///
+  /// Underlying stream __is NOT owned__ by the generator constructed,
+  /// so that generator will NOT close the Reader when
+  /// JsonGenerator\#close is called (unless auto-closing
+  /// feature,
+  /// com.fasterxml.jackson.core.JsonGenerator.Feature\#AUTO_CLOSE_TARGET is enabled).
+  /// Using application needs to close it explicitly.
+  ///@since 2.1
+  ///@param w Writer to use for writing JSON content
+  jni.JObject createGenerator2(
+    jni.JObject w,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_createGenerator2(reference, w.reference).object);
+  }
+
+  static final _createGenerator3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createGenerator3")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.File f, com.fasterxml.jackson.core.JsonEncoding enc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// to specified file, overwriting contents it might have (or creating
+  /// it if such file does not yet exist).
+  /// Encoding to use must be specified, and needs to be one of available
+  /// types (as per JSON specification).
+  ///
+  /// Underlying stream __is owned__ by the generator constructed,
+  /// i.e. generator will handle closing of file when
+  /// JsonGenerator\#close is called.
+  ///@param f File to write contents to
+  ///@param enc Character encoding to use
+  ///@since 2.1
+  jni.JObject createGenerator3(
+    jni.JObject f,
+    jni.JObject enc,
+  ) {
+    return const jni.JObjectType().fromRef(
+        _createGenerator3(reference, f.reference, enc.reference).object);
+  }
+
+  static final _createGenerator4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createGenerator4")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.DataOutput out, com.fasterxml.jackson.core.JsonEncoding enc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing generator for writing content using specified
+  /// DataOutput instance.
+  ///@since 2.8
+  jni.JObject createGenerator4(
+    jni.JObject out,
+    jni.JObject enc,
+  ) {
+    return const jni.JObjectType().fromRef(
+        _createGenerator4(reference, out.reference, enc.reference).object);
+  }
+
+  static final _createGenerator5 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createGenerator5")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.DataOutput out)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Convenience method for constructing generator that uses default
+  /// encoding of the format (UTF-8 for JSON and most other data formats).
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats).
+  ///@since 2.8
+  jni.JObject createGenerator5(
+    jni.JObject out,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_createGenerator5(reference, out.reference).object);
+  }
+
+  static final _createJsonParser = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createJsonParser")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.io.File f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// contents of specified file.
+  ///
+  /// Encoding is auto-detected from contents according to JSON
+  /// specification recommended mechanism. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///
+  ///
+  /// Underlying input stream (needed for reading contents)
+  /// will be __owned__ (and managed, i.e. closed as need be) by
+  /// the parser, since caller has no access to it.
+  ///@param f File that contains JSON content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(File) instead.
+  jsonparser_.JsonParser createJsonParser(
+    jni.JObject f,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createJsonParser(reference, f.reference).object);
+  }
+
+  static final _createJsonParser1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createJsonParser1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.net.URL url)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// contents of resource reference by given URL.
+  ///
+  /// Encoding is auto-detected from contents according to JSON
+  /// specification recommended mechanism. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///
+  /// Underlying input stream (needed for reading contents)
+  /// will be __owned__ (and managed, i.e. closed as need be) by
+  /// the parser, since caller has no access to it.
+  ///@param url URL pointing to resource that contains JSON content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(URL) instead.
+  jsonparser_.JsonParser createJsonParser1(
+    jni.JObject url,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createJsonParser1(reference, url.reference).object);
+  }
+
+  static final _createJsonParser2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createJsonParser2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.io.InputStream in)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// the contents accessed via specified input stream.
+  ///
+  /// The input stream will __not be owned__ by
+  /// the parser, it will still be managed (i.e. closed if
+  /// end-of-stream is reacher, or parser close method called)
+  /// if (and only if) com.fasterxml.jackson.core.JsonParser.Feature\#AUTO_CLOSE_SOURCE
+  /// is enabled.
+  ///
+  ///
+  /// Note: no encoding argument is taken since it can always be
+  /// auto-detected as suggested by JSON RFC. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///@param in InputStream to use for reading JSON content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(InputStream) instead.
+  jsonparser_.JsonParser createJsonParser2(
+    jni.JObject in0,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createJsonParser2(reference, in0.reference).object);
+  }
+
+  static final _createJsonParser3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createJsonParser3")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.io.Reader r)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents accessed via specified Reader.
+  ///
+  /// The read stream will __not be owned__ by
+  /// the parser, it will still be managed (i.e. closed if
+  /// end-of-stream is reacher, or parser close method called)
+  /// if (and only if) com.fasterxml.jackson.core.JsonParser.Feature\#AUTO_CLOSE_SOURCE
+  /// is enabled.
+  ///@param r Reader to use for reading JSON content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(Reader) instead.
+  jsonparser_.JsonParser createJsonParser3(
+    jni.JObject r,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createJsonParser3(reference, r.reference).object);
+  }
+
+  static final _createJsonParser4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createJsonParser4")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(byte[] data)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing the contents of given byte array.
+  ///@param data Input content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(byte[]) instead.
+  jsonparser_.JsonParser createJsonParser4(
+    jni.JArray<jni.jbyte> data,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createJsonParser4(reference, data.reference).object);
+  }
+
+  static final _createJsonParser5 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Int32)>>("JsonFactory__createJsonParser5")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int, int)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(byte[] data, int offset, int len)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents of given byte array.
+  ///@param data Buffer that contains data to parse
+  ///@param offset Offset of the first data byte within buffer
+  ///@param len Length of contents to parse within buffer
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(byte[],int,int) instead.
+  jsonparser_.JsonParser createJsonParser5(
+    jni.JArray<jni.jbyte> data,
+    int offset,
+    int len,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(
+        _createJsonParser5(reference, data.reference, offset, len).object);
+  }
+
+  static final _createJsonParser6 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createJsonParser6")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.lang.String content)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// contents of given String.
+  ///@param content Input content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(String) instead.
+  jsonparser_.JsonParser createJsonParser6(
+    jni.JString content,
+  ) {
+    return const jsonparser_.$JsonParserType()
+        .fromRef(_createJsonParser6(reference, content.reference).object);
+  }
+
+  static final _createJsonGenerator = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createJsonGenerator")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createJsonGenerator(java.io.OutputStream out, com.fasterxml.jackson.core.JsonEncoding enc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// using specified output stream.
+  /// Encoding to use must be specified, and needs to be one of available
+  /// types (as per JSON specification).
+  ///
+  /// Underlying stream __is NOT owned__ by the generator constructed,
+  /// so that generator will NOT close the output stream when
+  /// JsonGenerator\#close is called (unless auto-closing
+  /// feature,
+  /// com.fasterxml.jackson.core.JsonGenerator.Feature\#AUTO_CLOSE_TARGET
+  /// is enabled).
+  /// Using application needs to close it explicitly if this is the case.
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats)
+  /// and that ignore passed in encoding.
+  ///@param out OutputStream to use for writing JSON content
+  ///@param enc Character encoding to use
+  ///@return Generator constructed
+  ///@throws IOException if parser initialization fails due to I/O (write) problem
+  ///@deprecated Since 2.2, use \#createGenerator(OutputStream, JsonEncoding) instead.
+  jni.JObject createJsonGenerator(
+    jni.JObject out,
+    jni.JObject enc,
+  ) {
+    return const jni.JObjectType().fromRef(
+        _createJsonGenerator(reference, out.reference, enc.reference).object);
+  }
+
+  static final _createJsonGenerator1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createJsonGenerator1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createJsonGenerator(java.io.Writer out)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// using specified Writer.
+  ///
+  /// Underlying stream __is NOT owned__ by the generator constructed,
+  /// so that generator will NOT close the Reader when
+  /// JsonGenerator\#close is called (unless auto-closing
+  /// feature,
+  /// com.fasterxml.jackson.core.JsonGenerator.Feature\#AUTO_CLOSE_TARGET is enabled).
+  /// Using application needs to close it explicitly.
+  ///@param out Writer to use for writing JSON content
+  ///@return Generator constructed
+  ///@throws IOException if parser initialization fails due to I/O (write) problem
+  ///@deprecated Since 2.2, use \#createGenerator(Writer) instead.
+  jni.JObject createJsonGenerator1(
+    jni.JObject out,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_createJsonGenerator1(reference, out.reference).object);
+  }
+
+  static final _createJsonGenerator2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonFactory__createJsonGenerator2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createJsonGenerator(java.io.OutputStream out)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Convenience method for constructing generator that uses default
+  /// encoding of the format (UTF-8 for JSON and most other data formats).
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats).
+  ///@param out OutputStream to use for writing JSON content
+  ///@return Generator constructed
+  ///@throws IOException if parser initialization fails due to I/O (write) problem
+  ///@deprecated Since 2.2, use \#createGenerator(OutputStream) instead.
+  jni.JObject createJsonGenerator2(
+    jni.JObject out,
+  ) {
+    return const jni.JObjectType()
+        .fromRef(_createJsonGenerator2(reference, out.reference).object);
+  }
+}
+
+final class $JsonFactoryType extends jni.JObjType<JsonFactory> {
+  const $JsonFactoryType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonFactory;";
+
+  @override
+  JsonFactory fromRef(jni.JObjectPtr ref) => JsonFactory.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonFactoryType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonFactoryType) && other is $JsonFactoryType;
+  }
+}
+
+/// from: com.fasterxml.jackson.core.JsonFactory$Feature
+///
+/// Enumeration that defines all on/off features that can only be
+/// changed for JsonFactory.
+class JsonFactory_Feature extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonFactory_Feature> $type = type;
+
+  JsonFactory_Feature.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonFactory_FeatureType();
+  static final _values =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "JsonFactory_Feature__values")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.fasterxml.jackson.core.JsonFactory.Feature[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonFactory_Feature> values() {
+    return const jni.JArrayType($JsonFactory_FeatureType())
+        .fromRef(_values().object);
+  }
+
+  static final _valueOf = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory_Feature__valueOf")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.fasterxml.jackson.core.JsonFactory.Feature valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonFactory_Feature valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonFactory_FeatureType()
+        .fromRef(_valueOf(name.reference).object);
+  }
+
+  static final _collectDefaults =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "JsonFactory_Feature__collectDefaults")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public int collectDefaults()
+  ///
+  /// Method that calculates bit set (flags) of all features that
+  /// are enabled by default.
+  ///@return Bit field of features enabled by default
+  static int collectDefaults() {
+    return _collectDefaults().integer;
+  }
+
+  static final _enabledByDefault = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory_Feature__enabledByDefault")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean enabledByDefault()
+  bool enabledByDefault() {
+    return _enabledByDefault(reference).boolean;
+  }
+
+  static final _enabledIn = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("JsonFactory_Feature__enabledIn")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public boolean enabledIn(int flags)
+  bool enabledIn(
+    int flags,
+  ) {
+    return _enabledIn(reference, flags).boolean;
+  }
+
+  static final _getMask = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonFactory_Feature__getMask")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getMask()
+  int getMask() {
+    return _getMask(reference).integer;
+  }
+}
+
+final class $JsonFactory_FeatureType extends jni.JObjType<JsonFactory_Feature> {
+  const $JsonFactory_FeatureType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonFactory$Feature;";
+
+  @override
+  JsonFactory_Feature fromRef(jni.JObjectPtr ref) =>
+      JsonFactory_Feature.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonFactory_FeatureType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonFactory_FeatureType) &&
+        other is $JsonFactory_FeatureType;
+  }
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
new file mode 100644
index 0000000..4a1863d
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
@@ -0,0 +1,2847 @@
+// Generated from jackson-core which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+// See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+//
+// Copyright (c) 2007 - The Jackson Project Authors
+// 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+import "JsonToken.dart" as jsontoken_;
+import "../../../../_init.dart";
+
+/// from: com.fasterxml.jackson.core.JsonParser
+///
+/// Base class that defines public API for reading JSON content.
+/// Instances are created using factory methods of
+/// a JsonFactory instance.
+///@author Tatu Saloranta
+class JsonParser extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonParser> $type = type;
+
+  JsonParser.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonParserType();
+  static final _get_DEFAULT_READ_CAPABILITIES =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_JsonParser__DEFAULT_READ_CAPABILITIES")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static protected final com.fasterxml.jackson.core.util.JacksonFeatureSet<com.fasterxml.jackson.core.StreamReadCapability> DEFAULT_READ_CAPABILITIES
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Default set of StreamReadCapabilityies that may be used as
+  /// basis for format-specific readers (or as bogus instance if non-null
+  /// set needs to be passed).
+  ///@since 2.12
+  static jni.JObject get DEFAULT_READ_CAPABILITIES =>
+      const jni.JObjectType().fromRef(_get_DEFAULT_READ_CAPABILITIES().object);
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "JsonParser__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: protected void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory JsonParser() {
+    return JsonParser.fromRef(_new0().object);
+  }
+
+  static final _new1 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Int32)>>(
+              "JsonParser__new1")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: protected void <init>(int features)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory JsonParser.new1(
+    int features,
+  ) {
+    return JsonParser.fromRef(_new1(features).object);
+  }
+
+  static final _getCodec = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getCodec")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.ObjectCodec getCodec()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor for ObjectCodec associated with this
+  /// parser, if any. Codec is used by \#readValueAs(Class)
+  /// method (and its variants).
+  ///@return Codec assigned to this parser, if any; {@code null} if none
+  jni.JObject getCodec() {
+    return const jni.JObjectType().fromRef(_getCodec(reference).object);
+  }
+
+  static final _setCodec = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__setCodec")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract void setCodec(com.fasterxml.jackson.core.ObjectCodec oc)
+  ///
+  /// Setter that allows defining ObjectCodec associated with this
+  /// parser, if any. Codec is used by \#readValueAs(Class)
+  /// method (and its variants).
+  ///@param oc Codec to assign, if any; {@code null} if none
+  void setCodec(
+    jni.JObject oc,
+  ) {
+    return _setCodec(reference, oc.reference).check();
+  }
+
+  static final _getInputSource = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getInputSource")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object getInputSource()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be used to get access to object that is used
+  /// to access input being parsed; this is usually either
+  /// InputStream or Reader, depending on what
+  /// parser was constructed with.
+  /// Note that returned value may be null in some cases; including
+  /// case where parser implementation does not want to exposed raw
+  /// source to caller.
+  /// In cases where input has been decorated, object returned here
+  /// is the decorated version; this allows some level of interaction
+  /// between users of parser and decorator object.
+  ///
+  /// In general use of this accessor should be considered as
+  /// "last effort", i.e. only used if no other mechanism is applicable.
+  ///@return Input source this parser was configured with
+  jni.JObject getInputSource() {
+    return const jni.JObjectType().fromRef(_getInputSource(reference).object);
+  }
+
+  static final _setRequestPayloadOnError = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__setRequestPayloadOnError")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setRequestPayloadOnError(com.fasterxml.jackson.core.util.RequestPayload payload)
+  ///
+  /// Sets the payload to be passed if JsonParseException is thrown.
+  ///@param payload Payload to pass
+  ///@since 2.8
+  void setRequestPayloadOnError(
+    jni.JObject payload,
+  ) {
+    return _setRequestPayloadOnError(reference, payload.reference).check();
+  }
+
+  static final _setRequestPayloadOnError1 = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__setRequestPayloadOnError1")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setRequestPayloadOnError(byte[] payload, java.lang.String charset)
+  ///
+  /// Sets the byte[] request payload and the charset
+  ///@param payload Payload to pass
+  ///@param charset Character encoding for (lazily) decoding payload
+  ///@since 2.8
+  void setRequestPayloadOnError1(
+    jni.JArray<jni.jbyte> payload,
+    jni.JString charset,
+  ) {
+    return _setRequestPayloadOnError1(
+            reference, payload.reference, charset.reference)
+        .check();
+  }
+
+  static final _setRequestPayloadOnError2 = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__setRequestPayloadOnError2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setRequestPayloadOnError(java.lang.String payload)
+  ///
+  /// Sets the String request payload
+  ///@param payload Payload to pass
+  ///@since 2.8
+  void setRequestPayloadOnError2(
+    jni.JString payload,
+  ) {
+    return _setRequestPayloadOnError2(reference, payload.reference).check();
+  }
+
+  static final _setSchema = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__setSchema")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setSchema(com.fasterxml.jackson.core.FormatSchema schema)
+  ///
+  /// Method to call to make this parser use specified schema. Method must
+  /// be called before trying to parse any content, right after parser instance
+  /// has been created.
+  /// Note that not all parsers support schemas; and those that do usually only
+  /// accept specific types of schemas: ones defined for data format parser can read.
+  ///
+  /// If parser does not support specified schema, UnsupportedOperationException
+  /// is thrown.
+  ///@param schema Schema to use
+  ///@throws UnsupportedOperationException if parser does not support schema
+  void setSchema(
+    jni.JObject schema,
+  ) {
+    return _setSchema(reference, schema.reference).check();
+  }
+
+  static final _getSchema = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getSchema")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.FormatSchema getSchema()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for accessing Schema that this parser uses, if any.
+  /// Default implementation returns null.
+  ///@return Schema in use by this parser, if any; {@code null} if none
+  ///@since 2.1
+  jni.JObject getSchema() {
+    return const jni.JObjectType().fromRef(_getSchema(reference).object);
+  }
+
+  static final _canUseSchema = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__canUseSchema")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean canUseSchema(com.fasterxml.jackson.core.FormatSchema schema)
+  ///
+  /// Method that can be used to verify that given schema can be used with
+  /// this parser (using \#setSchema).
+  ///@param schema Schema to check
+  ///@return True if this parser can use given schema; false if not
+  bool canUseSchema(
+    jni.JObject schema,
+  ) {
+    return _canUseSchema(reference, schema.reference).boolean;
+  }
+
+  static final _requiresCustomCodec = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__requiresCustomCodec")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean requiresCustomCodec()
+  ///
+  /// Method that can be called to determine if a custom
+  /// ObjectCodec is needed for binding data parsed
+  /// using JsonParser constructed by this factory
+  /// (which typically also implies the same for serialization
+  /// with JsonGenerator).
+  ///@return True if format-specific codec is needed with this parser; false if a general
+  ///   ObjectCodec is enough
+  ///@since 2.1
+  bool requiresCustomCodec() {
+    return _requiresCustomCodec(reference).boolean;
+  }
+
+  static final _canParseAsync = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__canParseAsync")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean canParseAsync()
+  ///
+  /// Method that can be called to determine if this parser instance
+  /// uses non-blocking ("asynchronous") input access for decoding or not.
+  /// Access mode is determined by earlier calls via JsonFactory;
+  /// it may not be changed after construction.
+  ///
+  /// If non-blocking decoding is (@code true}, it is possible to call
+  /// \#getNonBlockingInputFeeder() to obtain object to use
+  /// for feeding input; otherwise (<code>false</code> returned)
+  /// input is read by blocking
+  ///@return True if this is a non-blocking ("asynchronous") parser
+  ///@since 2.9
+  bool canParseAsync() {
+    return _canParseAsync(reference).boolean;
+  }
+
+  static final _getNonBlockingInputFeeder = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getNonBlockingInputFeeder")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.async.NonBlockingInputFeeder getNonBlockingInputFeeder()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that will either return a feeder instance (if parser uses
+  /// non-blocking, aka asynchronous access); or <code>null</code> for
+  /// parsers that use blocking I/O.
+  ///@return Input feeder to use with non-blocking (async) parsing
+  ///@since 2.9
+  jni.JObject getNonBlockingInputFeeder() {
+    return const jni.JObjectType()
+        .fromRef(_getNonBlockingInputFeeder(reference).object);
+  }
+
+  static final _getReadCapabilities = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getReadCapabilities")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.util.JacksonFeatureSet<com.fasterxml.jackson.core.StreamReadCapability> getReadCapabilities()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor for getting metadata on capabilities of this parser, based on
+  /// underlying data format being read (directly or indirectly).
+  ///@return Set of read capabilities for content to read via this parser
+  ///@since 2.12
+  jni.JObject getReadCapabilities() {
+    return const jni.JObjectType()
+        .fromRef(_getReadCapabilities(reference).object);
+  }
+
+  static final _version = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__version")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.Version version()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor for getting version of the core package, given a parser instance.
+  /// Left for sub-classes to implement.
+  ///@return Version of this generator (derived from version declared for
+  ///   {@code jackson-core} jar that contains the class
+  jni.JObject version() {
+    return const jni.JObjectType().fromRef(_version(reference).object);
+  }
+
+  static final _close = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__close")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract void close()
+  ///
+  /// Closes the parser so that no further iteration or data access
+  /// can be made; will also close the underlying input source
+  /// if parser either __owns__ the input source, or feature
+  /// Feature\#AUTO_CLOSE_SOURCE is enabled.
+  /// Whether parser owns the input source depends on factory
+  /// method that was used to construct instance (so check
+  /// com.fasterxml.jackson.core.JsonFactory for details,
+  /// but the general
+  /// idea is that if caller passes in closable resource (such
+  /// as InputStream or Reader) parser does NOT
+  /// own the source; but if it passes a reference (such as
+  /// java.io.File or java.net.URL and creates
+  /// stream or reader it does own them.
+  ///@throws IOException if there is either an underlying I/O problem
+  void close() {
+    return _close(reference).check();
+  }
+
+  static final _isClosed = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__isClosed")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract boolean isClosed()
+  ///
+  /// Method that can be called to determine whether this parser
+  /// is closed or not. If it is closed, no new tokens can be
+  /// retrieved by calling \#nextToken (and the underlying
+  /// stream may be closed). Closing may be due to an explicit
+  /// call to \#close or because parser has encountered
+  /// end of input.
+  ///@return {@code True} if this parser instance has been closed
+  bool isClosed() {
+    return _isClosed(reference).boolean;
+  }
+
+  static final _getParsingContext = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getParsingContext")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonStreamContext getParsingContext()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be used to access current parsing context reader
+  /// is in. There are 3 different types: root, array and object contexts,
+  /// with slightly different available information. Contexts are
+  /// hierarchically nested, and can be used for example for figuring
+  /// out part of the input document that correspond to specific
+  /// array or object (for highlighting purposes, or error reporting).
+  /// Contexts can also be used for simple xpath-like matching of
+  /// input, if so desired.
+  ///@return Stream input context (JsonStreamContext) associated with this parser
+  jni.JObject getParsingContext() {
+    return const jni.JObjectType()
+        .fromRef(_getParsingContext(reference).object);
+  }
+
+  static final _currentLocation = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__currentLocation")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonLocation currentLocation()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that returns location of the last processed input unit (character
+  /// or byte) from the input;
+  /// usually for error reporting purposes.
+  ///
+  /// Note that the location is not guaranteed to be accurate (although most
+  /// implementation will try their best): some implementations may only
+  /// report specific boundary locations (start or end locations of tokens)
+  /// and others only return JsonLocation\#NA due to not having access
+  /// to input location information (when delegating actual decoding work
+  /// to other library)
+  ///@return Location of the last processed input unit (byte or character)
+  ///@since 2.13
+  jni.JObject currentLocation() {
+    return const jni.JObjectType().fromRef(_currentLocation(reference).object);
+  }
+
+  static final _currentTokenLocation = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__currentTokenLocation")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonLocation currentTokenLocation()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that return the __starting__ location of the current
+  /// (most recently returned)
+  /// token; that is, the position of the first input unit (character or byte) from input
+  /// that starts the current token.
+  ///
+  /// Note that the location is not guaranteed to be accurate (although most
+  /// implementation will try their best): some implementations may only
+  /// return JsonLocation\#NA due to not having access
+  /// to input location information (when delegating actual decoding work
+  /// to other library)
+  ///@return Starting location of the token parser currently points to
+  ///@since 2.13 (will eventually replace \#getTokenLocation)
+  jni.JObject currentTokenLocation() {
+    return const jni.JObjectType()
+        .fromRef(_currentTokenLocation(reference).object);
+  }
+
+  static final _getCurrentLocation = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getCurrentLocation")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonLocation getCurrentLocation()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias for \#currentLocation(), to be deprecated in later
+  /// Jackson 2.x versions (and removed from Jackson 3.0).
+  ///@return Location of the last processed input unit (byte or character)
+  jni.JObject getCurrentLocation() {
+    return const jni.JObjectType()
+        .fromRef(_getCurrentLocation(reference).object);
+  }
+
+  static final _getTokenLocation = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getTokenLocation")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonLocation getTokenLocation()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias for \#currentTokenLocation(), to be deprecated in later
+  /// Jackson 2.x versions (and removed from Jackson 3.0).
+  ///@return Starting location of the token parser currently points to
+  jni.JObject getTokenLocation() {
+    return const jni.JObjectType().fromRef(_getTokenLocation(reference).object);
+  }
+
+  static final _currentValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__currentValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object currentValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Helper method, usually equivalent to:
+  ///<code>
+  ///   getParsingContext().getCurrentValue();
+  ///</code>
+  ///
+  /// Note that "current value" is NOT populated (or used) by Streaming parser;
+  /// it is only used by higher-level data-binding functionality.
+  /// The reason it is included here is that it can be stored and accessed hierarchically,
+  /// and gets passed through data-binding.
+  ///@return "Current value" associated with the current input context (state) of this parser
+  ///@since 2.13 (added as replacement for older \#getCurrentValue()
+  jni.JObject currentValue() {
+    return const jni.JObjectType().fromRef(_currentValue(reference).object);
+  }
+
+  static final _assignCurrentValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__assignCurrentValue")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void assignCurrentValue(java.lang.Object v)
+  ///
+  /// Helper method, usually equivalent to:
+  ///<code>
+  ///   getParsingContext().setCurrentValue(v);
+  ///</code>
+  ///@param v Current value to assign for the current input context of this parser
+  ///@since 2.13 (added as replacement for older \#setCurrentValue
+  void assignCurrentValue(
+    jni.JObject v,
+  ) {
+    return _assignCurrentValue(reference, v.reference).check();
+  }
+
+  static final _getCurrentValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getCurrentValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object getCurrentValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias for \#currentValue(), to be deprecated in later
+  /// Jackson 2.x versions (and removed from Jackson 3.0).
+  ///@return Location of the last processed input unit (byte or character)
+  jni.JObject getCurrentValue() {
+    return const jni.JObjectType().fromRef(_getCurrentValue(reference).object);
+  }
+
+  static final _setCurrentValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__setCurrentValue")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setCurrentValue(java.lang.Object v)
+  ///
+  /// Alias for \#assignCurrentValue, to be deprecated in later
+  /// Jackson 2.x versions (and removed from Jackson 3.0).
+  ///@param v Current value to assign for the current input context of this parser
+  void setCurrentValue(
+    jni.JObject v,
+  ) {
+    return _setCurrentValue(reference, v.reference).check();
+  }
+
+  static final _releaseBuffered = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__releaseBuffered")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int releaseBuffered(java.io.OutputStream out)
+  ///
+  /// Method that can be called to push back any content that
+  /// has been read but not consumed by the parser. This is usually
+  /// done after reading all content of interest using parser.
+  /// Content is released by writing it to given stream if possible;
+  /// if underlying input is byte-based it can released, if not (char-based)
+  /// it can not.
+  ///@param out OutputStream to which buffered, undecoded content is written to
+  ///@return -1 if the underlying content source is not byte based
+  ///    (that is, input can not be sent to OutputStream;
+  ///    otherwise number of bytes released (0 if there was nothing to release)
+  ///@throws IOException if write to stream threw exception
+  int releaseBuffered(
+    jni.JObject out,
+  ) {
+    return _releaseBuffered(reference, out.reference).integer;
+  }
+
+  static final _releaseBuffered1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__releaseBuffered1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int releaseBuffered(java.io.Writer w)
+  ///
+  /// Method that can be called to push back any content that
+  /// has been read but not consumed by the parser.
+  /// This is usually
+  /// done after reading all content of interest using parser.
+  /// Content is released by writing it to given writer if possible;
+  /// if underlying input is char-based it can released, if not (byte-based)
+  /// it can not.
+  ///@param w Writer to which buffered but unprocessed content is written to
+  ///@return -1 if the underlying content source is not char-based
+  ///    (that is, input can not be sent to Writer;
+  ///    otherwise number of chars released (0 if there was nothing to release)
+  ///@throws IOException if write using Writer threw exception
+  int releaseBuffered1(
+    jni.JObject w,
+  ) {
+    return _releaseBuffered1(reference, w.reference).integer;
+  }
+
+  static final _enable = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__enable")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser enable(com.fasterxml.jackson.core.JsonParser.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling specified parser feature
+  /// (check Feature for list of features)
+  ///@param f Feature to enable
+  ///@return This parser, to allow call chaining
+  JsonParser enable(
+    JsonParser_Feature f,
+  ) {
+    return const $JsonParserType()
+        .fromRef(_enable(reference, f.reference).object);
+  }
+
+  static final _disable = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__disable")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser disable(com.fasterxml.jackson.core.JsonParser.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for disabling specified  feature
+  /// (check Feature for list of features)
+  ///@param f Feature to disable
+  ///@return This parser, to allow call chaining
+  JsonParser disable(
+    JsonParser_Feature f,
+  ) {
+    return const $JsonParserType()
+        .fromRef(_disable(reference, f.reference).object);
+  }
+
+  static final _configure = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>, ffi.Uint8)>>("JsonParser__configure")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser configure(com.fasterxml.jackson.core.JsonParser.Feature f, boolean state)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling or disabling specified feature
+  /// (check Feature for list of features)
+  ///@param f Feature to enable or disable
+  ///@param state Whether to enable feature ({@code true}) or disable ({@code false})
+  ///@return This parser, to allow call chaining
+  JsonParser configure(
+    JsonParser_Feature f,
+    bool state,
+  ) {
+    return const $JsonParserType()
+        .fromRef(_configure(reference, f.reference, state ? 1 : 0).object);
+  }
+
+  static final _isEnabled = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__isEnabled")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isEnabled(com.fasterxml.jackson.core.JsonParser.Feature f)
+  ///
+  /// Method for checking whether specified Feature is enabled.
+  ///@param f Feature to check
+  ///@return {@code True} if feature is enabled; {@code false} otherwise
+  bool isEnabled(
+    JsonParser_Feature f,
+  ) {
+    return _isEnabled(reference, f.reference).boolean;
+  }
+
+  static final _isEnabled1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__isEnabled1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isEnabled(com.fasterxml.jackson.core.StreamReadFeature f)
+  ///
+  /// Method for checking whether specified Feature is enabled.
+  ///@param f Feature to check
+  ///@return {@code True} if feature is enabled; {@code false} otherwise
+  ///@since 2.10
+  bool isEnabled1(
+    jni.JObject f,
+  ) {
+    return _isEnabled1(reference, f.reference).boolean;
+  }
+
+  static final _getFeatureMask = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getFeatureMask")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getFeatureMask()
+  ///
+  /// Bulk access method for getting state of all standard Features.
+  ///@return Bit mask that defines current states of all standard Features.
+  ///@since 2.3
+  int getFeatureMask() {
+    return _getFeatureMask(reference).integer;
+  }
+
+  static final _setFeatureMask = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("JsonParser__setFeatureMask")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser setFeatureMask(int mask)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Bulk set method for (re)setting states of all standard Features
+  ///@param mask Bit mask that defines set of features to enable
+  ///@return This parser, to allow call chaining
+  ///@since 2.3
+  ///@deprecated Since 2.7, use \#overrideStdFeatures(int, int) instead
+  JsonParser setFeatureMask(
+    int mask,
+  ) {
+    return const $JsonParserType()
+        .fromRef(_setFeatureMask(reference, mask).object);
+  }
+
+  static final _overrideStdFeatures = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Int32,
+                  ffi.Int32)>>("JsonParser__overrideStdFeatures")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int, int)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser overrideStdFeatures(int values, int mask)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Bulk set method for (re)setting states of features specified by <code>mask</code>.
+  /// Functionally equivalent to
+  ///<code>
+  ///    int oldState = getFeatureMask();
+  ///    int newState = (oldState &amp; ~mask) | (values &amp; mask);
+  ///    setFeatureMask(newState);
+  ///</code>
+  /// but preferred as this lets caller more efficiently specify actual changes made.
+  ///@param values Bit mask of set/clear state for features to change
+  ///@param mask Bit mask of features to change
+  ///@return This parser, to allow call chaining
+  ///@since 2.6
+  JsonParser overrideStdFeatures(
+    int values,
+    int mask,
+  ) {
+    return const $JsonParserType()
+        .fromRef(_overrideStdFeatures(reference, values, mask).object);
+  }
+
+  static final _getFormatFeatures = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getFormatFeatures")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getFormatFeatures()
+  ///
+  /// Bulk access method for getting state of all FormatFeatures, format-specific
+  /// on/off configuration settings.
+  ///@return Bit mask that defines current states of all standard FormatFeatures.
+  ///@since 2.6
+  int getFormatFeatures() {
+    return _getFormatFeatures(reference).integer;
+  }
+
+  static final _overrideFormatFeatures = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Int32,
+                  ffi.Int32)>>("JsonParser__overrideFormatFeatures")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int, int)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonParser overrideFormatFeatures(int values, int mask)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Bulk set method for (re)setting states of FormatFeatures,
+  /// by specifying values (set / clear) along with a mask, to determine
+  /// which features to change, if any.
+  ///
+  /// Default implementation will simply throw an exception to indicate that
+  /// the parser implementation does not support any FormatFeatures.
+  ///@param values Bit mask of set/clear state for features to change
+  ///@param mask Bit mask of features to change
+  ///@return This parser, to allow call chaining
+  ///@since 2.6
+  JsonParser overrideFormatFeatures(
+    int values,
+    int mask,
+  ) {
+    return const $JsonParserType()
+        .fromRef(_overrideFormatFeatures(reference, values, mask).object);
+  }
+
+  static final _nextToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__nextToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonToken nextToken()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Main iteration method, which will advance stream enough
+  /// to determine type of the next token, if any. If none
+  /// remaining (stream has no content other than possible
+  /// white space before ending), null will be returned.
+  ///@return Next token from the stream, if any found, or null
+  ///   to indicate end-of-input
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jsontoken_.JsonToken nextToken() {
+    return const jsontoken_.$JsonTokenType()
+        .fromRef(_nextToken(reference).object);
+  }
+
+  static final _nextValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__nextValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonToken nextValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Iteration method that will advance stream enough
+  /// to determine type of the next token that is a value type
+  /// (including JSON Array and Object start/end markers).
+  /// Or put another way, nextToken() will be called once,
+  /// and if JsonToken\#FIELD_NAME is returned, another
+  /// time to get the value for the field.
+  /// Method is most useful for iterating over value entries
+  /// of JSON objects; field name will still be available
+  /// by calling \#getCurrentName when parser points to
+  /// the value.
+  ///@return Next non-field-name token from the stream, if any found,
+  ///   or null to indicate end-of-input (or, for non-blocking
+  ///   parsers, JsonToken\#NOT_AVAILABLE if no tokens were
+  ///   available yet)
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jsontoken_.JsonToken nextValue() {
+    return const jsontoken_.$JsonTokenType()
+        .fromRef(_nextValue(reference).object);
+  }
+
+  static final _nextFieldName = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__nextFieldName")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean nextFieldName(com.fasterxml.jackson.core.SerializableString str)
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// verifies whether it is JsonToken\#FIELD_NAME with specified name
+  /// and returns result of that comparison.
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  return (nextToken() == JsonToken.FIELD_NAME) &amp;&amp; str.getValue().equals(getCurrentName());
+  ///</pre>
+  /// but may be faster for parser to verify, and can therefore be used if caller
+  /// expects to get such a property name from input next.
+  ///@param str Property name to compare next token to (if next token is
+  ///   <code>JsonToken.FIELD_NAME</code>)
+  ///@return {@code True} if parser advanced to {@code JsonToken.FIELD_NAME} with
+  ///    specified name; {@code false} otherwise (different token or non-matching name)
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  bool nextFieldName(
+    jni.JObject str,
+  ) {
+    return _nextFieldName(reference, str.reference).boolean;
+  }
+
+  static final _nextFieldName1 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__nextFieldName1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String nextFieldName()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// verifies whether it is JsonToken\#FIELD_NAME; if it is,
+  /// returns same as \#getCurrentName(), otherwise null.
+  ///@return Name of the the {@code JsonToken.FIELD_NAME} parser advanced to, if any;
+  ///   {@code null} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.5
+  jni.JString nextFieldName1() {
+    return const jni.JStringType().fromRef(_nextFieldName1(reference).object);
+  }
+
+  static final _nextTextValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__nextTextValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String nextTextValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// if it is JsonToken\#VALUE_STRING returns contained String value;
+  /// otherwise returns null.
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
+  ///</pre>
+  /// but may be faster for parser to process, and can therefore be used if caller
+  /// expects to get a String value next from input.
+  ///@return Text value of the {@code JsonToken.VALUE_STRING} token parser advanced
+  ///   to; or {@code null} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JString nextTextValue() {
+    return const jni.JStringType().fromRef(_nextTextValue(reference).object);
+  }
+
+  static final _nextIntValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("JsonParser__nextIntValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public int nextIntValue(int defaultValue)
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// if it is JsonToken\#VALUE_NUMBER_INT returns 32-bit int value;
+  /// otherwise returns specified default value
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getIntValue() : defaultValue;
+  ///</pre>
+  /// but may be faster for parser to process, and can therefore be used if caller
+  /// expects to get an int value next from input.
+  ///
+  /// NOTE: value checks are performed similar to \#getIntValue()
+  ///@param defaultValue Value to return if next token is NOT of type {@code JsonToken.VALUE_NUMBER_INT}
+  ///@return Integer ({@code int}) value of the {@code JsonToken.VALUE_NUMBER_INT} token parser advanced
+  ///   to; or {@code defaultValue} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@throws InputCoercionException if integer number does not fit in Java {@code int}
+  int nextIntValue(
+    int defaultValue,
+  ) {
+    return _nextIntValue(reference, defaultValue).integer;
+  }
+
+  static final _nextLongValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int64)>>("JsonParser__nextLongValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public long nextLongValue(long defaultValue)
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// if it is JsonToken\#VALUE_NUMBER_INT returns 64-bit long value;
+  /// otherwise returns specified default value
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getLongValue() : defaultValue;
+  ///</pre>
+  /// but may be faster for parser to process, and can therefore be used if caller
+  /// expects to get a long value next from input.
+  ///
+  /// NOTE: value checks are performed similar to \#getLongValue()
+  ///@param defaultValue Value to return if next token is NOT of type {@code JsonToken.VALUE_NUMBER_INT}
+  ///@return {@code long} value of the {@code JsonToken.VALUE_NUMBER_INT} token parser advanced
+  ///   to; or {@code defaultValue} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@throws InputCoercionException if integer number does not fit in Java {@code long}
+  int nextLongValue(
+    int defaultValue,
+  ) {
+    return _nextLongValue(reference, defaultValue).long;
+  }
+
+  static final _nextBooleanValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__nextBooleanValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Boolean nextBooleanValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// if it is JsonToken\#VALUE_TRUE or JsonToken\#VALUE_FALSE
+  /// returns matching Boolean value; otherwise return null.
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  JsonToken t = nextToken();
+  ///  if (t == JsonToken.VALUE_TRUE) return Boolean.TRUE;
+  ///  if (t == JsonToken.VALUE_FALSE) return Boolean.FALSE;
+  ///  return null;
+  ///</pre>
+  /// but may be faster for parser to process, and can therefore be used if caller
+  /// expects to get a Boolean value next from input.
+  ///@return {@code Boolean} value of the {@code JsonToken.VALUE_TRUE} or {@code JsonToken.VALUE_FALSE}
+  ///   token parser advanced to; or {@code null} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JBoolean nextBooleanValue() {
+    return const jni.JBooleanType()
+        .fromRef(_nextBooleanValue(reference).object);
+  }
+
+  static final _skipChildren = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__skipChildren")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonParser skipChildren()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that will skip all child tokens of an array or
+  /// object token that the parser currently points to,
+  /// iff stream points to
+  /// JsonToken\#START_OBJECT or JsonToken\#START_ARRAY.
+  /// If not, it will do nothing.
+  /// After skipping, stream will point to __matching__
+  /// JsonToken\#END_OBJECT or JsonToken\#END_ARRAY
+  /// (possibly skipping nested pairs of START/END OBJECT/ARRAY tokens
+  /// as well as value tokens).
+  /// The idea is that after calling this method, application
+  /// will call \#nextToken to point to the next
+  /// available token, if any.
+  ///@return This parser, to allow call chaining
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  JsonParser skipChildren() {
+    return const $JsonParserType().fromRef(_skipChildren(reference).object);
+  }
+
+  static final _finishToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__finishToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void finishToken()
+  ///
+  /// Method that may be used to force full handling of the current token
+  /// so that even if lazy processing is enabled, the whole contents are
+  /// read for possible retrieval. This is usually used to ensure that
+  /// the token end location is available, as well as token contents
+  /// (similar to what calling, say \#getTextCharacters(), would
+  /// achieve).
+  ///
+  /// Note that for many dataformat implementations this method
+  /// will not do anything; this is the default implementation unless
+  /// overridden by sub-classes.
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.8
+  void finishToken() {
+    return _finishToken(reference).check();
+  }
+
+  static final _currentToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__currentToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.fasterxml.jackson.core.JsonToken currentToken()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor to find which token parser currently points to, if any;
+  /// null will be returned if none.
+  /// If return value is non-null, data associated with the token
+  /// is available via other accessor methods.
+  ///@return Type of the token this parser currently points to,
+  ///   if any: null before any tokens have been read, and
+  ///   after end-of-input has been encountered, as well as
+  ///   if the current token has been explicitly cleared.
+  ///@since 2.8
+  jsontoken_.JsonToken currentToken() {
+    return const jsontoken_.$JsonTokenType()
+        .fromRef(_currentToken(reference).object);
+  }
+
+  static final _currentTokenId = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__currentTokenId")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int currentTokenId()
+  ///
+  /// Method similar to \#getCurrentToken() but that returns an
+  /// <code>int</code> instead of JsonToken (enum value).
+  ///
+  /// Use of int directly is typically more efficient on switch statements,
+  /// so this method may be useful when building low-overhead codecs.
+  /// Note, however, that effect may not be big enough to matter: make sure
+  /// to profile performance before deciding to use this method.
+  ///@since 2.8
+  ///@return {@code int} matching one of constants from JsonTokenId.
+  int currentTokenId() {
+    return _currentTokenId(reference).integer;
+  }
+
+  static final _getCurrentToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getCurrentToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonToken getCurrentToken()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias for \#currentToken(), may be deprecated sometime after
+  /// Jackson 2.13 (will be removed from 3.0).
+  ///@return Type of the token this parser currently points to,
+  ///   if any: null before any tokens have been read, and
+  jsontoken_.JsonToken getCurrentToken() {
+    return const jsontoken_.$JsonTokenType()
+        .fromRef(_getCurrentToken(reference).object);
+  }
+
+  static final _getCurrentTokenId = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getCurrentTokenId")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract int getCurrentTokenId()
+  ///
+  /// Deprecated alias for \#currentTokenId().
+  ///@return {@code int} matching one of constants from JsonTokenId.
+  ///@deprecated Since 2.12 use \#currentTokenId instead
+  int getCurrentTokenId() {
+    return _getCurrentTokenId(reference).integer;
+  }
+
+  static final _hasCurrentToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__hasCurrentToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract boolean hasCurrentToken()
+  ///
+  /// Method for checking whether parser currently points to
+  /// a token (and data for that token is available).
+  /// Equivalent to check for <code>parser.getCurrentToken() != null</code>.
+  ///@return True if the parser just returned a valid
+  ///   token via \#nextToken; false otherwise (parser
+  ///   was just constructed, encountered end-of-input
+  ///   and returned null from \#nextToken, or the token
+  ///   has been consumed)
+  bool hasCurrentToken() {
+    return _hasCurrentToken(reference).boolean;
+  }
+
+  static final _hasTokenId = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>, ffi.Int32)>>("JsonParser__hasTokenId")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public abstract boolean hasTokenId(int id)
+  ///
+  /// Method that is functionally equivalent to:
+  ///<code>
+  ///  return currentTokenId() == id
+  ///</code>
+  /// but may be more efficiently implemented.
+  ///
+  /// Note that no traversal or conversion is performed; so in some
+  /// cases calling method like \#isExpectedStartArrayToken()
+  /// is necessary instead.
+  ///@param id Token id to match (from (@link JsonTokenId})
+  ///@return {@code True} if the parser current points to specified token
+  ///@since 2.5
+  bool hasTokenId(
+    int id,
+  ) {
+    return _hasTokenId(reference, id).boolean;
+  }
+
+  static final _hasToken = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__hasToken")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract boolean hasToken(com.fasterxml.jackson.core.JsonToken t)
+  ///
+  /// Method that is functionally equivalent to:
+  ///<code>
+  ///  return currentToken() == t
+  ///</code>
+  /// but may be more efficiently implemented.
+  ///
+  /// Note that no traversal or conversion is performed; so in some
+  /// cases calling method like \#isExpectedStartArrayToken()
+  /// is necessary instead.
+  ///@param t Token to match
+  ///@return {@code True} if the parser current points to specified token
+  ///@since 2.6
+  bool hasToken(
+    jsontoken_.JsonToken t,
+  ) {
+    return _hasToken(reference, t.reference).boolean;
+  }
+
+  static final _isExpectedStartArrayToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__isExpectedStartArrayToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isExpectedStartArrayToken()
+  ///
+  /// Specialized accessor that can be used to verify that the current
+  /// token indicates start array (usually meaning that current token
+  /// is JsonToken\#START_ARRAY) when start array is expected.
+  /// For some specialized parsers this can return true for other cases
+  /// as well; this is usually done to emulate arrays in cases underlying
+  /// format is ambiguous (XML, for example, has no format-level difference
+  /// between Objects and Arrays; it just has elements).
+  ///
+  /// Default implementation is equivalent to:
+  ///<pre>
+  ///   currentToken() == JsonToken.START_ARRAY
+  ///</pre>
+  /// but may be overridden by custom parser implementations.
+  ///@return True if the current token can be considered as a
+  ///   start-array marker (such JsonToken\#START_ARRAY);
+  ///   {@code false} if not
+  bool isExpectedStartArrayToken() {
+    return _isExpectedStartArrayToken(reference).boolean;
+  }
+
+  static final _isExpectedStartObjectToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__isExpectedStartObjectToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isExpectedStartObjectToken()
+  ///
+  /// Similar to \#isExpectedStartArrayToken(), but checks whether stream
+  /// currently points to JsonToken\#START_OBJECT.
+  ///@return True if the current token can be considered as a
+  ///   start-array marker (such JsonToken\#START_OBJECT);
+  ///   {@code false} if not
+  ///@since 2.5
+  bool isExpectedStartObjectToken() {
+    return _isExpectedStartObjectToken(reference).boolean;
+  }
+
+  static final _isExpectedNumberIntToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__isExpectedNumberIntToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isExpectedNumberIntToken()
+  ///
+  /// Similar to \#isExpectedStartArrayToken(), but checks whether stream
+  /// currently points to JsonToken\#VALUE_NUMBER_INT.
+  ///
+  /// The initial use case is for XML backend to efficiently (attempt to) coerce
+  /// textual content into numbers.
+  ///@return True if the current token can be considered as a
+  ///   start-array marker (such JsonToken\#VALUE_NUMBER_INT);
+  ///   {@code false} if not
+  ///@since 2.12
+  bool isExpectedNumberIntToken() {
+    return _isExpectedNumberIntToken(reference).boolean;
+  }
+
+  static final _isNaN = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__isNaN")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean isNaN()
+  ///
+  /// Access for checking whether current token is a numeric value token, but
+  /// one that is of "not-a-number" (NaN) variety (including both "NaN" AND
+  /// positive/negative infinity!): not supported by all formats,
+  /// but often supported for JsonToken\#VALUE_NUMBER_FLOAT.
+  /// NOTE: roughly equivalent to calling <code>!Double.isFinite()</code>
+  /// on value you would get from calling \#getDoubleValue().
+  ///@return {@code True} if the current token is of type JsonToken\#VALUE_NUMBER_FLOAT
+  ///   but represents a "Not a Number"; {@code false} for other tokens and regular
+  ///   floating-point numbers
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.9
+  bool isNaN() {
+    return _isNaN(reference).boolean;
+  }
+
+  static final _clearCurrentToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__clearCurrentToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract void clearCurrentToken()
+  ///
+  /// Method called to "consume" the current token by effectively
+  /// removing it so that \#hasCurrentToken returns false, and
+  /// \#getCurrentToken null).
+  /// Cleared token value can still be accessed by calling
+  /// \#getLastClearedToken (if absolutely needed), but
+  /// usually isn't.
+  ///
+  /// Method was added to be used by the optional data binder, since
+  /// it has to be able to consume last token used for binding (so that
+  /// it will not be used again).
+  void clearCurrentToken() {
+    return _clearCurrentToken(reference).check();
+  }
+
+  static final _getLastClearedToken = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getLastClearedToken")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonToken getLastClearedToken()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be called to get the last token that was
+  /// cleared using \#clearCurrentToken. This is not necessarily
+  /// the latest token read.
+  /// Will return null if no tokens have been cleared,
+  /// or if parser has been closed.
+  ///@return Last cleared token, if any; {@code null} otherwise
+  jsontoken_.JsonToken getLastClearedToken() {
+    return const jsontoken_.$JsonTokenType()
+        .fromRef(_getLastClearedToken(reference).object);
+  }
+
+  static final _overrideCurrentName = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__overrideCurrentName")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract void overrideCurrentName(java.lang.String name)
+  ///
+  /// Method that can be used to change what is considered to be
+  /// the current (field) name.
+  /// May be needed to support non-JSON data formats or unusual binding
+  /// conventions; not needed for typical processing.
+  ///
+  /// Note that use of this method should only be done as sort of last
+  /// resort, as it is a work-around for regular operation.
+  ///@param name Name to use as the current name; may be null.
+  void overrideCurrentName(
+    jni.JString name,
+  ) {
+    return _overrideCurrentName(reference, name.reference).check();
+  }
+
+  static final _getCurrentName = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getCurrentName")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract java.lang.String getCurrentName()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias of \#currentName().
+  ///@return Name of the current field in the parsing context
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JString getCurrentName() {
+    return const jni.JStringType().fromRef(_getCurrentName(reference).object);
+  }
+
+  static final _currentName = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__currentName")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String currentName()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be called to get the name associated with
+  /// the current token: for JsonToken\#FIELD_NAMEs it will
+  /// be the same as what \#getText returns;
+  /// for field values it will be preceding field name;
+  /// and for others (array values, root-level values) null.
+  ///@return Name of the current field in the parsing context
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.10
+  jni.JString currentName() {
+    return const jni.JStringType().fromRef(_currentName(reference).object);
+  }
+
+  static final _getText = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getText")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract java.lang.String getText()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for accessing textual representation of the current token;
+  /// if no current token (before first call to \#nextToken, or
+  /// after encountering end-of-input), returns null.
+  /// Method can be called for any token type.
+  ///@return Textual value associated with the current token (one returned
+  ///   by \#nextToken() or other iteration methods)
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JString getText() {
+    return const jni.JStringType().fromRef(_getText(reference).object);
+  }
+
+  static final _getText1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__getText1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getText(java.io.Writer writer)
+  ///
+  /// Method to read the textual representation of the current token in chunks and
+  /// pass it to the given Writer.
+  /// Conceptually same as calling:
+  ///<pre>
+  ///  writer.write(parser.getText());
+  ///</pre>
+  /// but should typically be more efficient as longer content does need to
+  /// be combined into a single <code>String</code> to return, and write
+  /// can occur directly from intermediate buffers Jackson uses.
+  ///@param writer Writer to write textual content to
+  ///@return The number of characters written to the Writer
+  ///@throws IOException for low-level read issues or writes using passed
+  ///   {@code writer}, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.8
+  int getText1(
+    jni.JObject writer,
+  ) {
+    return _getText1(reference, writer.reference).integer;
+  }
+
+  static final _getTextCharacters = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getTextCharacters")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract char[] getTextCharacters()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method similar to \#getText, but that will return
+  /// underlying (unmodifiable) character array that contains
+  /// textual value, instead of constructing a String object
+  /// to contain this information.
+  /// Note, however, that:
+  ///<ul>
+  /// <li>Textual contents are not guaranteed to start at
+  ///   index 0 (rather, call \#getTextOffset) to
+  ///   know the actual offset
+  ///  </li>
+  /// <li>Length of textual contents may be less than the
+  ///  length of returned buffer: call \#getTextLength
+  ///  for actual length of returned content.
+  ///  </li>
+  /// </ul>
+  ///
+  /// Note that caller __MUST NOT__ modify the returned
+  /// character array in any way -- doing so may corrupt
+  /// current parser state and render parser instance useless.
+  ///
+  /// The only reason to call this method (over \#getText)
+  /// is to avoid construction of a String object (which
+  /// will make a copy of contents).
+  ///@return Buffer that contains the current textual value (but not necessarily
+  ///    at offset 0, and not necessarily until the end of buffer)
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JArray<jni.jchar> getTextCharacters() {
+    return const jni.JArrayType(jni.jcharType())
+        .fromRef(_getTextCharacters(reference).object);
+  }
+
+  static final _getTextLength = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getTextLength")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract int getTextLength()
+  ///
+  /// Accessor used with \#getTextCharacters, to know length
+  /// of String stored in returned buffer.
+  ///@return Number of characters within buffer returned
+  ///   by \#getTextCharacters that are part of
+  ///   textual content of the current token.
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getTextLength() {
+    return _getTextLength(reference).integer;
+  }
+
+  static final _getTextOffset = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getTextOffset")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract int getTextOffset()
+  ///
+  /// Accessor used with \#getTextCharacters, to know offset
+  /// of the first text content character within buffer.
+  ///@return Offset of the first character within buffer returned
+  ///   by \#getTextCharacters that is part of
+  ///   textual content of the current token.
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getTextOffset() {
+    return _getTextOffset(reference).integer;
+  }
+
+  static final _hasTextCharacters = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__hasTextCharacters")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract boolean hasTextCharacters()
+  ///
+  /// Method that can be used to determine whether calling of
+  /// \#getTextCharacters would be the most efficient
+  /// way to access textual content for the event parser currently
+  /// points to.
+  ///
+  /// Default implementation simply returns false since only actual
+  /// implementation class has knowledge of its internal buffering
+  /// state.
+  /// Implementations are strongly encouraged to properly override
+  /// this method, to allow efficient copying of content by other
+  /// code.
+  ///@return True if parser currently has character array that can
+  ///   be efficiently returned via \#getTextCharacters; false
+  ///   means that it may or may not exist
+  bool hasTextCharacters() {
+    return _hasTextCharacters(reference).boolean;
+  }
+
+  static final _getNumberValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getNumberValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract java.lang.Number getNumberValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Generic number value accessor method that will work for
+  /// all kinds of numeric values. It will return the optimal
+  /// (simplest/smallest possible) wrapper object that can
+  /// express the numeric value just parsed.
+  ///@return Numeric value of the current token in its most optimal
+  ///   representation
+  ///@throws IOException Problem with access: JsonParseException if
+  ///    the current token is not numeric, or if decoding of the value fails
+  ///    (invalid format for numbers); plain IOException if underlying
+  ///    content read fails (possible if values are extracted lazily)
+  jni.JNumber getNumberValue() {
+    return const jni.JNumberType().fromRef(_getNumberValue(reference).object);
+  }
+
+  static final _getNumberValueExact = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getNumberValueExact")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Number getNumberValueExact()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method similar to \#getNumberValue with the difference that
+  /// for floating-point numbers value returned may be BigDecimal
+  /// if the underlying format does not store floating-point numbers using
+  /// native representation: for example, textual formats represent numbers
+  /// as Strings (which are 10-based), and conversion to java.lang.Double
+  /// is potentially lossy operation.
+  ///
+  /// Default implementation simply returns \#getNumberValue()
+  ///@return Numeric value of the current token using most accurate representation
+  ///@throws IOException Problem with access: JsonParseException if
+  ///    the current token is not numeric, or if decoding of the value fails
+  ///    (invalid format for numbers); plain IOException if underlying
+  ///    content read fails (possible if values are extracted lazily)
+  ///@since 2.12
+  jni.JNumber getNumberValueExact() {
+    return const jni.JNumberType()
+        .fromRef(_getNumberValueExact(reference).object);
+  }
+
+  static final _getNumberType = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getNumberType")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonParser.NumberType getNumberType()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// If current token is of type
+  /// JsonToken\#VALUE_NUMBER_INT or
+  /// JsonToken\#VALUE_NUMBER_FLOAT, returns
+  /// one of NumberType constants; otherwise returns null.
+  ///@return Type of current number, if parser points to numeric token; {@code null} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  JsonParser_NumberType getNumberType() {
+    return const $JsonParser_NumberTypeType()
+        .fromRef(_getNumberType(reference).object);
+  }
+
+  static final _getByteValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getByteValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public byte getByteValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can be expressed as a value of Java byte primitive type.
+  /// Note that in addition to "natural" input range of {@code [-128, 127]},
+  /// this also allows "unsigned 8-bit byte" values {@code [128, 255]}:
+  /// but for this range value will be translated by truncation, leading
+  /// to sign change.
+  ///
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDoubleValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the resulting integer value falls outside range of
+  /// {@code [-128, 255]},
+  /// a InputCoercionException
+  /// will be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code byte} (if numeric token within
+  ///   range of {@code [-128, 255]}); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getByteValue() {
+    return _getByteValue(reference).byte;
+  }
+
+  static final _getShortValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getShortValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public short getShortValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can be expressed as a value of Java short primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDoubleValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the resulting integer value falls outside range of
+  /// Java short, a InputCoercionException
+  /// will be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code short} (if numeric token within
+  ///   Java 16-bit signed {@code short} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getShortValue() {
+    return _getShortValue(reference).short;
+  }
+
+  static final _getIntValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getIntValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract int getIntValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can be expressed as a value of Java int primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDoubleValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the resulting integer value falls outside range of
+  /// Java {@code int}, a InputCoercionException
+  /// may be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code int} (if numeric token within
+  ///   Java 32-bit signed {@code int} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getIntValue() {
+    return _getIntValue(reference).integer;
+  }
+
+  static final _getLongValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getLongValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract long getLongValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can be expressed as a Java long primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDoubleValue
+  /// and then casting to int; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the token is an integer, but its value falls
+  /// outside of range of Java long, a InputCoercionException
+  /// may be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code long} (if numeric token within
+  ///   Java 32-bit signed {@code long} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getLongValue() {
+    return _getLongValue(reference).long;
+  }
+
+  static final _getBigIntegerValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getBigIntegerValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract java.math.BigInteger getBigIntegerValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can not be used as a Java long primitive type due to its
+  /// magnitude.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDecimalValue
+  /// and then constructing a BigInteger from that value.
+  ///@return Current number value as BigInteger (if numeric token);
+  ///     otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JObject getBigIntegerValue() {
+    return const jni.JObjectType()
+        .fromRef(_getBigIntegerValue(reference).object);
+  }
+
+  static final _getFloatValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getFloatValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract float getFloatValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_FLOAT and
+  /// it can be expressed as a Java float primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_INT;
+  /// if so, it is equivalent to calling \#getLongValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the value falls
+  /// outside of range of Java float, a InputCoercionException
+  /// will be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code float} (if numeric token within
+  ///   Java {@code float} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  double getFloatValue() {
+    return _getFloatValue(reference).float;
+  }
+
+  static final _getDoubleValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getDoubleValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract double getDoubleValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_FLOAT and
+  /// it can be expressed as a Java double primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_INT;
+  /// if so, it is equivalent to calling \#getLongValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the value falls
+  /// outside of range of Java double, a InputCoercionException
+  /// will be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code double} (if numeric token within
+  ///   Java {@code double} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  double getDoubleValue() {
+    return _getDoubleValue(reference).doubleFloat;
+  }
+
+  static final _getDecimalValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getDecimalValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract java.math.BigDecimal getDecimalValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_FLOAT or
+  /// JsonToken\#VALUE_NUMBER_INT. No under/overflow exceptions
+  /// are ever thrown.
+  ///@return Current number value as BigDecimal (if numeric token);
+  ///   otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JObject getDecimalValue() {
+    return const jni.JObjectType().fromRef(_getDecimalValue(reference).object);
+  }
+
+  static final _getBooleanValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getBooleanValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean getBooleanValue()
+  ///
+  /// Convenience accessor that can be called when the current
+  /// token is JsonToken\#VALUE_TRUE or
+  /// JsonToken\#VALUE_FALSE, to return matching {@code boolean}
+  /// value.
+  /// If the current token is of some other type, JsonParseException
+  /// will be thrown
+  ///@return {@code True} if current token is {@code JsonToken.VALUE_TRUE},
+  ///   {@code false} if current token is {@code JsonToken.VALUE_FALSE};
+  ///   otherwise throws JsonParseException
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  bool getBooleanValue() {
+    return _getBooleanValue(reference).boolean;
+  }
+
+  static final _getEmbeddedObject = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getEmbeddedObject")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object getEmbeddedObject()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor that can be called if (and only if) the current token
+  /// is JsonToken\#VALUE_EMBEDDED_OBJECT. For other token types,
+  /// null is returned.
+  ///
+  /// Note: only some specialized parser implementations support
+  /// embedding of objects (usually ones that are facades on top
+  /// of non-streaming sources, such as object trees). One exception
+  /// is access to binary content (whether via base64 encoding or not)
+  /// which typically is accessible using this method, as well as
+  /// \#getBinaryValue().
+  ///@return Embedded value (usually of "native" type supported by format)
+  ///   for the current token, if any; {@code null otherwise}
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JObject getEmbeddedObject() {
+    return const jni.JObjectType()
+        .fromRef(_getEmbeddedObject(reference).object);
+  }
+
+  static final _getBinaryValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__getBinaryValue")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract byte[] getBinaryValue(com.fasterxml.jackson.core.Base64Variant bv)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be used to read (and consume -- results
+  /// may not be accessible using other methods after the call)
+  /// base64-encoded binary data
+  /// included in the current textual JSON value.
+  /// It works similar to getting String value via \#getText
+  /// and decoding result (except for decoding part),
+  /// but should be significantly more performant.
+  ///
+  /// Note that non-decoded textual contents of the current token
+  /// are not guaranteed to be accessible after this method
+  /// is called. Current implementation, for example, clears up
+  /// textual content during decoding.
+  /// Decoded binary content, however, will be retained until
+  /// parser is advanced to the next event.
+  ///@param bv Expected variant of base64 encoded
+  ///   content (see Base64Variants for definitions
+  ///   of "standard" variants).
+  ///@return Decoded binary data
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JArray<jni.jbyte> getBinaryValue(
+    jni.JObject bv,
+  ) {
+    return const jni.JArrayType(jni.jbyteType())
+        .fromRef(_getBinaryValue(reference, bv.reference).object);
+  }
+
+  static final _getBinaryValue1 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getBinaryValue1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public byte[] getBinaryValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Convenience alternative to \#getBinaryValue(Base64Variant)
+  /// that defaults to using
+  /// Base64Variants\#getDefaultVariant as the default encoding.
+  ///@return Decoded binary data
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JArray<jni.jbyte> getBinaryValue1() {
+    return const jni.JArrayType(jni.jbyteType())
+        .fromRef(_getBinaryValue1(reference).object);
+  }
+
+  static final _readBinaryValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__readBinaryValue")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int readBinaryValue(java.io.OutputStream out)
+  ///
+  /// Method that can be used as an alternative to \#getBigIntegerValue(),
+  /// especially when value can be large. The main difference (beyond method
+  /// of returning content using OutputStream instead of as byte array)
+  /// is that content will NOT remain accessible after method returns: any content
+  /// processed will be consumed and is not buffered in any way. If caller needs
+  /// buffering, it has to implement it.
+  ///@param out Output stream to use for passing decoded binary data
+  ///@return Number of bytes that were decoded and written via OutputStream
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.1
+  int readBinaryValue(
+    jni.JObject out,
+  ) {
+    return _readBinaryValue(reference, out.reference).integer;
+  }
+
+  static final _readBinaryValue1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__readBinaryValue1")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int readBinaryValue(com.fasterxml.jackson.core.Base64Variant bv, java.io.OutputStream out)
+  ///
+  /// Similar to \#readBinaryValue(OutputStream) but allows explicitly
+  /// specifying base64 variant to use.
+  ///@param bv base64 variant to use
+  ///@param out Output stream to use for passing decoded binary data
+  ///@return Number of bytes that were decoded and written via OutputStream
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.1
+  int readBinaryValue1(
+    jni.JObject bv,
+    jni.JObject out,
+  ) {
+    return _readBinaryValue1(reference, bv.reference, out.reference).integer;
+  }
+
+  static final _getValueAsInt = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getValueAsInt")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getValueAsInt()
+  ///
+  /// Method that will try to convert value of current token to a
+  /// Java {@code int} value.
+  /// Numbers are coerced using default Java rules; booleans convert to 0 (false)
+  /// and 1 (true), and Strings are parsed using default Java language integer
+  /// parsing rules.
+  ///
+  /// If representation can not be converted to an int (including structured type
+  /// markers like start/end Object/Array)
+  /// default value of __0__ will be returned; no exceptions are thrown.
+  ///@return {@code int} value current token is converted to, if possible; exception thrown
+  ///    otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getValueAsInt() {
+    return _getValueAsInt(reference).integer;
+  }
+
+  static final _getValueAsInt1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("JsonParser__getValueAsInt1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public int getValueAsInt(int def)
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __int__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0 (false)
+  /// and 1 (true), and Strings are parsed using default Java language integer
+  /// parsing rules.
+  ///
+  /// If representation can not be converted to an int (including structured type
+  /// markers like start/end Object/Array)
+  /// specified __def__ will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code int} is not possible
+  ///@return {@code int} value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getValueAsInt1(
+    int def,
+  ) {
+    return _getValueAsInt1(reference, def).integer;
+  }
+
+  static final _getValueAsLong = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getValueAsLong")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public long getValueAsLong()
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __long__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0 (false)
+  /// and 1 (true), and Strings are parsed using default Java language integer
+  /// parsing rules.
+  ///
+  /// If representation can not be converted to a long (including structured type
+  /// markers like start/end Object/Array)
+  /// default value of __0L__ will be returned; no exceptions are thrown.
+  ///@return {@code long} value current token is converted to, if possible; exception thrown
+  ///    otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getValueAsLong() {
+    return _getValueAsLong(reference).long;
+  }
+
+  static final _getValueAsLong1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int64)>>("JsonParser__getValueAsLong1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public long getValueAsLong(long def)
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __long__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0 (false)
+  /// and 1 (true), and Strings are parsed using default Java language integer
+  /// parsing rules.
+  ///
+  /// If representation can not be converted to a long (including structured type
+  /// markers like start/end Object/Array)
+  /// specified __def__ will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code long} is not possible
+  ///@return {@code long} value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getValueAsLong1(
+    int def,
+  ) {
+    return _getValueAsLong1(reference, def).long;
+  }
+
+  static final _getValueAsDouble = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getValueAsDouble")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public double getValueAsDouble()
+  ///
+  /// Method that will try to convert value of current token to a Java
+  /// __double__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0.0 (false)
+  /// and 1.0 (true), and Strings are parsed using default Java language floating
+  /// point parsing rules.
+  ///
+  /// If representation can not be converted to a double (including structured types
+  /// like Objects and Arrays),
+  /// default value of __0.0__ will be returned; no exceptions are thrown.
+  ///@return {@code double} value current token is converted to, if possible; exception thrown
+  ///    otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  double getValueAsDouble() {
+    return _getValueAsDouble(reference).doubleFloat;
+  }
+
+  static final _getValueAsDouble1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Double)>>("JsonParser__getValueAsDouble1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, double)>();
+
+  /// from: public double getValueAsDouble(double def)
+  ///
+  /// Method that will try to convert value of current token to a
+  /// Java __double__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0.0 (false)
+  /// and 1.0 (true), and Strings are parsed using default Java language floating
+  /// point parsing rules.
+  ///
+  /// If representation can not be converted to a double (including structured types
+  /// like Objects and Arrays),
+  /// specified __def__ will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code double} is not possible
+  ///@return {@code double} value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  double getValueAsDouble1(
+    double def,
+  ) {
+    return _getValueAsDouble1(reference, def).doubleFloat;
+  }
+
+  static final _getValueAsBoolean = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getValueAsBoolean")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean getValueAsBoolean()
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __boolean__.
+  /// JSON booleans map naturally; integer numbers other than 0 map to true, and
+  /// 0 maps to false
+  /// and Strings 'true' and 'false' map to corresponding values.
+  ///
+  /// If representation can not be converted to a boolean value (including structured types
+  /// like Objects and Arrays),
+  /// default value of __false__ will be returned; no exceptions are thrown.
+  ///@return {@code boolean} value current token is converted to, if possible; exception thrown
+  ///    otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  bool getValueAsBoolean() {
+    return _getValueAsBoolean(reference).boolean;
+  }
+
+  static final _getValueAsBoolean1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Uint8)>>("JsonParser__getValueAsBoolean1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public boolean getValueAsBoolean(boolean def)
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __boolean__.
+  /// JSON booleans map naturally; integer numbers other than 0 map to true, and
+  /// 0 maps to false
+  /// and Strings 'true' and 'false' map to corresponding values.
+  ///
+  /// If representation can not be converted to a boolean value (including structured types
+  /// like Objects and Arrays),
+  /// specified __def__ will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code boolean} is not possible
+  ///@return {@code boolean} value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  bool getValueAsBoolean1(
+    bool def,
+  ) {
+    return _getValueAsBoolean1(reference, def ? 1 : 0).boolean;
+  }
+
+  static final _getValueAsString = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getValueAsString")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getValueAsString()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that will try to convert value of current token to a
+  /// java.lang.String.
+  /// JSON Strings map naturally; scalar values get converted to
+  /// their textual representation.
+  /// If representation can not be converted to a String value (including structured types
+  /// like Objects and Arrays and {@code null} token), default value of
+  /// __null__ will be returned; no exceptions are thrown.
+  ///@return String value current token is converted to, if possible; {@code null} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.1
+  jni.JString getValueAsString() {
+    return const jni.JStringType().fromRef(_getValueAsString(reference).object);
+  }
+
+  static final _getValueAsString1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__getValueAsString1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract java.lang.String getValueAsString(java.lang.String def)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that will try to convert value of current token to a
+  /// java.lang.String.
+  /// JSON Strings map naturally; scalar values get converted to
+  /// their textual representation.
+  /// If representation can not be converted to a String value (including structured types
+  /// like Objects and Arrays and {@code null} token), specified default value
+  /// will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code String} is not possible
+  ///@return String value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.1
+  jni.JString getValueAsString1(
+    jni.JString def,
+  ) {
+    return const jni.JStringType()
+        .fromRef(_getValueAsString1(reference, def.reference).object);
+  }
+
+  static final _canReadObjectId = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__canReadObjectId")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean canReadObjectId()
+  ///
+  /// Introspection method that may be called to see if the underlying
+  /// data format supports some kind of Object Ids natively (many do not;
+  /// for example, JSON doesn't).
+  ///
+  /// Default implementation returns true; overridden by data formats
+  /// that do support native Object Ids. Caller is expected to either
+  /// use a non-native notation (explicit property or such), or fail,
+  /// in case it can not use native object ids.
+  ///@return {@code True} if the format being read supports native Object Ids;
+  ///    {@code false} if not
+  ///@since 2.3
+  bool canReadObjectId() {
+    return _canReadObjectId(reference).boolean;
+  }
+
+  static final _canReadTypeId = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__canReadTypeId")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean canReadTypeId()
+  ///
+  /// Introspection method that may be called to see if the underlying
+  /// data format supports some kind of Type Ids natively (many do not;
+  /// for example, JSON doesn't).
+  ///
+  /// Default implementation returns true; overridden by data formats
+  /// that do support native Type Ids. Caller is expected to either
+  /// use a non-native notation (explicit property or such), or fail,
+  /// in case it can not use native type ids.
+  ///@return {@code True} if the format being read supports native Type Ids;
+  ///    {@code false} if not
+  ///@since 2.3
+  bool canReadTypeId() {
+    return _canReadTypeId(reference).boolean;
+  }
+
+  static final _getObjectId = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getObjectId")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object getObjectId()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be called to check whether current token
+  /// (one that was just read) has an associated Object id, and if
+  /// so, return it.
+  /// Note that while typically caller should check with \#canReadObjectId
+  /// first, it is not illegal to call this method even if that method returns
+  /// true; but if so, it will return null. This may be used to simplify calling
+  /// code.
+  ///
+  /// Default implementation will simply return null.
+  ///@return Native Object id associated with the current token, if any; {@code null} if none
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.3
+  jni.JObject getObjectId() {
+    return const jni.JObjectType().fromRef(_getObjectId(reference).object);
+  }
+
+  static final _getTypeId = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__getTypeId")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object getTypeId()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be called to check whether current token
+  /// (one that was just read) has an associated type id, and if
+  /// so, return it.
+  /// Note that while typically caller should check with \#canReadTypeId
+  /// first, it is not illegal to call this method even if that method returns
+  /// true; but if so, it will return null. This may be used to simplify calling
+  /// code.
+  ///
+  /// Default implementation will simply return null.
+  ///@return Native Type Id associated with the current token, if any; {@code null} if none
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.3
+  jni.JObject getTypeId() {
+    return const jni.JObjectType().fromRef(_getTypeId(reference).object);
+  }
+
+  static final _readValueAs = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__readValueAs")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public T readValueAs(java.lang.Class<T> valueType)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method to deserialize JSON content into a non-container
+  /// type (it can be an array type, however): typically a bean, array
+  /// or a wrapper type (like java.lang.Boolean).
+  /// __Note__: method can only be called if the parser has
+  /// an object codec assigned; this is true for parsers constructed
+  /// by <code>MappingJsonFactory</code> (from "jackson-databind" jar)
+  /// but not for JsonFactory (unless its <code>setCodec</code>
+  /// method has been explicitly called).
+  ///
+  /// This method may advance the event stream, for structured types
+  /// the current token will be the closing end marker (END_ARRAY,
+  /// END_OBJECT) of the bound structure. For non-structured Json types
+  /// (and for JsonToken\#VALUE_EMBEDDED_OBJECT)
+  /// stream is not advanced.
+  ///
+  /// Note: this method should NOT be used if the result type is a
+  /// container (java.util.Collection or java.util.Map.
+  /// The reason is that due to type erasure, key and value types
+  /// can not be introspected when using this method.
+  ///@param <T> Nominal type parameter for value type
+  ///@param valueType Java type to read content as (passed to ObjectCodec that
+  ///    deserializes content)
+  ///@return Java value read from content
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  $T readValueAs<$T extends jni.JObject>(
+    jni.JObject valueType, {
+    required jni.JObjType<$T> T,
+  }) {
+    return T.fromRef(_readValueAs(reference, valueType.reference).object);
+  }
+
+  static final _readValueAs1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__readValueAs1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public T readValueAs(com.fasterxml.jackson.core.type.TypeReference<?> valueTypeRef)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method to deserialize JSON content into a Java type, reference
+  /// to which is passed as argument. Type is passed using so-called
+  /// "super type token"
+  /// and specifically needs to be used if the root type is a
+  /// parameterized (generic) container type.
+  /// __Note__: method can only be called if the parser has
+  /// an object codec assigned; this is true for parsers constructed
+  /// by <code>MappingJsonFactory</code> (defined in 'jackson-databind' bundle)
+  /// but not for JsonFactory (unless its <code>setCodec</code>
+  /// method has been explicitly called).
+  ///
+  /// This method may advance the event stream, for structured types
+  /// the current token will be the closing end marker (END_ARRAY,
+  /// END_OBJECT) of the bound structure. For non-structured Json types
+  /// (and for JsonToken\#VALUE_EMBEDDED_OBJECT)
+  /// stream is not advanced.
+  ///@param <T> Nominal type parameter for value type
+  ///@param valueTypeRef Java type to read content as (passed to ObjectCodec that
+  ///    deserializes content)
+  ///@return Java value read from content
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  $T readValueAs1<$T extends jni.JObject>(
+    jni.JObject valueTypeRef, {
+    required jni.JObjType<$T> T,
+  }) {
+    return T.fromRef(_readValueAs1(reference, valueTypeRef.reference).object);
+  }
+
+  static final _readValuesAs = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__readValuesAs")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Iterator<T> readValuesAs(java.lang.Class<T> valueType)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for reading sequence of Objects from parser stream,
+  /// all with same specified value type.
+  ///@param <T> Nominal type parameter for value type
+  ///@param valueType Java type to read content as (passed to ObjectCodec that
+  ///    deserializes content)
+  ///@return Iterator for reading multiple Java values from content
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  jni.JIterator<$T> readValuesAs<$T extends jni.JObject>(
+    jni.JObject valueType, {
+    required jni.JObjType<$T> T,
+  }) {
+    return jni.JIteratorType(T)
+        .fromRef(_readValuesAs(reference, valueType.reference).object);
+  }
+
+  static final _readValuesAs1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("JsonParser__readValuesAs1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Iterator<T> readValuesAs(com.fasterxml.jackson.core.type.TypeReference<T> valueTypeRef)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for reading sequence of Objects from parser stream,
+  /// all with same specified value type.
+  ///@param <T> Nominal type parameter for value type
+  ///@param valueTypeRef Java type to read content as (passed to ObjectCodec that
+  ///    deserializes content)
+  ///@return Iterator for reading multiple Java values from content
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  jni.JIterator<$T> readValuesAs1<$T extends jni.JObject>(
+    jni.JObject valueTypeRef, {
+    required jni.JObjType<$T> T,
+  }) {
+    return jni.JIteratorType(T)
+        .fromRef(_readValuesAs1(reference, valueTypeRef.reference).object);
+  }
+
+  static final _readValueAsTree = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser__readValueAsTree")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public T readValueAsTree()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method to deserialize JSON content into equivalent "tree model",
+  /// represented by root TreeNode of resulting model.
+  /// For JSON Arrays it will an array node (with child nodes),
+  /// for objects object node (with child nodes), and for other types
+  /// matching leaf node type. Empty or whitespace documents are null.
+  ///@param <T> Nominal type parameter for result node type (to reduce need for casting)
+  ///@return root of the document, or null if empty or whitespace.
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  $T readValueAsTree<$T extends jni.JObject>({
+    required jni.JObjType<$T> T,
+  }) {
+    return T.fromRef(_readValueAsTree(reference).object);
+  }
+}
+
+final class $JsonParserType extends jni.JObjType<JsonParser> {
+  const $JsonParserType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonParser;";
+
+  @override
+  JsonParser fromRef(jni.JObjectPtr ref) => JsonParser.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonParserType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonParserType) && other is $JsonParserType;
+  }
+}
+
+/// from: com.fasterxml.jackson.core.JsonParser$Feature
+///
+/// Enumeration that defines all on/off features for parsers.
+class JsonParser_Feature extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonParser_Feature> $type = type;
+
+  JsonParser_Feature.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonParser_FeatureType();
+  static final _values =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "JsonParser_Feature__values")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.fasterxml.jackson.core.JsonParser.Feature[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonParser_Feature> values() {
+    return const jni.JArrayType($JsonParser_FeatureType())
+        .fromRef(_values().object);
+  }
+
+  static final _valueOf = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser_Feature__valueOf")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.fasterxml.jackson.core.JsonParser.Feature valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonParser_Feature valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonParser_FeatureType()
+        .fromRef(_valueOf(name.reference).object);
+  }
+
+  static final _collectDefaults =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "JsonParser_Feature__collectDefaults")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public int collectDefaults()
+  ///
+  /// Method that calculates bit set (flags) of all features that
+  /// are enabled by default.
+  ///@return Bit mask of all features that are enabled by default
+  static int collectDefaults() {
+    return _collectDefaults().integer;
+  }
+
+  static final _enabledByDefault = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser_Feature__enabledByDefault")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean enabledByDefault()
+  bool enabledByDefault() {
+    return _enabledByDefault(reference).boolean;
+  }
+
+  static final _enabledIn = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Int32)>>("JsonParser_Feature__enabledIn")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public boolean enabledIn(int flags)
+  bool enabledIn(
+    int flags,
+  ) {
+    return _enabledIn(reference, flags).boolean;
+  }
+
+  static final _getMask = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser_Feature__getMask")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getMask()
+  int getMask() {
+    return _getMask(reference).integer;
+  }
+}
+
+final class $JsonParser_FeatureType extends jni.JObjType<JsonParser_Feature> {
+  const $JsonParser_FeatureType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonParser$Feature;";
+
+  @override
+  JsonParser_Feature fromRef(jni.JObjectPtr ref) =>
+      JsonParser_Feature.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonParser_FeatureType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonParser_FeatureType) &&
+        other is $JsonParser_FeatureType;
+  }
+}
+
+/// from: com.fasterxml.jackson.core.JsonParser$NumberType
+///
+/// Enumeration of possible "native" (optimal) types that can be
+/// used for numbers.
+class JsonParser_NumberType extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonParser_NumberType> $type = type;
+
+  JsonParser_NumberType.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonParser_NumberTypeType();
+  static final _values =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "JsonParser_NumberType__values")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.fasterxml.jackson.core.JsonParser.NumberType[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonParser_NumberType> values() {
+    return const jni.JArrayType($JsonParser_NumberTypeType())
+        .fromRef(_values().object);
+  }
+
+  static final _valueOf = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonParser_NumberType__valueOf")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.fasterxml.jackson.core.JsonParser.NumberType valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonParser_NumberType valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonParser_NumberTypeType()
+        .fromRef(_valueOf(name.reference).object);
+  }
+}
+
+final class $JsonParser_NumberTypeType
+    extends jni.JObjType<JsonParser_NumberType> {
+  const $JsonParser_NumberTypeType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonParser$NumberType;";
+
+  @override
+  JsonParser_NumberType fromRef(jni.JObjectPtr ref) =>
+      JsonParser_NumberType.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonParser_NumberTypeType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonParser_NumberTypeType) &&
+        other is $JsonParser_NumberTypeType;
+  }
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
new file mode 100644
index 0000000..2d036c5
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
@@ -0,0 +1,238 @@
+// Generated from jackson-core which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+// See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+//
+// Copyright (c) 2007 - The Jackson Project Authors
+// 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+import "../../../../_init.dart";
+
+/// from: com.fasterxml.jackson.core.JsonToken
+///
+/// Enumeration for basic token types used for returning results
+/// of parsing JSON content.
+class JsonToken extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonToken> $type = type;
+
+  JsonToken.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonTokenType();
+  static final _values =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "JsonToken__values")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.fasterxml.jackson.core.JsonToken[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonToken> values() {
+    return const jni.JArrayType($JsonTokenType()).fromRef(_values().object);
+  }
+
+  static final _valueOf = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonToken__valueOf")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.fasterxml.jackson.core.JsonToken valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonToken valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonTokenType().fromRef(_valueOf(name.reference).object);
+  }
+
+  static final _id = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("JsonToken__id")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final int id()
+  int id() {
+    return _id(reference).integer;
+  }
+
+  static final _asString = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonToken__asString")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final java.lang.String asString()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString asString() {
+    return const jni.JStringType().fromRef(_asString(reference).object);
+  }
+
+  static final _asCharArray = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonToken__asCharArray")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final char[] asCharArray()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JArray<jni.jchar> asCharArray() {
+    return const jni.JArrayType(jni.jcharType())
+        .fromRef(_asCharArray(reference).object);
+  }
+
+  static final _asByteArray = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonToken__asByteArray")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final byte[] asByteArray()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JArray<jni.jbyte> asByteArray() {
+    return const jni.JArrayType(jni.jbyteType())
+        .fromRef(_asByteArray(reference).object);
+  }
+
+  static final _isNumeric = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonToken__isNumeric")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isNumeric()
+  ///
+  /// @return {@code True} if this token is {@code VALUE_NUMBER_INT} or {@code VALUE_NUMBER_FLOAT},
+  ///   {@code false} otherwise
+  bool isNumeric() {
+    return _isNumeric(reference).boolean;
+  }
+
+  static final _isStructStart = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonToken__isStructStart")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isStructStart()
+  ///
+  /// Accessor that is functionally equivalent to:
+  /// <code>
+  ///    this == JsonToken.START_OBJECT || this == JsonToken.START_ARRAY
+  /// </code>
+  ///@return {@code True} if this token is {@code START_OBJECT} or {@code START_ARRAY},
+  ///   {@code false} otherwise
+  ///@since 2.3
+  bool isStructStart() {
+    return _isStructStart(reference).boolean;
+  }
+
+  static final _isStructEnd = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonToken__isStructEnd")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isStructEnd()
+  ///
+  /// Accessor that is functionally equivalent to:
+  /// <code>
+  ///    this == JsonToken.END_OBJECT || this == JsonToken.END_ARRAY
+  /// </code>
+  ///@return {@code True} if this token is {@code END_OBJECT} or {@code END_ARRAY},
+  ///   {@code false} otherwise
+  ///@since 2.3
+  bool isStructEnd() {
+    return _isStructEnd(reference).boolean;
+  }
+
+  static final _isScalarValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonToken__isScalarValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isScalarValue()
+  ///
+  /// Method that can be used to check whether this token represents
+  /// a valid non-structured value. This means all {@code VALUE_xxx} tokens;
+  /// excluding {@code START_xxx} and {@code END_xxx} tokens as well
+  /// {@code FIELD_NAME}.
+  ///@return {@code True} if this token is a scalar value token (one of
+  ///   {@code VALUE_xxx} tokens), {@code false} otherwise
+  bool isScalarValue() {
+    return _isScalarValue(reference).boolean;
+  }
+
+  static final _isBoolean = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonToken__isBoolean")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final boolean isBoolean()
+  ///
+  /// @return {@code True} if this token is {@code VALUE_TRUE} or {@code VALUE_FALSE},
+  ///   {@code false} otherwise
+  bool isBoolean() {
+    return _isBoolean(reference).boolean;
+  }
+}
+
+final class $JsonTokenType extends jni.JObjType<JsonToken> {
+  const $JsonTokenType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonToken;";
+
+  @override
+  JsonToken fromRef(jni.JObjectPtr ref) => JsonToken.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonTokenType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonTokenType) && other is $JsonTokenType;
+  }
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/_package.dart b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/_package.dart
new file mode 100644
index 0000000..cae2a52
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/_package.dart
@@ -0,0 +1,3 @@
+export "JsonFactory.dart";
+export "JsonParser.dart";
+export "JsonToken.dart";
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
new file mode 100644
index 0000000..5aab2d4
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart
@@ -0,0 +1,1896 @@
+// Generated from jackson-core which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+// See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+//
+// Copyright (c) 2007 - The Jackson Project Authors
+// 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+import "JsonParser.dart" as jsonparser_;
+
+/// from: com.fasterxml.jackson.core.JsonFactory
+///
+/// The main factory class of Jackson package, used to configure and
+/// construct reader (aka parser, JsonParser)
+/// and writer (aka generator, JsonGenerator)
+/// instances.
+///
+/// Factory instances are thread-safe and reusable after configuration
+/// (if any). Typically applications and services use only a single
+/// globally shared factory instance, unless they need differently
+/// configured factories. Factory reuse is important if efficiency matters;
+/// most recycling of expensive construct is done on per-factory basis.
+///
+/// Creation of a factory instance is a light-weight operation,
+/// and since there is no need for pluggable alternative implementations
+/// (as there is no "standard" JSON processor API to implement),
+/// the default constructor is used for constructing factory
+/// instances.
+///@author Tatu Saloranta
+class JsonFactory extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonFactory> $type = type;
+
+  JsonFactory.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/fasterxml/jackson/core/JsonFactory");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonFactoryType();
+
+  /// from: static public final java.lang.String FORMAT_NAME_JSON
+  ///
+  /// Name used to identify JSON format
+  /// (and returned by \#getFormatName()
+  static const FORMAT_NAME_JSON = r"""JSON""";
+
+  static final _id_DEFAULT_FACTORY_FEATURE_FLAGS =
+      jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"DEFAULT_FACTORY_FEATURE_FLAGS",
+    r"I",
+  );
+
+  /// from: static protected final int DEFAULT_FACTORY_FEATURE_FLAGS
+  ///
+  /// Bitfield (set of flags) of all factory features that are enabled by default.
+  static int get DEFAULT_FACTORY_FEATURE_FLAGS => jni.Jni.accessors
+      .getStaticField(_class.reference, _id_DEFAULT_FACTORY_FEATURE_FLAGS,
+          jni.JniCallType.intType)
+      .integer;
+
+  static final _id_DEFAULT_PARSER_FEATURE_FLAGS =
+      jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"DEFAULT_PARSER_FEATURE_FLAGS",
+    r"I",
+  );
+
+  /// from: static protected final int DEFAULT_PARSER_FEATURE_FLAGS
+  ///
+  /// Bitfield (set of flags) of all parser features that are enabled
+  /// by default.
+  static int get DEFAULT_PARSER_FEATURE_FLAGS => jni.Jni.accessors
+      .getStaticField(_class.reference, _id_DEFAULT_PARSER_FEATURE_FLAGS,
+          jni.JniCallType.intType)
+      .integer;
+
+  static final _id_DEFAULT_GENERATOR_FEATURE_FLAGS =
+      jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"DEFAULT_GENERATOR_FEATURE_FLAGS",
+    r"I",
+  );
+
+  /// from: static protected final int DEFAULT_GENERATOR_FEATURE_FLAGS
+  ///
+  /// Bitfield (set of flags) of all generator features that are enabled
+  /// by default.
+  static int get DEFAULT_GENERATOR_FEATURE_FLAGS => jni.Jni.accessors
+      .getStaticField(_class.reference, _id_DEFAULT_GENERATOR_FEATURE_FLAGS,
+          jni.JniCallType.intType)
+      .integer;
+
+  static final _id_DEFAULT_ROOT_VALUE_SEPARATOR =
+      jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"DEFAULT_ROOT_VALUE_SEPARATOR",
+    r"Lcom/fasterxml/jackson/core/SerializableString;",
+  );
+
+  /// from: static public final com.fasterxml.jackson.core.SerializableString DEFAULT_ROOT_VALUE_SEPARATOR
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JObject get DEFAULT_ROOT_VALUE_SEPARATOR =>
+      const jni.JObjectType().fromRef(jni.Jni.accessors
+          .getStaticField(_class.reference, _id_DEFAULT_ROOT_VALUE_SEPARATOR,
+              jni.JniCallType.objectType)
+          .object);
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Default constructor used to create factory instances.
+  /// Creation of a factory instance is a light-weight operation,
+  /// but it is still a good idea to reuse limited number of
+  /// factory instances (and quite often just a single instance):
+  /// factories are used as context for storing some reused
+  /// processing objects (such as symbol tables parsers use)
+  /// and this reuse only works within context of a single
+  /// factory instance.
+  factory JsonFactory() {
+    return JsonFactory.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+
+  static final _id_new1 = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"<init>", r"(Lcom/fasterxml/jackson/core/ObjectCodec;)V");
+
+  /// from: public void <init>(com.fasterxml.jackson.core.ObjectCodec oc)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory JsonFactory.new1(
+    jni.JObject oc,
+  ) {
+    return JsonFactory.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new1, [oc.reference]).object);
+  }
+
+  static final _id_new2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"<init>",
+      r"(Lcom/fasterxml/jackson/core/JsonFactory;Lcom/fasterxml/jackson/core/ObjectCodec;)V");
+
+  /// from: protected void <init>(com.fasterxml.jackson.core.JsonFactory src, com.fasterxml.jackson.core.ObjectCodec codec)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor used when copy()ing a factory instance.
+  ///@param src Original factory to copy settings from
+  ///@param codec Databinding-level codec to use, if any
+  ///@since 2.2.1
+  factory JsonFactory.new2(
+    JsonFactory src,
+    jni.JObject codec,
+  ) {
+    return JsonFactory.fromRef(jni.Jni.accessors.newObjectWithArgs(
+        _class.reference, _id_new2, [src.reference, codec.reference]).object);
+  }
+
+  static final _id_new3 = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"<init>", r"(Lcom/fasterxml/jackson/core/JsonFactoryBuilder;)V");
+
+  /// from: public void <init>(com.fasterxml.jackson.core.JsonFactoryBuilder b)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor used by JsonFactoryBuilder for instantiation.
+  ///@param b Builder that contains settings to use
+  ///@since 2.10
+  factory JsonFactory.new3(
+    jni.JObject b,
+  ) {
+    return JsonFactory.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new3, [b.reference]).object);
+  }
+
+  static final _id_new4 = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"<init>", r"(Lcom/fasterxml/jackson/core/TSFBuilder;Z)V");
+
+  /// from: protected void <init>(com.fasterxml.jackson.core.TSFBuilder<?,?> b, boolean bogus)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Constructor for subtypes; needed to work around the fact that before 3.0,
+  /// this factory has cumbersome dual role as generic type as well as actual
+  /// implementation for json.
+  ///@param b Builder that contains settings to use
+  ///@param bogus Argument only needed to separate constructor signature; ignored
+  factory JsonFactory.new4(
+    jni.JObject b,
+    bool bogus,
+  ) {
+    return JsonFactory.fromRef(jni.Jni.accessors.newObjectWithArgs(
+        _class.reference, _id_new4, [b.reference, bogus ? 1 : 0]).object);
+  }
+
+  static final _id_rebuild = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"rebuild", r"()Lcom/fasterxml/jackson/core/TSFBuilder;");
+
+  /// from: public com.fasterxml.jackson.core.TSFBuilder<?,?> rebuild()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that allows construction of differently configured factory, starting
+  /// with settings of this factory.
+  ///@return Builder instance to use
+  ///@since 2.10
+  jni.JObject rebuild() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_rebuild, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_builder = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"builder",
+      r"()Lcom/fasterxml/jackson/core/TSFBuilder;");
+
+  /// from: static public com.fasterxml.jackson.core.TSFBuilder<?,?> builder()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Main factory method to use for constructing JsonFactory instances with
+  /// different configuration: creates and returns a builder for collecting configuration
+  /// settings; instance created by calling {@code build()} after all configuration
+  /// set.
+  ///
+  /// NOTE: signature unfortunately does not expose true implementation type; this
+  /// will be fixed in 3.0.
+  ///@return Builder instance to use
+  static jni.JObject builder() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_builder,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_copy = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"copy", r"()Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory copy()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing a new JsonFactory that has
+  /// the same settings as this instance, but is otherwise
+  /// independent (i.e. nothing is actually shared, symbol tables
+  /// are separate).
+  /// Note that ObjectCodec reference is not copied but is
+  /// set to null; caller typically needs to set it after calling
+  /// this method. Reason for this is that the codec is used for
+  /// callbacks, and assumption is that there is strict 1-to-1
+  /// mapping between codec, factory. Caller has to, then, explicitly
+  /// set codec after making the copy.
+  ///@return Copy of this factory instance
+  ///@since 2.1
+  JsonFactory copy() {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(
+            reference, _id_copy, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_readResolve = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"readResolve", r"()Ljava/lang/Object;");
+
+  /// from: protected java.lang.Object readResolve()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that we need to override to actually make restoration go
+  /// through constructors etc: needed to allow JDK serializability of
+  /// factory instances.
+  ///
+  /// Note: must be overridden by sub-classes as well.
+  ///@return Newly constructed instance
+  jni.JObject readResolve() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_readResolve, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_requiresPropertyOrdering = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"requiresPropertyOrdering", r"()Z");
+
+  /// from: public boolean requiresPropertyOrdering()
+  ///
+  /// Introspection method that higher-level functionality may call
+  /// to see whether underlying data format requires a stable ordering
+  /// of object properties or not.
+  /// This is usually used for determining
+  /// whether to force a stable ordering (like alphabetic ordering by name)
+  /// if no ordering if explicitly specified.
+  ///
+  /// Default implementation returns <code>false</code> as JSON does NOT
+  /// require stable ordering. Formats that require ordering include positional
+  /// textual formats like <code>CSV</code>, and schema-based binary formats
+  /// like <code>Avro</code>.
+  ///@return Whether format supported by this factory
+  ///   requires Object properties to be ordered.
+  ///@since 2.3
+  bool requiresPropertyOrdering() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_requiresPropertyOrdering, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_canHandleBinaryNatively = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"canHandleBinaryNatively", r"()Z");
+
+  /// from: public boolean canHandleBinaryNatively()
+  ///
+  /// Introspection method that higher-level functionality may call
+  /// to see whether underlying data format can read and write binary
+  /// data natively; that is, embeded it as-is without using encodings
+  /// such as Base64.
+  ///
+  /// Default implementation returns <code>false</code> as JSON does not
+  /// support native access: all binary content must use Base64 encoding.
+  /// Most binary formats (like Smile and Avro) support native binary content.
+  ///@return Whether format supported by this factory
+  ///    supports native binary content
+  ///@since 2.3
+  bool canHandleBinaryNatively() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_canHandleBinaryNatively, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_canUseCharArrays = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"canUseCharArrays", r"()Z");
+
+  /// from: public boolean canUseCharArrays()
+  ///
+  /// Introspection method that can be used by base factory to check
+  /// whether access using <code>char[]</code> is something that actual
+  /// parser implementations can take advantage of, over having to
+  /// use java.io.Reader. Sub-types are expected to override
+  /// definition; default implementation (suitable for JSON) alleges
+  /// that optimization are possible; and thereby is likely to try
+  /// to access java.lang.String content by first copying it into
+  /// recyclable intermediate buffer.
+  ///@return Whether access to decoded textual content can be efficiently
+  ///   accessed using parser method {@code getTextCharacters()}.
+  ///@since 2.4
+  bool canUseCharArrays() {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_canUseCharArrays,
+        jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_canParseAsync = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"canParseAsync", r"()Z");
+
+  /// from: public boolean canParseAsync()
+  ///
+  /// Introspection method that can be used to check whether this
+  /// factory can create non-blocking parsers: parsers that do not
+  /// use blocking I/O abstractions but instead use a
+  /// com.fasterxml.jackson.core.async.NonBlockingInputFeeder.
+  ///@return Whether this factory supports non-blocking ("async") parsing or
+  ///    not (and consequently whether {@code createNonBlockingXxx()} method(s) work)
+  ///@since 2.9
+  bool canParseAsync() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_canParseAsync, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_getFormatReadFeatureType = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getFormatReadFeatureType", r"()Ljava/lang/Class;");
+
+  /// from: public java.lang.Class<? extends com.fasterxml.jackson.core.FormatFeature> getFormatReadFeatureType()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject getFormatReadFeatureType() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getFormatReadFeatureType,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getFormatWriteFeatureType = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getFormatWriteFeatureType", r"()Ljava/lang/Class;");
+
+  /// from: public java.lang.Class<? extends com.fasterxml.jackson.core.FormatFeature> getFormatWriteFeatureType()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject getFormatWriteFeatureType() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getFormatWriteFeatureType,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_canUseSchema = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"canUseSchema",
+      r"(Lcom/fasterxml/jackson/core/FormatSchema;)Z");
+
+  /// from: public boolean canUseSchema(com.fasterxml.jackson.core.FormatSchema schema)
+  ///
+  /// Method that can be used to quickly check whether given schema
+  /// is something that parsers and/or generators constructed by this
+  /// factory could use. Note that this means possible use, at the level
+  /// of data format (i.e. schema is for same data format as parsers and
+  /// generators this factory constructs); individual schema instances
+  /// may have further usage restrictions.
+  ///@param schema Schema instance to check
+  ///@return Whether parsers and generators constructed by this factory
+  ///   can use specified format schema instance
+  bool canUseSchema(
+    jni.JObject schema,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_canUseSchema,
+        jni.JniCallType.booleanType, [schema.reference]).boolean;
+  }
+
+  static final _id_getFormatName = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getFormatName", r"()Ljava/lang/String;");
+
+  /// from: public java.lang.String getFormatName()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that returns short textual id identifying format
+  /// this factory supports.
+  ///
+  /// Note: sub-classes should override this method; default
+  /// implementation will return null for all sub-classes
+  ///@return Name of the format handled by parsers, generators this factory creates
+  jni.JString getFormatName() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getFormatName, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_hasFormat = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"hasFormat",
+      r"(Lcom/fasterxml/jackson/core/format/InputAccessor;)Lcom/fasterxml/jackson/core/format/MatchStrength;");
+
+  /// from: public com.fasterxml.jackson.core.format.MatchStrength hasFormat(com.fasterxml.jackson.core.format.InputAccessor acc)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject hasFormat(
+    jni.JObject acc,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_hasFormat,
+        jni.JniCallType.objectType,
+        [acc.reference]).object);
+  }
+
+  static final _id_requiresCustomCodec = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"requiresCustomCodec", r"()Z");
+
+  /// from: public boolean requiresCustomCodec()
+  ///
+  /// Method that can be called to determine if a custom
+  /// ObjectCodec is needed for binding data parsed
+  /// using JsonParser constructed by this factory
+  /// (which typically also implies the same for serialization
+  /// with JsonGenerator).
+  ///@return True if custom codec is needed with parsers and
+  ///   generators created by this factory; false if a general
+  ///   ObjectCodec is enough
+  ///@since 2.1
+  bool requiresCustomCodec() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_requiresCustomCodec, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_hasJSONFormat = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"hasJSONFormat",
+      r"(Lcom/fasterxml/jackson/core/format/InputAccessor;)Lcom/fasterxml/jackson/core/format/MatchStrength;");
+
+  /// from: protected com.fasterxml.jackson.core.format.MatchStrength hasJSONFormat(com.fasterxml.jackson.core.format.InputAccessor acc)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject hasJSONFormat(
+    jni.JObject acc,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_hasJSONFormat,
+        jni.JniCallType.objectType,
+        [acc.reference]).object);
+  }
+
+  static final _id_version = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"version", r"()Lcom/fasterxml/jackson/core/Version;");
+
+  /// from: public com.fasterxml.jackson.core.Version version()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject version() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_version, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_configure = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"configure",
+      r"(Lcom/fasterxml/jackson/core/JsonFactory$Feature;Z)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public final com.fasterxml.jackson.core.JsonFactory configure(com.fasterxml.jackson.core.JsonFactory.Feature f, boolean state)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling or disabling specified parser feature
+  /// (check JsonParser.Feature for list of features)
+  ///@param f Feature to enable/disable
+  ///@param state Whether to enable or disable the feature
+  ///@return This factory instance (to allow call chaining)
+  ///@deprecated since 2.10 use JsonFactoryBuilder\#configure(JsonFactory.Feature, boolean) instead
+  JsonFactory configure(
+    JsonFactory_Feature f,
+    bool state,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_configure,
+            jni.JniCallType.objectType, [f.reference, state ? 1 : 0]).object);
+  }
+
+  static final _id_enable = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"enable",
+      r"(Lcom/fasterxml/jackson/core/JsonFactory$Feature;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory enable(com.fasterxml.jackson.core.JsonFactory.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling specified parser feature
+  /// (check JsonFactory.Feature for list of features)
+  ///@param f Feature to enable
+  ///@return This factory instance (to allow call chaining)
+  ///@deprecated since 2.10 use JsonFactoryBuilder\#configure(JsonFactory.Feature, boolean) instead
+  JsonFactory enable(
+    JsonFactory_Feature f,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_enable, jni.JniCallType.objectType,
+            [f.reference]).object);
+  }
+
+  static final _id_disable = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"disable",
+      r"(Lcom/fasterxml/jackson/core/JsonFactory$Feature;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory disable(com.fasterxml.jackson.core.JsonFactory.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for disabling specified parser features
+  /// (check JsonFactory.Feature for list of features)
+  ///@param f Feature to disable
+  ///@return This factory instance (to allow call chaining)
+  ///@deprecated since 2.10 use JsonFactoryBuilder\#configure(JsonFactory.Feature, boolean) instead
+  JsonFactory disable(
+    JsonFactory_Feature f,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_disable, jni.JniCallType.objectType,
+            [f.reference]).object);
+  }
+
+  static final _id_isEnabled = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"isEnabled", r"(Lcom/fasterxml/jackson/core/JsonFactory$Feature;)Z");
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.JsonFactory.Feature f)
+  ///
+  /// Checked whether specified parser feature is enabled.
+  ///@param f Feature to check
+  ///@return True if the specified feature is enabled
+  bool isEnabled(
+    JsonFactory_Feature f,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_isEnabled,
+        jni.JniCallType.booleanType, [f.reference]).boolean;
+  }
+
+  static final _id_getParserFeatures = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getParserFeatures", r"()I");
+
+  /// from: public final int getParserFeatures()
+  int getParserFeatures() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getParserFeatures, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_getGeneratorFeatures = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getGeneratorFeatures", r"()I");
+
+  /// from: public final int getGeneratorFeatures()
+  int getGeneratorFeatures() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_getGeneratorFeatures, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_getFormatParserFeatures = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getFormatParserFeatures", r"()I");
+
+  /// from: public int getFormatParserFeatures()
+  int getFormatParserFeatures() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_getFormatParserFeatures, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_getFormatGeneratorFeatures = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getFormatGeneratorFeatures", r"()I");
+
+  /// from: public int getFormatGeneratorFeatures()
+  int getFormatGeneratorFeatures() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_getFormatGeneratorFeatures, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_configure1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"configure",
+      r"(Lcom/fasterxml/jackson/core/JsonParser$Feature;Z)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public final com.fasterxml.jackson.core.JsonFactory configure(com.fasterxml.jackson.core.JsonParser.Feature f, boolean state)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling or disabling specified parser feature
+  /// (check JsonParser.Feature for list of features)
+  ///@param f Feature to enable/disable
+  ///@param state Whether to enable or disable the feature
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory configure1(
+    jsonparser_.JsonParser_Feature f,
+    bool state,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_configure1,
+            jni.JniCallType.objectType, [f.reference, state ? 1 : 0]).object);
+  }
+
+  static final _id_enable1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"enable",
+      r"(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory enable(com.fasterxml.jackson.core.JsonParser.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling specified parser feature
+  /// (check JsonParser.Feature for list of features)
+  ///@param f Feature to enable
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory enable1(
+    jsonparser_.JsonParser_Feature f,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_enable1, jni.JniCallType.objectType,
+            [f.reference]).object);
+  }
+
+  static final _id_disable1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"disable",
+      r"(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory disable(com.fasterxml.jackson.core.JsonParser.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for disabling specified parser features
+  /// (check JsonParser.Feature for list of features)
+  ///@param f Feature to disable
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory disable1(
+    jsonparser_.JsonParser_Feature f,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_disable1, jni.JniCallType.objectType,
+            [f.reference]).object);
+  }
+
+  static final _id_isEnabled1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"isEnabled",
+      r"(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Z");
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.JsonParser.Feature f)
+  ///
+  /// Method for checking if the specified parser feature is enabled.
+  ///@param f Feature to check
+  ///@return True if specified feature is enabled
+  bool isEnabled1(
+    jsonparser_.JsonParser_Feature f,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_isEnabled1,
+        jni.JniCallType.booleanType, [f.reference]).boolean;
+  }
+
+  static final _id_isEnabled2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"isEnabled",
+      r"(Lcom/fasterxml/jackson/core/StreamReadFeature;)Z");
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.StreamReadFeature f)
+  ///
+  /// Method for checking if the specified stream read feature is enabled.
+  ///@param f Feature to check
+  ///@return True if specified feature is enabled
+  ///@since 2.10
+  bool isEnabled2(
+    jni.JObject f,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_isEnabled2,
+        jni.JniCallType.booleanType, [f.reference]).boolean;
+  }
+
+  static final _id_getInputDecorator = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getInputDecorator",
+      r"()Lcom/fasterxml/jackson/core/io/InputDecorator;");
+
+  /// from: public com.fasterxml.jackson.core.io.InputDecorator getInputDecorator()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for getting currently configured input decorator (if any;
+  /// there is no default decorator).
+  ///@return InputDecorator configured, if any
+  jni.JObject getInputDecorator() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getInputDecorator,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setInputDecorator = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"setInputDecorator",
+      r"(Lcom/fasterxml/jackson/core/io/InputDecorator;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setInputDecorator(com.fasterxml.jackson.core.io.InputDecorator d)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for overriding currently configured input decorator
+  ///@param d Decorator to configure for this factory, if any ({@code null} if none)
+  ///@return This factory instance (to allow call chaining)
+  ///@deprecated Since 2.10 use JsonFactoryBuilder\#inputDecorator(InputDecorator) instead
+  JsonFactory setInputDecorator(
+    jni.JObject d,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_setInputDecorator,
+            jni.JniCallType.objectType, [d.reference]).object);
+  }
+
+  static final _id_configure2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"configure",
+      r"(Lcom/fasterxml/jackson/core/JsonGenerator$Feature;Z)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public final com.fasterxml.jackson.core.JsonFactory configure(com.fasterxml.jackson.core.JsonGenerator.Feature f, boolean state)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling or disabling specified generator feature
+  /// (check JsonGenerator.Feature for list of features)
+  ///@param f Feature to enable/disable
+  ///@param state Whether to enable or disable the feature
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory configure2(
+    jni.JObject f,
+    bool state,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_configure2,
+            jni.JniCallType.objectType, [f.reference, state ? 1 : 0]).object);
+  }
+
+  static final _id_enable2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"enable",
+      r"(Lcom/fasterxml/jackson/core/JsonGenerator$Feature;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory enable(com.fasterxml.jackson.core.JsonGenerator.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling specified generator features
+  /// (check JsonGenerator.Feature for list of features)
+  ///@param f Feature to enable
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory enable2(
+    jni.JObject f,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_enable2, jni.JniCallType.objectType,
+            [f.reference]).object);
+  }
+
+  static final _id_disable2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"disable",
+      r"(Lcom/fasterxml/jackson/core/JsonGenerator$Feature;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory disable(com.fasterxml.jackson.core.JsonGenerator.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for disabling specified generator feature
+  /// (check JsonGenerator.Feature for list of features)
+  ///@param f Feature to disable
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory disable2(
+    jni.JObject f,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_disable2, jni.JniCallType.objectType,
+            [f.reference]).object);
+  }
+
+  static final _id_isEnabled3 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"isEnabled",
+      r"(Lcom/fasterxml/jackson/core/JsonGenerator$Feature;)Z");
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.JsonGenerator.Feature f)
+  ///
+  /// Check whether specified generator feature is enabled.
+  ///@param f Feature to check
+  ///@return Whether specified feature is enabled
+  bool isEnabled3(
+    jni.JObject f,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_isEnabled3,
+        jni.JniCallType.booleanType, [f.reference]).boolean;
+  }
+
+  static final _id_isEnabled4 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"isEnabled",
+      r"(Lcom/fasterxml/jackson/core/StreamWriteFeature;)Z");
+
+  /// from: public final boolean isEnabled(com.fasterxml.jackson.core.StreamWriteFeature f)
+  ///
+  /// Check whether specified stream write feature is enabled.
+  ///@param f Feature to check
+  ///@return Whether specified feature is enabled
+  ///@since 2.10
+  bool isEnabled4(
+    jni.JObject f,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_isEnabled4,
+        jni.JniCallType.booleanType, [f.reference]).boolean;
+  }
+
+  static final _id_getCharacterEscapes = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getCharacterEscapes",
+      r"()Lcom/fasterxml/jackson/core/io/CharacterEscapes;");
+
+  /// from: public com.fasterxml.jackson.core.io.CharacterEscapes getCharacterEscapes()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for accessing custom escapes factory uses for JsonGenerators
+  /// it creates.
+  ///@return Configured {@code CharacterEscapes}, if any; {@code null} if none
+  jni.JObject getCharacterEscapes() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getCharacterEscapes,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setCharacterEscapes = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"setCharacterEscapes",
+      r"(Lcom/fasterxml/jackson/core/io/CharacterEscapes;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setCharacterEscapes(com.fasterxml.jackson.core.io.CharacterEscapes esc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for defining custom escapes factory uses for JsonGenerators
+  /// it creates.
+  ///@param esc CharaterEscapes to set (or {@code null} for "none")
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory setCharacterEscapes(
+    jni.JObject esc,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_setCharacterEscapes,
+            jni.JniCallType.objectType, [esc.reference]).object);
+  }
+
+  static final _id_getOutputDecorator = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getOutputDecorator",
+      r"()Lcom/fasterxml/jackson/core/io/OutputDecorator;");
+
+  /// from: public com.fasterxml.jackson.core.io.OutputDecorator getOutputDecorator()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for getting currently configured output decorator (if any;
+  /// there is no default decorator).
+  ///@return OutputDecorator configured for generators factory creates, if any;
+  ///    {@code null} if none.
+  jni.JObject getOutputDecorator() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getOutputDecorator,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setOutputDecorator = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"setOutputDecorator",
+      r"(Lcom/fasterxml/jackson/core/io/OutputDecorator;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setOutputDecorator(com.fasterxml.jackson.core.io.OutputDecorator d)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for overriding currently configured output decorator
+  ///@return This factory instance (to allow call chaining)
+  ///@param d Output decorator to use, if any
+  ///@deprecated Since 2.10 use JsonFactoryBuilder\#outputDecorator(OutputDecorator) instead
+  JsonFactory setOutputDecorator(
+    jni.JObject d,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_setOutputDecorator,
+            jni.JniCallType.objectType, [d.reference]).object);
+  }
+
+  static final _id_setRootValueSeparator = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"setRootValueSeparator",
+      r"(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setRootValueSeparator(java.lang.String sep)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that allows overriding String used for separating root-level
+  /// JSON values (default is single space character)
+  ///@param sep Separator to use, if any; null means that no separator is
+  ///   automatically added
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory setRootValueSeparator(
+    jni.JString sep,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_setRootValueSeparator,
+            jni.JniCallType.objectType, [sep.reference]).object);
+  }
+
+  static final _id_getRootValueSeparator = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getRootValueSeparator", r"()Ljava/lang/String;");
+
+  /// from: public java.lang.String getRootValueSeparator()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// @return Root value separator configured, if any
+  jni.JString getRootValueSeparator() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getRootValueSeparator,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setCodec = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"setCodec",
+      r"(Lcom/fasterxml/jackson/core/ObjectCodec;)Lcom/fasterxml/jackson/core/JsonFactory;");
+
+  /// from: public com.fasterxml.jackson.core.JsonFactory setCodec(com.fasterxml.jackson.core.ObjectCodec oc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for associating a ObjectCodec (typically
+  /// a <code>com.fasterxml.jackson.databind.ObjectMapper</code>)
+  /// with this factory (and more importantly, parsers and generators
+  /// it constructs). This is needed to use data-binding methods
+  /// of JsonParser and JsonGenerator instances.
+  ///@param oc Codec to use
+  ///@return This factory instance (to allow call chaining)
+  JsonFactory setCodec(
+    jni.JObject oc,
+  ) {
+    return const $JsonFactoryType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_setCodec, jni.JniCallType.objectType,
+            [oc.reference]).object);
+  }
+
+  static final _id_getCodec = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"getCodec", r"()Lcom/fasterxml/jackson/core/ObjectCodec;");
+
+  /// from: public com.fasterxml.jackson.core.ObjectCodec getCodec()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject getCodec() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getCodec, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_createParser = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"(Ljava/io/File;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.io.File f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// contents of specified file.
+  ///
+  ///
+  /// Encoding is auto-detected from contents according to JSON
+  /// specification recommended mechanism. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///
+  ///
+  /// Underlying input stream (needed for reading contents)
+  /// will be __owned__ (and managed, i.e. closed as need be) by
+  /// the parser, since caller has no access to it.
+  ///@param f File that contains JSON content to parse
+  ///@since 2.1
+  jsonparser_.JsonParser createParser(
+    jni.JObject f,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createParser,
+            jni.JniCallType.objectType, [f.reference]).object);
+  }
+
+  static final _id_createParser1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"(Ljava/net/URL;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.net.URL url)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// contents of resource reference by given URL.
+  ///
+  /// Encoding is auto-detected from contents according to JSON
+  /// specification recommended mechanism. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///
+  /// Underlying input stream (needed for reading contents)
+  /// will be __owned__ (and managed, i.e. closed as need be) by
+  /// the parser, since caller has no access to it.
+  ///@param url URL pointing to resource that contains JSON content to parse
+  ///@since 2.1
+  jsonparser_.JsonParser createParser1(
+    jni.JObject url,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createParser1,
+            jni.JniCallType.objectType, [url.reference]).object);
+  }
+
+  static final _id_createParser2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"(Ljava/io/InputStream;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.io.InputStream in)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// the contents accessed via specified input stream.
+  ///
+  /// The input stream will __not be owned__ by
+  /// the parser, it will still be managed (i.e. closed if
+  /// end-of-stream is reacher, or parser close method called)
+  /// if (and only if) com.fasterxml.jackson.core.StreamReadFeature\#AUTO_CLOSE_SOURCE
+  /// is enabled.
+  ///
+  ///
+  /// Note: no encoding argument is taken since it can always be
+  /// auto-detected as suggested by JSON RFC. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///@param in InputStream to use for reading JSON content to parse
+  ///@since 2.1
+  jsonparser_.JsonParser createParser2(
+    jni.JObject in0,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createParser2,
+            jni.JniCallType.objectType, [in0.reference]).object);
+  }
+
+  static final _id_createParser3 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"(Ljava/io/Reader;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.io.Reader r)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents accessed via specified Reader.
+  ///
+  /// The read stream will __not be owned__ by
+  /// the parser, it will still be managed (i.e. closed if
+  /// end-of-stream is reacher, or parser close method called)
+  /// if (and only if) com.fasterxml.jackson.core.StreamReadFeature\#AUTO_CLOSE_SOURCE
+  /// is enabled.
+  ///@param r Reader to use for reading JSON content to parse
+  ///@since 2.1
+  jsonparser_.JsonParser createParser3(
+    jni.JObject r,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createParser3,
+            jni.JniCallType.objectType, [r.reference]).object);
+  }
+
+  static final _id_createParser4 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"([B)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(byte[] data)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents of given byte array.
+  ///@since 2.1
+  jsonparser_.JsonParser createParser4(
+    jni.JArray<jni.jbyte> data,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createParser4,
+            jni.JniCallType.objectType, [data.reference]).object);
+  }
+
+  static final _id_createParser5 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"([BII)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(byte[] data, int offset, int len)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents of given byte array.
+  ///@param data Buffer that contains data to parse
+  ///@param offset Offset of the first data byte within buffer
+  ///@param len Length of contents to parse within buffer
+  ///@since 2.1
+  jsonparser_.JsonParser createParser5(
+    jni.JArray<jni.jbyte> data,
+    int offset,
+    int len,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(
+            reference, _id_createParser5, jni.JniCallType.objectType, [
+      data.reference,
+      jni.JValueInt(offset),
+      jni.JValueInt(len)
+    ]).object);
+  }
+
+  static final _id_createParser6 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.lang.String content)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// contents of given String.
+  ///@since 2.1
+  jsonparser_.JsonParser createParser6(
+    jni.JString content,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createParser6,
+            jni.JniCallType.objectType, [content.reference]).object);
+  }
+
+  static final _id_createParser7 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"([C)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(char[] content)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// contents of given char array.
+  ///@since 2.4
+  jsonparser_.JsonParser createParser7(
+    jni.JArray<jni.jchar> content,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createParser7,
+            jni.JniCallType.objectType, [content.reference]).object);
+  }
+
+  static final _id_createParser8 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"([CII)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(char[] content, int offset, int len)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing contents of given char array.
+  ///@since 2.4
+  jsonparser_.JsonParser createParser8(
+    jni.JArray<jni.jchar> content,
+    int offset,
+    int len,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(
+            reference, _id_createParser8, jni.JniCallType.objectType, [
+      content.reference,
+      jni.JValueInt(offset),
+      jni.JValueInt(len)
+    ]).object);
+  }
+
+  static final _id_createParser9 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createParser",
+      r"(Ljava/io/DataInput;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createParser(java.io.DataInput in)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Optional method for constructing parser for reading contents from specified DataInput
+  /// instance.
+  ///
+  /// If this factory does not support DataInput as source,
+  /// will throw UnsupportedOperationException
+  ///@since 2.8
+  jsonparser_.JsonParser createParser9(
+    jni.JObject in0,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createParser9,
+            jni.JniCallType.objectType, [in0.reference]).object);
+  }
+
+  static final _id_createNonBlockingByteArrayParser = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"createNonBlockingByteArrayParser",
+          r"()Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createNonBlockingByteArrayParser()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Optional method for constructing parser for non-blocking parsing
+  /// via com.fasterxml.jackson.core.async.ByteArrayFeeder
+  /// interface (accessed using JsonParser\#getNonBlockingInputFeeder()
+  /// from constructed instance).
+  ///
+  /// If this factory does not support non-blocking parsing (either at all,
+  /// or from byte array),
+  /// will throw UnsupportedOperationException.
+  ///
+  /// Note that JSON-backed factory only supports parsing of UTF-8 encoded JSON content
+  /// (and US-ASCII since it is proper subset); other encodings are not supported
+  /// at this point.
+  ///@since 2.9
+  jsonparser_.JsonParser createNonBlockingByteArrayParser() {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createNonBlockingByteArrayParser,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_createGenerator = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createGenerator",
+      r"(Ljava/io/OutputStream;Lcom/fasterxml/jackson/core/JsonEncoding;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.OutputStream out, com.fasterxml.jackson.core.JsonEncoding enc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// using specified output stream.
+  /// Encoding to use must be specified, and needs to be one of available
+  /// types (as per JSON specification).
+  ///
+  /// Underlying stream __is NOT owned__ by the generator constructed,
+  /// so that generator will NOT close the output stream when
+  /// JsonGenerator\#close is called (unless auto-closing
+  /// feature,
+  /// com.fasterxml.jackson.core.JsonGenerator.Feature\#AUTO_CLOSE_TARGET
+  /// is enabled).
+  /// Using application needs to close it explicitly if this is the case.
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats)
+  /// and that ignore passed in encoding.
+  ///@param out OutputStream to use for writing JSON content
+  ///@param enc Character encoding to use
+  ///@since 2.1
+  jni.JObject createGenerator(
+    jni.JObject out,
+    jni.JObject enc,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_createGenerator,
+        jni.JniCallType.objectType,
+        [out.reference, enc.reference]).object);
+  }
+
+  static final _id_createGenerator1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createGenerator",
+      r"(Ljava/io/OutputStream;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.OutputStream out)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Convenience method for constructing generator that uses default
+  /// encoding of the format (UTF-8 for JSON and most other data formats).
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats).
+  ///@since 2.1
+  jni.JObject createGenerator1(
+    jni.JObject out,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_createGenerator1,
+        jni.JniCallType.objectType,
+        [out.reference]).object);
+  }
+
+  static final _id_createGenerator2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createGenerator",
+      r"(Ljava/io/Writer;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.Writer w)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// using specified Writer.
+  ///
+  /// Underlying stream __is NOT owned__ by the generator constructed,
+  /// so that generator will NOT close the Reader when
+  /// JsonGenerator\#close is called (unless auto-closing
+  /// feature,
+  /// com.fasterxml.jackson.core.JsonGenerator.Feature\#AUTO_CLOSE_TARGET is enabled).
+  /// Using application needs to close it explicitly.
+  ///@since 2.1
+  ///@param w Writer to use for writing JSON content
+  jni.JObject createGenerator2(
+    jni.JObject w,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_createGenerator2,
+        jni.JniCallType.objectType,
+        [w.reference]).object);
+  }
+
+  static final _id_createGenerator3 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createGenerator",
+      r"(Ljava/io/File;Lcom/fasterxml/jackson/core/JsonEncoding;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.File f, com.fasterxml.jackson.core.JsonEncoding enc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// to specified file, overwriting contents it might have (or creating
+  /// it if such file does not yet exist).
+  /// Encoding to use must be specified, and needs to be one of available
+  /// types (as per JSON specification).
+  ///
+  /// Underlying stream __is owned__ by the generator constructed,
+  /// i.e. generator will handle closing of file when
+  /// JsonGenerator\#close is called.
+  ///@param f File to write contents to
+  ///@param enc Character encoding to use
+  ///@since 2.1
+  jni.JObject createGenerator3(
+    jni.JObject f,
+    jni.JObject enc,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_createGenerator3,
+        jni.JniCallType.objectType,
+        [f.reference, enc.reference]).object);
+  }
+
+  static final _id_createGenerator4 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createGenerator",
+      r"(Ljava/io/DataOutput;Lcom/fasterxml/jackson/core/JsonEncoding;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.DataOutput out, com.fasterxml.jackson.core.JsonEncoding enc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing generator for writing content using specified
+  /// DataOutput instance.
+  ///@since 2.8
+  jni.JObject createGenerator4(
+    jni.JObject out,
+    jni.JObject enc,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_createGenerator4,
+        jni.JniCallType.objectType,
+        [out.reference, enc.reference]).object);
+  }
+
+  static final _id_createGenerator5 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createGenerator",
+      r"(Ljava/io/DataOutput;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createGenerator(java.io.DataOutput out)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Convenience method for constructing generator that uses default
+  /// encoding of the format (UTF-8 for JSON and most other data formats).
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats).
+  ///@since 2.8
+  jni.JObject createGenerator5(
+    jni.JObject out,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_createGenerator5,
+        jni.JniCallType.objectType,
+        [out.reference]).object);
+  }
+
+  static final _id_createJsonParser = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonParser",
+      r"(Ljava/io/File;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.io.File f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// contents of specified file.
+  ///
+  /// Encoding is auto-detected from contents according to JSON
+  /// specification recommended mechanism. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///
+  ///
+  /// Underlying input stream (needed for reading contents)
+  /// will be __owned__ (and managed, i.e. closed as need be) by
+  /// the parser, since caller has no access to it.
+  ///@param f File that contains JSON content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(File) instead.
+  jsonparser_.JsonParser createJsonParser(
+    jni.JObject f,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createJsonParser,
+            jni.JniCallType.objectType, [f.reference]).object);
+  }
+
+  static final _id_createJsonParser1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonParser",
+      r"(Ljava/net/URL;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.net.URL url)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// contents of resource reference by given URL.
+  ///
+  /// Encoding is auto-detected from contents according to JSON
+  /// specification recommended mechanism. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///
+  /// Underlying input stream (needed for reading contents)
+  /// will be __owned__ (and managed, i.e. closed as need be) by
+  /// the parser, since caller has no access to it.
+  ///@param url URL pointing to resource that contains JSON content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(URL) instead.
+  jsonparser_.JsonParser createJsonParser1(
+    jni.JObject url,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createJsonParser1,
+            jni.JniCallType.objectType, [url.reference]).object);
+  }
+
+  static final _id_createJsonParser2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonParser",
+      r"(Ljava/io/InputStream;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.io.InputStream in)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON parser instance to parse
+  /// the contents accessed via specified input stream.
+  ///
+  /// The input stream will __not be owned__ by
+  /// the parser, it will still be managed (i.e. closed if
+  /// end-of-stream is reacher, or parser close method called)
+  /// if (and only if) com.fasterxml.jackson.core.JsonParser.Feature\#AUTO_CLOSE_SOURCE
+  /// is enabled.
+  ///
+  ///
+  /// Note: no encoding argument is taken since it can always be
+  /// auto-detected as suggested by JSON RFC. Json specification
+  /// supports only UTF-8, UTF-16 and UTF-32 as valid encodings,
+  /// so auto-detection implemented only for this charsets.
+  /// For other charsets use \#createParser(java.io.Reader).
+  ///@param in InputStream to use for reading JSON content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(InputStream) instead.
+  jsonparser_.JsonParser createJsonParser2(
+    jni.JObject in0,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createJsonParser2,
+            jni.JniCallType.objectType, [in0.reference]).object);
+  }
+
+  static final _id_createJsonParser3 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonParser",
+      r"(Ljava/io/Reader;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.io.Reader r)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents accessed via specified Reader.
+  ///
+  /// The read stream will __not be owned__ by
+  /// the parser, it will still be managed (i.e. closed if
+  /// end-of-stream is reacher, or parser close method called)
+  /// if (and only if) com.fasterxml.jackson.core.JsonParser.Feature\#AUTO_CLOSE_SOURCE
+  /// is enabled.
+  ///@param r Reader to use for reading JSON content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(Reader) instead.
+  jsonparser_.JsonParser createJsonParser3(
+    jni.JObject r,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createJsonParser3,
+            jni.JniCallType.objectType, [r.reference]).object);
+  }
+
+  static final _id_createJsonParser4 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonParser",
+      r"([B)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(byte[] data)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing the contents of given byte array.
+  ///@param data Input content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(byte[]) instead.
+  jsonparser_.JsonParser createJsonParser4(
+    jni.JArray<jni.jbyte> data,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createJsonParser4,
+            jni.JniCallType.objectType, [data.reference]).object);
+  }
+
+  static final _id_createJsonParser5 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonParser",
+      r"([BII)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(byte[] data, int offset, int len)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// the contents of given byte array.
+  ///@param data Buffer that contains data to parse
+  ///@param offset Offset of the first data byte within buffer
+  ///@param len Length of contents to parse within buffer
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(byte[],int,int) instead.
+  jsonparser_.JsonParser createJsonParser5(
+    jni.JArray<jni.jbyte> data,
+    int offset,
+    int len,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(
+            reference, _id_createJsonParser5, jni.JniCallType.objectType, [
+      data.reference,
+      jni.JValueInt(offset),
+      jni.JValueInt(len)
+    ]).object);
+  }
+
+  static final _id_createJsonParser6 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonParser",
+      r"(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser createJsonParser(java.lang.String content)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing parser for parsing
+  /// contents of given String.
+  ///@param content Input content to parse
+  ///@return Parser constructed
+  ///@throws IOException if parser initialization fails due to I/O (read) problem
+  ///@throws JsonParseException if parser initialization fails due to content decoding problem
+  ///@deprecated Since 2.2, use \#createParser(String) instead.
+  jsonparser_.JsonParser createJsonParser6(
+    jni.JString content,
+  ) {
+    return const jsonparser_.$JsonParserType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_createJsonParser6,
+            jni.JniCallType.objectType, [content.reference]).object);
+  }
+
+  static final _id_createJsonGenerator = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonGenerator",
+      r"(Ljava/io/OutputStream;Lcom/fasterxml/jackson/core/JsonEncoding;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createJsonGenerator(java.io.OutputStream out, com.fasterxml.jackson.core.JsonEncoding enc)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// using specified output stream.
+  /// Encoding to use must be specified, and needs to be one of available
+  /// types (as per JSON specification).
+  ///
+  /// Underlying stream __is NOT owned__ by the generator constructed,
+  /// so that generator will NOT close the output stream when
+  /// JsonGenerator\#close is called (unless auto-closing
+  /// feature,
+  /// com.fasterxml.jackson.core.JsonGenerator.Feature\#AUTO_CLOSE_TARGET
+  /// is enabled).
+  /// Using application needs to close it explicitly if this is the case.
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats)
+  /// and that ignore passed in encoding.
+  ///@param out OutputStream to use for writing JSON content
+  ///@param enc Character encoding to use
+  ///@return Generator constructed
+  ///@throws IOException if parser initialization fails due to I/O (write) problem
+  ///@deprecated Since 2.2, use \#createGenerator(OutputStream, JsonEncoding) instead.
+  jni.JObject createJsonGenerator(
+    jni.JObject out,
+    jni.JObject enc,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_createJsonGenerator,
+        jni.JniCallType.objectType,
+        [out.reference, enc.reference]).object);
+  }
+
+  static final _id_createJsonGenerator1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonGenerator",
+      r"(Ljava/io/Writer;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createJsonGenerator(java.io.Writer out)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for constructing JSON generator for writing JSON content
+  /// using specified Writer.
+  ///
+  /// Underlying stream __is NOT owned__ by the generator constructed,
+  /// so that generator will NOT close the Reader when
+  /// JsonGenerator\#close is called (unless auto-closing
+  /// feature,
+  /// com.fasterxml.jackson.core.JsonGenerator.Feature\#AUTO_CLOSE_TARGET is enabled).
+  /// Using application needs to close it explicitly.
+  ///@param out Writer to use for writing JSON content
+  ///@return Generator constructed
+  ///@throws IOException if parser initialization fails due to I/O (write) problem
+  ///@deprecated Since 2.2, use \#createGenerator(Writer) instead.
+  jni.JObject createJsonGenerator1(
+    jni.JObject out,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_createJsonGenerator1,
+        jni.JniCallType.objectType,
+        [out.reference]).object);
+  }
+
+  static final _id_createJsonGenerator2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"createJsonGenerator",
+      r"(Ljava/io/OutputStream;)Lcom/fasterxml/jackson/core/JsonGenerator;");
+
+  /// from: public com.fasterxml.jackson.core.JsonGenerator createJsonGenerator(java.io.OutputStream out)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Convenience method for constructing generator that uses default
+  /// encoding of the format (UTF-8 for JSON and most other data formats).
+  ///
+  /// Note: there are formats that use fixed encoding (like most binary data formats).
+  ///@param out OutputStream to use for writing JSON content
+  ///@return Generator constructed
+  ///@throws IOException if parser initialization fails due to I/O (write) problem
+  ///@deprecated Since 2.2, use \#createGenerator(OutputStream) instead.
+  jni.JObject createJsonGenerator2(
+    jni.JObject out,
+  ) {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_createJsonGenerator2,
+        jni.JniCallType.objectType,
+        [out.reference]).object);
+  }
+}
+
+final class $JsonFactoryType extends jni.JObjType<JsonFactory> {
+  const $JsonFactoryType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonFactory;";
+
+  @override
+  JsonFactory fromRef(jni.JObjectPtr ref) => JsonFactory.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonFactoryType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonFactoryType) && other is $JsonFactoryType;
+  }
+}
+
+/// from: com.fasterxml.jackson.core.JsonFactory$Feature
+///
+/// Enumeration that defines all on/off features that can only be
+/// changed for JsonFactory.
+class JsonFactory_Feature extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonFactory_Feature> $type = type;
+
+  JsonFactory_Feature.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/fasterxml/jackson/core/JsonFactory$Feature");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonFactory_FeatureType();
+  static final _id_values = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"values",
+      r"()[Lcom/fasterxml/jackson/core/JsonFactory$Feature;");
+
+  /// from: static public com.fasterxml.jackson.core.JsonFactory.Feature[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonFactory_Feature> values() {
+    return const jni.JArrayType($JsonFactory_FeatureType()).fromRef(
+        jni.Jni.accessors.callStaticMethodWithArgs(_class.reference, _id_values,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_valueOf = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"valueOf",
+      r"(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonFactory$Feature;");
+
+  /// from: static public com.fasterxml.jackson.core.JsonFactory.Feature valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonFactory_Feature valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonFactory_FeatureType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_valueOf,
+            jni.JniCallType.objectType, [name.reference]).object);
+  }
+
+  static final _id_collectDefaults = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"collectDefaults", r"()I");
+
+  /// from: static public int collectDefaults()
+  ///
+  /// Method that calculates bit set (flags) of all features that
+  /// are enabled by default.
+  ///@return Bit field of features enabled by default
+  static int collectDefaults() {
+    return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+        _id_collectDefaults, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_enabledByDefault = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"enabledByDefault", r"()Z");
+
+  /// from: public boolean enabledByDefault()
+  bool enabledByDefault() {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_enabledByDefault,
+        jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_enabledIn =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"enabledIn", r"(I)Z");
+
+  /// from: public boolean enabledIn(int flags)
+  bool enabledIn(
+    int flags,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_enabledIn,
+        jni.JniCallType.booleanType, [jni.JValueInt(flags)]).boolean;
+  }
+
+  static final _id_getMask =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"getMask", r"()I");
+
+  /// from: public int getMask()
+  int getMask() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getMask, jni.JniCallType.intType, []).integer;
+  }
+}
+
+final class $JsonFactory_FeatureType extends jni.JObjType<JsonFactory_Feature> {
+  const $JsonFactory_FeatureType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonFactory$Feature;";
+
+  @override
+  JsonFactory_Feature fromRef(jni.JObjectPtr ref) =>
+      JsonFactory_Feature.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonFactory_FeatureType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonFactory_FeatureType) &&
+        other is $JsonFactory_FeatureType;
+  }
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
new file mode 100644
index 0000000..2c27ecc
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonParser.dart
@@ -0,0 +1,2701 @@
+// Generated from jackson-core which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+// See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+//
+// Copyright (c) 2007 - The Jackson Project Authors
+// 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+import "JsonToken.dart" as jsontoken_;
+
+/// from: com.fasterxml.jackson.core.JsonParser
+///
+/// Base class that defines public API for reading JSON content.
+/// Instances are created using factory methods of
+/// a JsonFactory instance.
+///@author Tatu Saloranta
+class JsonParser extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonParser> $type = type;
+
+  JsonParser.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/fasterxml/jackson/core/JsonParser");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonParserType();
+  static final _id_DEFAULT_READ_CAPABILITIES =
+      jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"DEFAULT_READ_CAPABILITIES",
+    r"Lcom/fasterxml/jackson/core/util/JacksonFeatureSet;",
+  );
+
+  /// from: static protected final com.fasterxml.jackson.core.util.JacksonFeatureSet<com.fasterxml.jackson.core.StreamReadCapability> DEFAULT_READ_CAPABILITIES
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Default set of StreamReadCapabilityies that may be used as
+  /// basis for format-specific readers (or as bogus instance if non-null
+  /// set needs to be passed).
+  ///@since 2.12
+  static jni.JObject get DEFAULT_READ_CAPABILITIES =>
+      const jni.JObjectType().fromRef(jni.Jni.accessors
+          .getStaticField(_class.reference, _id_DEFAULT_READ_CAPABILITIES,
+              jni.JniCallType.objectType)
+          .object);
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: protected void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory JsonParser() {
+    return JsonParser.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+
+  static final _id_new1 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(I)V");
+
+  /// from: protected void <init>(int features)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory JsonParser.new1(
+    int features,
+  ) {
+    return JsonParser.fromRef(jni.Jni.accessors.newObjectWithArgs(
+        _class.reference, _id_new1, [jni.JValueInt(features)]).object);
+  }
+
+  static final _id_getCodec = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"getCodec", r"()Lcom/fasterxml/jackson/core/ObjectCodec;");
+
+  /// from: public abstract com.fasterxml.jackson.core.ObjectCodec getCodec()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor for ObjectCodec associated with this
+  /// parser, if any. Codec is used by \#readValueAs(Class)
+  /// method (and its variants).
+  ///@return Codec assigned to this parser, if any; {@code null} if none
+  jni.JObject getCodec() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getCodec, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setCodec = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"setCodec", r"(Lcom/fasterxml/jackson/core/ObjectCodec;)V");
+
+  /// from: public abstract void setCodec(com.fasterxml.jackson.core.ObjectCodec oc)
+  ///
+  /// Setter that allows defining ObjectCodec associated with this
+  /// parser, if any. Codec is used by \#readValueAs(Class)
+  /// method (and its variants).
+  ///@param oc Codec to assign, if any; {@code null} if none
+  void setCodec(
+    jni.JObject oc,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_setCodec,
+        jni.JniCallType.voidType, [oc.reference]).check();
+  }
+
+  static final _id_getInputSource = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getInputSource", r"()Ljava/lang/Object;");
+
+  /// from: public java.lang.Object getInputSource()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be used to get access to object that is used
+  /// to access input being parsed; this is usually either
+  /// InputStream or Reader, depending on what
+  /// parser was constructed with.
+  /// Note that returned value may be null in some cases; including
+  /// case where parser implementation does not want to exposed raw
+  /// source to caller.
+  /// In cases where input has been decorated, object returned here
+  /// is the decorated version; this allows some level of interaction
+  /// between users of parser and decorator object.
+  ///
+  /// In general use of this accessor should be considered as
+  /// "last effort", i.e. only used if no other mechanism is applicable.
+  ///@return Input source this parser was configured with
+  jni.JObject getInputSource() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getInputSource, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setRequestPayloadOnError = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"setRequestPayloadOnError",
+      r"(Lcom/fasterxml/jackson/core/util/RequestPayload;)V");
+
+  /// from: public void setRequestPayloadOnError(com.fasterxml.jackson.core.util.RequestPayload payload)
+  ///
+  /// Sets the payload to be passed if JsonParseException is thrown.
+  ///@param payload Payload to pass
+  ///@since 2.8
+  void setRequestPayloadOnError(
+    jni.JObject payload,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_setRequestPayloadOnError,
+        jni.JniCallType.voidType,
+        [payload.reference]).check();
+  }
+
+  static final _id_setRequestPayloadOnError1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"setRequestPayloadOnError",
+      r"([BLjava/lang/String;)V");
+
+  /// from: public void setRequestPayloadOnError(byte[] payload, java.lang.String charset)
+  ///
+  /// Sets the byte[] request payload and the charset
+  ///@param payload Payload to pass
+  ///@param charset Character encoding for (lazily) decoding payload
+  ///@since 2.8
+  void setRequestPayloadOnError1(
+    jni.JArray<jni.jbyte> payload,
+    jni.JString charset,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_setRequestPayloadOnError1,
+        jni.JniCallType.voidType,
+        [payload.reference, charset.reference]).check();
+  }
+
+  static final _id_setRequestPayloadOnError2 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"setRequestPayloadOnError", r"(Ljava/lang/String;)V");
+
+  /// from: public void setRequestPayloadOnError(java.lang.String payload)
+  ///
+  /// Sets the String request payload
+  ///@param payload Payload to pass
+  ///@since 2.8
+  void setRequestPayloadOnError2(
+    jni.JString payload,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_setRequestPayloadOnError2,
+        jni.JniCallType.voidType,
+        [payload.reference]).check();
+  }
+
+  static final _id_setSchema = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"setSchema", r"(Lcom/fasterxml/jackson/core/FormatSchema;)V");
+
+  /// from: public void setSchema(com.fasterxml.jackson.core.FormatSchema schema)
+  ///
+  /// Method to call to make this parser use specified schema. Method must
+  /// be called before trying to parse any content, right after parser instance
+  /// has been created.
+  /// Note that not all parsers support schemas; and those that do usually only
+  /// accept specific types of schemas: ones defined for data format parser can read.
+  ///
+  /// If parser does not support specified schema, UnsupportedOperationException
+  /// is thrown.
+  ///@param schema Schema to use
+  ///@throws UnsupportedOperationException if parser does not support schema
+  void setSchema(
+    jni.JObject schema,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_setSchema,
+        jni.JniCallType.voidType, [schema.reference]).check();
+  }
+
+  static final _id_getSchema = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"getSchema", r"()Lcom/fasterxml/jackson/core/FormatSchema;");
+
+  /// from: public com.fasterxml.jackson.core.FormatSchema getSchema()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for accessing Schema that this parser uses, if any.
+  /// Default implementation returns null.
+  ///@return Schema in use by this parser, if any; {@code null} if none
+  ///@since 2.1
+  jni.JObject getSchema() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getSchema, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_canUseSchema = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"canUseSchema",
+      r"(Lcom/fasterxml/jackson/core/FormatSchema;)Z");
+
+  /// from: public boolean canUseSchema(com.fasterxml.jackson.core.FormatSchema schema)
+  ///
+  /// Method that can be used to verify that given schema can be used with
+  /// this parser (using \#setSchema).
+  ///@param schema Schema to check
+  ///@return True if this parser can use given schema; false if not
+  bool canUseSchema(
+    jni.JObject schema,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_canUseSchema,
+        jni.JniCallType.booleanType, [schema.reference]).boolean;
+  }
+
+  static final _id_requiresCustomCodec = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"requiresCustomCodec", r"()Z");
+
+  /// from: public boolean requiresCustomCodec()
+  ///
+  /// Method that can be called to determine if a custom
+  /// ObjectCodec is needed for binding data parsed
+  /// using JsonParser constructed by this factory
+  /// (which typically also implies the same for serialization
+  /// with JsonGenerator).
+  ///@return True if format-specific codec is needed with this parser; false if a general
+  ///   ObjectCodec is enough
+  ///@since 2.1
+  bool requiresCustomCodec() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_requiresCustomCodec, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_canParseAsync = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"canParseAsync", r"()Z");
+
+  /// from: public boolean canParseAsync()
+  ///
+  /// Method that can be called to determine if this parser instance
+  /// uses non-blocking ("asynchronous") input access for decoding or not.
+  /// Access mode is determined by earlier calls via JsonFactory;
+  /// it may not be changed after construction.
+  ///
+  /// If non-blocking decoding is (@code true}, it is possible to call
+  /// \#getNonBlockingInputFeeder() to obtain object to use
+  /// for feeding input; otherwise (<code>false</code> returned)
+  /// input is read by blocking
+  ///@return True if this is a non-blocking ("asynchronous") parser
+  ///@since 2.9
+  bool canParseAsync() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_canParseAsync, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_getNonBlockingInputFeeder = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getNonBlockingInputFeeder",
+      r"()Lcom/fasterxml/jackson/core/async/NonBlockingInputFeeder;");
+
+  /// from: public com.fasterxml.jackson.core.async.NonBlockingInputFeeder getNonBlockingInputFeeder()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that will either return a feeder instance (if parser uses
+  /// non-blocking, aka asynchronous access); or <code>null</code> for
+  /// parsers that use blocking I/O.
+  ///@return Input feeder to use with non-blocking (async) parsing
+  ///@since 2.9
+  jni.JObject getNonBlockingInputFeeder() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getNonBlockingInputFeeder,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getReadCapabilities = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getReadCapabilities",
+      r"()Lcom/fasterxml/jackson/core/util/JacksonFeatureSet;");
+
+  /// from: public com.fasterxml.jackson.core.util.JacksonFeatureSet<com.fasterxml.jackson.core.StreamReadCapability> getReadCapabilities()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor for getting metadata on capabilities of this parser, based on
+  /// underlying data format being read (directly or indirectly).
+  ///@return Set of read capabilities for content to read via this parser
+  ///@since 2.12
+  jni.JObject getReadCapabilities() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getReadCapabilities,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_version = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"version", r"()Lcom/fasterxml/jackson/core/Version;");
+
+  /// from: public abstract com.fasterxml.jackson.core.Version version()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor for getting version of the core package, given a parser instance.
+  /// Left for sub-classes to implement.
+  ///@return Version of this generator (derived from version declared for
+  ///   {@code jackson-core} jar that contains the class
+  jni.JObject version() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_version, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_close =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"close", r"()V");
+
+  /// from: public abstract void close()
+  ///
+  /// Closes the parser so that no further iteration or data access
+  /// can be made; will also close the underlying input source
+  /// if parser either __owns__ the input source, or feature
+  /// Feature\#AUTO_CLOSE_SOURCE is enabled.
+  /// Whether parser owns the input source depends on factory
+  /// method that was used to construct instance (so check
+  /// com.fasterxml.jackson.core.JsonFactory for details,
+  /// but the general
+  /// idea is that if caller passes in closable resource (such
+  /// as InputStream or Reader) parser does NOT
+  /// own the source; but if it passes a reference (such as
+  /// java.io.File or java.net.URL and creates
+  /// stream or reader it does own them.
+  ///@throws IOException if there is either an underlying I/O problem
+  void close() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_close, jni.JniCallType.voidType, []).check();
+  }
+
+  static final _id_isClosed =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"isClosed", r"()Z");
+
+  /// from: public abstract boolean isClosed()
+  ///
+  /// Method that can be called to determine whether this parser
+  /// is closed or not. If it is closed, no new tokens can be
+  /// retrieved by calling \#nextToken (and the underlying
+  /// stream may be closed). Closing may be due to an explicit
+  /// call to \#close or because parser has encountered
+  /// end of input.
+  ///@return {@code True} if this parser instance has been closed
+  bool isClosed() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_isClosed, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_getParsingContext = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getParsingContext",
+      r"()Lcom/fasterxml/jackson/core/JsonStreamContext;");
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonStreamContext getParsingContext()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be used to access current parsing context reader
+  /// is in. There are 3 different types: root, array and object contexts,
+  /// with slightly different available information. Contexts are
+  /// hierarchically nested, and can be used for example for figuring
+  /// out part of the input document that correspond to specific
+  /// array or object (for highlighting purposes, or error reporting).
+  /// Contexts can also be used for simple xpath-like matching of
+  /// input, if so desired.
+  ///@return Stream input context (JsonStreamContext) associated with this parser
+  jni.JObject getParsingContext() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getParsingContext,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_currentLocation = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"currentLocation",
+      r"()Lcom/fasterxml/jackson/core/JsonLocation;");
+
+  /// from: public com.fasterxml.jackson.core.JsonLocation currentLocation()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that returns location of the last processed input unit (character
+  /// or byte) from the input;
+  /// usually for error reporting purposes.
+  ///
+  /// Note that the location is not guaranteed to be accurate (although most
+  /// implementation will try their best): some implementations may only
+  /// report specific boundary locations (start or end locations of tokens)
+  /// and others only return JsonLocation\#NA due to not having access
+  /// to input location information (when delegating actual decoding work
+  /// to other library)
+  ///@return Location of the last processed input unit (byte or character)
+  ///@since 2.13
+  jni.JObject currentLocation() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_currentLocation, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_currentTokenLocation = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"currentTokenLocation",
+      r"()Lcom/fasterxml/jackson/core/JsonLocation;");
+
+  /// from: public com.fasterxml.jackson.core.JsonLocation currentTokenLocation()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that return the __starting__ location of the current
+  /// (most recently returned)
+  /// token; that is, the position of the first input unit (character or byte) from input
+  /// that starts the current token.
+  ///
+  /// Note that the location is not guaranteed to be accurate (although most
+  /// implementation will try their best): some implementations may only
+  /// return JsonLocation\#NA due to not having access
+  /// to input location information (when delegating actual decoding work
+  /// to other library)
+  ///@return Starting location of the token parser currently points to
+  ///@since 2.13 (will eventually replace \#getTokenLocation)
+  jni.JObject currentTokenLocation() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_currentTokenLocation,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getCurrentLocation = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getCurrentLocation",
+      r"()Lcom/fasterxml/jackson/core/JsonLocation;");
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonLocation getCurrentLocation()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias for \#currentLocation(), to be deprecated in later
+  /// Jackson 2.x versions (and removed from Jackson 3.0).
+  ///@return Location of the last processed input unit (byte or character)
+  jni.JObject getCurrentLocation() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getCurrentLocation,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getTokenLocation = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getTokenLocation",
+      r"()Lcom/fasterxml/jackson/core/JsonLocation;");
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonLocation getTokenLocation()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias for \#currentTokenLocation(), to be deprecated in later
+  /// Jackson 2.x versions (and removed from Jackson 3.0).
+  ///@return Starting location of the token parser currently points to
+  jni.JObject getTokenLocation() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getTokenLocation,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_currentValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"currentValue", r"()Ljava/lang/Object;");
+
+  /// from: public java.lang.Object currentValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Helper method, usually equivalent to:
+  ///<code>
+  ///   getParsingContext().getCurrentValue();
+  ///</code>
+  ///
+  /// Note that "current value" is NOT populated (or used) by Streaming parser;
+  /// it is only used by higher-level data-binding functionality.
+  /// The reason it is included here is that it can be stored and accessed hierarchically,
+  /// and gets passed through data-binding.
+  ///@return "Current value" associated with the current input context (state) of this parser
+  ///@since 2.13 (added as replacement for older \#getCurrentValue()
+  jni.JObject currentValue() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_currentValue, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_assignCurrentValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"assignCurrentValue", r"(Ljava/lang/Object;)V");
+
+  /// from: public void assignCurrentValue(java.lang.Object v)
+  ///
+  /// Helper method, usually equivalent to:
+  ///<code>
+  ///   getParsingContext().setCurrentValue(v);
+  ///</code>
+  ///@param v Current value to assign for the current input context of this parser
+  ///@since 2.13 (added as replacement for older \#setCurrentValue
+  void assignCurrentValue(
+    jni.JObject v,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_assignCurrentValue,
+        jni.JniCallType.voidType,
+        [v.reference]).check();
+  }
+
+  static final _id_getCurrentValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getCurrentValue", r"()Ljava/lang/Object;");
+
+  /// from: public java.lang.Object getCurrentValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias for \#currentValue(), to be deprecated in later
+  /// Jackson 2.x versions (and removed from Jackson 3.0).
+  ///@return Location of the last processed input unit (byte or character)
+  jni.JObject getCurrentValue() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getCurrentValue, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setCurrentValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"setCurrentValue", r"(Ljava/lang/Object;)V");
+
+  /// from: public void setCurrentValue(java.lang.Object v)
+  ///
+  /// Alias for \#assignCurrentValue, to be deprecated in later
+  /// Jackson 2.x versions (and removed from Jackson 3.0).
+  ///@param v Current value to assign for the current input context of this parser
+  void setCurrentValue(
+    jni.JObject v,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_setCurrentValue,
+        jni.JniCallType.voidType, [v.reference]).check();
+  }
+
+  static final _id_releaseBuffered = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"releaseBuffered", r"(Ljava/io/OutputStream;)I");
+
+  /// from: public int releaseBuffered(java.io.OutputStream out)
+  ///
+  /// Method that can be called to push back any content that
+  /// has been read but not consumed by the parser. This is usually
+  /// done after reading all content of interest using parser.
+  /// Content is released by writing it to given stream if possible;
+  /// if underlying input is byte-based it can released, if not (char-based)
+  /// it can not.
+  ///@param out OutputStream to which buffered, undecoded content is written to
+  ///@return -1 if the underlying content source is not byte based
+  ///    (that is, input can not be sent to OutputStream;
+  ///    otherwise number of bytes released (0 if there was nothing to release)
+  ///@throws IOException if write to stream threw exception
+  int releaseBuffered(
+    jni.JObject out,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_releaseBuffered,
+        jni.JniCallType.intType, [out.reference]).integer;
+  }
+
+  static final _id_releaseBuffered1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"releaseBuffered", r"(Ljava/io/Writer;)I");
+
+  /// from: public int releaseBuffered(java.io.Writer w)
+  ///
+  /// Method that can be called to push back any content that
+  /// has been read but not consumed by the parser.
+  /// This is usually
+  /// done after reading all content of interest using parser.
+  /// Content is released by writing it to given writer if possible;
+  /// if underlying input is char-based it can released, if not (byte-based)
+  /// it can not.
+  ///@param w Writer to which buffered but unprocessed content is written to
+  ///@return -1 if the underlying content source is not char-based
+  ///    (that is, input can not be sent to Writer;
+  ///    otherwise number of chars released (0 if there was nothing to release)
+  ///@throws IOException if write using Writer threw exception
+  int releaseBuffered1(
+    jni.JObject w,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_releaseBuffered1,
+        jni.JniCallType.intType, [w.reference]).integer;
+  }
+
+  static final _id_enable = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"enable",
+      r"(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser enable(com.fasterxml.jackson.core.JsonParser.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling specified parser feature
+  /// (check Feature for list of features)
+  ///@param f Feature to enable
+  ///@return This parser, to allow call chaining
+  JsonParser enable(
+    JsonParser_Feature f,
+  ) {
+    return const $JsonParserType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_enable,
+        jni.JniCallType.objectType,
+        [f.reference]).object);
+  }
+
+  static final _id_disable = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"disable",
+      r"(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser disable(com.fasterxml.jackson.core.JsonParser.Feature f)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for disabling specified  feature
+  /// (check Feature for list of features)
+  ///@param f Feature to disable
+  ///@return This parser, to allow call chaining
+  JsonParser disable(
+    JsonParser_Feature f,
+  ) {
+    return const $JsonParserType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_disable,
+        jni.JniCallType.objectType,
+        [f.reference]).object);
+  }
+
+  static final _id_configure = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"configure",
+      r"(Lcom/fasterxml/jackson/core/JsonParser$Feature;Z)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser configure(com.fasterxml.jackson.core.JsonParser.Feature f, boolean state)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for enabling or disabling specified feature
+  /// (check Feature for list of features)
+  ///@param f Feature to enable or disable
+  ///@param state Whether to enable feature ({@code true}) or disable ({@code false})
+  ///@return This parser, to allow call chaining
+  JsonParser configure(
+    JsonParser_Feature f,
+    bool state,
+  ) {
+    return const $JsonParserType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_configure,
+        jni.JniCallType.objectType,
+        [f.reference, state ? 1 : 0]).object);
+  }
+
+  static final _id_isEnabled = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"isEnabled", r"(Lcom/fasterxml/jackson/core/JsonParser$Feature;)Z");
+
+  /// from: public boolean isEnabled(com.fasterxml.jackson.core.JsonParser.Feature f)
+  ///
+  /// Method for checking whether specified Feature is enabled.
+  ///@param f Feature to check
+  ///@return {@code True} if feature is enabled; {@code false} otherwise
+  bool isEnabled(
+    JsonParser_Feature f,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_isEnabled,
+        jni.JniCallType.booleanType, [f.reference]).boolean;
+  }
+
+  static final _id_isEnabled1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"isEnabled",
+      r"(Lcom/fasterxml/jackson/core/StreamReadFeature;)Z");
+
+  /// from: public boolean isEnabled(com.fasterxml.jackson.core.StreamReadFeature f)
+  ///
+  /// Method for checking whether specified Feature is enabled.
+  ///@param f Feature to check
+  ///@return {@code True} if feature is enabled; {@code false} otherwise
+  ///@since 2.10
+  bool isEnabled1(
+    jni.JObject f,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_isEnabled1,
+        jni.JniCallType.booleanType, [f.reference]).boolean;
+  }
+
+  static final _id_getFeatureMask = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getFeatureMask", r"()I");
+
+  /// from: public int getFeatureMask()
+  ///
+  /// Bulk access method for getting state of all standard Features.
+  ///@return Bit mask that defines current states of all standard Features.
+  ///@since 2.3
+  int getFeatureMask() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getFeatureMask, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_setFeatureMask = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"setFeatureMask",
+      r"(I)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser setFeatureMask(int mask)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Bulk set method for (re)setting states of all standard Features
+  ///@param mask Bit mask that defines set of features to enable
+  ///@return This parser, to allow call chaining
+  ///@since 2.3
+  ///@deprecated Since 2.7, use \#overrideStdFeatures(int, int) instead
+  JsonParser setFeatureMask(
+    int mask,
+  ) {
+    return const $JsonParserType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_setFeatureMask,
+        jni.JniCallType.objectType,
+        [jni.JValueInt(mask)]).object);
+  }
+
+  static final _id_overrideStdFeatures = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"overrideStdFeatures",
+      r"(II)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser overrideStdFeatures(int values, int mask)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Bulk set method for (re)setting states of features specified by <code>mask</code>.
+  /// Functionally equivalent to
+  ///<code>
+  ///    int oldState = getFeatureMask();
+  ///    int newState = (oldState &amp; ~mask) | (values &amp; mask);
+  ///    setFeatureMask(newState);
+  ///</code>
+  /// but preferred as this lets caller more efficiently specify actual changes made.
+  ///@param values Bit mask of set/clear state for features to change
+  ///@param mask Bit mask of features to change
+  ///@return This parser, to allow call chaining
+  ///@since 2.6
+  JsonParser overrideStdFeatures(
+    int values,
+    int mask,
+  ) {
+    return const $JsonParserType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_overrideStdFeatures,
+        jni.JniCallType.objectType,
+        [jni.JValueInt(values), jni.JValueInt(mask)]).object);
+  }
+
+  static final _id_getFormatFeatures = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getFormatFeatures", r"()I");
+
+  /// from: public int getFormatFeatures()
+  ///
+  /// Bulk access method for getting state of all FormatFeatures, format-specific
+  /// on/off configuration settings.
+  ///@return Bit mask that defines current states of all standard FormatFeatures.
+  ///@since 2.6
+  int getFormatFeatures() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getFormatFeatures, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_overrideFormatFeatures = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"overrideFormatFeatures",
+      r"(II)Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public com.fasterxml.jackson.core.JsonParser overrideFormatFeatures(int values, int mask)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Bulk set method for (re)setting states of FormatFeatures,
+  /// by specifying values (set / clear) along with a mask, to determine
+  /// which features to change, if any.
+  ///
+  /// Default implementation will simply throw an exception to indicate that
+  /// the parser implementation does not support any FormatFeatures.
+  ///@param values Bit mask of set/clear state for features to change
+  ///@param mask Bit mask of features to change
+  ///@return This parser, to allow call chaining
+  ///@since 2.6
+  JsonParser overrideFormatFeatures(
+    int values,
+    int mask,
+  ) {
+    return const $JsonParserType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_overrideFormatFeatures,
+        jni.JniCallType.objectType,
+        [jni.JValueInt(values), jni.JValueInt(mask)]).object);
+  }
+
+  static final _id_nextToken = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"nextToken", r"()Lcom/fasterxml/jackson/core/JsonToken;");
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonToken nextToken()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Main iteration method, which will advance stream enough
+  /// to determine type of the next token, if any. If none
+  /// remaining (stream has no content other than possible
+  /// white space before ending), null will be returned.
+  ///@return Next token from the stream, if any found, or null
+  ///   to indicate end-of-input
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jsontoken_.JsonToken nextToken() {
+    return const jsontoken_.$JsonTokenType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(
+            reference, _id_nextToken, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_nextValue = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"nextValue", r"()Lcom/fasterxml/jackson/core/JsonToken;");
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonToken nextValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Iteration method that will advance stream enough
+  /// to determine type of the next token that is a value type
+  /// (including JSON Array and Object start/end markers).
+  /// Or put another way, nextToken() will be called once,
+  /// and if JsonToken\#FIELD_NAME is returned, another
+  /// time to get the value for the field.
+  /// Method is most useful for iterating over value entries
+  /// of JSON objects; field name will still be available
+  /// by calling \#getCurrentName when parser points to
+  /// the value.
+  ///@return Next non-field-name token from the stream, if any found,
+  ///   or null to indicate end-of-input (or, for non-blocking
+  ///   parsers, JsonToken\#NOT_AVAILABLE if no tokens were
+  ///   available yet)
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jsontoken_.JsonToken nextValue() {
+    return const jsontoken_.$JsonTokenType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(
+            reference, _id_nextValue, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_nextFieldName = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"nextFieldName",
+      r"(Lcom/fasterxml/jackson/core/SerializableString;)Z");
+
+  /// from: public boolean nextFieldName(com.fasterxml.jackson.core.SerializableString str)
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// verifies whether it is JsonToken\#FIELD_NAME with specified name
+  /// and returns result of that comparison.
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  return (nextToken() == JsonToken.FIELD_NAME) &amp;&amp; str.getValue().equals(getCurrentName());
+  ///</pre>
+  /// but may be faster for parser to verify, and can therefore be used if caller
+  /// expects to get such a property name from input next.
+  ///@param str Property name to compare next token to (if next token is
+  ///   <code>JsonToken.FIELD_NAME</code>)
+  ///@return {@code True} if parser advanced to {@code JsonToken.FIELD_NAME} with
+  ///    specified name; {@code false} otherwise (different token or non-matching name)
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  bool nextFieldName(
+    jni.JObject str,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_nextFieldName,
+        jni.JniCallType.booleanType, [str.reference]).boolean;
+  }
+
+  static final _id_nextFieldName1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"nextFieldName", r"()Ljava/lang/String;");
+
+  /// from: public java.lang.String nextFieldName()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// verifies whether it is JsonToken\#FIELD_NAME; if it is,
+  /// returns same as \#getCurrentName(), otherwise null.
+  ///@return Name of the the {@code JsonToken.FIELD_NAME} parser advanced to, if any;
+  ///   {@code null} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.5
+  jni.JString nextFieldName1() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_nextFieldName1, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_nextTextValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"nextTextValue", r"()Ljava/lang/String;");
+
+  /// from: public java.lang.String nextTextValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// if it is JsonToken\#VALUE_STRING returns contained String value;
+  /// otherwise returns null.
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
+  ///</pre>
+  /// but may be faster for parser to process, and can therefore be used if caller
+  /// expects to get a String value next from input.
+  ///@return Text value of the {@code JsonToken.VALUE_STRING} token parser advanced
+  ///   to; or {@code null} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JString nextTextValue() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_nextTextValue, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_nextIntValue = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"nextIntValue", r"(I)I");
+
+  /// from: public int nextIntValue(int defaultValue)
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// if it is JsonToken\#VALUE_NUMBER_INT returns 32-bit int value;
+  /// otherwise returns specified default value
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getIntValue() : defaultValue;
+  ///</pre>
+  /// but may be faster for parser to process, and can therefore be used if caller
+  /// expects to get an int value next from input.
+  ///
+  /// NOTE: value checks are performed similar to \#getIntValue()
+  ///@param defaultValue Value to return if next token is NOT of type {@code JsonToken.VALUE_NUMBER_INT}
+  ///@return Integer ({@code int}) value of the {@code JsonToken.VALUE_NUMBER_INT} token parser advanced
+  ///   to; or {@code defaultValue} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@throws InputCoercionException if integer number does not fit in Java {@code int}
+  int nextIntValue(
+    int defaultValue,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_nextIntValue,
+        jni.JniCallType.intType, [jni.JValueInt(defaultValue)]).integer;
+  }
+
+  static final _id_nextLongValue = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"nextLongValue", r"(J)J");
+
+  /// from: public long nextLongValue(long defaultValue)
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// if it is JsonToken\#VALUE_NUMBER_INT returns 64-bit long value;
+  /// otherwise returns specified default value
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getLongValue() : defaultValue;
+  ///</pre>
+  /// but may be faster for parser to process, and can therefore be used if caller
+  /// expects to get a long value next from input.
+  ///
+  /// NOTE: value checks are performed similar to \#getLongValue()
+  ///@param defaultValue Value to return if next token is NOT of type {@code JsonToken.VALUE_NUMBER_INT}
+  ///@return {@code long} value of the {@code JsonToken.VALUE_NUMBER_INT} token parser advanced
+  ///   to; or {@code defaultValue} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@throws InputCoercionException if integer number does not fit in Java {@code long}
+  int nextLongValue(
+    int defaultValue,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_nextLongValue,
+        jni.JniCallType.longType, [defaultValue]).long;
+  }
+
+  static final _id_nextBooleanValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"nextBooleanValue", r"()Ljava/lang/Boolean;");
+
+  /// from: public java.lang.Boolean nextBooleanValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that fetches next token (as if calling \#nextToken) and
+  /// if it is JsonToken\#VALUE_TRUE or JsonToken\#VALUE_FALSE
+  /// returns matching Boolean value; otherwise return null.
+  /// It is functionally equivalent to:
+  ///<pre>
+  ///  JsonToken t = nextToken();
+  ///  if (t == JsonToken.VALUE_TRUE) return Boolean.TRUE;
+  ///  if (t == JsonToken.VALUE_FALSE) return Boolean.FALSE;
+  ///  return null;
+  ///</pre>
+  /// but may be faster for parser to process, and can therefore be used if caller
+  /// expects to get a Boolean value next from input.
+  ///@return {@code Boolean} value of the {@code JsonToken.VALUE_TRUE} or {@code JsonToken.VALUE_FALSE}
+  ///   token parser advanced to; or {@code null} if next token is of some other type
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JBoolean nextBooleanValue() {
+    return const jni.JBooleanType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_nextBooleanValue,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_skipChildren = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"skipChildren",
+      r"()Lcom/fasterxml/jackson/core/JsonParser;");
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonParser skipChildren()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that will skip all child tokens of an array or
+  /// object token that the parser currently points to,
+  /// iff stream points to
+  /// JsonToken\#START_OBJECT or JsonToken\#START_ARRAY.
+  /// If not, it will do nothing.
+  /// After skipping, stream will point to __matching__
+  /// JsonToken\#END_OBJECT or JsonToken\#END_ARRAY
+  /// (possibly skipping nested pairs of START/END OBJECT/ARRAY tokens
+  /// as well as value tokens).
+  /// The idea is that after calling this method, application
+  /// will call \#nextToken to point to the next
+  /// available token, if any.
+  ///@return This parser, to allow call chaining
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  JsonParser skipChildren() {
+    return const $JsonParserType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_skipChildren, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_finishToken =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"finishToken", r"()V");
+
+  /// from: public void finishToken()
+  ///
+  /// Method that may be used to force full handling of the current token
+  /// so that even if lazy processing is enabled, the whole contents are
+  /// read for possible retrieval. This is usually used to ensure that
+  /// the token end location is available, as well as token contents
+  /// (similar to what calling, say \#getTextCharacters(), would
+  /// achieve).
+  ///
+  /// Note that for many dataformat implementations this method
+  /// will not do anything; this is the default implementation unless
+  /// overridden by sub-classes.
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.8
+  void finishToken() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_finishToken, jni.JniCallType.voidType, []).check();
+  }
+
+  static final _id_currentToken = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"currentToken",
+      r"()Lcom/fasterxml/jackson/core/JsonToken;");
+
+  /// from: public com.fasterxml.jackson.core.JsonToken currentToken()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor to find which token parser currently points to, if any;
+  /// null will be returned if none.
+  /// If return value is non-null, data associated with the token
+  /// is available via other accessor methods.
+  ///@return Type of the token this parser currently points to,
+  ///   if any: null before any tokens have been read, and
+  ///   after end-of-input has been encountered, as well as
+  ///   if the current token has been explicitly cleared.
+  ///@since 2.8
+  jsontoken_.JsonToken currentToken() {
+    return const jsontoken_.$JsonTokenType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_currentToken,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_currentTokenId = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"currentTokenId", r"()I");
+
+  /// from: public int currentTokenId()
+  ///
+  /// Method similar to \#getCurrentToken() but that returns an
+  /// <code>int</code> instead of JsonToken (enum value).
+  ///
+  /// Use of int directly is typically more efficient on switch statements,
+  /// so this method may be useful when building low-overhead codecs.
+  /// Note, however, that effect may not be big enough to matter: make sure
+  /// to profile performance before deciding to use this method.
+  ///@since 2.8
+  ///@return {@code int} matching one of constants from JsonTokenId.
+  int currentTokenId() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_currentTokenId, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_getCurrentToken = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getCurrentToken",
+      r"()Lcom/fasterxml/jackson/core/JsonToken;");
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonToken getCurrentToken()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias for \#currentToken(), may be deprecated sometime after
+  /// Jackson 2.13 (will be removed from 3.0).
+  ///@return Type of the token this parser currently points to,
+  ///   if any: null before any tokens have been read, and
+  jsontoken_.JsonToken getCurrentToken() {
+    return const jsontoken_.$JsonTokenType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_getCurrentToken,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getCurrentTokenId = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getCurrentTokenId", r"()I");
+
+  /// from: public abstract int getCurrentTokenId()
+  ///
+  /// Deprecated alias for \#currentTokenId().
+  ///@return {@code int} matching one of constants from JsonTokenId.
+  ///@deprecated Since 2.12 use \#currentTokenId instead
+  int getCurrentTokenId() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getCurrentTokenId, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_hasCurrentToken = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"hasCurrentToken", r"()Z");
+
+  /// from: public abstract boolean hasCurrentToken()
+  ///
+  /// Method for checking whether parser currently points to
+  /// a token (and data for that token is available).
+  /// Equivalent to check for <code>parser.getCurrentToken() != null</code>.
+  ///@return True if the parser just returned a valid
+  ///   token via \#nextToken; false otherwise (parser
+  ///   was just constructed, encountered end-of-input
+  ///   and returned null from \#nextToken, or the token
+  ///   has been consumed)
+  bool hasCurrentToken() {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_hasCurrentToken,
+        jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_hasTokenId =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"hasTokenId", r"(I)Z");
+
+  /// from: public abstract boolean hasTokenId(int id)
+  ///
+  /// Method that is functionally equivalent to:
+  ///<code>
+  ///  return currentTokenId() == id
+  ///</code>
+  /// but may be more efficiently implemented.
+  ///
+  /// Note that no traversal or conversion is performed; so in some
+  /// cases calling method like \#isExpectedStartArrayToken()
+  /// is necessary instead.
+  ///@param id Token id to match (from (@link JsonTokenId})
+  ///@return {@code True} if the parser current points to specified token
+  ///@since 2.5
+  bool hasTokenId(
+    int id,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_hasTokenId,
+        jni.JniCallType.booleanType, [jni.JValueInt(id)]).boolean;
+  }
+
+  static final _id_hasToken = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"hasToken", r"(Lcom/fasterxml/jackson/core/JsonToken;)Z");
+
+  /// from: public abstract boolean hasToken(com.fasterxml.jackson.core.JsonToken t)
+  ///
+  /// Method that is functionally equivalent to:
+  ///<code>
+  ///  return currentToken() == t
+  ///</code>
+  /// but may be more efficiently implemented.
+  ///
+  /// Note that no traversal or conversion is performed; so in some
+  /// cases calling method like \#isExpectedStartArrayToken()
+  /// is necessary instead.
+  ///@param t Token to match
+  ///@return {@code True} if the parser current points to specified token
+  ///@since 2.6
+  bool hasToken(
+    jsontoken_.JsonToken t,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_hasToken,
+        jni.JniCallType.booleanType, [t.reference]).boolean;
+  }
+
+  static final _id_isExpectedStartArrayToken = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"isExpectedStartArrayToken", r"()Z");
+
+  /// from: public boolean isExpectedStartArrayToken()
+  ///
+  /// Specialized accessor that can be used to verify that the current
+  /// token indicates start array (usually meaning that current token
+  /// is JsonToken\#START_ARRAY) when start array is expected.
+  /// For some specialized parsers this can return true for other cases
+  /// as well; this is usually done to emulate arrays in cases underlying
+  /// format is ambiguous (XML, for example, has no format-level difference
+  /// between Objects and Arrays; it just has elements).
+  ///
+  /// Default implementation is equivalent to:
+  ///<pre>
+  ///   currentToken() == JsonToken.START_ARRAY
+  ///</pre>
+  /// but may be overridden by custom parser implementations.
+  ///@return True if the current token can be considered as a
+  ///   start-array marker (such JsonToken\#START_ARRAY);
+  ///   {@code false} if not
+  bool isExpectedStartArrayToken() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_isExpectedStartArrayToken, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_isExpectedStartObjectToken = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"isExpectedStartObjectToken", r"()Z");
+
+  /// from: public boolean isExpectedStartObjectToken()
+  ///
+  /// Similar to \#isExpectedStartArrayToken(), but checks whether stream
+  /// currently points to JsonToken\#START_OBJECT.
+  ///@return True if the current token can be considered as a
+  ///   start-array marker (such JsonToken\#START_OBJECT);
+  ///   {@code false} if not
+  ///@since 2.5
+  bool isExpectedStartObjectToken() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_isExpectedStartObjectToken,
+        jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_isExpectedNumberIntToken = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"isExpectedNumberIntToken", r"()Z");
+
+  /// from: public boolean isExpectedNumberIntToken()
+  ///
+  /// Similar to \#isExpectedStartArrayToken(), but checks whether stream
+  /// currently points to JsonToken\#VALUE_NUMBER_INT.
+  ///
+  /// The initial use case is for XML backend to efficiently (attempt to) coerce
+  /// textual content into numbers.
+  ///@return True if the current token can be considered as a
+  ///   start-array marker (such JsonToken\#VALUE_NUMBER_INT);
+  ///   {@code false} if not
+  ///@since 2.12
+  bool isExpectedNumberIntToken() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_isExpectedNumberIntToken, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_isNaN =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"isNaN", r"()Z");
+
+  /// from: public boolean isNaN()
+  ///
+  /// Access for checking whether current token is a numeric value token, but
+  /// one that is of "not-a-number" (NaN) variety (including both "NaN" AND
+  /// positive/negative infinity!): not supported by all formats,
+  /// but often supported for JsonToken\#VALUE_NUMBER_FLOAT.
+  /// NOTE: roughly equivalent to calling <code>!Double.isFinite()</code>
+  /// on value you would get from calling \#getDoubleValue().
+  ///@return {@code True} if the current token is of type JsonToken\#VALUE_NUMBER_FLOAT
+  ///   but represents a "Not a Number"; {@code false} for other tokens and regular
+  ///   floating-point numbers
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.9
+  bool isNaN() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_isNaN, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_clearCurrentToken = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"clearCurrentToken", r"()V");
+
+  /// from: public abstract void clearCurrentToken()
+  ///
+  /// Method called to "consume" the current token by effectively
+  /// removing it so that \#hasCurrentToken returns false, and
+  /// \#getCurrentToken null).
+  /// Cleared token value can still be accessed by calling
+  /// \#getLastClearedToken (if absolutely needed), but
+  /// usually isn't.
+  ///
+  /// Method was added to be used by the optional data binder, since
+  /// it has to be able to consume last token used for binding (so that
+  /// it will not be used again).
+  void clearCurrentToken() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_clearCurrentToken, jni.JniCallType.voidType, []).check();
+  }
+
+  static final _id_getLastClearedToken = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getLastClearedToken",
+      r"()Lcom/fasterxml/jackson/core/JsonToken;");
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonToken getLastClearedToken()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be called to get the last token that was
+  /// cleared using \#clearCurrentToken. This is not necessarily
+  /// the latest token read.
+  /// Will return null if no tokens have been cleared,
+  /// or if parser has been closed.
+  ///@return Last cleared token, if any; {@code null} otherwise
+  jsontoken_.JsonToken getLastClearedToken() {
+    return const jsontoken_.$JsonTokenType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_getLastClearedToken,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_overrideCurrentName = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"overrideCurrentName", r"(Ljava/lang/String;)V");
+
+  /// from: public abstract void overrideCurrentName(java.lang.String name)
+  ///
+  /// Method that can be used to change what is considered to be
+  /// the current (field) name.
+  /// May be needed to support non-JSON data formats or unusual binding
+  /// conventions; not needed for typical processing.
+  ///
+  /// Note that use of this method should only be done as sort of last
+  /// resort, as it is a work-around for regular operation.
+  ///@param name Name to use as the current name; may be null.
+  void overrideCurrentName(
+    jni.JString name,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_overrideCurrentName,
+        jni.JniCallType.voidType,
+        [name.reference]).check();
+  }
+
+  static final _id_getCurrentName = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getCurrentName", r"()Ljava/lang/String;");
+
+  /// from: public abstract java.lang.String getCurrentName()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Alias of \#currentName().
+  ///@return Name of the current field in the parsing context
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JString getCurrentName() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getCurrentName, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_currentName = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"currentName", r"()Ljava/lang/String;");
+
+  /// from: public java.lang.String currentName()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be called to get the name associated with
+  /// the current token: for JsonToken\#FIELD_NAMEs it will
+  /// be the same as what \#getText returns;
+  /// for field values it will be preceding field name;
+  /// and for others (array values, root-level values) null.
+  ///@return Name of the current field in the parsing context
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.10
+  jni.JString currentName() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_currentName, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getText = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getText", r"()Ljava/lang/String;");
+
+  /// from: public abstract java.lang.String getText()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for accessing textual representation of the current token;
+  /// if no current token (before first call to \#nextToken, or
+  /// after encountering end-of-input), returns null.
+  /// Method can be called for any token type.
+  ///@return Textual value associated with the current token (one returned
+  ///   by \#nextToken() or other iteration methods)
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JString getText() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getText, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getText1 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getText", r"(Ljava/io/Writer;)I");
+
+  /// from: public int getText(java.io.Writer writer)
+  ///
+  /// Method to read the textual representation of the current token in chunks and
+  /// pass it to the given Writer.
+  /// Conceptually same as calling:
+  ///<pre>
+  ///  writer.write(parser.getText());
+  ///</pre>
+  /// but should typically be more efficient as longer content does need to
+  /// be combined into a single <code>String</code> to return, and write
+  /// can occur directly from intermediate buffers Jackson uses.
+  ///@param writer Writer to write textual content to
+  ///@return The number of characters written to the Writer
+  ///@throws IOException for low-level read issues or writes using passed
+  ///   {@code writer}, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.8
+  int getText1(
+    jni.JObject writer,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_getText1,
+        jni.JniCallType.intType, [writer.reference]).integer;
+  }
+
+  static final _id_getTextCharacters = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getTextCharacters", r"()[C");
+
+  /// from: public abstract char[] getTextCharacters()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method similar to \#getText, but that will return
+  /// underlying (unmodifiable) character array that contains
+  /// textual value, instead of constructing a String object
+  /// to contain this information.
+  /// Note, however, that:
+  ///<ul>
+  /// <li>Textual contents are not guaranteed to start at
+  ///   index 0 (rather, call \#getTextOffset) to
+  ///   know the actual offset
+  ///  </li>
+  /// <li>Length of textual contents may be less than the
+  ///  length of returned buffer: call \#getTextLength
+  ///  for actual length of returned content.
+  ///  </li>
+  /// </ul>
+  ///
+  /// Note that caller __MUST NOT__ modify the returned
+  /// character array in any way -- doing so may corrupt
+  /// current parser state and render parser instance useless.
+  ///
+  /// The only reason to call this method (over \#getText)
+  /// is to avoid construction of a String object (which
+  /// will make a copy of contents).
+  ///@return Buffer that contains the current textual value (but not necessarily
+  ///    at offset 0, and not necessarily until the end of buffer)
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JArray<jni.jchar> getTextCharacters() {
+    return const jni.JArrayType(jni.jcharType()).fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_getTextCharacters,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getTextLength = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getTextLength", r"()I");
+
+  /// from: public abstract int getTextLength()
+  ///
+  /// Accessor used with \#getTextCharacters, to know length
+  /// of String stored in returned buffer.
+  ///@return Number of characters within buffer returned
+  ///   by \#getTextCharacters that are part of
+  ///   textual content of the current token.
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getTextLength() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getTextLength, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_getTextOffset = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getTextOffset", r"()I");
+
+  /// from: public abstract int getTextOffset()
+  ///
+  /// Accessor used with \#getTextCharacters, to know offset
+  /// of the first text content character within buffer.
+  ///@return Offset of the first character within buffer returned
+  ///   by \#getTextCharacters that is part of
+  ///   textual content of the current token.
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getTextOffset() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getTextOffset, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_hasTextCharacters = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"hasTextCharacters", r"()Z");
+
+  /// from: public abstract boolean hasTextCharacters()
+  ///
+  /// Method that can be used to determine whether calling of
+  /// \#getTextCharacters would be the most efficient
+  /// way to access textual content for the event parser currently
+  /// points to.
+  ///
+  /// Default implementation simply returns false since only actual
+  /// implementation class has knowledge of its internal buffering
+  /// state.
+  /// Implementations are strongly encouraged to properly override
+  /// this method, to allow efficient copying of content by other
+  /// code.
+  ///@return True if parser currently has character array that can
+  ///   be efficiently returned via \#getTextCharacters; false
+  ///   means that it may or may not exist
+  bool hasTextCharacters() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_hasTextCharacters, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_getNumberValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getNumberValue", r"()Ljava/lang/Number;");
+
+  /// from: public abstract java.lang.Number getNumberValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Generic number value accessor method that will work for
+  /// all kinds of numeric values. It will return the optimal
+  /// (simplest/smallest possible) wrapper object that can
+  /// express the numeric value just parsed.
+  ///@return Numeric value of the current token in its most optimal
+  ///   representation
+  ///@throws IOException Problem with access: JsonParseException if
+  ///    the current token is not numeric, or if decoding of the value fails
+  ///    (invalid format for numbers); plain IOException if underlying
+  ///    content read fails (possible if values are extracted lazily)
+  jni.JNumber getNumberValue() {
+    return const jni.JNumberType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getNumberValue, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getNumberValueExact = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getNumberValueExact", r"()Ljava/lang/Number;");
+
+  /// from: public java.lang.Number getNumberValueExact()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method similar to \#getNumberValue with the difference that
+  /// for floating-point numbers value returned may be BigDecimal
+  /// if the underlying format does not store floating-point numbers using
+  /// native representation: for example, textual formats represent numbers
+  /// as Strings (which are 10-based), and conversion to java.lang.Double
+  /// is potentially lossy operation.
+  ///
+  /// Default implementation simply returns \#getNumberValue()
+  ///@return Numeric value of the current token using most accurate representation
+  ///@throws IOException Problem with access: JsonParseException if
+  ///    the current token is not numeric, or if decoding of the value fails
+  ///    (invalid format for numbers); plain IOException if underlying
+  ///    content read fails (possible if values are extracted lazily)
+  ///@since 2.12
+  jni.JNumber getNumberValueExact() {
+    return const jni.JNumberType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getNumberValueExact,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getNumberType = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getNumberType",
+      r"()Lcom/fasterxml/jackson/core/JsonParser$NumberType;");
+
+  /// from: public abstract com.fasterxml.jackson.core.JsonParser.NumberType getNumberType()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// If current token is of type
+  /// JsonToken\#VALUE_NUMBER_INT or
+  /// JsonToken\#VALUE_NUMBER_FLOAT, returns
+  /// one of NumberType constants; otherwise returns null.
+  ///@return Type of current number, if parser points to numeric token; {@code null} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  JsonParser_NumberType getNumberType() {
+    return const $JsonParser_NumberTypeType().fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_getNumberType,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getByteValue = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getByteValue", r"()B");
+
+  /// from: public byte getByteValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can be expressed as a value of Java byte primitive type.
+  /// Note that in addition to "natural" input range of {@code [-128, 127]},
+  /// this also allows "unsigned 8-bit byte" values {@code [128, 255]}:
+  /// but for this range value will be translated by truncation, leading
+  /// to sign change.
+  ///
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDoubleValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the resulting integer value falls outside range of
+  /// {@code [-128, 255]},
+  /// a InputCoercionException
+  /// will be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code byte} (if numeric token within
+  ///   range of {@code [-128, 255]}); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getByteValue() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getByteValue, jni.JniCallType.byteType, []).byte;
+  }
+
+  static final _id_getShortValue = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getShortValue", r"()S");
+
+  /// from: public short getShortValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can be expressed as a value of Java short primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDoubleValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the resulting integer value falls outside range of
+  /// Java short, a InputCoercionException
+  /// will be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code short} (if numeric token within
+  ///   Java 16-bit signed {@code short} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getShortValue() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getShortValue, jni.JniCallType.shortType, []).short;
+  }
+
+  static final _id_getIntValue =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"getIntValue", r"()I");
+
+  /// from: public abstract int getIntValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can be expressed as a value of Java int primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDoubleValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the resulting integer value falls outside range of
+  /// Java {@code int}, a InputCoercionException
+  /// may be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code int} (if numeric token within
+  ///   Java 32-bit signed {@code int} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getIntValue() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getIntValue, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_getLongValue = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getLongValue", r"()J");
+
+  /// from: public abstract long getLongValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can be expressed as a Java long primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDoubleValue
+  /// and then casting to int; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the token is an integer, but its value falls
+  /// outside of range of Java long, a InputCoercionException
+  /// may be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code long} (if numeric token within
+  ///   Java 32-bit signed {@code long} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getLongValue() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getLongValue, jni.JniCallType.longType, []).long;
+  }
+
+  static final _id_getBigIntegerValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getBigIntegerValue", r"()Ljava/math/BigInteger;");
+
+  /// from: public abstract java.math.BigInteger getBigIntegerValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_INT and
+  /// it can not be used as a Java long primitive type due to its
+  /// magnitude.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_FLOAT;
+  /// if so, it is equivalent to calling \#getDecimalValue
+  /// and then constructing a BigInteger from that value.
+  ///@return Current number value as BigInteger (if numeric token);
+  ///     otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JObject getBigIntegerValue() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getBigIntegerValue,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getFloatValue = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getFloatValue", r"()F");
+
+  /// from: public abstract float getFloatValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_FLOAT and
+  /// it can be expressed as a Java float primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_INT;
+  /// if so, it is equivalent to calling \#getLongValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the value falls
+  /// outside of range of Java float, a InputCoercionException
+  /// will be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code float} (if numeric token within
+  ///   Java {@code float} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  double getFloatValue() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getFloatValue, jni.JniCallType.floatType, []).float;
+  }
+
+  static final _id_getDoubleValue = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getDoubleValue", r"()D");
+
+  /// from: public abstract double getDoubleValue()
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_FLOAT and
+  /// it can be expressed as a Java double primitive type.
+  /// It can also be called for JsonToken\#VALUE_NUMBER_INT;
+  /// if so, it is equivalent to calling \#getLongValue
+  /// and then casting; except for possible overflow/underflow
+  /// exception.
+  ///
+  /// Note: if the value falls
+  /// outside of range of Java double, a InputCoercionException
+  /// will be thrown to indicate numeric overflow/underflow.
+  ///@return Current number value as {@code double} (if numeric token within
+  ///   Java {@code double} range); otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  double getDoubleValue() {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_getDoubleValue,
+        jni.JniCallType.doubleType, []).doubleFloat;
+  }
+
+  static final _id_getDecimalValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getDecimalValue", r"()Ljava/math/BigDecimal;");
+
+  /// from: public abstract java.math.BigDecimal getDecimalValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Numeric accessor that can be called when the current
+  /// token is of type JsonToken\#VALUE_NUMBER_FLOAT or
+  /// JsonToken\#VALUE_NUMBER_INT. No under/overflow exceptions
+  /// are ever thrown.
+  ///@return Current number value as BigDecimal (if numeric token);
+  ///   otherwise exception thrown
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JObject getDecimalValue() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getDecimalValue, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getBooleanValue = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getBooleanValue", r"()Z");
+
+  /// from: public boolean getBooleanValue()
+  ///
+  /// Convenience accessor that can be called when the current
+  /// token is JsonToken\#VALUE_TRUE or
+  /// JsonToken\#VALUE_FALSE, to return matching {@code boolean}
+  /// value.
+  /// If the current token is of some other type, JsonParseException
+  /// will be thrown
+  ///@return {@code True} if current token is {@code JsonToken.VALUE_TRUE},
+  ///   {@code false} if current token is {@code JsonToken.VALUE_FALSE};
+  ///   otherwise throws JsonParseException
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  bool getBooleanValue() {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_getBooleanValue,
+        jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_getEmbeddedObject = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getEmbeddedObject", r"()Ljava/lang/Object;");
+
+  /// from: public java.lang.Object getEmbeddedObject()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Accessor that can be called if (and only if) the current token
+  /// is JsonToken\#VALUE_EMBEDDED_OBJECT. For other token types,
+  /// null is returned.
+  ///
+  /// Note: only some specialized parser implementations support
+  /// embedding of objects (usually ones that are facades on top
+  /// of non-streaming sources, such as object trees). One exception
+  /// is access to binary content (whether via base64 encoding or not)
+  /// which typically is accessible using this method, as well as
+  /// \#getBinaryValue().
+  ///@return Embedded value (usually of "native" type supported by format)
+  ///   for the current token, if any; {@code null otherwise}
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JObject getEmbeddedObject() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getEmbeddedObject,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getBinaryValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getBinaryValue",
+      r"(Lcom/fasterxml/jackson/core/Base64Variant;)[B");
+
+  /// from: public abstract byte[] getBinaryValue(com.fasterxml.jackson.core.Base64Variant bv)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be used to read (and consume -- results
+  /// may not be accessible using other methods after the call)
+  /// base64-encoded binary data
+  /// included in the current textual JSON value.
+  /// It works similar to getting String value via \#getText
+  /// and decoding result (except for decoding part),
+  /// but should be significantly more performant.
+  ///
+  /// Note that non-decoded textual contents of the current token
+  /// are not guaranteed to be accessible after this method
+  /// is called. Current implementation, for example, clears up
+  /// textual content during decoding.
+  /// Decoded binary content, however, will be retained until
+  /// parser is advanced to the next event.
+  ///@param bv Expected variant of base64 encoded
+  ///   content (see Base64Variants for definitions
+  ///   of "standard" variants).
+  ///@return Decoded binary data
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JArray<jni.jbyte> getBinaryValue(
+    jni.JObject bv,
+  ) {
+    return const jni.JArrayType(jni.jbyteType()).fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_getBinaryValue,
+            jni.JniCallType.objectType, [bv.reference]).object);
+  }
+
+  static final _id_getBinaryValue1 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getBinaryValue", r"()[B");
+
+  /// from: public byte[] getBinaryValue()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Convenience alternative to \#getBinaryValue(Base64Variant)
+  /// that defaults to using
+  /// Base64Variants\#getDefaultVariant as the default encoding.
+  ///@return Decoded binary data
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  jni.JArray<jni.jbyte> getBinaryValue1() {
+    return const jni.JArrayType(jni.jbyteType()).fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_getBinaryValue1,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_readBinaryValue = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"readBinaryValue", r"(Ljava/io/OutputStream;)I");
+
+  /// from: public int readBinaryValue(java.io.OutputStream out)
+  ///
+  /// Method that can be used as an alternative to \#getBigIntegerValue(),
+  /// especially when value can be large. The main difference (beyond method
+  /// of returning content using OutputStream instead of as byte array)
+  /// is that content will NOT remain accessible after method returns: any content
+  /// processed will be consumed and is not buffered in any way. If caller needs
+  /// buffering, it has to implement it.
+  ///@param out Output stream to use for passing decoded binary data
+  ///@return Number of bytes that were decoded and written via OutputStream
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.1
+  int readBinaryValue(
+    jni.JObject out,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_readBinaryValue,
+        jni.JniCallType.intType, [out.reference]).integer;
+  }
+
+  static final _id_readBinaryValue1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"readBinaryValue",
+      r"(Lcom/fasterxml/jackson/core/Base64Variant;Ljava/io/OutputStream;)I");
+
+  /// from: public int readBinaryValue(com.fasterxml.jackson.core.Base64Variant bv, java.io.OutputStream out)
+  ///
+  /// Similar to \#readBinaryValue(OutputStream) but allows explicitly
+  /// specifying base64 variant to use.
+  ///@param bv base64 variant to use
+  ///@param out Output stream to use for passing decoded binary data
+  ///@return Number of bytes that were decoded and written via OutputStream
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.1
+  int readBinaryValue1(
+    jni.JObject bv,
+    jni.JObject out,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_readBinaryValue1,
+        jni.JniCallType.intType, [bv.reference, out.reference]).integer;
+  }
+
+  static final _id_getValueAsInt = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getValueAsInt", r"()I");
+
+  /// from: public int getValueAsInt()
+  ///
+  /// Method that will try to convert value of current token to a
+  /// Java {@code int} value.
+  /// Numbers are coerced using default Java rules; booleans convert to 0 (false)
+  /// and 1 (true), and Strings are parsed using default Java language integer
+  /// parsing rules.
+  ///
+  /// If representation can not be converted to an int (including structured type
+  /// markers like start/end Object/Array)
+  /// default value of __0__ will be returned; no exceptions are thrown.
+  ///@return {@code int} value current token is converted to, if possible; exception thrown
+  ///    otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getValueAsInt() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getValueAsInt, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_getValueAsInt1 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getValueAsInt", r"(I)I");
+
+  /// from: public int getValueAsInt(int def)
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __int__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0 (false)
+  /// and 1 (true), and Strings are parsed using default Java language integer
+  /// parsing rules.
+  ///
+  /// If representation can not be converted to an int (including structured type
+  /// markers like start/end Object/Array)
+  /// specified __def__ will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code int} is not possible
+  ///@return {@code int} value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getValueAsInt1(
+    int def,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_getValueAsInt1,
+        jni.JniCallType.intType, [jni.JValueInt(def)]).integer;
+  }
+
+  static final _id_getValueAsLong = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getValueAsLong", r"()J");
+
+  /// from: public long getValueAsLong()
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __long__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0 (false)
+  /// and 1 (true), and Strings are parsed using default Java language integer
+  /// parsing rules.
+  ///
+  /// If representation can not be converted to a long (including structured type
+  /// markers like start/end Object/Array)
+  /// default value of __0L__ will be returned; no exceptions are thrown.
+  ///@return {@code long} value current token is converted to, if possible; exception thrown
+  ///    otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getValueAsLong() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getValueAsLong, jni.JniCallType.longType, []).long;
+  }
+
+  static final _id_getValueAsLong1 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getValueAsLong", r"(J)J");
+
+  /// from: public long getValueAsLong(long def)
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __long__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0 (false)
+  /// and 1 (true), and Strings are parsed using default Java language integer
+  /// parsing rules.
+  ///
+  /// If representation can not be converted to a long (including structured type
+  /// markers like start/end Object/Array)
+  /// specified __def__ will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code long} is not possible
+  ///@return {@code long} value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  int getValueAsLong1(
+    int def,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getValueAsLong1, jni.JniCallType.longType, [def]).long;
+  }
+
+  static final _id_getValueAsDouble = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getValueAsDouble", r"()D");
+
+  /// from: public double getValueAsDouble()
+  ///
+  /// Method that will try to convert value of current token to a Java
+  /// __double__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0.0 (false)
+  /// and 1.0 (true), and Strings are parsed using default Java language floating
+  /// point parsing rules.
+  ///
+  /// If representation can not be converted to a double (including structured types
+  /// like Objects and Arrays),
+  /// default value of __0.0__ will be returned; no exceptions are thrown.
+  ///@return {@code double} value current token is converted to, if possible; exception thrown
+  ///    otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  double getValueAsDouble() {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_getValueAsDouble,
+        jni.JniCallType.doubleType, []).doubleFloat;
+  }
+
+  static final _id_getValueAsDouble1 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getValueAsDouble", r"(D)D");
+
+  /// from: public double getValueAsDouble(double def)
+  ///
+  /// Method that will try to convert value of current token to a
+  /// Java __double__.
+  /// Numbers are coerced using default Java rules; booleans convert to 0.0 (false)
+  /// and 1.0 (true), and Strings are parsed using default Java language floating
+  /// point parsing rules.
+  ///
+  /// If representation can not be converted to a double (including structured types
+  /// like Objects and Arrays),
+  /// specified __def__ will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code double} is not possible
+  ///@return {@code double} value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  double getValueAsDouble1(
+    double def,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_getValueAsDouble1, jni.JniCallType.doubleType, [def]).doubleFloat;
+  }
+
+  static final _id_getValueAsBoolean = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getValueAsBoolean", r"()Z");
+
+  /// from: public boolean getValueAsBoolean()
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __boolean__.
+  /// JSON booleans map naturally; integer numbers other than 0 map to true, and
+  /// 0 maps to false
+  /// and Strings 'true' and 'false' map to corresponding values.
+  ///
+  /// If representation can not be converted to a boolean value (including structured types
+  /// like Objects and Arrays),
+  /// default value of __false__ will be returned; no exceptions are thrown.
+  ///@return {@code boolean} value current token is converted to, if possible; exception thrown
+  ///    otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  bool getValueAsBoolean() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_getValueAsBoolean, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_getValueAsBoolean1 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getValueAsBoolean", r"(Z)Z");
+
+  /// from: public boolean getValueAsBoolean(boolean def)
+  ///
+  /// Method that will try to convert value of current token to a
+  /// __boolean__.
+  /// JSON booleans map naturally; integer numbers other than 0 map to true, and
+  /// 0 maps to false
+  /// and Strings 'true' and 'false' map to corresponding values.
+  ///
+  /// If representation can not be converted to a boolean value (including structured types
+  /// like Objects and Arrays),
+  /// specified __def__ will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code boolean} is not possible
+  ///@return {@code boolean} value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  bool getValueAsBoolean1(
+    bool def,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getValueAsBoolean1,
+        jni.JniCallType.booleanType,
+        [def ? 1 : 0]).boolean;
+  }
+
+  static final _id_getValueAsString = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"getValueAsString", r"()Ljava/lang/String;");
+
+  /// from: public java.lang.String getValueAsString()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that will try to convert value of current token to a
+  /// java.lang.String.
+  /// JSON Strings map naturally; scalar values get converted to
+  /// their textual representation.
+  /// If representation can not be converted to a String value (including structured types
+  /// like Objects and Arrays and {@code null} token), default value of
+  /// __null__ will be returned; no exceptions are thrown.
+  ///@return String value current token is converted to, if possible; {@code null} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.1
+  jni.JString getValueAsString() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getValueAsString,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getValueAsString1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getValueAsString",
+      r"(Ljava/lang/String;)Ljava/lang/String;");
+
+  /// from: public abstract java.lang.String getValueAsString(java.lang.String def)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that will try to convert value of current token to a
+  /// java.lang.String.
+  /// JSON Strings map naturally; scalar values get converted to
+  /// their textual representation.
+  /// If representation can not be converted to a String value (including structured types
+  /// like Objects and Arrays and {@code null} token), specified default value
+  /// will be returned; no exceptions are thrown.
+  ///@param def Default value to return if conversion to {@code String} is not possible
+  ///@return String value current token is converted to, if possible; {@code def} otherwise
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.1
+  jni.JString getValueAsString1(
+    jni.JString def,
+  ) {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getValueAsString1,
+        jni.JniCallType.objectType,
+        [def.reference]).object);
+  }
+
+  static final _id_canReadObjectId = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"canReadObjectId", r"()Z");
+
+  /// from: public boolean canReadObjectId()
+  ///
+  /// Introspection method that may be called to see if the underlying
+  /// data format supports some kind of Object Ids natively (many do not;
+  /// for example, JSON doesn't).
+  ///
+  /// Default implementation returns true; overridden by data formats
+  /// that do support native Object Ids. Caller is expected to either
+  /// use a non-native notation (explicit property or such), or fail,
+  /// in case it can not use native object ids.
+  ///@return {@code True} if the format being read supports native Object Ids;
+  ///    {@code false} if not
+  ///@since 2.3
+  bool canReadObjectId() {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_canReadObjectId,
+        jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_canReadTypeId = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"canReadTypeId", r"()Z");
+
+  /// from: public boolean canReadTypeId()
+  ///
+  /// Introspection method that may be called to see if the underlying
+  /// data format supports some kind of Type Ids natively (many do not;
+  /// for example, JSON doesn't).
+  ///
+  /// Default implementation returns true; overridden by data formats
+  /// that do support native Type Ids. Caller is expected to either
+  /// use a non-native notation (explicit property or such), or fail,
+  /// in case it can not use native type ids.
+  ///@return {@code True} if the format being read supports native Type Ids;
+  ///    {@code false} if not
+  ///@since 2.3
+  bool canReadTypeId() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_canReadTypeId, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_getObjectId = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getObjectId", r"()Ljava/lang/Object;");
+
+  /// from: public java.lang.Object getObjectId()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be called to check whether current token
+  /// (one that was just read) has an associated Object id, and if
+  /// so, return it.
+  /// Note that while typically caller should check with \#canReadObjectId
+  /// first, it is not illegal to call this method even if that method returns
+  /// true; but if so, it will return null. This may be used to simplify calling
+  /// code.
+  ///
+  /// Default implementation will simply return null.
+  ///@return Native Object id associated with the current token, if any; {@code null} if none
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.3
+  jni.JObject getObjectId() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getObjectId, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getTypeId = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getTypeId", r"()Ljava/lang/Object;");
+
+  /// from: public java.lang.Object getTypeId()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method that can be called to check whether current token
+  /// (one that was just read) has an associated type id, and if
+  /// so, return it.
+  /// Note that while typically caller should check with \#canReadTypeId
+  /// first, it is not illegal to call this method even if that method returns
+  /// true; but if so, it will return null. This may be used to simplify calling
+  /// code.
+  ///
+  /// Default implementation will simply return null.
+  ///@return Native Type Id associated with the current token, if any; {@code null} if none
+  ///@throws IOException for low-level read issues, or
+  ///   JsonParseException for decoding problems
+  ///@since 2.3
+  jni.JObject getTypeId() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getTypeId, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_readValueAs = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"readValueAs",
+      r"(Ljava/lang/Class;)Ljava/lang/Object;");
+
+  /// from: public T readValueAs(java.lang.Class<T> valueType)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method to deserialize JSON content into a non-container
+  /// type (it can be an array type, however): typically a bean, array
+  /// or a wrapper type (like java.lang.Boolean).
+  /// __Note__: method can only be called if the parser has
+  /// an object codec assigned; this is true for parsers constructed
+  /// by <code>MappingJsonFactory</code> (from "jackson-databind" jar)
+  /// but not for JsonFactory (unless its <code>setCodec</code>
+  /// method has been explicitly called).
+  ///
+  /// This method may advance the event stream, for structured types
+  /// the current token will be the closing end marker (END_ARRAY,
+  /// END_OBJECT) of the bound structure. For non-structured Json types
+  /// (and for JsonToken\#VALUE_EMBEDDED_OBJECT)
+  /// stream is not advanced.
+  ///
+  /// Note: this method should NOT be used if the result type is a
+  /// container (java.util.Collection or java.util.Map.
+  /// The reason is that due to type erasure, key and value types
+  /// can not be introspected when using this method.
+  ///@param <T> Nominal type parameter for value type
+  ///@param valueType Java type to read content as (passed to ObjectCodec that
+  ///    deserializes content)
+  ///@return Java value read from content
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  $T readValueAs<$T extends jni.JObject>(
+    jni.JObject valueType, {
+    required jni.JObjType<$T> T,
+  }) {
+    return T.fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_readValueAs,
+        jni.JniCallType.objectType,
+        [valueType.reference]).object);
+  }
+
+  static final _id_readValueAs1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"readValueAs",
+      r"(Lcom/fasterxml/jackson/core/type/TypeReference;)Ljava/lang/Object;");
+
+  /// from: public T readValueAs(com.fasterxml.jackson.core.type.TypeReference<?> valueTypeRef)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method to deserialize JSON content into a Java type, reference
+  /// to which is passed as argument. Type is passed using so-called
+  /// "super type token"
+  /// and specifically needs to be used if the root type is a
+  /// parameterized (generic) container type.
+  /// __Note__: method can only be called if the parser has
+  /// an object codec assigned; this is true for parsers constructed
+  /// by <code>MappingJsonFactory</code> (defined in 'jackson-databind' bundle)
+  /// but not for JsonFactory (unless its <code>setCodec</code>
+  /// method has been explicitly called).
+  ///
+  /// This method may advance the event stream, for structured types
+  /// the current token will be the closing end marker (END_ARRAY,
+  /// END_OBJECT) of the bound structure. For non-structured Json types
+  /// (and for JsonToken\#VALUE_EMBEDDED_OBJECT)
+  /// stream is not advanced.
+  ///@param <T> Nominal type parameter for value type
+  ///@param valueTypeRef Java type to read content as (passed to ObjectCodec that
+  ///    deserializes content)
+  ///@return Java value read from content
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  $T readValueAs1<$T extends jni.JObject>(
+    jni.JObject valueTypeRef, {
+    required jni.JObjType<$T> T,
+  }) {
+    return T.fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_readValueAs1,
+        jni.JniCallType.objectType,
+        [valueTypeRef.reference]).object);
+  }
+
+  static final _id_readValuesAs = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"readValuesAs",
+      r"(Ljava/lang/Class;)Ljava/util/Iterator;");
+
+  /// from: public java.util.Iterator<T> readValuesAs(java.lang.Class<T> valueType)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for reading sequence of Objects from parser stream,
+  /// all with same specified value type.
+  ///@param <T> Nominal type parameter for value type
+  ///@param valueType Java type to read content as (passed to ObjectCodec that
+  ///    deserializes content)
+  ///@return Iterator for reading multiple Java values from content
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  jni.JIterator<$T> readValuesAs<$T extends jni.JObject>(
+    jni.JObject valueType, {
+    required jni.JObjType<$T> T,
+  }) {
+    return jni.JIteratorType(T).fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_readValuesAs,
+        jni.JniCallType.objectType,
+        [valueType.reference]).object);
+  }
+
+  static final _id_readValuesAs1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"readValuesAs",
+      r"(Lcom/fasterxml/jackson/core/type/TypeReference;)Ljava/util/Iterator;");
+
+  /// from: public java.util.Iterator<T> readValuesAs(com.fasterxml.jackson.core.type.TypeReference<T> valueTypeRef)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method for reading sequence of Objects from parser stream,
+  /// all with same specified value type.
+  ///@param <T> Nominal type parameter for value type
+  ///@param valueTypeRef Java type to read content as (passed to ObjectCodec that
+  ///    deserializes content)
+  ///@return Iterator for reading multiple Java values from content
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  jni.JIterator<$T> readValuesAs1<$T extends jni.JObject>(
+    jni.JObject valueTypeRef, {
+    required jni.JObjType<$T> T,
+  }) {
+    return jni.JIteratorType(T).fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_readValuesAs1,
+        jni.JniCallType.objectType,
+        [valueTypeRef.reference]).object);
+  }
+
+  static final _id_readValueAsTree = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"readValueAsTree",
+      r"()Lcom/fasterxml/jackson/core/TreeNode;");
+
+  /// from: public T readValueAsTree()
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Method to deserialize JSON content into equivalent "tree model",
+  /// represented by root TreeNode of resulting model.
+  /// For JSON Arrays it will an array node (with child nodes),
+  /// for objects object node (with child nodes), and for other types
+  /// matching leaf node type. Empty or whitespace documents are null.
+  ///@param <T> Nominal type parameter for result node type (to reduce need for casting)
+  ///@return root of the document, or null if empty or whitespace.
+  ///@throws IOException if there is either an underlying I/O problem or decoding
+  ///    issue at format layer
+  $T readValueAsTree<$T extends jni.JObject>({
+    required jni.JObjType<$T> T,
+  }) {
+    return T.fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_readValueAsTree, jni.JniCallType.objectType, []).object);
+  }
+}
+
+final class $JsonParserType extends jni.JObjType<JsonParser> {
+  const $JsonParserType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonParser;";
+
+  @override
+  JsonParser fromRef(jni.JObjectPtr ref) => JsonParser.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonParserType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonParserType) && other is $JsonParserType;
+  }
+}
+
+/// from: com.fasterxml.jackson.core.JsonParser$Feature
+///
+/// Enumeration that defines all on/off features for parsers.
+class JsonParser_Feature extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonParser_Feature> $type = type;
+
+  JsonParser_Feature.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/fasterxml/jackson/core/JsonParser$Feature");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonParser_FeatureType();
+  static final _id_values = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"values",
+      r"()[Lcom/fasterxml/jackson/core/JsonParser$Feature;");
+
+  /// from: static public com.fasterxml.jackson.core.JsonParser.Feature[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonParser_Feature> values() {
+    return const jni.JArrayType($JsonParser_FeatureType()).fromRef(
+        jni.Jni.accessors.callStaticMethodWithArgs(_class.reference, _id_values,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_valueOf = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"valueOf",
+      r"(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonParser$Feature;");
+
+  /// from: static public com.fasterxml.jackson.core.JsonParser.Feature valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonParser_Feature valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonParser_FeatureType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_valueOf,
+            jni.JniCallType.objectType, [name.reference]).object);
+  }
+
+  static final _id_collectDefaults = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"collectDefaults", r"()I");
+
+  /// from: static public int collectDefaults()
+  ///
+  /// Method that calculates bit set (flags) of all features that
+  /// are enabled by default.
+  ///@return Bit mask of all features that are enabled by default
+  static int collectDefaults() {
+    return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+        _id_collectDefaults, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_enabledByDefault = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"enabledByDefault", r"()Z");
+
+  /// from: public boolean enabledByDefault()
+  bool enabledByDefault() {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_enabledByDefault,
+        jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_enabledIn =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"enabledIn", r"(I)Z");
+
+  /// from: public boolean enabledIn(int flags)
+  bool enabledIn(
+    int flags,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_enabledIn,
+        jni.JniCallType.booleanType, [jni.JValueInt(flags)]).boolean;
+  }
+
+  static final _id_getMask =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"getMask", r"()I");
+
+  /// from: public int getMask()
+  int getMask() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getMask, jni.JniCallType.intType, []).integer;
+  }
+}
+
+final class $JsonParser_FeatureType extends jni.JObjType<JsonParser_Feature> {
+  const $JsonParser_FeatureType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonParser$Feature;";
+
+  @override
+  JsonParser_Feature fromRef(jni.JObjectPtr ref) =>
+      JsonParser_Feature.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonParser_FeatureType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonParser_FeatureType) &&
+        other is $JsonParser_FeatureType;
+  }
+}
+
+/// from: com.fasterxml.jackson.core.JsonParser$NumberType
+///
+/// Enumeration of possible "native" (optimal) types that can be
+/// used for numbers.
+class JsonParser_NumberType extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonParser_NumberType> $type = type;
+
+  JsonParser_NumberType.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/fasterxml/jackson/core/JsonParser$NumberType");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonParser_NumberTypeType();
+  static final _id_values = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"values",
+      r"()[Lcom/fasterxml/jackson/core/JsonParser$NumberType;");
+
+  /// from: static public com.fasterxml.jackson.core.JsonParser.NumberType[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonParser_NumberType> values() {
+    return const jni.JArrayType($JsonParser_NumberTypeType()).fromRef(
+        jni.Jni.accessors.callStaticMethodWithArgs(_class.reference, _id_values,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_valueOf = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"valueOf",
+      r"(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonParser$NumberType;");
+
+  /// from: static public com.fasterxml.jackson.core.JsonParser.NumberType valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonParser_NumberType valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonParser_NumberTypeType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_valueOf,
+            jni.JniCallType.objectType, [name.reference]).object);
+  }
+}
+
+final class $JsonParser_NumberTypeType
+    extends jni.JObjType<JsonParser_NumberType> {
+  const $JsonParser_NumberTypeType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonParser$NumberType;";
+
+  @override
+  JsonParser_NumberType fromRef(jni.JObjectPtr ref) =>
+      JsonParser_NumberType.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonParser_NumberTypeType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonParser_NumberTypeType) &&
+        other is $JsonParser_NumberTypeType;
+  }
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
new file mode 100644
index 0000000..4e67a2d
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonToken.dart
@@ -0,0 +1,225 @@
+// Generated from jackson-core which is licensed under the Apache License 2.0.
+// The following copyright from the original authors applies.
+// See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+//
+// Copyright (c) 2007 - The Jackson Project Authors
+// 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.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+/// from: com.fasterxml.jackson.core.JsonToken
+///
+/// Enumeration for basic token types used for returning results
+/// of parsing JSON content.
+class JsonToken extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonToken> $type = type;
+
+  JsonToken.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/fasterxml/jackson/core/JsonToken");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonTokenType();
+  static final _id_values = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"values",
+      r"()[Lcom/fasterxml/jackson/core/JsonToken;");
+
+  /// from: static public com.fasterxml.jackson.core.JsonToken[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonToken> values() {
+    return const jni.JArrayType($JsonTokenType()).fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_values,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_valueOf = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"valueOf",
+      r"(Ljava/lang/String;)Lcom/fasterxml/jackson/core/JsonToken;");
+
+  /// from: static public com.fasterxml.jackson.core.JsonToken valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonToken valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonTokenType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_valueOf,
+            jni.JniCallType.objectType, [name.reference]).object);
+  }
+
+  static final _id_id =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"id", r"()I");
+
+  /// from: public final int id()
+  int id() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_id, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_asString = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"asString", r"()Ljava/lang/String;");
+
+  /// from: public final java.lang.String asString()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString asString() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_asString, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_asCharArray = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"asCharArray", r"()[C");
+
+  /// from: public final char[] asCharArray()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JArray<jni.jchar> asCharArray() {
+    return const jni.JArrayType(jni.jcharType()).fromRef(jni.Jni.accessors
+        .callMethodWithArgs(
+            reference, _id_asCharArray, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_asByteArray = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"asByteArray", r"()[B");
+
+  /// from: public final byte[] asByteArray()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JArray<jni.jbyte> asByteArray() {
+    return const jni.JArrayType(jni.jbyteType()).fromRef(jni.Jni.accessors
+        .callMethodWithArgs(
+            reference, _id_asByteArray, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_isNumeric =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"isNumeric", r"()Z");
+
+  /// from: public final boolean isNumeric()
+  ///
+  /// @return {@code True} if this token is {@code VALUE_NUMBER_INT} or {@code VALUE_NUMBER_FLOAT},
+  ///   {@code false} otherwise
+  bool isNumeric() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_isNumeric, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_isStructStart = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"isStructStart", r"()Z");
+
+  /// from: public final boolean isStructStart()
+  ///
+  /// Accessor that is functionally equivalent to:
+  /// <code>
+  ///    this == JsonToken.START_OBJECT || this == JsonToken.START_ARRAY
+  /// </code>
+  ///@return {@code True} if this token is {@code START_OBJECT} or {@code START_ARRAY},
+  ///   {@code false} otherwise
+  ///@since 2.3
+  bool isStructStart() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_isStructStart, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_isStructEnd =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"isStructEnd", r"()Z");
+
+  /// from: public final boolean isStructEnd()
+  ///
+  /// Accessor that is functionally equivalent to:
+  /// <code>
+  ///    this == JsonToken.END_OBJECT || this == JsonToken.END_ARRAY
+  /// </code>
+  ///@return {@code True} if this token is {@code END_OBJECT} or {@code END_ARRAY},
+  ///   {@code false} otherwise
+  ///@since 2.3
+  bool isStructEnd() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_isStructEnd, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_isScalarValue = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"isScalarValue", r"()Z");
+
+  /// from: public final boolean isScalarValue()
+  ///
+  /// Method that can be used to check whether this token represents
+  /// a valid non-structured value. This means all {@code VALUE_xxx} tokens;
+  /// excluding {@code START_xxx} and {@code END_xxx} tokens as well
+  /// {@code FIELD_NAME}.
+  ///@return {@code True} if this token is a scalar value token (one of
+  ///   {@code VALUE_xxx} tokens), {@code false} otherwise
+  bool isScalarValue() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_isScalarValue, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_isBoolean =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"isBoolean", r"()Z");
+
+  /// from: public final boolean isBoolean()
+  ///
+  /// @return {@code True} if this token is {@code VALUE_TRUE} or {@code VALUE_FALSE},
+  ///   {@code false} otherwise
+  bool isBoolean() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_isBoolean, jni.JniCallType.booleanType, []).boolean;
+  }
+}
+
+final class $JsonTokenType extends jni.JObjType<JsonToken> {
+  const $JsonTokenType();
+
+  @override
+  String get signature => r"Lcom/fasterxml/jackson/core/JsonToken;";
+
+  @override
+  JsonToken fromRef(jni.JObjectPtr ref) => JsonToken.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonTokenType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonTokenType) && other is $JsonTokenType;
+  }
+}
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/_package.dart b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/_package.dart
new file mode 100644
index 0000000..cae2a52
--- /dev/null
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/_package.dart
@@ -0,0 +1,3 @@
+export "JsonFactory.dart";
+export "JsonParser.dart";
+export "JsonToken.dart";
diff --git a/pkgs/jnigen/test/kotlin_test/.gitignore b/pkgs/jnigen/test/kotlin_test/.gitignore
new file mode 100644
index 0000000..39de10f
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/.gitignore
@@ -0,0 +1,5 @@
+build/
+*.class
+test_lib/
+test_src/
+target/
diff --git a/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/.clang-format b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/.clang-format
new file mode 100644
index 0000000..a256c2f
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/.clang-format
@@ -0,0 +1,15 @@
+# From dart SDK: https://github.com/dart-lang/sdk/blob/main/.clang-format
+
+# Defines the Chromium style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
+
+# clang-format doesn't seem to do a good job of this for longer comments.
+ReflowComments: 'false'
+
+# We have lots of these. Though we need to put them all in curly braces,
+# clang-format can't do that.
+AllowShortIfStatementsOnASingleLine: 'true'
+
+# Put escaped newlines into the rightmost column.
+AlignEscapedNewlinesLeft: false
diff --git a/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/CMakeLists.txt b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/CMakeLists.txt
new file mode 100644
index 0000000..3889f54
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/CMakeLists.txt
@@ -0,0 +1,32 @@
+# jni_native_build (Build with jni:setup. Do not delete this line.)
+
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project(kotlin VERSION 0.0.1 LANGUAGES C)
+
+add_library(kotlin SHARED
+  "./kotlin.c"
+)
+
+set_target_properties(kotlin PROPERTIES
+  OUTPUT_NAME "kotlin"
+)
+
+target_compile_definitions(kotlin PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries(kotlin log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries(kotlin ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/dartjni.h b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/dartjni.h
new file mode 100644
index 0000000..8f1dc74
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/dartjni.h
@@ -0,0 +1,424 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h.
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
+#if _WIN32
+#define FFI_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FFI_PLUGIN_EXPORT
+#endif
+
+#if defined _WIN32
+#define thread_local __declspec(thread)
+#else
+#define thread_local __thread
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#ifdef __ANDROID__
+#define __ENVP_CAST (JNIEnv**)
+#else
+#define __ENVP_CAST (void**)
+#endif
+
+/// Locking functions for windows and pthread.
+
+#if defined _WIN32
+#include <windows.h>
+
+typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  InitializeCriticalSection(lock);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  EnterCriticalSection(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  LeaveCriticalSection(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  DeleteCriticalSection(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  // Not available.
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ ||         \
+    defined __GNUC__
+#include <pthread.h>
+
+typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  pthread_mutex_init(lock, NULL);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  pthread_mutex_lock(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  pthread_mutex_unlock(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  pthread_mutex_destroy(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  pthread_cond_destroy(cond);
+}
+
+#else
+
+#error "No locking/condition variable support; Possibly unsupported platform"
+
+#endif
+
+typedef struct CallbackResult {
+  MutexLock lock;
+  ConditionVariable cond;
+  int ready;
+  jobject object;
+} CallbackResult;
+
+typedef struct JniLocks {
+  MutexLock classLoadingLock;
+  MutexLock methodLoadingLock;
+  MutexLock fieldLoadingLock;
+} JniLocks;
+
+/// Represents the error when dart-jni layer has already spawned singleton VM.
+#define DART_JNI_SINGLETON_EXISTS (-99);
+
+/// Stores the global state of the JNI.
+typedef struct JniContext {
+  JavaVM* jvm;
+  jobject classLoader;
+  jmethodID loadClassMethod;
+  jobject currentActivity;
+  jobject appContext;
+  JniLocks locks;
+} JniContext;
+
+// jniEnv for this thread, used by inline functions in this header,
+// therefore declared as extern.
+extern thread_local JNIEnv* jniEnv;
+
+extern JniContext* jni;
+
+/// Types used by JNI API to distinguish between primitive types.
+enum JniType {
+  booleanType = 0,
+  byteType = 1,
+  shortType = 2,
+  charType = 3,
+  intType = 4,
+  longType = 5,
+  floatType = 6,
+  doubleType = 7,
+  objectType = 8,
+  voidType = 9,
+};
+
+/// Result type for use by JNI.
+///
+/// If [exception] is null, it means the result is valid.
+/// It's assumed that the caller knows the expected type in [result].
+typedef struct JniResult {
+  jvalue value;
+  jthrowable exception;
+} JniResult;
+
+/// Similar to [JniResult] but for class lookups.
+typedef struct JniClassLookupResult {
+  jclass value;
+  jthrowable exception;
+} JniClassLookupResult;
+
+/// Similar to [JniResult] but for method/field ID lookups.
+typedef struct JniPointerResult {
+  const void* value;
+  jthrowable exception;
+} JniPointerResult;
+
+/// JniExceptionDetails holds 2 jstring objects, one is the result of
+/// calling `toString` on exception object, other is stack trace;
+typedef struct JniExceptionDetails {
+  jstring message;
+  jstring stacktrace;
+} JniExceptionDetails;
+
+/// This struct contains functions which wrap method call / field access conveniently along with
+/// exception checking.
+///
+/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us
+/// to check for and clear the exception before returning to dart code, which requires these functions
+/// to return result types.
+typedef struct JniAccessorsStruct {
+  JniClassLookupResult (*getClass)(char* internalName);
+  JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature);
+  JniPointerResult (*getStaticFieldID)(jclass cls,
+                                       char* fieldName,
+                                       char* signature);
+  JniPointerResult (*getMethodID)(jclass cls,
+                                  char* methodName,
+                                  char* signature);
+  JniPointerResult (*getStaticMethodID)(jclass cls,
+                                        char* methodName,
+                                        char* signature);
+  JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args);
+  JniResult (*newPrimitiveArray)(jsize length, int type);
+  JniResult (*newObjectArray)(jsize length,
+                              jclass elementClass,
+                              jobject initialElement);
+  JniResult (*getArrayElement)(jarray array, int index, int type);
+  JniResult (*callMethod)(jobject obj,
+                          jmethodID methodID,
+                          int callType,
+                          jvalue* args);
+  JniResult (*callStaticMethod)(jclass cls,
+                                jmethodID methodID,
+                                int callType,
+                                jvalue* args);
+  JniResult (*getField)(jobject obj, jfieldID fieldID, int callType);
+  JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType);
+  JniExceptionDetails (*getExceptionDetails)(jthrowable exception);
+} JniAccessorsStruct;
+
+FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors();
+
+FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void);
+
+FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void);
+
+/// Spawn a JVM with given arguments.
+///
+/// Returns JNI_OK on success, and one of the documented JNI error codes on
+/// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple
+/// JVMs is made, even if the underlying API potentially supports multiple VMs.
+FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args);
+
+/// Load class through platform-specific mechanism.
+///
+/// Currently uses application classloader on android,
+/// and JNIEnv->FindClass on other platforms.
+FFI_PLUGIN_EXPORT jclass FindClass(const char* name);
+
+/// Returns Application classLoader (on Android),
+/// which can be used to load application and platform classes.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetClassLoader(void);
+
+/// Returns application context on Android.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetApplicationContext(void);
+
+/// Returns current activity of the app on Android.
+FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void);
+
+static inline void attach_thread() {
+  if (jniEnv == NULL) {
+    (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL);
+  }
+}
+
+/// Load class into [cls] using platform specific mechanism
+static inline void load_class_platform(jclass* cls, const char* name) {
+#ifdef __ANDROID__
+  jstring className = (*jniEnv)->NewStringUTF(jniEnv, name);
+  *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader,
+                                     jni->loadClassMethod, className);
+  (*jniEnv)->DeleteLocalRef(jniEnv, className);
+#else
+  *cls = (*jniEnv)->FindClass(jniEnv, name);
+#endif
+}
+
+static inline void load_class_local_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(cls, name);
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_class_global_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    jclass tmp = NULL;
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(&tmp, name);
+      if (!(*jniEnv)->ExceptionCheck(jniEnv)) {
+        *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp);
+        (*jniEnv)->DeleteLocalRef(jniEnv, tmp);
+      }
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_method(jclass cls,
+                               jmethodID* res,
+                               const char* name,
+                               const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_static_method(jclass cls,
+                                      jmethodID* res,
+                                      const char* name,
+                                      const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_field(jclass cls,
+                              jfieldID* res,
+                              const char* name,
+                              const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline void load_static_field(jclass cls,
+                                     jfieldID* res,
+                                     const char* name,
+                                     const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline jobject to_global_ref(jobject ref) {
+  jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref);
+  (*jniEnv)->DeleteLocalRef(jniEnv, ref);
+  return g;
+}
+
+// These functions are useful for C+Dart bindings, and not required for pure dart bindings.
+
+FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr();
+
+/// For use by jni_gen's generated code
+/// don't use these.
+
+// these 2 fn ptr vars will be defined by generated code library
+extern JniContext* (*context_getter)(void);
+extern JNIEnv* (*env_getter)(void);
+
+// this function will be exported by generated code library
+// it will set above 2 variables.
+FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void),
+                                     JNIEnv* (*eg)(void));
+
+static inline void load_env() {
+  if (jniEnv == NULL) {
+    jni = context_getter();
+    jniEnv = env_getter();
+  }
+}
+
+static inline jthrowable check_exception() {
+  jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv);
+  if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv);
+  if (exception == NULL) return NULL;
+  return to_global_ref(exception);
+}
+
+static inline JniResult to_global_ref_result(jobject ref) {
+  JniResult result;
+  result.exception = check_exception();
+  if (result.exception == NULL) {
+    result.value.l = to_global_ref(ref);
+  }
+  return result;
+}
+
+FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+
+FFI_PLUGIN_EXPORT
+JniResult DartException__ctor(jstring message);
+
+FFI_PLUGIN_EXPORT
+JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+                                 int64_t port,
+                                 int64_t functionPtr);
+
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
diff --git a/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/kotlin.c b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/kotlin.c
new file mode 100644
index 0000000..e3c227e
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/c_based/c_bindings/kotlin.c
@@ -0,0 +1,76 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+#include <stdint.h>
+#include "dartjni.h"
+#include "jni.h"
+
+thread_local JNIEnv* jniEnv;
+JniContext* jni;
+
+JniContext* (*context_getter)(void);
+JNIEnv* (*env_getter)(void);
+
+void setJniGetters(JniContext* (*cg)(void), JNIEnv* (*eg)(void)) {
+  context_getter = cg;
+  env_getter = eg;
+}
+
+// com.github.dart_lang.jnigen.SuspendFun
+jclass _c_SuspendFun = NULL;
+
+jmethodID _m_SuspendFun__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult SuspendFun__new0() {
+  load_env();
+  load_class_global_ref(&_c_SuspendFun,
+                        "com/github/dart_lang/jnigen/SuspendFun");
+  if (_c_SuspendFun == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_SuspendFun, &_m_SuspendFun__new0, "<init>", "()V");
+  if (_m_SuspendFun__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_SuspendFun, _m_SuspendFun__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_SuspendFun__sayHello = NULL;
+FFI_PLUGIN_EXPORT
+JniResult SuspendFun__sayHello(jobject self_, jobject continuation) {
+  load_env();
+  load_class_global_ref(&_c_SuspendFun,
+                        "com/github/dart_lang/jnigen/SuspendFun");
+  if (_c_SuspendFun == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_SuspendFun, &_m_SuspendFun__sayHello, "sayHello",
+              "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;");
+  if (_m_SuspendFun__sayHello == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_SuspendFun__sayHello, continuation);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_SuspendFun__sayHello1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult SuspendFun__sayHello1(jobject self_,
+                                jobject string,
+                                jobject continuation) {
+  load_env();
+  load_class_global_ref(&_c_SuspendFun,
+                        "com/github/dart_lang/jnigen/SuspendFun");
+  if (_c_SuspendFun == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_SuspendFun, &_m_SuspendFun__sayHello1, "sayHello",
+      "(Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;");
+  if (_m_SuspendFun__sayHello1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_SuspendFun__sayHello1, string, continuation);
+  return to_global_ref_result(_result);
+}
diff --git a/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart b/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart
new file mode 100644
index 0000000..e96d08c
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/c_based/dart_bindings/kotlin.dart
@@ -0,0 +1,128 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+// Auto-generated initialization code.
+
+final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
+    ProtectedJniExtensions.initGeneratedLibrary("kotlin");
+
+/// from: com.github.dart_lang.jnigen.SuspendFun
+class SuspendFun extends jni.JObject {
+  @override
+  late final jni.JObjType<SuspendFun> $type = type;
+
+  SuspendFun.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $SuspendFunType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "SuspendFun__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory SuspendFun() {
+    return SuspendFun.fromRef(_new0().object);
+  }
+
+  static final _sayHello = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("SuspendFun__sayHello")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final java.lang.Object sayHello(kotlin.coroutines.Continuation continuation)
+  /// The returned object must be released after use, by calling the [release] method.
+  Future<jni.JString> sayHello() async {
+    final $p = ReceivePort();
+    final $c =
+        jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
+    _sayHello(reference, $c.reference).object;
+    final $o = jni.JObjectPtr.fromAddress(await $p.first);
+    final $k = const jni.JStringType().getClass().reference;
+    if (!jni.Jni.env.IsInstanceOf($o, $k)) {
+      throw "Failed";
+    }
+    return const jni.JStringType().fromRef($o);
+  }
+
+  static final _sayHello1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("SuspendFun__sayHello1")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final java.lang.Object sayHello(java.lang.String string, kotlin.coroutines.Continuation continuation)
+  /// The returned object must be released after use, by calling the [release] method.
+  Future<jni.JString> sayHello1(
+    jni.JString string,
+  ) async {
+    final $p = ReceivePort();
+    final $c =
+        jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
+    _sayHello1(reference, string.reference, $c.reference).object;
+    final $o = jni.JObjectPtr.fromAddress(await $p.first);
+    final $k = const jni.JStringType().getClass().reference;
+    if (!jni.Jni.env.IsInstanceOf($o, $k)) {
+      throw "Failed";
+    }
+    return const jni.JStringType().fromRef($o);
+  }
+}
+
+final class $SuspendFunType extends jni.JObjType<SuspendFun> {
+  const $SuspendFunType();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/SuspendFun;";
+
+  @override
+  SuspendFun fromRef(jni.JObjectPtr ref) => SuspendFun.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($SuspendFunType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($SuspendFunType) && other is $SuspendFunType;
+  }
+}
diff --git a/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart b/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart
new file mode 100644
index 0000000..b3a1985
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/dart_only/dart_bindings/kotlin.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+/// from: com.github.dart_lang.jnigen.SuspendFun
+class SuspendFun extends jni.JObject {
+  @override
+  late final jni.JObjType<SuspendFun> $type = type;
+
+  SuspendFun.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/SuspendFun");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $SuspendFunType();
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory SuspendFun() {
+    return SuspendFun.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+
+  static final _id_sayHello = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"sayHello", r"(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;");
+
+  /// from: public final java.lang.Object sayHello(kotlin.coroutines.Continuation continuation)
+  /// The returned object must be released after use, by calling the [release] method.
+  Future<jni.JString> sayHello() async {
+    final $p = ReceivePort();
+    final $c =
+        jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
+    jni.Jni.accessors.callMethodWithArgs(reference, _id_sayHello,
+        jni.JniCallType.objectType, [$c.reference]).object;
+    final $o = jni.JObjectPtr.fromAddress(await $p.first);
+    final $k = const jni.JStringType().getClass().reference;
+    if (!jni.Jni.env.IsInstanceOf($o, $k)) {
+      throw "Failed";
+    }
+    return const jni.JStringType().fromRef($o);
+  }
+
+  static final _id_sayHello1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"sayHello",
+      r"(Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;");
+
+  /// from: public final java.lang.Object sayHello(java.lang.String string, kotlin.coroutines.Continuation continuation)
+  /// The returned object must be released after use, by calling the [release] method.
+  Future<jni.JString> sayHello1(
+    jni.JString string,
+  ) async {
+    final $p = ReceivePort();
+    final $c =
+        jni.JObject.fromRef(ProtectedJniExtensions.newPortContinuation($p));
+    jni.Jni.accessors.callMethodWithArgs(reference, _id_sayHello1,
+        jni.JniCallType.objectType, [string.reference, $c.reference]).object;
+    final $o = jni.JObjectPtr.fromAddress(await $p.first);
+    final $k = const jni.JStringType().getClass().reference;
+    if (!jni.Jni.env.IsInstanceOf($o, $k)) {
+      throw "Failed";
+    }
+    return const jni.JStringType().fromRef($o);
+  }
+}
+
+final class $SuspendFunType extends jni.JObjType<SuspendFun> {
+  const $SuspendFunType();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/SuspendFun;";
+
+  @override
+  SuspendFun fromRef(jni.JObjectPtr ref) => SuspendFun.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($SuspendFunType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($SuspendFunType) && other is $SuspendFunType;
+  }
+}
diff --git a/pkgs/jnigen/test/kotlin_test/generate.dart b/pkgs/jnigen/test/kotlin_test/generate.dart
new file mode 100644
index 0000000..6168667
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/generate.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:logging/logging.dart';
+import 'package:path/path.dart';
+import 'package:jnigen/jnigen.dart';
+
+const testName = 'kotlin_test';
+const jarFile = '$testName.jar';
+
+final testRoot = join('test', testName);
+final kotlinPath = join(testRoot, 'kotlin');
+final jarPath = join(kotlinPath, 'target', jarFile);
+
+const preamble = '''
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+''';
+
+void compileKotlinSources(String workingDir) async {
+  final procRes = Process.runSync(
+    'mvn',
+    ['package'],
+    workingDirectory: workingDir,
+    runInShell: true,
+  );
+  if (procRes.exitCode != 0) {
+    throw "mvn exited with ${procRes.exitCode}\n"
+        "${procRes.stderr}";
+  }
+}
+
+Config getConfig([BindingsType bindingsType = BindingsType.cBased]) {
+  compileKotlinSources(kotlinPath);
+  final typeDir = bindingsType.getConfigString();
+  final cWrapperDir = Uri.directory(join(testRoot, typeDir, "c_bindings"));
+  final dartWrappersRoot = Uri.directory(
+    join(testRoot, typeDir, "dart_bindings"),
+  );
+  final config = Config(
+    classPath: [Uri.file(jarPath)],
+    classes: [
+      // Generating the entire library.
+      //
+      // This makes sure that no private class generated by Kotlin can make its
+      // way to the generated code.
+      'com.github.dart_lang.jnigen',
+    ],
+    logLevel: Level.ALL,
+    outputConfig: OutputConfig(
+      bindingsType: bindingsType,
+      cConfig: CCodeOutputConfig(
+        path: cWrapperDir,
+        libraryName: 'kotlin',
+      ),
+      dartConfig: DartCodeOutputConfig(
+        path: dartWrappersRoot.resolve('kotlin.dart'),
+        structure: OutputStructure.singleFile,
+      ),
+    ),
+    summarizerOptions: SummarizerOptions(backend: SummarizerBackend.asm),
+    preamble: preamble,
+  );
+  return config;
+}
+
+void main() async {
+  await generateJniBindings(getConfig(BindingsType.cBased));
+  await generateJniBindings(getConfig(BindingsType.dartOnly));
+}
diff --git a/pkgs/jnigen/test/kotlin_test/generated_files_test.dart b/pkgs/jnigen/test/kotlin_test/generated_files_test.dart
new file mode 100644
index 0000000..b96c643
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/generated_files_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:jnigen/jnigen.dart';
+import 'package:test/test.dart';
+
+import 'generate.dart';
+import '../test_util/test_util.dart';
+
+void main() async {
+  // This is not run in setupAll, because we want to exit with one line of
+  // error message, not throw a long exception.
+  await checkLocallyBuiltDependencies();
+  generateAndCompareBothModes(
+    'Generate and compare bindings for kotlin_test',
+    getConfig(BindingsType.cBased),
+    getConfig(BindingsType.dartOnly),
+  );
+  test(
+    "Generate and analyze bindings for kotlin_test - pure dart",
+    () async {
+      await generateAndAnalyzeBindings(
+        getConfig(BindingsType.dartOnly),
+      );
+    },
+    timeout: const Timeout.factor(1.5),
+    tags: largeTestTag,
+  );
+}
diff --git a/pkgs/jnigen/test/kotlin_test/kotlin/pom.xml b/pkgs/jnigen/test/kotlin_test/kotlin/pom.xml
new file mode 100644
index 0000000..b32d4ed
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/kotlin/pom.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         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>
+
+    <artifactId>kotlin</artifactId>
+    <groupId>com.github.dart_lang.jnigen</groupId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+    <name>consoleApp</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <kotlin.code.style>official</kotlin.code.style>
+        <main.class>com.github.dart_lang.jnigen.SuspendFun</main.class>
+        <kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
+    </properties>
+
+    <repositories>
+        <repository>
+            <id>mavenCentral</id>
+            <url>https://repo1.maven.org/maven2/</url>
+        </repository>
+    </repositories>
+
+    <build>
+        <finalName>kotlin_test</finalName>
+        <sourceDirectory>src/main/kotlin</sourceDirectory>
+        <testSourceDirectory>src/test/kotlin</testSourceDirectory>
+        <plugins>
+            <plugin>
+                <groupId>org.jetbrains.kotlin</groupId>
+                <artifactId>kotlin-maven-plugin</artifactId>
+                <version>1.7.20</version>
+                <executions>
+                    <execution>
+                        <id>compile</id>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>compile</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>test-compile</id>
+                        <phase>test-compile</phase>
+                        <goals>
+                            <goal>test-compile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.22.2</version>
+            </plugin>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>2.22.2</version>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <version>1.6.0</version>
+                <configuration>
+                    <mainClass>MainKt</mainClass>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.6</version>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals> <goal>single</goal> </goals>
+                        <configuration>
+                            <archive>
+                                <manifest>
+                                    <mainClass>${main.class}</mainClass>
+                                </manifest>
+                            </archive>
+                            <descriptorRefs>
+                                <descriptorRef>jar-with-dependencies</descriptorRef>
+                            </descriptorRefs>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-test-junit5</artifactId>
+            <version>1.7.20</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-coroutines-core</artifactId>
+            <version>1.6.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.8.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-stdlib-jdk8</artifactId>
+            <version>1.7.20</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/SuspendFun.kt b/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/SuspendFun.kt
new file mode 100644
index 0000000..ffd4376
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/kotlin/src/main/kotlin/com/github/dart_lang/jnigen/SuspendFun.kt
@@ -0,0 +1,21 @@
+/* Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ */
+
+package com.github.dart_lang.jnigen
+
+import kotlinx.coroutines.delay
+import kotlin.coroutines.Continuation
+
+public class SuspendFun {
+    suspend fun sayHello(): String {
+        delay(100L)
+        return "Hello!"
+    }
+
+    suspend fun sayHello(name: String): String {
+        delay(100L)
+        return "Hello $name!"
+    }
+}
\ No newline at end of file
diff --git a/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart b/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart
new file mode 100644
index 0000000..6a9696b
--- /dev/null
+++ b/pkgs/jnigen/test/kotlin_test/runtime_test_registrant.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+import 'package:jni/jni.dart';
+
+import '../test_util/callback_types.dart';
+
+import 'c_based/dart_bindings/kotlin.dart';
+
+void registerTests(String groupName, TestRunnerCallback test) {
+  group(groupName, () {
+    test('Suspend functions', () async {
+      await using((arena) async {
+        final suspendFun = SuspendFun()..releasedBy(arena);
+        final hello = await suspendFun.sayHello();
+        expect(hello.toDartString(releaseOriginal: true), "Hello!");
+        const name = "Bob";
+        final helloBob =
+            await suspendFun.sayHello1(name.toJString()..releasedBy(arena));
+        expect(helloBob.toDartString(releaseOriginal: true), "Hello $name!");
+      });
+    });
+  });
+}
diff --git a/pkgs/jnigen/test/package_resolver_test.dart b/pkgs/jnigen/test/package_resolver_test.dart
new file mode 100644
index 0000000..a29e043
--- /dev/null
+++ b/pkgs/jnigen/test/package_resolver_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:jnigen/src/bindings/resolver.dart';
+import 'package:jnigen/src/elements/elements.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+class ResolverTest {
+  ResolverTest(this.binaryName, this.expectedImport, this.expectedName);
+  String binaryName;
+  String expectedImport;
+  String expectedName;
+}
+
+void main() async {
+  await checkLocallyBuiltDependencies();
+  final resolver = Resolver(
+    importedClasses: {
+      'org.apache.pdfbox.pdmodel.PDDocument': ClassDecl(
+        declKind: DeclKind.classKind,
+        binaryName: 'org.apache.pdfbox.pdmodel.PDDocument',
+      )..path = 'package:pdfbox/pdfbox.dart',
+      'android.os.Process': ClassDecl(
+        declKind: DeclKind.classKind,
+        binaryName: 'android.os.Process',
+      )..path = 'package:android/os.dart',
+    },
+    currentClass: 'a.b.N',
+    inputClassNames: {
+      'a.b.C',
+      'a.b.c.D',
+      'a.b.c.d.E',
+      'a.X',
+      'e.f.G',
+      'e.F',
+      'a.g.Y',
+      'a.m.n.P'
+    },
+  );
+
+  final tests = [
+    // Absolute imports resolved using import map
+    ResolverTest('android.os.Process', 'package:android/os.dart', 'process_.'),
+    ResolverTest('org.apache.pdfbox.pdmodel.PDDocument',
+        'package:pdfbox/pdfbox.dart', 'pddocument_.'),
+    // Relative imports
+    // inner package
+    ResolverTest('a.b.c.D', 'c/D.dart', 'd_.'),
+    // inner package, deeper
+    ResolverTest('a.b.c.d.E', 'c/d/E.dart', 'e_.'),
+    // parent package
+    ResolverTest('a.X', '../X.dart', 'x_.'),
+    // unrelated package in same translation unit
+    ResolverTest('e.f.G', '../../e/f/G.dart', 'g_.'),
+    ResolverTest('e.F', '../../e/F.dart', 'f_.'),
+    // neighbour package
+    ResolverTest('a.g.Y', '../g/Y.dart', 'y_.'),
+    // inner package of a neighbour package
+    ResolverTest('a.m.n.P', '../m/n/P.dart', 'p_.'),
+  ];
+
+  for (var testCase in tests) {
+    final binaryName = testCase.binaryName;
+    final packageName = Resolver.getFileClassName(binaryName);
+    test(
+        'getImport $binaryName',
+        () => expect(resolver.getImport(packageName, binaryName),
+            equals(testCase.expectedImport)));
+    test(
+        'resolve $binaryName',
+        () => expect(
+            resolver.resolvePrefix(ClassDecl(
+              declKind: DeclKind.classKind,
+              binaryName: binaryName,
+            )..path = ''),
+            equals(testCase.expectedName)));
+  }
+}
diff --git a/pkgs/jnigen/test/regenerate_examples_test.dart b/pkgs/jnigen/test/regenerate_examples_test.dart
new file mode 100644
index 0000000..35ab2e0
--- /dev/null
+++ b/pkgs/jnigen/test/regenerate_examples_test.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+import 'package:path/path.dart' hide equals;
+
+import 'package:jnigen/jnigen.dart';
+import 'package:jnigen/tools.dart';
+
+import 'test_util/test_util.dart';
+
+final inAppJava = join('example', 'in_app_java');
+final inAppJavaYaml = join(inAppJava, 'jnigen.yaml');
+final notificationPlugin = join('example', 'notification_plugin');
+final notificationPluginYaml = join(notificationPlugin, 'jnigen.yaml');
+final kotlinPlugin = join('example', 'kotlin_plugin');
+final kotlinPluginYaml = join(kotlinPlugin, 'jnigen.yaml');
+
+/// Generates bindings using jnigen config in [exampleName] and compares
+/// them to provided reference outputs.
+///
+/// [dartOutput] and [cOutput] are relative paths from example project dir.
+///
+/// Pass [isLargeTest] as true if the test will take considerable time.
+void testExample(String exampleName, String dartOutput, String? cOutput,
+    {bool isLargeTest = false}) {
+  test(
+    'Generate and compare bindings for $exampleName',
+    timeout: const Timeout.factor(3),
+    () async {
+      final examplePath = join('example', exampleName);
+      final configPath = join(examplePath, 'jnigen.yaml');
+
+      final config = Config.parseArgs(['--config', configPath]);
+      try {
+        await generateAndCompareBindings(config);
+      } on GradleException catch (_) {
+        stderr.writeln('Skip: $exampleName');
+      }
+    },
+    tags: isLargeTest ? largeTestTag : null,
+  );
+}
+
+void main() async {
+  await checkLocallyBuiltDependencies();
+  testExample(
+    'in_app_java',
+    join('lib', 'android_utils.dart'),
+    join('src', 'android_utils'),
+    isLargeTest: true,
+  );
+  testExample(
+    'pdfbox_plugin',
+    join('lib', 'src', 'third_party'),
+    'src',
+    isLargeTest: false,
+  );
+  testExample(
+    'notification_plugin',
+    join('lib', 'notifications.dart'),
+    'src',
+    isLargeTest: true,
+  );
+  testExample(
+    'kotlin_plugin',
+    join('lib', 'kotlin_bindings.dart'),
+    'src',
+    isLargeTest: true,
+  );
+}
diff --git a/pkgs/jnigen/test/simple_package_test/.gitignore b/pkgs/jnigen/test/simple_package_test/.gitignore
new file mode 100644
index 0000000..1db9cf8
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/.gitignore
@@ -0,0 +1,6 @@
+build/
+*.class
+test_lib/
+test_src/
+*_dartonly_generated.dart ## Generated test replicas
+generated_runtime_test.dart
\ No newline at end of file
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/.clang-format b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/.clang-format
new file mode 100644
index 0000000..a256c2f
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/.clang-format
@@ -0,0 +1,15 @@
+# From dart SDK: https://github.com/dart-lang/sdk/blob/main/.clang-format
+
+# Defines the Chromium style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
+
+# clang-format doesn't seem to do a good job of this for longer comments.
+ReflowComments: 'false'
+
+# We have lots of these. Though we need to put them all in curly braces,
+# clang-format can't do that.
+AllowShortIfStatementsOnASingleLine: 'true'
+
+# Put escaped newlines into the rightmost column.
+AlignEscapedNewlinesLeft: false
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/CMakeLists.txt b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/CMakeLists.txt
new file mode 100644
index 0000000..eeb352d
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/CMakeLists.txt
@@ -0,0 +1,32 @@
+# jni_native_build (Build with jni:setup. Do not delete this line.)
+
+# The Flutter tooling requires that developers have CMake 3.10 or later
+# installed. You should not increase this version, as doing so will cause
+# the plugin to fail to compile for some customers of the plugin.
+cmake_minimum_required(VERSION 3.10)
+
+project(simple_package VERSION 0.0.1 LANGUAGES C)
+
+add_library(simple_package SHARED
+  "./simple_package.c"
+)
+
+set_target_properties(simple_package PROPERTIES
+  OUTPUT_NAME "simple_package"
+)
+
+target_compile_definitions(simple_package PUBLIC DART_SHARED_LIB)
+
+if(WIN32)
+	set_target_properties(${TARGET_NAME} PROPERTIES
+		LINK_FLAGS "/DELAYLOAD:jvm.dll")
+endif()
+
+if (ANDROID)
+	target_link_libraries(simple_package log)
+else()
+	find_package(Java REQUIRED)
+	find_package(JNI REQUIRED)
+	include_directories(${JNI_INCLUDE_DIRS})
+	target_link_libraries(simple_package ${JNI_LIBRARIES})
+endif()
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/dartjni.h b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/dartjni.h
new file mode 100644
index 0000000..8f1dc74
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/dartjni.h
@@ -0,0 +1,424 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h.
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if _WIN32
+#include <windows.h>
+#else
+#include <pthread.h>
+#include <unistd.h>
+#endif
+
+#if _WIN32
+#define FFI_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FFI_PLUGIN_EXPORT
+#endif
+
+#if defined _WIN32
+#define thread_local __declspec(thread)
+#else
+#define thread_local __thread
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#ifdef __ANDROID__
+#define __ENVP_CAST (JNIEnv**)
+#else
+#define __ENVP_CAST (void**)
+#endif
+
+/// Locking functions for windows and pthread.
+
+#if defined _WIN32
+#include <windows.h>
+
+typedef CRITICAL_SECTION MutexLock;
+typedef CONDITION_VARIABLE ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  InitializeCriticalSection(lock);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  EnterCriticalSection(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  LeaveCriticalSection(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  DeleteCriticalSection(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  InitializeConditionVariable(cond);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  WakeConditionVariable(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  SleepConditionVariableCS(cond, lock, INFINITE);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  // Not available.
+}
+
+#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ ||         \
+    defined __GNUC__
+#include <pthread.h>
+
+typedef pthread_mutex_t MutexLock;
+typedef pthread_cond_t ConditionVariable;
+
+static inline void init_lock(MutexLock* lock) {
+  pthread_mutex_init(lock, NULL);
+}
+
+static inline void acquire_lock(MutexLock* lock) {
+  pthread_mutex_lock(lock);
+}
+
+static inline void release_lock(MutexLock* lock) {
+  pthread_mutex_unlock(lock);
+}
+
+static inline void destroy_lock(MutexLock* lock) {
+  pthread_mutex_destroy(lock);
+}
+
+static inline void init_cond(ConditionVariable* cond) {
+  pthread_cond_init(cond, NULL);
+}
+
+static inline void signal_cond(ConditionVariable* cond) {
+  pthread_cond_signal(cond);
+}
+
+static inline void wait_for(ConditionVariable* cond, MutexLock* lock) {
+  pthread_cond_wait(cond, lock);
+}
+
+static inline void destroy_cond(ConditionVariable* cond) {
+  pthread_cond_destroy(cond);
+}
+
+#else
+
+#error "No locking/condition variable support; Possibly unsupported platform"
+
+#endif
+
+typedef struct CallbackResult {
+  MutexLock lock;
+  ConditionVariable cond;
+  int ready;
+  jobject object;
+} CallbackResult;
+
+typedef struct JniLocks {
+  MutexLock classLoadingLock;
+  MutexLock methodLoadingLock;
+  MutexLock fieldLoadingLock;
+} JniLocks;
+
+/// Represents the error when dart-jni layer has already spawned singleton VM.
+#define DART_JNI_SINGLETON_EXISTS (-99);
+
+/// Stores the global state of the JNI.
+typedef struct JniContext {
+  JavaVM* jvm;
+  jobject classLoader;
+  jmethodID loadClassMethod;
+  jobject currentActivity;
+  jobject appContext;
+  JniLocks locks;
+} JniContext;
+
+// jniEnv for this thread, used by inline functions in this header,
+// therefore declared as extern.
+extern thread_local JNIEnv* jniEnv;
+
+extern JniContext* jni;
+
+/// Types used by JNI API to distinguish between primitive types.
+enum JniType {
+  booleanType = 0,
+  byteType = 1,
+  shortType = 2,
+  charType = 3,
+  intType = 4,
+  longType = 5,
+  floatType = 6,
+  doubleType = 7,
+  objectType = 8,
+  voidType = 9,
+};
+
+/// Result type for use by JNI.
+///
+/// If [exception] is null, it means the result is valid.
+/// It's assumed that the caller knows the expected type in [result].
+typedef struct JniResult {
+  jvalue value;
+  jthrowable exception;
+} JniResult;
+
+/// Similar to [JniResult] but for class lookups.
+typedef struct JniClassLookupResult {
+  jclass value;
+  jthrowable exception;
+} JniClassLookupResult;
+
+/// Similar to [JniResult] but for method/field ID lookups.
+typedef struct JniPointerResult {
+  const void* value;
+  jthrowable exception;
+} JniPointerResult;
+
+/// JniExceptionDetails holds 2 jstring objects, one is the result of
+/// calling `toString` on exception object, other is stack trace;
+typedef struct JniExceptionDetails {
+  jstring message;
+  jstring stacktrace;
+} JniExceptionDetails;
+
+/// This struct contains functions which wrap method call / field access conveniently along with
+/// exception checking.
+///
+/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us
+/// to check for and clear the exception before returning to dart code, which requires these functions
+/// to return result types.
+typedef struct JniAccessorsStruct {
+  JniClassLookupResult (*getClass)(char* internalName);
+  JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature);
+  JniPointerResult (*getStaticFieldID)(jclass cls,
+                                       char* fieldName,
+                                       char* signature);
+  JniPointerResult (*getMethodID)(jclass cls,
+                                  char* methodName,
+                                  char* signature);
+  JniPointerResult (*getStaticMethodID)(jclass cls,
+                                        char* methodName,
+                                        char* signature);
+  JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args);
+  JniResult (*newPrimitiveArray)(jsize length, int type);
+  JniResult (*newObjectArray)(jsize length,
+                              jclass elementClass,
+                              jobject initialElement);
+  JniResult (*getArrayElement)(jarray array, int index, int type);
+  JniResult (*callMethod)(jobject obj,
+                          jmethodID methodID,
+                          int callType,
+                          jvalue* args);
+  JniResult (*callStaticMethod)(jclass cls,
+                                jmethodID methodID,
+                                int callType,
+                                jvalue* args);
+  JniResult (*getField)(jobject obj, jfieldID fieldID, int callType);
+  JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType);
+  JniExceptionDetails (*getExceptionDetails)(jthrowable exception);
+} JniAccessorsStruct;
+
+FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors();
+
+FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void);
+
+FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void);
+
+/// Spawn a JVM with given arguments.
+///
+/// Returns JNI_OK on success, and one of the documented JNI error codes on
+/// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple
+/// JVMs is made, even if the underlying API potentially supports multiple VMs.
+FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args);
+
+/// Load class through platform-specific mechanism.
+///
+/// Currently uses application classloader on android,
+/// and JNIEnv->FindClass on other platforms.
+FFI_PLUGIN_EXPORT jclass FindClass(const char* name);
+
+/// Returns Application classLoader (on Android),
+/// which can be used to load application and platform classes.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetClassLoader(void);
+
+/// Returns application context on Android.
+///
+/// On other platforms, NULL is returned.
+FFI_PLUGIN_EXPORT jobject GetApplicationContext(void);
+
+/// Returns current activity of the app on Android.
+FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void);
+
+static inline void attach_thread() {
+  if (jniEnv == NULL) {
+    (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL);
+  }
+}
+
+/// Load class into [cls] using platform specific mechanism
+static inline void load_class_platform(jclass* cls, const char* name) {
+#ifdef __ANDROID__
+  jstring className = (*jniEnv)->NewStringUTF(jniEnv, name);
+  *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader,
+                                     jni->loadClassMethod, className);
+  (*jniEnv)->DeleteLocalRef(jniEnv, className);
+#else
+  *cls = (*jniEnv)->FindClass(jniEnv, name);
+#endif
+}
+
+static inline void load_class_local_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(cls, name);
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_class_global_ref(jclass* cls, const char* name) {
+  if (*cls == NULL) {
+    jclass tmp = NULL;
+    acquire_lock(&jni->locks.classLoadingLock);
+    if (*cls == NULL) {
+      load_class_platform(&tmp, name);
+      if (!(*jniEnv)->ExceptionCheck(jniEnv)) {
+        *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp);
+        (*jniEnv)->DeleteLocalRef(jniEnv, tmp);
+      }
+    }
+    release_lock(&jni->locks.classLoadingLock);
+  }
+}
+
+static inline void load_method(jclass cls,
+                               jmethodID* res,
+                               const char* name,
+                               const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_static_method(jclass cls,
+                                      jmethodID* res,
+                                      const char* name,
+                                      const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.methodLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.methodLoadingLock);
+  }
+}
+
+static inline void load_field(jclass cls,
+                              jfieldID* res,
+                              const char* name,
+                              const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline void load_static_field(jclass cls,
+                                     jfieldID* res,
+                                     const char* name,
+                                     const char* sig) {
+  if (*res == NULL) {
+    acquire_lock(&jni->locks.fieldLoadingLock);
+    if (*res == NULL) {
+      *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig);
+    }
+    release_lock(&jni->locks.fieldLoadingLock);
+  }
+}
+
+static inline jobject to_global_ref(jobject ref) {
+  jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref);
+  (*jniEnv)->DeleteLocalRef(jniEnv, ref);
+  return g;
+}
+
+// These functions are useful for C+Dart bindings, and not required for pure dart bindings.
+
+FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr();
+
+/// For use by jni_gen's generated code
+/// don't use these.
+
+// these 2 fn ptr vars will be defined by generated code library
+extern JniContext* (*context_getter)(void);
+extern JNIEnv* (*env_getter)(void);
+
+// this function will be exported by generated code library
+// it will set above 2 variables.
+FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void),
+                                     JNIEnv* (*eg)(void));
+
+static inline void load_env() {
+  if (jniEnv == NULL) {
+    jni = context_getter();
+    jniEnv = env_getter();
+  }
+}
+
+static inline jthrowable check_exception() {
+  jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv);
+  if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv);
+  if (exception == NULL) return NULL;
+  return to_global_ref(exception);
+}
+
+static inline JniResult to_global_ref_result(jobject ref) {
+  JniResult result;
+  result.exception = check_exception();
+  if (result.exception == NULL) {
+    result.value.l = to_global_ref(ref);
+  }
+  return result;
+}
+
+FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data);
+
+FFI_PLUGIN_EXPORT
+JniResult DartException__ctor(jstring message);
+
+FFI_PLUGIN_EXPORT
+JniResult PortContinuation__ctor(int64_t j);
+
+FFI_PLUGIN_EXPORT
+JniResult PortProxy__newInstance(jobject binaryName,
+                                 int64_t port,
+                                 int64_t functionPtr);
+
+FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object);
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c
new file mode 100644
index 0000000..20c7e7e
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c
@@ -0,0 +1,2954 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+#include <stdint.h>
+#include "dartjni.h"
+#include "jni.h"
+
+thread_local JNIEnv* jniEnv;
+JniContext* jni;
+
+JniContext* (*context_getter)(void);
+JNIEnv* (*env_getter)(void);
+
+void setJniGetters(JniContext* (*cg)(void), JNIEnv* (*eg)(void)) {
+  context_getter = cg;
+  env_getter = eg;
+}
+
+// com.github.dart_lang.jnigen.simple_package.Color
+jclass _c_Color = NULL;
+
+jmethodID _m_Color__values = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Color__values() {
+  load_env();
+  load_class_global_ref(&_c_Color,
+                        "com/github/dart_lang/jnigen/simple_package/Color");
+  if (_c_Color == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Color, &_m_Color__values, "values",
+                     "()[Lcom/github/dart_lang/jnigen/simple_package/Color;");
+  if (_m_Color__values == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_Color, _m_Color__values);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Color__valueOf = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Color__valueOf(jobject name) {
+  load_env();
+  load_class_global_ref(&_c_Color,
+                        "com/github/dart_lang/jnigen/simple_package/Color");
+  if (_c_Color == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_Color, &_m_Color__valueOf, "valueOf",
+      "(Ljava/lang/String;)Lcom/github/dart_lang/jnigen/simple_package/Color;");
+  if (_m_Color__valueOf == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_Color,
+                                                      _m_Color__valueOf, name);
+  return to_global_ref_result(_result);
+}
+
+// com.github.dart_lang.jnigen.simple_package.Example
+jclass _c_Example = NULL;
+
+jmethodID _m_Example__getAmount = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getAmount() {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__getAmount, "getAmount", "()I");
+  if (_m_Example__getAmount == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallStaticIntMethod(jniEnv, _c_Example, _m_Example__getAmount);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getPi = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getPi() {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__getPi, "getPi", "()D");
+  if (_m_Example__getPi == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  double _result =
+      (*jniEnv)->CallStaticDoubleMethod(jniEnv, _c_Example, _m_Example__getPi);
+  return (JniResult){.value = {.d = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getAsterisk = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getAsterisk() {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__getAsterisk, "getAsterisk",
+                     "()C");
+  if (_m_Example__getAsterisk == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint16_t _result = (*jniEnv)->CallStaticCharMethod(jniEnv, _c_Example,
+                                                     _m_Example__getAsterisk);
+  return (JniResult){.value = {.c = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getName = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getName() {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__getName, "getName",
+                     "()Ljava/lang/String;");
+  if (_m_Example__getName == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_Example,
+                                                      _m_Example__getName);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__getNestedInstance = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getNestedInstance() {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_Example, &_m_Example__getNestedInstance, "getNestedInstance",
+      "()Lcom/github/dart_lang/jnigen/simple_package/Example$Nested;");
+  if (_m_Example__getNestedInstance == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_Example, _m_Example__getNestedInstance);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__setAmount = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__setAmount(int32_t newAmount) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__setAmount, "setAmount", "(I)V");
+  if (_m_Example__setAmount == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_Example, _m_Example__setAmount,
+                                  newAmount);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__setName = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__setName(jobject newName) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__setName, "setName",
+                     "(Ljava/lang/String;)V");
+  if (_m_Example__setName == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_Example, _m_Example__setName,
+                                  newName);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__setNestedInstance = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__setNestedInstance(jobject newNested) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_Example, &_m_Example__setNestedInstance, "setNestedInstance",
+      "(Lcom/github/dart_lang/jnigen/simple_package/Example$Nested;)V");
+  if (_m_Example__setNestedInstance == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_Example,
+                                  _m_Example__setNestedInstance, newNested);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__max4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__max4(int32_t a, int32_t b, int32_t c, int32_t d) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__max4, "max4", "(IIII)I");
+  if (_m_Example__max4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallStaticIntMethod(
+      jniEnv, _c_Example, _m_Example__max4, a, b, c, d);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__max8 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__max8(int32_t a,
+                        int32_t b,
+                        int32_t c,
+                        int32_t d,
+                        int32_t e,
+                        int32_t f,
+                        int32_t g,
+                        int32_t h) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__max8, "max8", "(IIIIIIII)I");
+  if (_m_Example__max8 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallStaticIntMethod(
+      jniEnv, _c_Example, _m_Example__max8, a, b, c, d, e, f, g, h);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getNumber = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getNumber(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__getNumber, "getNumber", "()I");
+  if (_m_Example__getNumber == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_Example__getNumber);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__setNumber = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__setNumber(jobject self_, int32_t number) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__setNumber, "setNumber", "(I)V");
+  if (_m_Example__setNumber == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__setNumber, number);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getIsUp = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getIsUp(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__getIsUp, "getIsUp", "()Z");
+  if (_m_Example__getIsUp == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_Example__getIsUp);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__setUp = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__setUp(jobject self_, uint8_t isUp) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__setUp, "setUp", "(Z)V");
+  if (_m_Example__setUp == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__setUp, isUp);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getCodename = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getCodename(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__getCodename, "getCodename",
+              "()Ljava/lang/String;");
+  if (_m_Example__getCodename == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_Example__getCodename);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__setCodename = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__setCodename(jobject self_, jobject codename) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__setCodename, "setCodename",
+              "(Ljava/lang/String;)V");
+  if (_m_Example__setCodename == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__setCodename, codename);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getRandom = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getRandom(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__getRandom, "getRandom",
+              "()Ljava/util/Random;");
+  if (_m_Example__getRandom == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_Example__getRandom);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__setRandom = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__setRandom(jobject self_, jobject random) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__setRandom, "setRandom",
+              "(Ljava/util/Random;)V");
+  if (_m_Example__setRandom == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__setRandom, random);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getRandomLong = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getRandomLong(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__getRandomLong, "getRandomLong", "()J");
+  if (_m_Example__getRandomLong == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int64_t _result =
+      (*jniEnv)->CallLongMethod(jniEnv, self_, _m_Example__getRandomLong);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__add4Longs = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__add4Longs(jobject self_,
+                             int64_t a,
+                             int64_t b,
+                             int64_t c,
+                             int64_t d) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__add4Longs, "add4Longs", "(JJJJ)J");
+  if (_m_Example__add4Longs == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int64_t _result = (*jniEnv)->CallLongMethod(
+      jniEnv, self_, _m_Example__add4Longs, a, b, c, d);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__add8Longs = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__add8Longs(jobject self_,
+                             int64_t a,
+                             int64_t b,
+                             int64_t c,
+                             int64_t d,
+                             int64_t e,
+                             int64_t f,
+                             int64_t g,
+                             int64_t h) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__add8Longs, "add8Longs", "(JJJJJJJJ)J");
+  if (_m_Example__add8Longs == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int64_t _result = (*jniEnv)->CallLongMethod(
+      jniEnv, self_, _m_Example__add8Longs, a, b, c, d, e, f, g, h);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getRandomNumericString = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getRandomNumericString(jobject self_, jobject random) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__getRandomNumericString,
+              "getRandomNumericString",
+              "(Ljava/util/Random;)Ljava/lang/String;");
+  if (_m_Example__getRandomNumericString == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_Example__getRandomNumericString, random);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__protectedMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__protectedMethod(jobject self_, jobject a, jobject b) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__protectedMethod, "protectedMethod",
+              "(Ljava/lang/String;Ljava/lang/String;)V");
+  if (_m_Example__protectedMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__protectedMethod, a, b);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__finalMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__finalMethod(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__finalMethod, "finalMethod", "()V");
+  if (_m_Example__finalMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__finalMethod);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getList = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getList(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__getList, "getList",
+              "()Ljava/util/List;");
+  if (_m_Example__getList == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_Example__getList);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__joinStrings = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__joinStrings(jobject self_, jobject values, jobject delim) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__joinStrings, "joinStrings",
+              "(Ljava/util/List;Ljava/lang/String;)Ljava/lang/String;");
+  if (_m_Example__joinStrings == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_Example__joinStrings, values, delim);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__methodWithSeveralParams = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__methodWithSeveralParams(jobject self_,
+                                           uint16_t ch,
+                                           jobject s,
+                                           jobject a,
+                                           jobject t,
+                                           jobject lt,
+                                           jobject wm) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__methodWithSeveralParams,
+              "methodWithSeveralParams",
+              "(CLjava/lang/String;[ILjava/lang/CharSequence;Ljava/util/"
+              "List;Ljava/util/Map;)V");
+  if (_m_Example__methodWithSeveralParams == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__methodWithSeveralParams,
+                            ch, s, a, t, lt, wm);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__new0() {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__new0, "<init>", "()V");
+  if (_m_Example__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Example, _m_Example__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__new1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__new1(int32_t number) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__new1, "<init>", "(I)V");
+  if (_m_Example__new1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_Example, _m_Example__new1, number);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__new2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__new2(int32_t number, uint8_t isUp) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__new2, "<init>", "(IZ)V");
+  if (_m_Example__new2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_Example, _m_Example__new2, number, isUp);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__new3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__new3(int32_t number, uint8_t isUp, jobject codename) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__new3, "<init>",
+              "(IZLjava/lang/String;)V");
+  if (_m_Example__new3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Example, _m_Example__new3,
+                                         number, isUp, codename);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__new4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__new4(int32_t a,
+                        int32_t b,
+                        int32_t c,
+                        int32_t d,
+                        int32_t e,
+                        int32_t f,
+                        int32_t g,
+                        int32_t h) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__new4, "<init>", "(IIIIIIII)V");
+  if (_m_Example__new4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Example, _m_Example__new4,
+                                         a, b, c, d, e, f, g, h);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__whichExample = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__whichExample(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__whichExample, "whichExample", "()I");
+  if (_m_Example__whichExample == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_Example__whichExample);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__addInts = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__addInts(int32_t a, int32_t b) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__addInts, "addInts", "(II)I");
+  if (_m_Example__addInts == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallStaticIntMethod(jniEnv, _c_Example,
+                                                   _m_Example__addInts, a, b);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getArr = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getArr() {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__getArr, "getArr", "()[I");
+  if (_m_Example__getArr == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_Example, _m_Example__getArr);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__addAll = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__addAll(jobject arr) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__addAll, "addAll", "([I)I");
+  if (_m_Example__addAll == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallStaticIntMethod(jniEnv, _c_Example,
+                                                   _m_Example__addAll, arr);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__getSelf = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__getSelf(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__getSelf, "getSelf",
+              "()Lcom/github/dart_lang/jnigen/simple_package/Example;");
+  if (_m_Example__getSelf == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_Example__getSelf);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example__throwException = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__throwException() {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Example, &_m_Example__throwException, "throwException",
+                     "()V");
+  if (_m_Example__throwException == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_Example,
+                                  _m_Example__throwException);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__overloaded = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__overloaded(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__overloaded, "overloaded", "()V");
+  if (_m_Example__overloaded == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__overloaded);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__overloaded1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__overloaded1(jobject self_, int32_t a, jobject b) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__overloaded1, "overloaded",
+              "(ILjava/lang/String;)V");
+  if (_m_Example__overloaded1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__overloaded1, a, b);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__overloaded2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__overloaded2(jobject self_, int32_t a) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__overloaded2, "overloaded", "(I)V");
+  if (_m_Example__overloaded2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__overloaded2, a);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__overloaded3 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__overloaded3(jobject self_, jobject a, jobject b) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__overloaded3, "overloaded",
+              "(Ljava/util/List;Ljava/lang/String;)V");
+  if (_m_Example__overloaded3 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__overloaded3, a, b);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example__overloaded4 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example__overloaded4(jobject self_, jobject a) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example, &_m_Example__overloaded4, "overloaded",
+              "(Ljava/util/List;)V");
+  if (_m_Example__overloaded4 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example__overloaded4, a);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Example__unusedRandom = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Example__unusedRandom() {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Example, &_f_Example__unusedRandom, "unusedRandom",
+                    "Ljava/util/Random;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Example,
+                                                    _f_Example__unusedRandom);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Example__protectedField = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Example__protectedField(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Example, &_f_Example__protectedField, "protectedField",
+             "Ljava/util/Random;");
+  jobject _result =
+      (*jniEnv)->GetObjectField(jniEnv, self_, _f_Example__protectedField);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Example__protectedField(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_Example,
+                        "com/github/dart_lang/jnigen/simple_package/Example");
+  if (_c_Example == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Example, &_f_Example__protectedField, "protectedField",
+             "Ljava/util/Random;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_Example__protectedField, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.simple_package.Example$Nested
+jclass _c_Example_Nested = NULL;
+
+jmethodID _m_Example_Nested__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example_Nested__new0(uint8_t value) {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_Nested,
+      "com/github/dart_lang/jnigen/simple_package/Example$Nested");
+  if (_c_Example_Nested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example_Nested, &_m_Example_Nested__new0, "<init>", "(Z)V");
+  if (_m_Example_Nested__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Example_Nested,
+                                         _m_Example_Nested__new0, value);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example_Nested__usesAnonymousInnerClass = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example_Nested__usesAnonymousInnerClass(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_Nested,
+      "com/github/dart_lang/jnigen/simple_package/Example$Nested");
+  if (_c_Example_Nested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example_Nested, &_m_Example_Nested__usesAnonymousInnerClass,
+              "usesAnonymousInnerClass", "()V");
+  if (_m_Example_Nested__usesAnonymousInnerClass == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_Example_Nested__usesAnonymousInnerClass);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_Example_Nested__getValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example_Nested__getValue(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_Nested,
+      "com/github/dart_lang/jnigen/simple_package/Example$Nested");
+  if (_c_Example_Nested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example_Nested, &_m_Example_Nested__getValue, "getValue",
+              "()Z");
+  if (_m_Example_Nested__getValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  uint8_t _result =
+      (*jniEnv)->CallBooleanMethod(jniEnv, self_, _m_Example_Nested__getValue);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Example_Nested__setValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example_Nested__setValue(jobject self_, uint8_t value) {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_Nested,
+      "com/github/dart_lang/jnigen/simple_package/Example$Nested");
+  if (_c_Example_Nested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example_Nested, &_m_Example_Nested__setValue, "setValue",
+              "(Z)V");
+  if (_m_Example_Nested__setValue == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_Example_Nested__setValue, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.simple_package.Example$Nested$NestedTwice
+jclass _c_Example_Nested_NestedTwice = NULL;
+
+jmethodID _m_Example_Nested_NestedTwice__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example_Nested_NestedTwice__new0() {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_Nested_NestedTwice,
+      "com/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice");
+  if (_c_Example_Nested_NestedTwice == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example_Nested_NestedTwice,
+              &_m_Example_Nested_NestedTwice__new0, "<init>", "()V");
+  if (_m_Example_Nested_NestedTwice__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Example_Nested_NestedTwice,
+                                         _m_Example_Nested_NestedTwice__new0);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Example_Nested_NestedTwice__ZERO = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Example_Nested_NestedTwice__ZERO() {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_Nested_NestedTwice,
+      "com/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice");
+  if (_c_Example_Nested_NestedTwice == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Example_Nested_NestedTwice,
+                    &_f_Example_Nested_NestedTwice__ZERO, "ZERO", "I");
+  int32_t _result =
+      (*jniEnv)->GetStaticIntField(jniEnv, _c_Example_Nested_NestedTwice,
+                                   _f_Example_Nested_NestedTwice__ZERO);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Example_Nested_NestedTwice__ZERO(int32_t value) {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_Nested_NestedTwice,
+      "com/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice");
+  if (_c_Example_Nested_NestedTwice == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Example_Nested_NestedTwice,
+                    &_f_Example_Nested_NestedTwice__ZERO, "ZERO", "I");
+  (*jniEnv)->SetStaticIntField(jniEnv, _c_Example_Nested_NestedTwice,
+                               _f_Example_Nested_NestedTwice__ZERO, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.simple_package.Example$NonStaticNested
+jclass _c_Example_NonStaticNested = NULL;
+
+jmethodID _m_Example_NonStaticNested__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example_NonStaticNested__new0(jobject _parent) {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_NonStaticNested,
+      "com/github/dart_lang/jnigen/simple_package/Example$NonStaticNested");
+  if (_c_Example_NonStaticNested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example_NonStaticNested, &_m_Example_NonStaticNested__new0,
+              "<init>",
+              "(Lcom/github/dart_lang/jnigen/simple_package/Example;)V");
+  if (_m_Example_NonStaticNested__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_Example_NonStaticNested,
+                           _m_Example_NonStaticNested__new0, _parent);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Example_NonStaticNested__ok = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Example_NonStaticNested__ok(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_NonStaticNested,
+      "com/github/dart_lang/jnigen/simple_package/Example$NonStaticNested");
+  if (_c_Example_NonStaticNested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Example_NonStaticNested, &_f_Example_NonStaticNested__ok, "ok",
+             "Z");
+  uint8_t _result =
+      (*jniEnv)->GetBooleanField(jniEnv, self_, _f_Example_NonStaticNested__ok);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Example_NonStaticNested__ok(jobject self_, uint8_t value) {
+  load_env();
+  load_class_global_ref(
+      &_c_Example_NonStaticNested,
+      "com/github/dart_lang/jnigen/simple_package/Example$NonStaticNested");
+  if (_c_Example_NonStaticNested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Example_NonStaticNested, &_f_Example_NonStaticNested__ok, "ok",
+             "Z");
+  (*jniEnv)->SetBooleanField(jniEnv, self_, _f_Example_NonStaticNested__ok,
+                             value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.simple_package.Exceptions
+jclass _c_Exceptions = NULL;
+
+jmethodID _m_Exceptions__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__new0() {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__new0, "<init>", "()V");
+  if (_m_Exceptions__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_Exceptions, _m_Exceptions__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__new1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__new1(float x) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__new1, "<init>", "(F)V");
+  if (_m_Exceptions__new1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_Exceptions, _m_Exceptions__new1, x);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__new2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__new2(int32_t a,
+                           int32_t b,
+                           int32_t c,
+                           int32_t d,
+                           int32_t e,
+                           int32_t f) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__new2, "<init>", "(IIIIII)V");
+  if (_m_Exceptions__new2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Exceptions,
+                                         _m_Exceptions__new2, a, b, c, d, e, f);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__staticObjectMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__staticObjectMethod() {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Exceptions, &_m_Exceptions__staticObjectMethod,
+                     "staticObjectMethod", "()Ljava/lang/Object;");
+  if (_m_Exceptions__staticObjectMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_Exceptions, _m_Exceptions__staticObjectMethod);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__staticIntMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__staticIntMethod() {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Exceptions, &_m_Exceptions__staticIntMethod,
+                     "staticIntMethod", "()I");
+  if (_m_Exceptions__staticIntMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallStaticIntMethod(
+      jniEnv, _c_Exceptions, _m_Exceptions__staticIntMethod);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Exceptions__staticObjectArrayMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__staticObjectArrayMethod() {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Exceptions, &_m_Exceptions__staticObjectArrayMethod,
+                     "staticObjectArrayMethod", "()[Ljava/lang/Object;");
+  if (_m_Exceptions__staticObjectArrayMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_Exceptions, _m_Exceptions__staticObjectArrayMethod);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__staticIntArrayMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__staticIntArrayMethod() {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Exceptions, &_m_Exceptions__staticIntArrayMethod,
+                     "staticIntArrayMethod", "()[I");
+  if (_m_Exceptions__staticIntArrayMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_Exceptions, _m_Exceptions__staticIntArrayMethod);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__objectMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__objectMethod(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__objectMethod, "objectMethod",
+              "()Ljava/lang/Object;");
+  if (_m_Exceptions__objectMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_Exceptions__objectMethod);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__intMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__intMethod(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__intMethod, "intMethod", "()I");
+  if (_m_Exceptions__intMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_Exceptions__intMethod);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Exceptions__objectArrayMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__objectArrayMethod(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__objectArrayMethod,
+              "objectArrayMethod", "()[Ljava/lang/Object;");
+  if (_m_Exceptions__objectArrayMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_Exceptions__objectArrayMethod);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__intArrayMethod = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__intArrayMethod(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__intArrayMethod, "intArrayMethod",
+              "()[I");
+  if (_m_Exceptions__intArrayMethod == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_Exceptions__intArrayMethod);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__throwNullPointerException = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__throwNullPointerException(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__throwNullPointerException,
+              "throwNullPointerException", "()I");
+  if (_m_Exceptions__throwNullPointerException == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_Exceptions__throwNullPointerException);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Exceptions__throwFileNotFoundException = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__throwFileNotFoundException(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__throwFileNotFoundException,
+              "throwFileNotFoundException", "()Ljava/io/InputStream;");
+  if (_m_Exceptions__throwFileNotFoundException == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_Exceptions__throwFileNotFoundException);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__throwClassCastException = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__throwClassCastException(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__throwClassCastException,
+              "throwClassCastException", "()Ljava/io/FileInputStream;");
+  if (_m_Exceptions__throwClassCastException == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_Exceptions__throwClassCastException);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Exceptions__throwArrayIndexException = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__throwArrayIndexException(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__throwArrayIndexException,
+              "throwArrayIndexException", "()I");
+  if (_m_Exceptions__throwArrayIndexException == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_Exceptions__throwArrayIndexException);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Exceptions__throwArithmeticException = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__throwArithmeticException(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Exceptions, &_m_Exceptions__throwArithmeticException,
+              "throwArithmeticException", "()I");
+  if (_m_Exceptions__throwArithmeticException == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(
+      jniEnv, self_, _m_Exceptions__throwArithmeticException);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+jmethodID _m_Exceptions__throwLoremIpsum = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Exceptions__throwLoremIpsum() {
+  load_env();
+  load_class_global_ref(
+      &_c_Exceptions, "com/github/dart_lang/jnigen/simple_package/Exceptions");
+  if (_c_Exceptions == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_Exceptions, &_m_Exceptions__throwLoremIpsum,
+                     "throwLoremIpsum", "()V");
+  if (_m_Exceptions__throwLoremIpsum == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_Exceptions,
+                                  _m_Exceptions__throwLoremIpsum);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.simple_package.Fields
+jclass _c_Fields = NULL;
+
+jmethodID _m_Fields__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Fields__new0() {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Fields, &_m_Fields__new0, "<init>", "()V");
+  if (_m_Fields__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Fields, _m_Fields__new0);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Fields__amount = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__amount() {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__amount, "amount", "I");
+  int32_t _result =
+      (*jniEnv)->GetStaticIntField(jniEnv, _c_Fields, _f_Fields__amount);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__amount(int32_t value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__amount, "amount", "I");
+  (*jniEnv)->SetStaticIntField(jniEnv, _c_Fields, _f_Fields__amount, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields__pi = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__pi() {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__pi, "pi", "D");
+  double _result =
+      (*jniEnv)->GetStaticDoubleField(jniEnv, _c_Fields, _f_Fields__pi);
+  return (JniResult){.value = {.d = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__pi(double value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__pi, "pi", "D");
+  (*jniEnv)->SetStaticDoubleField(jniEnv, _c_Fields, _f_Fields__pi, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields__asterisk = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__asterisk() {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__asterisk, "asterisk", "C");
+  uint16_t _result =
+      (*jniEnv)->GetStaticCharField(jniEnv, _c_Fields, _f_Fields__asterisk);
+  return (JniResult){.value = {.c = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__asterisk(uint16_t value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__asterisk, "asterisk", "C");
+  (*jniEnv)->SetStaticCharField(jniEnv, _c_Fields, _f_Fields__asterisk, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields__name = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__name() {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__name, "name", "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetStaticObjectField(jniEnv, _c_Fields, _f_Fields__name);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__name(jobject value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__name, "name", "Ljava/lang/String;");
+  (*jniEnv)->SetStaticObjectField(jniEnv, _c_Fields, _f_Fields__name, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields__i = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__i(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__i, "i", "Ljava/lang/Integer;");
+  jobject _result = (*jniEnv)->GetObjectField(jniEnv, self_, _f_Fields__i);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__i(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__i, "i", "Ljava/lang/Integer;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_Fields__i, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields__trillion = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__trillion(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__trillion, "trillion", "J");
+  int64_t _result = (*jniEnv)->GetLongField(jniEnv, self_, _f_Fields__trillion);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__trillion(jobject self_, int64_t value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__trillion, "trillion", "J");
+  (*jniEnv)->SetLongField(jniEnv, self_, _f_Fields__trillion, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields__isAchillesDead = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__isAchillesDead(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__isAchillesDead, "isAchillesDead", "Z");
+  uint8_t _result =
+      (*jniEnv)->GetBooleanField(jniEnv, self_, _f_Fields__isAchillesDead);
+  return (JniResult){.value = {.z = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__isAchillesDead(jobject self_, uint8_t value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__isAchillesDead, "isAchillesDead", "Z");
+  (*jniEnv)->SetBooleanField(jniEnv, self_, _f_Fields__isAchillesDead, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields__bestFighterInGreece = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__bestFighterInGreece(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__bestFighterInGreece, "bestFighterInGreece",
+             "Ljava/lang/String;");
+  jobject _result =
+      (*jniEnv)->GetObjectField(jniEnv, self_, _f_Fields__bestFighterInGreece);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__bestFighterInGreece(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__bestFighterInGreece, "bestFighterInGreece",
+             "Ljava/lang/String;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_Fields__bestFighterInGreece,
+                            value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields__random = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__random(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__random, "random", "Ljava/util/Random;");
+  jobject _result = (*jniEnv)->GetObjectField(jniEnv, self_, _f_Fields__random);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__random(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields, &_f_Fields__random, "random", "Ljava/util/Random;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_Fields__random, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields__euroSymbol = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields__euroSymbol() {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__euroSymbol, "euroSymbol", "C");
+  uint16_t _result =
+      (*jniEnv)->GetStaticCharField(jniEnv, _c_Fields, _f_Fields__euroSymbol);
+  return (JniResult){.value = {.c = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields__euroSymbol(uint16_t value) {
+  load_env();
+  load_class_global_ref(&_c_Fields,
+                        "com/github/dart_lang/jnigen/simple_package/Fields");
+  if (_c_Fields == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields, &_f_Fields__euroSymbol, "euroSymbol", "C");
+  (*jniEnv)->SetStaticCharField(jniEnv, _c_Fields, _f_Fields__euroSymbol,
+                                value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.simple_package.Fields$Nested
+jclass _c_Fields_Nested = NULL;
+
+jmethodID _m_Fields_Nested__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Fields_Nested__new0() {
+  load_env();
+  load_class_global_ref(
+      &_c_Fields_Nested,
+      "com/github/dart_lang/jnigen/simple_package/Fields$Nested");
+  if (_c_Fields_Nested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Fields_Nested, &_m_Fields_Nested__new0, "<init>", "()V");
+  if (_m_Fields_Nested__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_Fields_Nested, _m_Fields_Nested__new0);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_Fields_Nested__hundred = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields_Nested__hundred(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_Fields_Nested,
+      "com/github/dart_lang/jnigen/simple_package/Fields$Nested");
+  if (_c_Fields_Nested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields_Nested, &_f_Fields_Nested__hundred, "hundred", "J");
+  int64_t _result =
+      (*jniEnv)->GetLongField(jniEnv, self_, _f_Fields_Nested__hundred);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields_Nested__hundred(jobject self_, int64_t value) {
+  load_env();
+  load_class_global_ref(
+      &_c_Fields_Nested,
+      "com/github/dart_lang/jnigen/simple_package/Fields$Nested");
+  if (_c_Fields_Nested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_Fields_Nested, &_f_Fields_Nested__hundred, "hundred", "J");
+  (*jniEnv)->SetLongField(jniEnv, self_, _f_Fields_Nested__hundred, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_Fields_Nested__BEST_GOD = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_Fields_Nested__BEST_GOD() {
+  load_env();
+  load_class_global_ref(
+      &_c_Fields_Nested,
+      "com/github/dart_lang/jnigen/simple_package/Fields$Nested");
+  if (_c_Fields_Nested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields_Nested, &_f_Fields_Nested__BEST_GOD, "BEST_GOD",
+                    "Ljava/lang/String;");
+  jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Fields_Nested,
+                                                    _f_Fields_Nested__BEST_GOD);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_Fields_Nested__BEST_GOD(jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_Fields_Nested,
+      "com/github/dart_lang/jnigen/simple_package/Fields$Nested");
+  if (_c_Fields_Nested == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_Fields_Nested, &_f_Fields_Nested__BEST_GOD, "BEST_GOD",
+                    "Ljava/lang/String;");
+  (*jniEnv)->SetStaticObjectField(jniEnv, _c_Fields_Nested,
+                                  _f_Fields_Nested__BEST_GOD, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.pkg2.C2
+jclass _c_C2 = NULL;
+
+jmethodID _m_C2__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult C2__new0() {
+  load_env();
+  load_class_global_ref(&_c_C2, "com/github/dart_lang/jnigen/pkg2/C2");
+  if (_c_C2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_C2, &_m_C2__new0, "<init>", "()V");
+  if (_m_C2__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_C2, _m_C2__new0);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_C2__CONSTANT = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_C2__CONSTANT() {
+  load_env();
+  load_class_global_ref(&_c_C2, "com/github/dart_lang/jnigen/pkg2/C2");
+  if (_c_C2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_C2, &_f_C2__CONSTANT, "CONSTANT", "I");
+  int32_t _result =
+      (*jniEnv)->GetStaticIntField(jniEnv, _c_C2, _f_C2__CONSTANT);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_C2__CONSTANT(int32_t value) {
+  load_env();
+  load_class_global_ref(&_c_C2, "com/github/dart_lang/jnigen/pkg2/C2");
+  if (_c_C2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_field(_c_C2, &_f_C2__CONSTANT, "CONSTANT", "I");
+  (*jniEnv)->SetStaticIntField(jniEnv, _c_C2, _f_C2__CONSTANT, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.pkg2.Example
+jclass _c_Example1 = NULL;
+
+jmethodID _m_Example1__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example1__new0() {
+  load_env();
+  load_class_global_ref(&_c_Example1,
+                        "com/github/dart_lang/jnigen/pkg2/Example");
+  if (_c_Example1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example1, &_m_Example1__new0, "<init>", "()V");
+  if (_m_Example1__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_Example1, _m_Example1__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_Example1__whichExample = NULL;
+FFI_PLUGIN_EXPORT
+JniResult Example1__whichExample(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_Example1,
+                        "com/github/dart_lang/jnigen/pkg2/Example");
+  if (_c_Example1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_Example1, &_m_Example1__whichExample, "whichExample", "()I");
+  if (_m_Example1__whichExample == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result =
+      (*jniEnv)->CallIntMethod(jniEnv, self_, _m_Example1__whichExample);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.generics.GenericTypeParams
+jclass _c_GenericTypeParams = NULL;
+
+jmethodID _m_GenericTypeParams__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GenericTypeParams__new0() {
+  load_env();
+  load_class_global_ref(
+      &_c_GenericTypeParams,
+      "com/github/dart_lang/jnigen/generics/GenericTypeParams");
+  if (_c_GenericTypeParams == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_GenericTypeParams, &_m_GenericTypeParams__new0, "<init>",
+              "()V");
+  if (_m_GenericTypeParams__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_GenericTypeParams,
+                                         _m_GenericTypeParams__new0);
+  return to_global_ref_result(_result);
+}
+
+// com.github.dart_lang.jnigen.generics.GrandParent
+jclass _c_GrandParent = NULL;
+
+jmethodID _m_GrandParent__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent__new0(jobject value) {
+  load_env();
+  load_class_global_ref(&_c_GrandParent,
+                        "com/github/dart_lang/jnigen/generics/GrandParent");
+  if (_c_GrandParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_GrandParent, &_m_GrandParent__new0, "<init>",
+              "(Ljava/lang/Object;)V");
+  if (_m_GrandParent__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_GrandParent, _m_GrandParent__new0, value);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_GrandParent__stringParent = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent__stringParent(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_GrandParent,
+                        "com/github/dart_lang/jnigen/generics/GrandParent");
+  if (_c_GrandParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_GrandParent, &_m_GrandParent__stringParent, "stringParent",
+              "()Lcom/github/dart_lang/jnigen/generics/GrandParent$Parent;");
+  if (_m_GrandParent__stringParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_GrandParent__stringParent);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_GrandParent__varParent = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent__varParent(jobject self_, jobject nestedValue) {
+  load_env();
+  load_class_global_ref(&_c_GrandParent,
+                        "com/github/dart_lang/jnigen/generics/GrandParent");
+  if (_c_GrandParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_GrandParent, &_m_GrandParent__varParent, "varParent",
+              "(Ljava/lang/Object;)Lcom/github/dart_lang/jnigen/generics/"
+              "GrandParent$Parent;");
+  if (_m_GrandParent__varParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_GrandParent__varParent, nestedValue);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_GrandParent__stringStaticParent = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent__stringStaticParent() {
+  load_env();
+  load_class_global_ref(&_c_GrandParent,
+                        "com/github/dart_lang/jnigen/generics/GrandParent");
+  if (_c_GrandParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_GrandParent, &_m_GrandParent__stringStaticParent, "stringStaticParent",
+      "()Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent;");
+  if (_m_GrandParent__stringStaticParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_GrandParent, _m_GrandParent__stringStaticParent);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_GrandParent__varStaticParent = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent__varStaticParent(jobject value) {
+  load_env();
+  load_class_global_ref(&_c_GrandParent,
+                        "com/github/dart_lang/jnigen/generics/GrandParent");
+  if (_c_GrandParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_GrandParent, &_m_GrandParent__varStaticParent,
+                     "varStaticParent",
+                     "(Ljava/lang/Object;)Lcom/github/dart_lang/jnigen/"
+                     "generics/GrandParent$StaticParent;");
+  if (_m_GrandParent__varStaticParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_GrandParent, _m_GrandParent__varStaticParent, value);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_GrandParent__staticParentWithSameType = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent__staticParentWithSameType(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_GrandParent,
+                        "com/github/dart_lang/jnigen/generics/GrandParent");
+  if (_c_GrandParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_GrandParent, &_m_GrandParent__staticParentWithSameType,
+      "staticParentWithSameType",
+      "()Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent;");
+  if (_m_GrandParent__staticParentWithSameType == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_GrandParent__staticParentWithSameType);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_GrandParent__value = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_GrandParent__value(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_GrandParent,
+                        "com/github/dart_lang/jnigen/generics/GrandParent");
+  if (_c_GrandParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent, &_f_GrandParent__value, "value",
+             "Ljava/lang/Object;");
+  jobject _result =
+      (*jniEnv)->GetObjectField(jniEnv, self_, _f_GrandParent__value);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_GrandParent__value(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_GrandParent,
+                        "com/github/dart_lang/jnigen/generics/GrandParent");
+  if (_c_GrandParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent, &_f_GrandParent__value, "value",
+             "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_GrandParent__value, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.generics.GrandParent$Parent
+jclass _c_GrandParent_Parent = NULL;
+
+jmethodID _m_GrandParent_Parent__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent_Parent__new0(jobject _parent, jobject newValue) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent");
+  if (_c_GrandParent_Parent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_GrandParent_Parent, &_m_GrandParent_Parent__new0, "<init>",
+              "(Lcom/github/dart_lang/jnigen/generics/GrandParent;Ljava/lang/"
+              "Object;)V");
+  if (_m_GrandParent_Parent__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_GrandParent_Parent,
+                           _m_GrandParent_Parent__new0, _parent, newValue);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_GrandParent_Parent__parentValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_GrandParent_Parent__parentValue(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent");
+  if (_c_GrandParent_Parent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent, &_f_GrandParent_Parent__parentValue,
+             "parentValue", "Ljava/lang/Object;");
+  jobject _result = (*jniEnv)->GetObjectField(
+      jniEnv, self_, _f_GrandParent_Parent__parentValue);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_GrandParent_Parent__parentValue(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent");
+  if (_c_GrandParent_Parent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent, &_f_GrandParent_Parent__parentValue,
+             "parentValue", "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_GrandParent_Parent__parentValue,
+                            value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_GrandParent_Parent__value = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_GrandParent_Parent__value(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent");
+  if (_c_GrandParent_Parent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent, &_f_GrandParent_Parent__value, "value",
+             "Ljava/lang/Object;");
+  jobject _result =
+      (*jniEnv)->GetObjectField(jniEnv, self_, _f_GrandParent_Parent__value);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_GrandParent_Parent__value(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent");
+  if (_c_GrandParent_Parent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent, &_f_GrandParent_Parent__value, "value",
+             "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_GrandParent_Parent__value, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.generics.GrandParent$Parent$Child
+jclass _c_GrandParent_Parent_Child = NULL;
+
+jmethodID _m_GrandParent_Parent_Child__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent_Parent_Child__new0(jobject _parent, jobject newValue) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent$Child");
+  if (_c_GrandParent_Parent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_GrandParent_Parent_Child, &_m_GrandParent_Parent_Child__new0,
+              "<init>",
+              "(Lcom/github/dart_lang/jnigen/generics/GrandParent$Parent;Ljava/"
+              "lang/Object;)V");
+  if (_m_GrandParent_Parent_Child__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_GrandParent_Parent_Child,
+                                         _m_GrandParent_Parent_Child__new0,
+                                         _parent, newValue);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_GrandParent_Parent_Child__grandParentValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_GrandParent_Parent_Child__grandParentValue(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent$Child");
+  if (_c_GrandParent_Parent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent_Child,
+             &_f_GrandParent_Parent_Child__grandParentValue, "grandParentValue",
+             "Ljava/lang/Object;");
+  jobject _result = (*jniEnv)->GetObjectField(
+      jniEnv, self_, _f_GrandParent_Parent_Child__grandParentValue);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_GrandParent_Parent_Child__grandParentValue(jobject self_,
+                                                         jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent$Child");
+  if (_c_GrandParent_Parent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent_Child,
+             &_f_GrandParent_Parent_Child__grandParentValue, "grandParentValue",
+             "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(
+      jniEnv, self_, _f_GrandParent_Parent_Child__grandParentValue, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_GrandParent_Parent_Child__parentValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_GrandParent_Parent_Child__parentValue(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent$Child");
+  if (_c_GrandParent_Parent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent_Child,
+             &_f_GrandParent_Parent_Child__parentValue, "parentValue",
+             "Ljava/lang/Object;");
+  jobject _result = (*jniEnv)->GetObjectField(
+      jniEnv, self_, _f_GrandParent_Parent_Child__parentValue);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_GrandParent_Parent_Child__parentValue(jobject self_,
+                                                    jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent$Child");
+  if (_c_GrandParent_Parent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent_Child,
+             &_f_GrandParent_Parent_Child__parentValue, "parentValue",
+             "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(jniEnv, self_,
+                            _f_GrandParent_Parent_Child__parentValue, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_GrandParent_Parent_Child__value = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_GrandParent_Parent_Child__value(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent$Child");
+  if (_c_GrandParent_Parent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent_Child, &_f_GrandParent_Parent_Child__value,
+             "value", "Ljava/lang/Object;");
+  jobject _result = (*jniEnv)->GetObjectField(
+      jniEnv, self_, _f_GrandParent_Parent_Child__value);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_GrandParent_Parent_Child__value(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_Parent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$Parent$Child");
+  if (_c_GrandParent_Parent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_Parent_Child, &_f_GrandParent_Parent_Child__value,
+             "value", "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_GrandParent_Parent_Child__value,
+                            value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.generics.GrandParent$StaticParent
+jclass _c_GrandParent_StaticParent = NULL;
+
+jmethodID _m_GrandParent_StaticParent__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent_StaticParent__new0(jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_StaticParent,
+      "com/github/dart_lang/jnigen/generics/GrandParent$StaticParent");
+  if (_c_GrandParent_StaticParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_GrandParent_StaticParent, &_m_GrandParent_StaticParent__new0,
+              "<init>", "(Ljava/lang/Object;)V");
+  if (_m_GrandParent_StaticParent__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_GrandParent_StaticParent,
+                           _m_GrandParent_StaticParent__new0, value);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_GrandParent_StaticParent__value = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_GrandParent_StaticParent__value(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_StaticParent,
+      "com/github/dart_lang/jnigen/generics/GrandParent$StaticParent");
+  if (_c_GrandParent_StaticParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_StaticParent, &_f_GrandParent_StaticParent__value,
+             "value", "Ljava/lang/Object;");
+  jobject _result = (*jniEnv)->GetObjectField(
+      jniEnv, self_, _f_GrandParent_StaticParent__value);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_GrandParent_StaticParent__value(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_StaticParent,
+      "com/github/dart_lang/jnigen/generics/GrandParent$StaticParent");
+  if (_c_GrandParent_StaticParent == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_StaticParent, &_f_GrandParent_StaticParent__value,
+             "value", "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_GrandParent_StaticParent__value,
+                            value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.generics.GrandParent$StaticParent$Child
+jclass _c_GrandParent_StaticParent_Child = NULL;
+
+jmethodID _m_GrandParent_StaticParent_Child__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult GrandParent_StaticParent_Child__new0(jobject _parent,
+                                               jobject parentValue,
+                                               jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_StaticParent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$StaticParent$Child");
+  if (_c_GrandParent_StaticParent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(
+      _c_GrandParent_StaticParent_Child,
+      &_m_GrandParent_StaticParent_Child__new0, "<init>",
+      "(Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent;Ljava/"
+      "lang/Object;Ljava/lang/Object;)V");
+  if (_m_GrandParent_StaticParent_Child__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(
+      jniEnv, _c_GrandParent_StaticParent_Child,
+      _m_GrandParent_StaticParent_Child__new0, _parent, parentValue, value);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_GrandParent_StaticParent_Child__parentValue = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_GrandParent_StaticParent_Child__parentValue(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_StaticParent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$StaticParent$Child");
+  if (_c_GrandParent_StaticParent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_StaticParent_Child,
+             &_f_GrandParent_StaticParent_Child__parentValue, "parentValue",
+             "Ljava/lang/Object;");
+  jobject _result = (*jniEnv)->GetObjectField(
+      jniEnv, self_, _f_GrandParent_StaticParent_Child__parentValue);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_GrandParent_StaticParent_Child__parentValue(jobject self_,
+                                                          jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_StaticParent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$StaticParent$Child");
+  if (_c_GrandParent_StaticParent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_StaticParent_Child,
+             &_f_GrandParent_StaticParent_Child__parentValue, "parentValue",
+             "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(
+      jniEnv, self_, _f_GrandParent_StaticParent_Child__parentValue, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_GrandParent_StaticParent_Child__value = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_GrandParent_StaticParent_Child__value(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_StaticParent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$StaticParent$Child");
+  if (_c_GrandParent_StaticParent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_StaticParent_Child,
+             &_f_GrandParent_StaticParent_Child__value, "value",
+             "Ljava/lang/Object;");
+  jobject _result = (*jniEnv)->GetObjectField(
+      jniEnv, self_, _f_GrandParent_StaticParent_Child__value);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_GrandParent_StaticParent_Child__value(jobject self_,
+                                                    jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_GrandParent_StaticParent_Child,
+      "com/github/dart_lang/jnigen/generics/GrandParent$StaticParent$Child");
+  if (_c_GrandParent_StaticParent_Child == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_GrandParent_StaticParent_Child,
+             &_f_GrandParent_StaticParent_Child__value, "value",
+             "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(jniEnv, self_,
+                            _f_GrandParent_StaticParent_Child__value, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.generics.MyMap
+jclass _c_MyMap = NULL;
+
+jmethodID _m_MyMap__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyMap__new0() {
+  load_env();
+  load_class_global_ref(&_c_MyMap,
+                        "com/github/dart_lang/jnigen/generics/MyMap");
+  if (_c_MyMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyMap, &_m_MyMap__new0, "<init>", "()V");
+  if (_m_MyMap__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_MyMap, _m_MyMap__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyMap__get0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyMap__get0(jobject self_, jobject key) {
+  load_env();
+  load_class_global_ref(&_c_MyMap,
+                        "com/github/dart_lang/jnigen/generics/MyMap");
+  if (_c_MyMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyMap, &_m_MyMap__get0, "get",
+              "(Ljava/lang/Object;)Ljava/lang/Object;");
+  if (_m_MyMap__get0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_MyMap__get0, key);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyMap__put = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyMap__put(jobject self_, jobject key, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_MyMap,
+                        "com/github/dart_lang/jnigen/generics/MyMap");
+  if (_c_MyMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyMap, &_m_MyMap__put, "put",
+              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+  if (_m_MyMap__put == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_MyMap__put, key, value);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyMap__entryStack = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyMap__entryStack(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_MyMap,
+                        "com/github/dart_lang/jnigen/generics/MyMap");
+  if (_c_MyMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyMap, &_m_MyMap__entryStack, "entryStack",
+              "()Lcom/github/dart_lang/jnigen/generics/MyStack;");
+  if (_m_MyMap__entryStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_MyMap__entryStack);
+  return to_global_ref_result(_result);
+}
+
+// com.github.dart_lang.jnigen.generics.MyMap$MyEntry
+jclass _c_MyMap_MyEntry = NULL;
+
+jmethodID _m_MyMap_MyEntry__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyMap_MyEntry__new0(jobject _parent, jobject key, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_MyMap_MyEntry,
+                        "com/github/dart_lang/jnigen/generics/MyMap$MyEntry");
+  if (_c_MyMap_MyEntry == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyMap_MyEntry, &_m_MyMap_MyEntry__new0, "<init>",
+              "(Lcom/github/dart_lang/jnigen/generics/MyMap;Ljava/lang/"
+              "Object;Ljava/lang/Object;)V");
+  if (_m_MyMap_MyEntry__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(
+      jniEnv, _c_MyMap_MyEntry, _m_MyMap_MyEntry__new0, _parent, key, value);
+  return to_global_ref_result(_result);
+}
+
+jfieldID _f_MyMap_MyEntry__key = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_MyMap_MyEntry__key(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_MyMap_MyEntry,
+                        "com/github/dart_lang/jnigen/generics/MyMap$MyEntry");
+  if (_c_MyMap_MyEntry == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_MyMap_MyEntry, &_f_MyMap_MyEntry__key, "key",
+             "Ljava/lang/Object;");
+  jobject _result =
+      (*jniEnv)->GetObjectField(jniEnv, self_, _f_MyMap_MyEntry__key);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_MyMap_MyEntry__key(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_MyMap_MyEntry,
+                        "com/github/dart_lang/jnigen/generics/MyMap$MyEntry");
+  if (_c_MyMap_MyEntry == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_MyMap_MyEntry, &_f_MyMap_MyEntry__key, "key",
+             "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_MyMap_MyEntry__key, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_MyMap_MyEntry__value = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_MyMap_MyEntry__value(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_MyMap_MyEntry,
+                        "com/github/dart_lang/jnigen/generics/MyMap$MyEntry");
+  if (_c_MyMap_MyEntry == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_MyMap_MyEntry, &_f_MyMap_MyEntry__value, "value",
+             "Ljava/lang/Object;");
+  jobject _result =
+      (*jniEnv)->GetObjectField(jniEnv, self_, _f_MyMap_MyEntry__value);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_MyMap_MyEntry__value(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(&_c_MyMap_MyEntry,
+                        "com/github/dart_lang/jnigen/generics/MyMap$MyEntry");
+  if (_c_MyMap_MyEntry == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_MyMap_MyEntry, &_f_MyMap_MyEntry__value, "value",
+             "Ljava/lang/Object;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_MyMap_MyEntry__value, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.generics.MyStack
+jclass _c_MyStack = NULL;
+
+jmethodID _m_MyStack__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyStack__new0() {
+  load_env();
+  load_class_global_ref(&_c_MyStack,
+                        "com/github/dart_lang/jnigen/generics/MyStack");
+  if (_c_MyStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyStack, &_m_MyStack__new0, "<init>", "()V");
+  if (_m_MyStack__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_MyStack, _m_MyStack__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyStack__fromArray = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyStack__fromArray(jobject arr) {
+  load_env();
+  load_class_global_ref(&_c_MyStack,
+                        "com/github/dart_lang/jnigen/generics/MyStack");
+  if (_c_MyStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_MyStack, &_m_MyStack__fromArray, "fromArray",
+      "([Ljava/lang/Object;)Lcom/github/dart_lang/jnigen/generics/MyStack;");
+  if (_m_MyStack__fromArray == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_MyStack, _m_MyStack__fromArray, arr);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyStack__fromArrayOfArrayOfGrandParents = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyStack__fromArrayOfArrayOfGrandParents(jobject arr) {
+  load_env();
+  load_class_global_ref(&_c_MyStack,
+                        "com/github/dart_lang/jnigen/generics/MyStack");
+  if (_c_MyStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_MyStack, &_m_MyStack__fromArrayOfArrayOfGrandParents,
+      "fromArrayOfArrayOfGrandParents",
+      "([[Lcom/github/dart_lang/jnigen/generics/GrandParent;)Lcom/github/"
+      "dart_lang/jnigen/generics/MyStack;");
+  if (_m_MyStack__fromArrayOfArrayOfGrandParents == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_MyStack, _m_MyStack__fromArrayOfArrayOfGrandParents, arr);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyStack__of = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyStack__of() {
+  load_env();
+  load_class_global_ref(&_c_MyStack,
+                        "com/github/dart_lang/jnigen/generics/MyStack");
+  if (_c_MyStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_MyStack, &_m_MyStack__of, "of",
+                     "()Lcom/github/dart_lang/jnigen/generics/MyStack;");
+  if (_m_MyStack__of == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_MyStack, _m_MyStack__of);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyStack__of1 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyStack__of1(jobject obj) {
+  load_env();
+  load_class_global_ref(&_c_MyStack,
+                        "com/github/dart_lang/jnigen/generics/MyStack");
+  if (_c_MyStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_MyStack, &_m_MyStack__of1, "of",
+      "(Ljava/lang/Object;)Lcom/github/dart_lang/jnigen/generics/MyStack;");
+  if (_m_MyStack__of1 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(jniEnv, _c_MyStack,
+                                                      _m_MyStack__of1, obj);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyStack__of2 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyStack__of2(jobject obj, jobject obj2) {
+  load_env();
+  load_class_global_ref(&_c_MyStack,
+                        "com/github/dart_lang/jnigen/generics/MyStack");
+  if (_c_MyStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_MyStack, &_m_MyStack__of2, "of",
+                     "(Ljava/lang/Object;Ljava/lang/Object;)Lcom/github/"
+                     "dart_lang/jnigen/generics/MyStack;");
+  if (_m_MyStack__of2 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_MyStack, _m_MyStack__of2, obj, obj2);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyStack__push = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyStack__push(jobject self_, jobject item) {
+  load_env();
+  load_class_global_ref(&_c_MyStack,
+                        "com/github/dart_lang/jnigen/generics/MyStack");
+  if (_c_MyStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyStack, &_m_MyStack__push, "push", "(Ljava/lang/Object;)V");
+  if (_m_MyStack__push == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_MyStack__push, item);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_MyStack__pop = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyStack__pop(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_MyStack,
+                        "com/github/dart_lang/jnigen/generics/MyStack");
+  if (_c_MyStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyStack, &_m_MyStack__pop, "pop", "()Ljava/lang/Object;");
+  if (_m_MyStack__pop == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_MyStack__pop);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyStack__size = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyStack__size(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_MyStack,
+                        "com/github/dart_lang/jnigen/generics/MyStack");
+  if (_c_MyStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyStack, &_m_MyStack__size, "size", "()I");
+  if (_m_MyStack__size == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int32_t _result = (*jniEnv)->CallIntMethod(jniEnv, self_, _m_MyStack__size);
+  return (JniResult){.value = {.i = _result}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.generics.StringKeyedMap
+jclass _c_StringKeyedMap = NULL;
+
+jmethodID _m_StringKeyedMap__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult StringKeyedMap__new0() {
+  load_env();
+  load_class_global_ref(&_c_StringKeyedMap,
+                        "com/github/dart_lang/jnigen/generics/StringKeyedMap");
+  if (_c_StringKeyedMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_StringKeyedMap, &_m_StringKeyedMap__new0, "<init>", "()V");
+  if (_m_StringKeyedMap__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_StringKeyedMap, _m_StringKeyedMap__new0);
+  return to_global_ref_result(_result);
+}
+
+// com.github.dart_lang.jnigen.generics.StringMap
+jclass _c_StringMap = NULL;
+
+jmethodID _m_StringMap__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult StringMap__new0() {
+  load_env();
+  load_class_global_ref(&_c_StringMap,
+                        "com/github/dart_lang/jnigen/generics/StringMap");
+  if (_c_StringMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_StringMap, &_m_StringMap__new0, "<init>", "()V");
+  if (_m_StringMap__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_StringMap, _m_StringMap__new0);
+  return to_global_ref_result(_result);
+}
+
+// com.github.dart_lang.jnigen.generics.StringStack
+jclass _c_StringStack = NULL;
+
+jmethodID _m_StringStack__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult StringStack__new0() {
+  load_env();
+  load_class_global_ref(&_c_StringStack,
+                        "com/github/dart_lang/jnigen/generics/StringStack");
+  if (_c_StringStack == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_StringStack, &_m_StringStack__new0, "<init>", "()V");
+  if (_m_StringStack__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_StringStack, _m_StringStack__new0);
+  return to_global_ref_result(_result);
+}
+
+// com.github.dart_lang.jnigen.generics.StringValuedMap
+jclass _c_StringValuedMap = NULL;
+
+jmethodID _m_StringValuedMap__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult StringValuedMap__new0() {
+  load_env();
+  load_class_global_ref(&_c_StringValuedMap,
+                        "com/github/dart_lang/jnigen/generics/StringValuedMap");
+  if (_c_StringValuedMap == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_StringValuedMap, &_m_StringValuedMap__new0, "<init>", "()V");
+  if (_m_StringValuedMap__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_StringValuedMap,
+                                         _m_StringValuedMap__new0);
+  return to_global_ref_result(_result);
+}
+
+// com.github.dart_lang.jnigen.interfaces.MyInterface
+jclass _c_MyInterface = NULL;
+
+jmethodID _m_MyInterface__voidCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterface__voidCallback(jobject self_, jobject s) {
+  load_env();
+  load_class_global_ref(&_c_MyInterface,
+                        "com/github/dart_lang/jnigen/interfaces/MyInterface");
+  if (_c_MyInterface == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyInterface, &_m_MyInterface__voidCallback, "voidCallback",
+              "(Ljava/lang/String;)V");
+  if (_m_MyInterface__voidCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_MyInterface__voidCallback, s);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_MyInterface__stringCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterface__stringCallback(jobject self_, jobject s) {
+  load_env();
+  load_class_global_ref(&_c_MyInterface,
+                        "com/github/dart_lang/jnigen/interfaces/MyInterface");
+  if (_c_MyInterface == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyInterface, &_m_MyInterface__stringCallback, "stringCallback",
+              "(Ljava/lang/String;)Ljava/lang/String;");
+  if (_m_MyInterface__stringCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(
+      jniEnv, self_, _m_MyInterface__stringCallback, s);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyInterface__varCallback = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterface__varCallback(jobject self_, jobject t) {
+  load_env();
+  load_class_global_ref(&_c_MyInterface,
+                        "com/github/dart_lang/jnigen/interfaces/MyInterface");
+  if (_c_MyInterface == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyInterface, &_m_MyInterface__varCallback, "varCallback",
+              "(Ljava/lang/Object;)Ljava/lang/Object;");
+  if (_m_MyInterface__varCallback == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallObjectMethod(jniEnv, self_,
+                                                _m_MyInterface__varCallback, t);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyInterface__manyPrimitives = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterface__manyPrimitives(jobject self_,
+                                      int32_t a,
+                                      uint8_t b,
+                                      uint16_t c,
+                                      double d) {
+  load_env();
+  load_class_global_ref(&_c_MyInterface,
+                        "com/github/dart_lang/jnigen/interfaces/MyInterface");
+  if (_c_MyInterface == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyInterface, &_m_MyInterface__manyPrimitives, "manyPrimitives",
+              "(IZCD)J");
+  if (_m_MyInterface__manyPrimitives == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  int64_t _result = (*jniEnv)->CallLongMethod(
+      jniEnv, self_, _m_MyInterface__manyPrimitives, a, b, c, d);
+  return (JniResult){.value = {.j = _result}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.interfaces.MyInterfaceConsumer
+jclass _c_MyInterfaceConsumer = NULL;
+
+jmethodID _m_MyInterfaceConsumer__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterfaceConsumer__new0() {
+  load_env();
+  load_class_global_ref(
+      &_c_MyInterfaceConsumer,
+      "com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer");
+  if (_c_MyInterfaceConsumer == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyInterfaceConsumer, &_m_MyInterfaceConsumer__new0, "<init>",
+              "()V");
+  if (_m_MyInterfaceConsumer__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_MyInterfaceConsumer,
+                                         _m_MyInterfaceConsumer__new0);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyInterfaceConsumer__consumeOnAnotherThread = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterfaceConsumer__consumeOnAnotherThread(jobject myInterface,
+                                                      jobject s,
+                                                      int32_t a,
+                                                      uint8_t b,
+                                                      uint16_t c,
+                                                      double d,
+                                                      jobject t) {
+  load_env();
+  load_class_global_ref(
+      &_c_MyInterfaceConsumer,
+      "com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer");
+  if (_c_MyInterfaceConsumer == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_MyInterfaceConsumer,
+                     &_m_MyInterfaceConsumer__consumeOnAnotherThread,
+                     "consumeOnAnotherThread",
+                     "(Lcom/github/dart_lang/jnigen/interfaces/"
+                     "MyInterface;Ljava/lang/String;IZCDLjava/lang/Object;)V");
+  if (_m_MyInterfaceConsumer__consumeOnAnotherThread == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(
+      jniEnv, _c_MyInterfaceConsumer,
+      _m_MyInterfaceConsumer__consumeOnAnotherThread, myInterface, s, a, b, c,
+      d, t);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_MyInterfaceConsumer__consumeOnSameThread = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyInterfaceConsumer__consumeOnSameThread(jobject myInterface,
+                                                   jobject s,
+                                                   int32_t a,
+                                                   uint8_t b,
+                                                   uint16_t c,
+                                                   double d,
+                                                   jobject t) {
+  load_env();
+  load_class_global_ref(
+      &_c_MyInterfaceConsumer,
+      "com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer");
+  if (_c_MyInterfaceConsumer == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_MyInterfaceConsumer,
+                     &_m_MyInterfaceConsumer__consumeOnSameThread,
+                     "consumeOnSameThread",
+                     "(Lcom/github/dart_lang/jnigen/interfaces/"
+                     "MyInterface;Ljava/lang/String;IZCDLjava/lang/Object;)V");
+  if (_m_MyInterfaceConsumer__consumeOnSameThread == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallStaticVoidMethod(jniEnv, _c_MyInterfaceConsumer,
+                                  _m_MyInterfaceConsumer__consumeOnSameThread,
+                                  myInterface, s, a, b, c, d, t);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.interfaces.MyRunnable
+jclass _c_MyRunnable = NULL;
+
+jmethodID _m_MyRunnable__run = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyRunnable__run(jobject self_) {
+  load_env();
+  load_class_global_ref(&_c_MyRunnable,
+                        "com/github/dart_lang/jnigen/interfaces/MyRunnable");
+  if (_c_MyRunnable == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyRunnable, &_m_MyRunnable__run, "run", "()V");
+  if (_m_MyRunnable__run == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_, _m_MyRunnable__run);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.interfaces.MyRunnableRunner
+jclass _c_MyRunnableRunner = NULL;
+
+jmethodID _m_MyRunnableRunner__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyRunnableRunner__new0(jobject runnable) {
+  load_env();
+  load_class_global_ref(
+      &_c_MyRunnableRunner,
+      "com/github/dart_lang/jnigen/interfaces/MyRunnableRunner");
+  if (_c_MyRunnableRunner == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyRunnableRunner, &_m_MyRunnableRunner__new0, "<init>",
+              "(Lcom/github/dart_lang/jnigen/interfaces/MyRunnable;)V");
+  if (_m_MyRunnableRunner__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->NewObject(jniEnv, _c_MyRunnableRunner,
+                                         _m_MyRunnableRunner__new0, runnable);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_MyRunnableRunner__runOnSameThread = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyRunnableRunner__runOnSameThread(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_MyRunnableRunner,
+      "com/github/dart_lang/jnigen/interfaces/MyRunnableRunner");
+  if (_c_MyRunnableRunner == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyRunnableRunner, &_m_MyRunnableRunner__runOnSameThread,
+              "runOnSameThread", "()V");
+  if (_m_MyRunnableRunner__runOnSameThread == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_MyRunnableRunner__runOnSameThread);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jmethodID _m_MyRunnableRunner__runOnAnotherThread = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyRunnableRunner__runOnAnotherThread(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_MyRunnableRunner,
+      "com/github/dart_lang/jnigen/interfaces/MyRunnableRunner");
+  if (_c_MyRunnableRunner == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyRunnableRunner, &_m_MyRunnableRunner__runOnAnotherThread,
+              "runOnAnotherThread", "()V");
+  if (_m_MyRunnableRunner__runOnAnotherThread == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  (*jniEnv)->CallVoidMethod(jniEnv, self_,
+                            _m_MyRunnableRunner__runOnAnotherThread);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+jfieldID _f_MyRunnableRunner__error = NULL;
+FFI_PLUGIN_EXPORT
+JniResult get_MyRunnableRunner__error(jobject self_) {
+  load_env();
+  load_class_global_ref(
+      &_c_MyRunnableRunner,
+      "com/github/dart_lang/jnigen/interfaces/MyRunnableRunner");
+  if (_c_MyRunnableRunner == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_MyRunnableRunner, &_f_MyRunnableRunner__error, "error",
+             "Ljava/lang/Throwable;");
+  jobject _result =
+      (*jniEnv)->GetObjectField(jniEnv, self_, _f_MyRunnableRunner__error);
+  return to_global_ref_result(_result);
+}
+
+FFI_PLUGIN_EXPORT
+JniResult set_MyRunnableRunner__error(jobject self_, jobject value) {
+  load_env();
+  load_class_global_ref(
+      &_c_MyRunnableRunner,
+      "com/github/dart_lang/jnigen/interfaces/MyRunnableRunner");
+  if (_c_MyRunnableRunner == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_field(_c_MyRunnableRunner, &_f_MyRunnableRunner__error, "error",
+             "Ljava/lang/Throwable;");
+  (*jniEnv)->SetObjectField(jniEnv, self_, _f_MyRunnableRunner__error, value);
+  return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+}
+
+// com.github.dart_lang.jnigen.annotations.JsonSerializable$Case
+jclass _c_JsonSerializable_Case = NULL;
+
+jmethodID _m_JsonSerializable_Case__values = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonSerializable_Case__values() {
+  load_env();
+  load_class_global_ref(
+      &_c_JsonSerializable_Case,
+      "com/github/dart_lang/jnigen/annotations/JsonSerializable$Case");
+  if (_c_JsonSerializable_Case == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(
+      _c_JsonSerializable_Case, &_m_JsonSerializable_Case__values, "values",
+      "()[Lcom/github/dart_lang/jnigen/annotations/JsonSerializable$Case;");
+  if (_m_JsonSerializable_Case__values == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_JsonSerializable_Case, _m_JsonSerializable_Case__values);
+  return to_global_ref_result(_result);
+}
+
+jmethodID _m_JsonSerializable_Case__valueOf = NULL;
+FFI_PLUGIN_EXPORT
+JniResult JsonSerializable_Case__valueOf(jobject name) {
+  load_env();
+  load_class_global_ref(
+      &_c_JsonSerializable_Case,
+      "com/github/dart_lang/jnigen/annotations/JsonSerializable$Case");
+  if (_c_JsonSerializable_Case == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_static_method(_c_JsonSerializable_Case,
+                     &_m_JsonSerializable_Case__valueOf, "valueOf",
+                     "(Ljava/lang/String;)Lcom/github/dart_lang/jnigen/"
+                     "annotations/JsonSerializable$Case;");
+  if (_m_JsonSerializable_Case__valueOf == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result = (*jniEnv)->CallStaticObjectMethod(
+      jniEnv, _c_JsonSerializable_Case, _m_JsonSerializable_Case__valueOf,
+      name);
+  return to_global_ref_result(_result);
+}
+
+// com.github.dart_lang.jnigen.annotations.MyDataClass
+jclass _c_MyDataClass = NULL;
+
+jmethodID _m_MyDataClass__new0 = NULL;
+FFI_PLUGIN_EXPORT
+JniResult MyDataClass__new0() {
+  load_env();
+  load_class_global_ref(&_c_MyDataClass,
+                        "com/github/dart_lang/jnigen/annotations/MyDataClass");
+  if (_c_MyDataClass == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  load_method(_c_MyDataClass, &_m_MyDataClass__new0, "<init>", "()V");
+  if (_m_MyDataClass__new0 == NULL)
+    return (JniResult){.value = {.j = 0}, .exception = check_exception()};
+  jobject _result =
+      (*jniEnv)->NewObject(jniEnv, _c_MyDataClass, _m_MyDataClass__new0);
+  return to_global_ref_result(_result);
+}
diff --git a/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart b/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
new file mode 100644
index 0000000..8149d4e
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart
@@ -0,0 +1,3999 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+// Auto-generated initialization code.
+
+final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
+    ProtectedJniExtensions.initGeneratedLibrary("simple_package");
+
+/// from: com.github.dart_lang.jnigen.simple_package.Color
+class Color extends jni.JObject {
+  @override
+  late final jni.JObjType<Color> $type = type;
+
+  Color.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $ColorType();
+  static final _values =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("Color__values")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.github.dart_lang.jnigen.simple_package.Color[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<Color> values() {
+    return const jni.JArrayType($ColorType()).fromRef(_values().object);
+  }
+
+  static final _valueOf = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("Color__valueOf")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.github.dart_lang.jnigen.simple_package.Color valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static Color valueOf(
+    jni.JString name,
+  ) {
+    return const $ColorType().fromRef(_valueOf(name.reference).object);
+  }
+}
+
+final class $ColorType extends jni.JObjType<Color> {
+  const $ColorType();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/simple_package/Color;";
+
+  @override
+  Color fromRef(jni.JObjectPtr ref) => Color.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($ColorType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($ColorType) && other is $ColorType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Example
+class Example extends jni.JObject {
+  @override
+  late final jni.JObjType<Example> $type = type;
+
+  Example.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $ExampleType();
+
+  /// from: static public final int ON
+  static const ON = 1;
+
+  /// from: static public final int OFF
+  static const OFF = 0;
+
+  /// from: static public final double PI
+  static const PI = 3.14159;
+
+  /// from: static public final char SEMICOLON
+  static const SEMICOLON = 59;
+
+  /// from: static public final java.lang.String SEMICOLON_STRING
+  static const SEMICOLON_STRING = r""";""";
+
+  static final _get_unusedRandom =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Example__unusedRandom")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public final java.util.Random unusedRandom
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JObject get unusedRandom =>
+      const jni.JObjectType().fromRef(_get_unusedRandom().object);
+
+  static final _get_protectedField = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_Example__protectedField")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_protectedField = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Pointer<ffi.Void>)>>("set_Example__protectedField")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected java.util.Random protectedField
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject get protectedField =>
+      const jni.JObjectType().fromRef(_get_protectedField(reference).object);
+
+  /// from: protected java.util.Random protectedField
+  /// The returned object must be released after use, by calling the [release] method.
+  set protectedField(jni.JObject value) =>
+      _set_protectedField(reference, value.reference).check();
+
+  static final _getAmount =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Example__getAmount")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public int getAmount()
+  static int getAmount() {
+    return _getAmount().integer;
+  }
+
+  static final _getPi =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("Example__getPi")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public double getPi()
+  static double getPi() {
+    return _getPi().doubleFloat;
+  }
+
+  static final _getAsterisk =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Example__getAsterisk")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public char getAsterisk()
+  static int getAsterisk() {
+    return _getAsterisk().char;
+  }
+
+  static final _getName =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Example__getName")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public java.lang.String getName()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString getName() {
+    return const jni.JStringType().fromRef(_getName().object);
+  }
+
+  static final _getNestedInstance =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Example__getNestedInstance")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.github.dart_lang.jnigen.simple_package.Example.Nested getNestedInstance()
+  /// The returned object must be released after use, by calling the [release] method.
+  static Example_Nested getNestedInstance() {
+    return const $Example_NestedType().fromRef(_getNestedInstance().object);
+  }
+
+  static final _setAmount =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Int32)>>(
+              "Example__setAmount")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: static public void setAmount(int newAmount)
+  static void setAmount(
+    int newAmount,
+  ) {
+    return _setAmount(newAmount).check();
+  }
+
+  static final _setName = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__setName")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public void setName(java.lang.String newName)
+  static void setName(
+    jni.JString newName,
+  ) {
+    return _setName(newName.reference).check();
+  }
+
+  static final _setNestedInstance = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__setNestedInstance")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public void setNestedInstance(com.github.dart_lang.jnigen.simple_package.Example.Nested newNested)
+  static void setNestedInstance(
+    Example_Nested newNested,
+  ) {
+    return _setNestedInstance(newNested.reference).check();
+  }
+
+  static final _max4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Int32, ffi.Int32, ffi.Int32, ffi.Int32)>>("Example__max4")
+      .asFunction<jni.JniResult Function(int, int, int, int)>();
+
+  /// from: static public int max4(int a, int b, int c, int d)
+  static int max4(
+    int a,
+    int b,
+    int c,
+    int d,
+  ) {
+    return _max4(a, b, c, d).integer;
+  }
+
+  static final _max8 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Int32, ffi.Int32, ffi.Int32, ffi.Int32,
+                  ffi.Int32, ffi.Int32, ffi.Int32, ffi.Int32)>>("Example__max8")
+      .asFunction<
+          jni.JniResult Function(int, int, int, int, int, int, int, int)>();
+
+  /// from: static public int max8(int a, int b, int c, int d, int e, int f, int g, int h)
+  static int max8(
+    int a,
+    int b,
+    int c,
+    int d,
+    int e,
+    int f,
+    int g,
+    int h,
+  ) {
+    return _max8(a, b, c, d, e, f, g, h).integer;
+  }
+
+  static final _getNumber = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__getNumber")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int getNumber()
+  int getNumber() {
+    return _getNumber(reference).integer;
+  }
+
+  static final _setNumber = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>, ffi.Int32)>>("Example__setNumber")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setNumber(int number)
+  void setNumber(
+    int number,
+  ) {
+    return _setNumber(reference, number).check();
+  }
+
+  static final _getIsUp = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__getIsUp")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean getIsUp()
+  bool getIsUp() {
+    return _getIsUp(reference).boolean;
+  }
+
+  static final _setUp = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>, ffi.Uint8)>>("Example__setUp")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setUp(boolean isUp)
+  void setUp(
+    bool isUp,
+  ) {
+    return _setUp(reference, isUp ? 1 : 0).check();
+  }
+
+  static final _getCodename = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__getCodename")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getCodename()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString getCodename() {
+    return const jni.JStringType().fromRef(_getCodename(reference).object);
+  }
+
+  static final _setCodename = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Example__setCodename")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setCodename(java.lang.String codename)
+  void setCodename(
+    jni.JString codename,
+  ) {
+    return _setCodename(reference, codename.reference).check();
+  }
+
+  static final _getRandom = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__getRandom")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Random getRandom()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject getRandom() {
+    return const jni.JObjectType().fromRef(_getRandom(reference).object);
+  }
+
+  static final _setRandom = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Example__setRandom")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void setRandom(java.util.Random random)
+  void setRandom(
+    jni.JObject random,
+  ) {
+    return _setRandom(reference, random.reference).check();
+  }
+
+  static final _getRandomLong = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__getRandomLong")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public long getRandomLong()
+  int getRandomLong() {
+    return _getRandomLong(reference).long;
+  }
+
+  static final _add4Longs = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Int64,
+                  ffi.Int64, ffi.Int64, ffi.Int64)>>("Example__add4Longs")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, int, int, int, int)>();
+
+  /// from: public long add4Longs(long a, long b, long c, long d)
+  int add4Longs(
+    int a,
+    int b,
+    int c,
+    int d,
+  ) {
+    return _add4Longs(reference, a, b, c, d).long;
+  }
+
+  static final _add8Longs = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int64,
+                  ffi.Int64,
+                  ffi.Int64,
+                  ffi.Int64,
+                  ffi.Int64,
+                  ffi.Int64,
+                  ffi.Int64,
+                  ffi.Int64)>>("Example__add8Longs")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, int, int, int, int, int, int, int, int)>();
+
+  /// from: public long add8Longs(long a, long b, long c, long d, long e, long f, long g, long h)
+  int add8Longs(
+    int a,
+    int b,
+    int c,
+    int d,
+    int e,
+    int f,
+    int g,
+    int h,
+  ) {
+    return _add8Longs(reference, a, b, c, d, e, f, g, h).long;
+  }
+
+  static final _getRandomNumericString = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Example__getRandomNumericString")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String getRandomNumericString(java.util.Random random)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString getRandomNumericString(
+    jni.JObject random,
+  ) {
+    return const jni.JStringType()
+        .fromRef(_getRandomNumericString(reference, random.reference).object);
+  }
+
+  static final _protectedMethod = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Example__protectedMethod")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: protected void protectedMethod(java.lang.String a, java.lang.String b)
+  void protectedMethod(
+    jni.JString a,
+    jni.JString b,
+  ) {
+    return _protectedMethod(reference, a.reference, b.reference).check();
+  }
+
+  static final _finalMethod = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__finalMethod")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public final void finalMethod()
+  void finalMethod() {
+    return _finalMethod(reference).check();
+  }
+
+  static final _getList = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__getList")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.List<java.lang.String> getList()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JList<jni.JString> getList() {
+    return const jni.JListType(jni.JStringType())
+        .fromRef(_getList(reference).object);
+  }
+
+  static final _joinStrings = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Example__joinStrings")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String joinStrings(java.util.List<java.lang.String> values, java.lang.String delim)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Joins the strings in the list using the given delimiter.
+  jni.JString joinStrings(
+    jni.JList<jni.JString> values,
+    jni.JString delim,
+  ) {
+    return const jni.JStringType().fromRef(
+        _joinStrings(reference, values.reference, delim.reference).object);
+  }
+
+  static final _methodWithSeveralParams = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Uint16,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Example__methodWithSeveralParams")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>,
+              int,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void methodWithSeveralParams(char ch, java.lang.String s, int[] a, T t, java.util.List<T> lt, java.util.Map<java.lang.String,? extends java.lang.CharSequence> wm)
+  void methodWithSeveralParams<$T extends jni.JObject>(
+    int ch,
+    jni.JString s,
+    jni.JArray<jni.jint> a,
+    $T t,
+    jni.JList<$T> lt,
+    jni.JMap<jni.JString, jni.JObject> wm, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      (lt.$type as jni.JListType).E,
+      t.$type,
+    ]) as jni.JObjType<$T>;
+    return _methodWithSeveralParams(reference, ch, s.reference, a.reference,
+            t.reference, lt.reference, wm.reference)
+        .check();
+  }
+
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("Example__new0")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example() {
+    return Example.fromRef(_new0().object);
+  }
+
+  static final _new1 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Int32)>>(
+              "Example__new1")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: public void <init>(int number)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example.new1(
+    int number,
+  ) {
+    return Example.fromRef(_new1(number).object);
+  }
+
+  static final _new2 = jniLookup<
+              ffi.NativeFunction<jni.JniResult Function(ffi.Int32, ffi.Uint8)>>(
+          "Example__new2")
+      .asFunction<jni.JniResult Function(int, int)>();
+
+  /// from: public void <init>(int number, boolean isUp)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example.new2(
+    int number,
+    bool isUp,
+  ) {
+    return Example.fromRef(_new2(number, isUp ? 1 : 0).object);
+  }
+
+  static final _new3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Int32, ffi.Uint8,
+                  ffi.Pointer<ffi.Void>)>>("Example__new3")
+      .asFunction<jni.JniResult Function(int, int, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(int number, boolean isUp, java.lang.String codename)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example.new3(
+    int number,
+    bool isUp,
+    jni.JString codename,
+  ) {
+    return Example.fromRef(
+        _new3(number, isUp ? 1 : 0, codename.reference).object);
+  }
+
+  static final _new4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Int32, ffi.Int32, ffi.Int32, ffi.Int32,
+                  ffi.Int32, ffi.Int32, ffi.Int32, ffi.Int32)>>("Example__new4")
+      .asFunction<
+          jni.JniResult Function(int, int, int, int, int, int, int, int)>();
+
+  /// from: public void <init>(int a, int b, int c, int d, int e, int f, int g, int h)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example.new4(
+    int a,
+    int b,
+    int c,
+    int d,
+    int e,
+    int f,
+    int g,
+    int h,
+  ) {
+    return Example.fromRef(_new4(a, b, c, d, e, f, g, h).object);
+  }
+
+  static final _whichExample = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__whichExample")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int whichExample()
+  int whichExample() {
+    return _whichExample(reference).integer;
+  }
+
+  static final _addInts = jniLookup<
+              ffi.NativeFunction<jni.JniResult Function(ffi.Int32, ffi.Int32)>>(
+          "Example__addInts")
+      .asFunction<jni.JniResult Function(int, int)>();
+
+  /// from: static public int addInts(int a, int b)
+  static int addInts(
+    int a,
+    int b,
+  ) {
+    return _addInts(a, b).integer;
+  }
+
+  static final _getArr =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("Example__getArr")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public int[] getArr()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<jni.jint> getArr() {
+    return const jni.JArrayType(jni.jintType()).fromRef(_getArr().object);
+  }
+
+  static final _addAll = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("Example__addAll")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public int addAll(int[] arr)
+  static int addAll(
+    jni.JArray<jni.jint> arr,
+  ) {
+    return _addAll(arr.reference).integer;
+  }
+
+  static final _getSelf = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__getSelf")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.github.dart_lang.jnigen.simple_package.Example getSelf()
+  /// The returned object must be released after use, by calling the [release] method.
+  Example getSelf() {
+    return const $ExampleType().fromRef(_getSelf(reference).object);
+  }
+
+  static final _throwException =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Example__throwException")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public void throwException()
+  static void throwException() {
+    return _throwException().check();
+  }
+
+  static final _overloaded = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example__overloaded")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void overloaded()
+  void overloaded() {
+    return _overloaded(reference).check();
+  }
+
+  static final _overloaded1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Int32,
+                  ffi.Pointer<ffi.Void>)>>("Example__overloaded1")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, int, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void overloaded(int a, java.lang.String b)
+  void overloaded1(
+    int a,
+    jni.JString b,
+  ) {
+    return _overloaded1(reference, a, b.reference).check();
+  }
+
+  static final _overloaded2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>, ffi.Int32)>>("Example__overloaded2")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void overloaded(int a)
+  void overloaded2(
+    int a,
+  ) {
+    return _overloaded2(reference, a).check();
+  }
+
+  static final _overloaded3 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Example__overloaded3")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void overloaded(java.util.List<java.lang.Integer> a, java.lang.String b)
+  void overloaded3(
+    jni.JList<jni.JInteger> a,
+    jni.JString b,
+  ) {
+    return _overloaded3(reference, a.reference, b.reference).check();
+  }
+
+  static final _overloaded4 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("Example__overloaded4")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void overloaded(java.util.List<java.lang.Integer> a)
+  void overloaded4(
+    jni.JList<jni.JInteger> a,
+  ) {
+    return _overloaded4(reference, a.reference).check();
+  }
+}
+
+final class $ExampleType extends jni.JObjType<Example> {
+  const $ExampleType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Example;";
+
+  @override
+  Example fromRef(jni.JObjectPtr ref) => Example.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($ExampleType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($ExampleType) && other is $ExampleType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Example$Nested
+class Example_Nested extends jni.JObject {
+  @override
+  late final jni.JObjType<Example_Nested> $type = type;
+
+  Example_Nested.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Example_NestedType();
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Uint8)>>(
+              "Example_Nested__new0")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: public void <init>(boolean value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example_Nested(
+    bool value,
+  ) {
+    return Example_Nested.fromRef(_new0(value ? 1 : 0).object);
+  }
+
+  static final _usesAnonymousInnerClass = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example_Nested__usesAnonymousInnerClass")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void usesAnonymousInnerClass()
+  void usesAnonymousInnerClass() {
+    return _usesAnonymousInnerClass(reference).check();
+  }
+
+  static final _getValue = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example_Nested__getValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public boolean getValue()
+  bool getValue() {
+    return _getValue(reference).boolean;
+  }
+
+  static final _setValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Uint8)>>("Example_Nested__setValue")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>, int)>();
+
+  /// from: public void setValue(boolean value)
+  void setValue(
+    bool value,
+  ) {
+    return _setValue(reference, value ? 1 : 0).check();
+  }
+}
+
+final class $Example_NestedType extends jni.JObjType<Example_Nested> {
+  const $Example_NestedType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Example$Nested;";
+
+  @override
+  Example_Nested fromRef(jni.JObjectPtr ref) => Example_Nested.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Example_NestedType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Example_NestedType) &&
+        other is $Example_NestedType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Example$Nested$NestedTwice
+class Example_Nested_NestedTwice extends jni.JObject {
+  @override
+  late final jni.JObjType<Example_Nested_NestedTwice> $type = type;
+
+  Example_Nested_NestedTwice.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Example_Nested_NestedTwiceType();
+  static final _get_ZERO =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Example_Nested_NestedTwice__ZERO")
+          .asFunction<jni.JniResult Function()>();
+
+  static final _set_ZERO =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Int32)>>(
+              "set_Example_Nested_NestedTwice__ZERO")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: static public int ZERO
+  static int get ZERO => _get_ZERO().integer;
+
+  /// from: static public int ZERO
+  static set ZERO(int value) => _set_ZERO(value).check();
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "Example_Nested_NestedTwice__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example_Nested_NestedTwice() {
+    return Example_Nested_NestedTwice.fromRef(_new0().object);
+  }
+}
+
+final class $Example_Nested_NestedTwiceType
+    extends jni.JObjType<Example_Nested_NestedTwice> {
+  const $Example_Nested_NestedTwiceType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice;";
+
+  @override
+  Example_Nested_NestedTwice fromRef(jni.JObjectPtr ref) =>
+      Example_Nested_NestedTwice.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Example_Nested_NestedTwiceType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Example_Nested_NestedTwiceType) &&
+        other is $Example_Nested_NestedTwiceType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Example$NonStaticNested
+class Example_NonStaticNested extends jni.JObject {
+  @override
+  late final jni.JObjType<Example_NonStaticNested> $type = type;
+
+  Example_NonStaticNested.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Example_NonStaticNestedType();
+  static final _get_ok = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_Example_NonStaticNested__ok")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_ok = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Uint8)>>("set_Example_NonStaticNested__ok")
+      .asFunction<jni.JniResult Function(jni.JObjectPtr, int)>();
+
+  /// from: public boolean ok
+  bool get ok => _get_ok(reference).boolean;
+
+  /// from: public boolean ok
+  set ok(bool value) => _set_ok(reference, value ? 1 : 0).check();
+
+  static final _new0 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example_NonStaticNested__new0")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.simple_package.Example $parent)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example_NonStaticNested(
+    Example $parent,
+  ) {
+    return Example_NonStaticNested.fromRef(_new0($parent.reference).object);
+  }
+}
+
+final class $Example_NonStaticNestedType
+    extends jni.JObjType<Example_NonStaticNested> {
+  const $Example_NonStaticNestedType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Example$NonStaticNested;";
+
+  @override
+  Example_NonStaticNested fromRef(jni.JObjectPtr ref) =>
+      Example_NonStaticNested.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Example_NonStaticNestedType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Example_NonStaticNestedType) &&
+        other is $Example_NonStaticNestedType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Exceptions
+class Exceptions extends jni.JObject {
+  @override
+  late final jni.JObjType<Exceptions> $type = type;
+
+  Exceptions.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $ExceptionsType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "Exceptions__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Exceptions() {
+    return Exceptions.fromRef(_new0().object);
+  }
+
+  static final _new1 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Float)>>(
+              "Exceptions__new1")
+          .asFunction<jni.JniResult Function(double)>();
+
+  /// from: public void <init>(float x)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Exceptions.new1(
+    double x,
+  ) {
+    return Exceptions.fromRef(_new1(x).object);
+  }
+
+  static final _new2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Int32, ffi.Int32, ffi.Int32, ffi.Int32,
+                  ffi.Int32, ffi.Int32)>>("Exceptions__new2")
+      .asFunction<jni.JniResult Function(int, int, int, int, int, int)>();
+
+  /// from: public void <init>(int a, int b, int c, int d, int e, int f)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Exceptions.new2(
+    int a,
+    int b,
+    int c,
+    int d,
+    int e,
+    int f,
+  ) {
+    return Exceptions.fromRef(_new2(a, b, c, d, e, f).object);
+  }
+
+  static final _staticObjectMethod =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Exceptions__staticObjectMethod")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public java.lang.Object staticObjectMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JObject staticObjectMethod() {
+    return const jni.JObjectType().fromRef(_staticObjectMethod().object);
+  }
+
+  static final _staticIntMethod =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Exceptions__staticIntMethod")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public int staticIntMethod()
+  static int staticIntMethod() {
+    return _staticIntMethod().integer;
+  }
+
+  static final _staticObjectArrayMethod =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Exceptions__staticObjectArrayMethod")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public java.lang.Object[] staticObjectArrayMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<jni.JObject> staticObjectArrayMethod() {
+    return const jni.JArrayType(jni.JObjectType())
+        .fromRef(_staticObjectArrayMethod().object);
+  }
+
+  static final _staticIntArrayMethod =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Exceptions__staticIntArrayMethod")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public int[] staticIntArrayMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<jni.jint> staticIntArrayMethod() {
+    return const jni.JArrayType(jni.jintType())
+        .fromRef(_staticIntArrayMethod().object);
+  }
+
+  static final _objectMethod = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Exceptions__objectMethod")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object objectMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject objectMethod() {
+    return const jni.JObjectType().fromRef(_objectMethod(reference).object);
+  }
+
+  static final _intMethod = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Exceptions__intMethod")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int intMethod()
+  int intMethod() {
+    return _intMethod(reference).integer;
+  }
+
+  static final _objectArrayMethod = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Exceptions__objectArrayMethod")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Object[] objectArrayMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JArray<jni.JObject> objectArrayMethod() {
+    return const jni.JArrayType(jni.JObjectType())
+        .fromRef(_objectArrayMethod(reference).object);
+  }
+
+  static final _intArrayMethod = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Exceptions__intArrayMethod")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int[] intArrayMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JArray<jni.jint> intArrayMethod() {
+    return const jni.JArrayType(jni.jintType())
+        .fromRef(_intArrayMethod(reference).object);
+  }
+
+  static final _throwNullPointerException = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Exceptions__throwNullPointerException")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int throwNullPointerException()
+  int throwNullPointerException() {
+    return _throwNullPointerException(reference).integer;
+  }
+
+  static final _throwFileNotFoundException = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Exceptions__throwFileNotFoundException")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.io.InputStream throwFileNotFoundException()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject throwFileNotFoundException() {
+    return const jni.JObjectType()
+        .fromRef(_throwFileNotFoundException(reference).object);
+  }
+
+  static final _throwClassCastException = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Exceptions__throwClassCastException")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.io.FileInputStream throwClassCastException()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject throwClassCastException() {
+    return const jni.JObjectType()
+        .fromRef(_throwClassCastException(reference).object);
+  }
+
+  static final _throwArrayIndexException = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Exceptions__throwArrayIndexException")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int throwArrayIndexException()
+  int throwArrayIndexException() {
+    return _throwArrayIndexException(reference).integer;
+  }
+
+  static final _throwArithmeticException = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Exceptions__throwArithmeticException")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int throwArithmeticException()
+  int throwArithmeticException() {
+    return _throwArithmeticException(reference).integer;
+  }
+
+  static final _throwLoremIpsum =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "Exceptions__throwLoremIpsum")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public void throwLoremIpsum()
+  static void throwLoremIpsum() {
+    return _throwLoremIpsum().check();
+  }
+}
+
+final class $ExceptionsType extends jni.JObjType<Exceptions> {
+  const $ExceptionsType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Exceptions;";
+
+  @override
+  Exceptions fromRef(jni.JObjectPtr ref) => Exceptions.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($ExceptionsType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($ExceptionsType) && other is $ExceptionsType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Fields
+class Fields extends jni.JObject {
+  @override
+  late final jni.JObjType<Fields> $type = type;
+
+  Fields.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $FieldsType();
+  static final _get_amount =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Fields__amount")
+          .asFunction<jni.JniResult Function()>();
+
+  static final _set_amount =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Int32)>>(
+              "set_Fields__amount")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: static public int amount
+  static int get amount => _get_amount().integer;
+
+  /// from: static public int amount
+  static set amount(int value) => _set_amount(value).check();
+
+  static final _get_pi =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("get_Fields__pi")
+          .asFunction<jni.JniResult Function()>();
+
+  static final _set_pi =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Double)>>(
+              "set_Fields__pi")
+          .asFunction<jni.JniResult Function(double)>();
+
+  /// from: static public double pi
+  static double get pi => _get_pi().doubleFloat;
+
+  /// from: static public double pi
+  static set pi(double value) => _set_pi(value).check();
+
+  static final _get_asterisk =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Fields__asterisk")
+          .asFunction<jni.JniResult Function()>();
+
+  static final _set_asterisk =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Uint16)>>(
+              "set_Fields__asterisk")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: static public char asterisk
+  static int get asterisk => _get_asterisk().char;
+
+  /// from: static public char asterisk
+  static set asterisk(int value) => _set_asterisk(value).check();
+
+  static final _get_name =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Fields__name")
+          .asFunction<jni.JniResult Function()>();
+
+  static final _set_name = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "set_Fields__name")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public java.lang.String name
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get name =>
+      const jni.JStringType().fromRef(_get_name().object);
+
+  /// from: static public java.lang.String name
+  /// The returned object must be released after use, by calling the [release] method.
+  static set name(jni.JString value) => _set_name(value.reference).check();
+
+  static final _get_i = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_Fields__i")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_i = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>("set_Fields__i")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Integer i
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JInteger get i =>
+      const jni.JIntegerType().fromRef(_get_i(reference).object);
+
+  /// from: public java.lang.Integer i
+  /// The returned object must be released after use, by calling the [release] method.
+  set i(jni.JInteger value) => _set_i(reference, value.reference).check();
+
+  static final _get_trillion = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_Fields__trillion")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_trillion = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  jni.JObjectPtr, ffi.Int64)>>("set_Fields__trillion")
+      .asFunction<jni.JniResult Function(jni.JObjectPtr, int)>();
+
+  /// from: public long trillion
+  int get trillion => _get_trillion(reference).long;
+
+  /// from: public long trillion
+  set trillion(int value) => _set_trillion(reference, value).check();
+
+  static final _get_isAchillesDead = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_Fields__isAchillesDead")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_isAchillesDead = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  jni.JObjectPtr, ffi.Uint8)>>("set_Fields__isAchillesDead")
+      .asFunction<jni.JniResult Function(jni.JObjectPtr, int)>();
+
+  /// from: public boolean isAchillesDead
+  bool get isAchillesDead => _get_isAchillesDead(reference).boolean;
+
+  /// from: public boolean isAchillesDead
+  set isAchillesDead(bool value) =>
+      _set_isAchillesDead(reference, value ? 1 : 0).check();
+
+  static final _get_bestFighterInGreece = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_Fields__bestFighterInGreece")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_bestFighterInGreece = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Pointer<ffi.Void>)>>("set_Fields__bestFighterInGreece")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.String bestFighterInGreece
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString get bestFighterInGreece => const jni.JStringType()
+      .fromRef(_get_bestFighterInGreece(reference).object);
+
+  /// from: public java.lang.String bestFighterInGreece
+  /// The returned object must be released after use, by calling the [release] method.
+  set bestFighterInGreece(jni.JString value) =>
+      _set_bestFighterInGreece(reference, value.reference).check();
+
+  static final _get_random = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_Fields__random")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_random = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>("set_Fields__random")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.util.Random random
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject get random =>
+      const jni.JObjectType().fromRef(_get_random(reference).object);
+
+  /// from: public java.util.Random random
+  /// The returned object must be released after use, by calling the [release] method.
+  set random(jni.JObject value) =>
+      _set_random(reference, value.reference).check();
+
+  static final _get_euroSymbol =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Fields__euroSymbol")
+          .asFunction<jni.JniResult Function()>();
+
+  static final _set_euroSymbol =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Uint16)>>(
+              "set_Fields__euroSymbol")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: static public char euroSymbol
+  static int get euroSymbol => _get_euroSymbol().char;
+
+  /// from: static public char euroSymbol
+  static set euroSymbol(int value) => _set_euroSymbol(value).check();
+
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("Fields__new0")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Fields() {
+    return Fields.fromRef(_new0().object);
+  }
+}
+
+final class $FieldsType extends jni.JObjType<Fields> {
+  const $FieldsType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Fields;";
+
+  @override
+  Fields fromRef(jni.JObjectPtr ref) => Fields.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($FieldsType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($FieldsType) && other is $FieldsType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Fields$Nested
+class Fields_Nested extends jni.JObject {
+  @override
+  late final jni.JObjType<Fields_Nested> $type = type;
+
+  Fields_Nested.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Fields_NestedType();
+  static final _get_hundred = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_Fields_Nested__hundred")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_hundred = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  jni.JObjectPtr, ffi.Int64)>>("set_Fields_Nested__hundred")
+      .asFunction<jni.JniResult Function(jni.JObjectPtr, int)>();
+
+  /// from: public long hundred
+  int get hundred => _get_hundred(reference).long;
+
+  /// from: public long hundred
+  set hundred(int value) => _set_hundred(reference, value).check();
+
+  static final _get_BEST_GOD =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_Fields_Nested__BEST_GOD")
+          .asFunction<jni.JniResult Function()>();
+
+  static final _set_BEST_GOD = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "set_Fields_Nested__BEST_GOD")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public java.lang.String BEST_GOD
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get BEST_GOD =>
+      const jni.JStringType().fromRef(_get_BEST_GOD().object);
+
+  /// from: static public java.lang.String BEST_GOD
+  /// The returned object must be released after use, by calling the [release] method.
+  static set BEST_GOD(jni.JString value) =>
+      _set_BEST_GOD(value.reference).check();
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "Fields_Nested__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Fields_Nested() {
+    return Fields_Nested.fromRef(_new0().object);
+  }
+}
+
+final class $Fields_NestedType extends jni.JObjType<Fields_Nested> {
+  const $Fields_NestedType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Fields$Nested;";
+
+  @override
+  Fields_Nested fromRef(jni.JObjectPtr ref) => Fields_Nested.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Fields_NestedType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Fields_NestedType) &&
+        other is $Fields_NestedType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.pkg2.C2
+class C2 extends jni.JObject {
+  @override
+  late final jni.JObjType<C2> $type = type;
+
+  C2.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $C2Type();
+  static final _get_CONSTANT =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "get_C2__CONSTANT")
+          .asFunction<jni.JniResult Function()>();
+
+  static final _set_CONSTANT =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Int32)>>(
+              "set_C2__CONSTANT")
+          .asFunction<jni.JniResult Function(int)>();
+
+  /// from: static public int CONSTANT
+  static int get CONSTANT => _get_CONSTANT().integer;
+
+  /// from: static public int CONSTANT
+  static set CONSTANT(int value) => _set_CONSTANT(value).check();
+
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("C2__new0")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory C2() {
+    return C2.fromRef(_new0().object);
+  }
+}
+
+final class $C2Type extends jni.JObjType<C2> {
+  const $C2Type();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/pkg2/C2;";
+
+  @override
+  C2 fromRef(jni.JObjectPtr ref) => C2.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($C2Type).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($C2Type) && other is $C2Type;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.pkg2.Example
+class Example1 extends jni.JObject {
+  @override
+  late final jni.JObjType<Example1> $type = type;
+
+  Example1.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Example1Type();
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("Example1__new0")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example1() {
+    return Example1.fromRef(_new0().object);
+  }
+
+  static final _whichExample = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "Example1__whichExample")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int whichExample()
+  int whichExample() {
+    return _whichExample(reference).integer;
+  }
+}
+
+final class $Example1Type extends jni.JObjType<Example1> {
+  const $Example1Type();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/pkg2/Example;";
+
+  @override
+  Example1 fromRef(jni.JObjectPtr ref) => Example1.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Example1Type).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Example1Type) && other is $Example1Type;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GenericTypeParams
+class GenericTypeParams<$S extends jni.JObject, $K extends jni.JObject>
+    extends jni.JObject {
+  @override
+  late final jni.JObjType<GenericTypeParams<$S, $K>> $type = type(S, K);
+
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$K> K;
+
+  GenericTypeParams.fromRef(
+    this.S,
+    this.K,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $GenericTypeParamsType<$S, $K>
+      type<$S extends jni.JObject, $K extends jni.JObject>(
+    jni.JObjType<$S> S,
+    jni.JObjType<$K> K,
+  ) {
+    return $GenericTypeParamsType(
+      S,
+      K,
+    );
+  }
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "GenericTypeParams__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GenericTypeParams({
+    required jni.JObjType<$S> S,
+    required jni.JObjType<$K> K,
+  }) {
+    return GenericTypeParams.fromRef(S, K, _new0().object);
+  }
+}
+
+final class $GenericTypeParamsType<$S extends jni.JObject,
+    $K extends jni.JObject> extends jni.JObjType<GenericTypeParams<$S, $K>> {
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$K> K;
+
+  const $GenericTypeParamsType(
+    this.S,
+    this.K,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GenericTypeParams;";
+
+  @override
+  GenericTypeParams<$S, $K> fromRef(jni.JObjectPtr ref) =>
+      GenericTypeParams.fromRef(S, K, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GenericTypeParamsType, S, K);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GenericTypeParamsType<$S, $K>) &&
+        other is $GenericTypeParamsType<$S, $K> &&
+        S == other.S &&
+        K == other.K;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent
+class GrandParent<$T extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent<$T>> $type = type(T);
+
+  final jni.JObjType<$T> T;
+
+  GrandParent.fromRef(
+    this.T,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParentType<$T> type<$T extends jni.JObject>(
+    jni.JObjType<$T> T,
+  ) {
+    return $GrandParentType(
+      T,
+    );
+  }
+
+  static final _get_value = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_GrandParent__value")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_value = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Pointer<ffi.Void>)>>("set_GrandParent__value")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public T value
+  /// The returned object must be released after use, by calling the [release] method.
+  $T get value => T.fromRef(_get_value(reference).object);
+
+  /// from: public T value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($T value) => _set_value(reference, value.reference).check();
+
+  static final _new0 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "GrandParent__new0")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(T value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent(
+    $T value, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      value.$type,
+    ]) as jni.JObjType<$T>;
+    return GrandParent.fromRef(T, _new0(value.reference).object);
+  }
+
+  static final _stringParent = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "GrandParent__stringParent")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.github.dart_lang.jnigen.generics.GrandParent<T>.Parent<java.lang.String> stringParent()
+  /// The returned object must be released after use, by calling the [release] method.
+  GrandParent_Parent<jni.JObject, jni.JString> stringParent() {
+    return const $GrandParent_ParentType(jni.JObjectType(), jni.JStringType())
+        .fromRef(_stringParent(reference).object);
+  }
+
+  static final _varParent = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("GrandParent__varParent")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.github.dart_lang.jnigen.generics.GrandParent<T>.Parent<S> varParent(S nestedValue)
+  /// The returned object must be released after use, by calling the [release] method.
+  GrandParent_Parent<jni.JObject, $S> varParent<$S extends jni.JObject>(
+    $S nestedValue, {
+    jni.JObjType<$S>? S,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      nestedValue.$type,
+    ]) as jni.JObjType<$S>;
+    return $GrandParent_ParentType(const jni.JObjectType(), S)
+        .fromRef(_varParent(reference, nestedValue.reference).object);
+  }
+
+  static final _stringStaticParent =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "GrandParent__stringStaticParent")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.github.dart_lang.jnigen.generics.GrandParent.StaticParent<java.lang.String> stringStaticParent()
+  /// The returned object must be released after use, by calling the [release] method.
+  static GrandParent_StaticParent<jni.JString> stringStaticParent() {
+    return const $GrandParent_StaticParentType(jni.JStringType())
+        .fromRef(_stringStaticParent().object);
+  }
+
+  static final _varStaticParent = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "GrandParent__varStaticParent")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.github.dart_lang.jnigen.generics.GrandParent.StaticParent<S> varStaticParent(S value)
+  /// The returned object must be released after use, by calling the [release] method.
+  static GrandParent_StaticParent<$S> varStaticParent<$S extends jni.JObject>(
+    $S value, {
+    jni.JObjType<$S>? S,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      value.$type,
+    ]) as jni.JObjType<$S>;
+    return $GrandParent_StaticParentType(S)
+        .fromRef(_varStaticParent(value.reference).object);
+  }
+
+  static final _staticParentWithSameType = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "GrandParent__staticParentWithSameType")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.github.dart_lang.jnigen.generics.GrandParent.StaticParent<T> staticParentWithSameType()
+  /// The returned object must be released after use, by calling the [release] method.
+  GrandParent_StaticParent<$T> staticParentWithSameType() {
+    return $GrandParent_StaticParentType(T)
+        .fromRef(_staticParentWithSameType(reference).object);
+  }
+}
+
+final class $GrandParentType<$T extends jni.JObject>
+    extends jni.JObjType<GrandParent<$T>> {
+  final jni.JObjType<$T> T;
+
+  const $GrandParentType(
+    this.T,
+  );
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/GrandParent;";
+
+  @override
+  GrandParent<$T> fromRef(jni.JObjectPtr ref) => GrandParent.fromRef(T, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParentType, T);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParentType<$T>) &&
+        other is $GrandParentType<$T> &&
+        T == other.T;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent$Parent
+class GrandParent_Parent<$T extends jni.JObject, $S extends jni.JObject>
+    extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent_Parent<$T, $S>> $type = type(T, S);
+
+  final jni.JObjType<$T> T;
+  final jni.JObjType<$S> S;
+
+  GrandParent_Parent.fromRef(
+    this.T,
+    this.S,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParent_ParentType<$T, $S>
+      type<$T extends jni.JObject, $S extends jni.JObject>(
+    jni.JObjType<$T> T,
+    jni.JObjType<$S> S,
+  ) {
+    return $GrandParent_ParentType(
+      T,
+      S,
+    );
+  }
+
+  static final _get_parentValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_GrandParent_Parent__parentValue")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_parentValue = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>(
+          "set_GrandParent_Parent__parentValue")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public T parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  $T get parentValue => T.fromRef(_get_parentValue(reference).object);
+
+  /// from: public T parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  set parentValue($T value) =>
+      _set_parentValue(reference, value.reference).check();
+
+  static final _get_value = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_GrandParent_Parent__value")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_value = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Pointer<ffi.Void>)>>("set_GrandParent_Parent__value")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public S value
+  /// The returned object must be released after use, by calling the [release] method.
+  $S get value => S.fromRef(_get_value(reference).object);
+
+  /// from: public S value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($S value) => _set_value(reference, value.reference).check();
+
+  static final _new0 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("GrandParent_Parent__new0")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.generics.GrandParent $parent, S newValue)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent_Parent(
+    GrandParent<$T> $parent,
+    $S newValue, {
+    jni.JObjType<$T>? T,
+    jni.JObjType<$S>? S,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      ($parent.$type as $GrandParentType).T,
+    ]) as jni.JObjType<$T>;
+    S ??= jni.lowestCommonSuperType([
+      newValue.$type,
+    ]) as jni.JObjType<$S>;
+    return GrandParent_Parent.fromRef(
+        T, S, _new0($parent.reference, newValue.reference).object);
+  }
+}
+
+final class $GrandParent_ParentType<$T extends jni.JObject,
+    $S extends jni.JObject> extends jni.JObjType<GrandParent_Parent<$T, $S>> {
+  final jni.JObjType<$T> T;
+  final jni.JObjType<$S> S;
+
+  const $GrandParent_ParentType(
+    this.T,
+    this.S,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GrandParent$Parent;";
+
+  @override
+  GrandParent_Parent<$T, $S> fromRef(jni.JObjectPtr ref) =>
+      GrandParent_Parent.fromRef(T, S, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParent_ParentType, T, S);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParent_ParentType<$T, $S>) &&
+        other is $GrandParent_ParentType<$T, $S> &&
+        T == other.T &&
+        S == other.S;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent$Parent$Child
+class GrandParent_Parent_Child<$T extends jni.JObject, $S extends jni.JObject,
+    $U extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent_Parent_Child<$T, $S, $U>> $type =
+      type(T, S, U);
+
+  final jni.JObjType<$T> T;
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$U> U;
+
+  GrandParent_Parent_Child.fromRef(
+    this.T,
+    this.S,
+    this.U,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParent_Parent_ChildType<$T, $S, $U> type<$T extends jni.JObject,
+      $S extends jni.JObject, $U extends jni.JObject>(
+    jni.JObjType<$T> T,
+    jni.JObjType<$S> S,
+    jni.JObjType<$U> U,
+  ) {
+    return $GrandParent_Parent_ChildType(
+      T,
+      S,
+      U,
+    );
+  }
+
+  static final _get_grandParentValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_GrandParent_Parent_Child__grandParentValue")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_grandParentValue = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>(
+          "set_GrandParent_Parent_Child__grandParentValue")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public T grandParentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  $T get grandParentValue => T.fromRef(_get_grandParentValue(reference).object);
+
+  /// from: public T grandParentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  set grandParentValue($T value) =>
+      _set_grandParentValue(reference, value.reference).check();
+
+  static final _get_parentValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_GrandParent_Parent_Child__parentValue")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_parentValue = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>(
+          "set_GrandParent_Parent_Child__parentValue")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public S parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  $S get parentValue => S.fromRef(_get_parentValue(reference).object);
+
+  /// from: public S parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  set parentValue($S value) =>
+      _set_parentValue(reference, value.reference).check();
+
+  static final _get_value = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_GrandParent_Parent_Child__value")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_value = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>(
+          "set_GrandParent_Parent_Child__value")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public U value
+  /// The returned object must be released after use, by calling the [release] method.
+  $U get value => U.fromRef(_get_value(reference).object);
+
+  /// from: public U value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($U value) => _set_value(reference, value.reference).check();
+
+  static final _new0 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("GrandParent_Parent_Child__new0")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.generics.GrandParent$Parent $parent, U newValue)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent_Parent_Child(
+    GrandParent_Parent<$T, $S> $parent,
+    $U newValue, {
+    jni.JObjType<$T>? T,
+    jni.JObjType<$S>? S,
+    jni.JObjType<$U>? U,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      ($parent.$type as $GrandParent_ParentType).T,
+    ]) as jni.JObjType<$T>;
+    S ??= jni.lowestCommonSuperType([
+      ($parent.$type as $GrandParent_ParentType).S,
+    ]) as jni.JObjType<$S>;
+    U ??= jni.lowestCommonSuperType([
+      newValue.$type,
+    ]) as jni.JObjType<$U>;
+    return GrandParent_Parent_Child.fromRef(
+        T, S, U, _new0($parent.reference, newValue.reference).object);
+  }
+}
+
+final class $GrandParent_Parent_ChildType<$T extends jni.JObject,
+        $S extends jni.JObject, $U extends jni.JObject>
+    extends jni.JObjType<GrandParent_Parent_Child<$T, $S, $U>> {
+  final jni.JObjType<$T> T;
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$U> U;
+
+  const $GrandParent_Parent_ChildType(
+    this.T,
+    this.S,
+    this.U,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GrandParent$Parent$Child;";
+
+  @override
+  GrandParent_Parent_Child<$T, $S, $U> fromRef(jni.JObjectPtr ref) =>
+      GrandParent_Parent_Child.fromRef(T, S, U, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParent_Parent_ChildType, T, S, U);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParent_Parent_ChildType<$T, $S, $U>) &&
+        other is $GrandParent_Parent_ChildType<$T, $S, $U> &&
+        T == other.T &&
+        S == other.S &&
+        U == other.U;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent$StaticParent
+class GrandParent_StaticParent<$S extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent_StaticParent<$S>> $type = type(S);
+
+  final jni.JObjType<$S> S;
+
+  GrandParent_StaticParent.fromRef(
+    this.S,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParent_StaticParentType<$S> type<$S extends jni.JObject>(
+    jni.JObjType<$S> S,
+  ) {
+    return $GrandParent_StaticParentType(
+      S,
+    );
+  }
+
+  static final _get_value = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_GrandParent_StaticParent__value")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_value = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>(
+          "set_GrandParent_StaticParent__value")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public S value
+  /// The returned object must be released after use, by calling the [release] method.
+  $S get value => S.fromRef(_get_value(reference).object);
+
+  /// from: public S value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($S value) => _set_value(reference, value.reference).check();
+
+  static final _new0 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "GrandParent_StaticParent__new0")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(S value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent_StaticParent(
+    $S value, {
+    jni.JObjType<$S>? S,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      value.$type,
+    ]) as jni.JObjType<$S>;
+    return GrandParent_StaticParent.fromRef(S, _new0(value.reference).object);
+  }
+}
+
+final class $GrandParent_StaticParentType<$S extends jni.JObject>
+    extends jni.JObjType<GrandParent_StaticParent<$S>> {
+  final jni.JObjType<$S> S;
+
+  const $GrandParent_StaticParentType(
+    this.S,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent;";
+
+  @override
+  GrandParent_StaticParent<$S> fromRef(jni.JObjectPtr ref) =>
+      GrandParent_StaticParent.fromRef(S, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParent_StaticParentType, S);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParent_StaticParentType<$S>) &&
+        other is $GrandParent_StaticParentType<$S> &&
+        S == other.S;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent$StaticParent$Child
+class GrandParent_StaticParent_Child<$S extends jni.JObject,
+    $U extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent_StaticParent_Child<$S, $U>> $type =
+      type(S, U);
+
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$U> U;
+
+  GrandParent_StaticParent_Child.fromRef(
+    this.S,
+    this.U,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParent_StaticParent_ChildType<$S, $U>
+      type<$S extends jni.JObject, $U extends jni.JObject>(
+    jni.JObjType<$S> S,
+    jni.JObjType<$U> U,
+  ) {
+    return $GrandParent_StaticParent_ChildType(
+      S,
+      U,
+    );
+  }
+
+  static final _get_parentValue = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_GrandParent_StaticParent_Child__parentValue")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_parentValue = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>(
+          "set_GrandParent_StaticParent_Child__parentValue")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public S parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  $S get parentValue => S.fromRef(_get_parentValue(reference).object);
+
+  /// from: public S parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  set parentValue($S value) =>
+      _set_parentValue(reference, value.reference).check();
+
+  static final _get_value = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_GrandParent_StaticParent_Child__value")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_value = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      jni.JObjectPtr, ffi.Pointer<ffi.Void>)>>(
+          "set_GrandParent_StaticParent_Child__value")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public U value
+  /// The returned object must be released after use, by calling the [release] method.
+  $U get value => U.fromRef(_get_value(reference).object);
+
+  /// from: public U value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($U value) => _set_value(reference, value.reference).check();
+
+  static final _new0 = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>(
+          "GrandParent_StaticParent_Child__new0")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.generics.GrandParent$StaticParent $parent, S parentValue, U value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent_StaticParent_Child(
+    GrandParent_StaticParent<$S> $parent,
+    $S parentValue,
+    $U value, {
+    jni.JObjType<$S>? S,
+    jni.JObjType<$U>? U,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      parentValue.$type,
+      ($parent.$type as $GrandParent_StaticParentType).S,
+    ]) as jni.JObjType<$S>;
+    U ??= jni.lowestCommonSuperType([
+      value.$type,
+    ]) as jni.JObjType<$U>;
+    return GrandParent_StaticParent_Child.fromRef(
+        S,
+        U,
+        _new0($parent.reference, parentValue.reference, value.reference)
+            .object);
+  }
+}
+
+final class $GrandParent_StaticParent_ChildType<$S extends jni.JObject,
+        $U extends jni.JObject>
+    extends jni.JObjType<GrandParent_StaticParent_Child<$S, $U>> {
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$U> U;
+
+  const $GrandParent_StaticParent_ChildType(
+    this.S,
+    this.U,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent$Child;";
+
+  @override
+  GrandParent_StaticParent_Child<$S, $U> fromRef(jni.JObjectPtr ref) =>
+      GrandParent_StaticParent_Child.fromRef(S, U, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParent_StaticParent_ChildType, S, U);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParent_StaticParent_ChildType<$S, $U>) &&
+        other is $GrandParent_StaticParent_ChildType<$S, $U> &&
+        S == other.S &&
+        U == other.U;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.MyMap
+class MyMap<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObject {
+  @override
+  late final jni.JObjType<MyMap<$K, $V>> $type = type(K, V);
+
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  MyMap.fromRef(
+    this.K,
+    this.V,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $MyMapType<$K, $V>
+      type<$K extends jni.JObject, $V extends jni.JObject>(
+    jni.JObjType<$K> K,
+    jni.JObjType<$V> V,
+  ) {
+    return $MyMapType(
+      K,
+      V,
+    );
+  }
+
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("MyMap__new0")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyMap({
+    required jni.JObjType<$K> K,
+    required jni.JObjType<$V> V,
+  }) {
+    return MyMap.fromRef(K, V, _new0().object);
+  }
+
+  static final _get0 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>("MyMap__get0")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V get(K key)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V get0(
+    $K key,
+  ) {
+    return V.fromRef(_get0(reference, key.reference).object);
+  }
+
+  static final _put = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>>("MyMap__put")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V put(K key, V value)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V put(
+    $K key,
+    $V value,
+  ) {
+    return V.fromRef(_put(reference, key.reference, value.reference).object);
+  }
+
+  static final _entryStack = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "MyMap__entryStack")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public com.github.dart_lang.jnigen.generics.MyStack<com.github.dart_lang.jnigen.generics.MyMap<K,V>.MyEntry> entryStack()
+  /// The returned object must be released after use, by calling the [release] method.
+  MyStack<MyMap_MyEntry<jni.JObject, jni.JObject>> entryStack() {
+    return const $MyStackType(
+            $MyMap_MyEntryType(jni.JObjectType(), jni.JObjectType()))
+        .fromRef(_entryStack(reference).object);
+  }
+}
+
+final class $MyMapType<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObjType<MyMap<$K, $V>> {
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  const $MyMapType(
+    this.K,
+    this.V,
+  );
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/MyMap;";
+
+  @override
+  MyMap<$K, $V> fromRef(jni.JObjectPtr ref) => MyMap.fromRef(K, V, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($MyMapType, K, V);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyMapType<$K, $V>) &&
+        other is $MyMapType<$K, $V> &&
+        K == other.K &&
+        V == other.V;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.MyMap$MyEntry
+class MyMap_MyEntry<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObject {
+  @override
+  late final jni.JObjType<MyMap_MyEntry<$K, $V>> $type = type(K, V);
+
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  MyMap_MyEntry.fromRef(
+    this.K,
+    this.V,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $MyMap_MyEntryType<$K, $V>
+      type<$K extends jni.JObject, $V extends jni.JObject>(
+    jni.JObjType<$K> K,
+    jni.JObjType<$V> V,
+  ) {
+    return $MyMap_MyEntryType(
+      K,
+      V,
+    );
+  }
+
+  static final _get_key = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_MyMap_MyEntry__key")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_key = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Pointer<ffi.Void>)>>("set_MyMap_MyEntry__key")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public K key
+  /// The returned object must be released after use, by calling the [release] method.
+  $K get key => K.fromRef(_get_key(reference).object);
+
+  /// from: public K key
+  /// The returned object must be released after use, by calling the [release] method.
+  set key($K value) => _set_key(reference, value.reference).check();
+
+  static final _get_value = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_MyMap_MyEntry__value")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_value = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Pointer<ffi.Void>)>>("set_MyMap_MyEntry__value")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public V value
+  /// The returned object must be released after use, by calling the [release] method.
+  $V get value => V.fromRef(_get_value(reference).object);
+
+  /// from: public V value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($V value) => _set_value(reference, value.reference).check();
+
+  static final _new0 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("MyMap_MyEntry__new0")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.generics.MyMap $parent, K key, V value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyMap_MyEntry(
+    MyMap<$K, $V> $parent,
+    $K key,
+    $V value, {
+    jni.JObjType<$K>? K,
+    jni.JObjType<$V>? V,
+  }) {
+    K ??= jni.lowestCommonSuperType([
+      key.$type,
+      ($parent.$type as $MyMapType).K,
+    ]) as jni.JObjType<$K>;
+    V ??= jni.lowestCommonSuperType([
+      value.$type,
+      ($parent.$type as $MyMapType).V,
+    ]) as jni.JObjType<$V>;
+    return MyMap_MyEntry.fromRef(
+        K, V, _new0($parent.reference, key.reference, value.reference).object);
+  }
+}
+
+final class $MyMap_MyEntryType<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObjType<MyMap_MyEntry<$K, $V>> {
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  const $MyMap_MyEntryType(
+    this.K,
+    this.V,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/MyMap$MyEntry;";
+
+  @override
+  MyMap_MyEntry<$K, $V> fromRef(jni.JObjectPtr ref) =>
+      MyMap_MyEntry.fromRef(K, V, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($MyMap_MyEntryType, K, V);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyMap_MyEntryType<$K, $V>) &&
+        other is $MyMap_MyEntryType<$K, $V> &&
+        K == other.K &&
+        V == other.V;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.MyStack
+class MyStack<$T extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<MyStack<$T>> $type = type(T);
+
+  final jni.JObjType<$T> T;
+
+  MyStack.fromRef(
+    this.T,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $MyStackType<$T> type<$T extends jni.JObject>(
+    jni.JObjType<$T> T,
+  ) {
+    return $MyStackType(
+      T,
+    );
+  }
+
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("MyStack__new0")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyStack({
+    required jni.JObjType<$T> T,
+  }) {
+    return MyStack.fromRef(T, _new0().object);
+  }
+
+  static final _fromArray = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "MyStack__fromArray")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<T> fromArray(T[] arr)
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$T> fromArray<$T extends jni.JObject>(
+    jni.JArray<$T> arr, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      ((arr.$type as jni.JArrayType).elementType as jni.JObjType),
+    ]) as jni.JObjType<$T>;
+    return $MyStackType(T).fromRef(_fromArray(arr.reference).object);
+  }
+
+  static final _fromArrayOfArrayOfGrandParents = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "MyStack__fromArrayOfArrayOfGrandParents")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<S> fromArrayOfArrayOfGrandParents(com.github.dart_lang.jnigen.generics.GrandParent<S>[][] arr)
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$S> fromArrayOfArrayOfGrandParents<$S extends jni.JObject>(
+    jni.JArray<jni.JArray<GrandParent<$S>>> arr, {
+    jni.JObjType<$S>? S,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      (((((arr.$type as jni.JArrayType).elementType as jni.JObjType)
+                  as jni.JArrayType)
+              .elementType as jni.JObjType) as $GrandParentType)
+          .T,
+    ]) as jni.JObjType<$S>;
+    return $MyStackType(S)
+        .fromRef(_fromArrayOfArrayOfGrandParents(arr.reference).object);
+  }
+
+  static final _of =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("MyStack__of")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<T> of()
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$T> of<$T extends jni.JObject>({
+    required jni.JObjType<$T> T,
+  }) {
+    return $MyStackType(T).fromRef(_of().object);
+  }
+
+  static final _of1 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("MyStack__of1")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<T> of(T obj)
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$T> of1<$T extends jni.JObject>(
+    $T obj, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      obj.$type,
+    ]) as jni.JObjType<$T>;
+    return $MyStackType(T).fromRef(_of1(obj.reference).object);
+  }
+
+  static final _of2 = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("MyStack__of2")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<T> of(T obj, T obj2)
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$T> of2<$T extends jni.JObject>(
+    $T obj,
+    $T obj2, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      obj2.$type,
+      obj.$type,
+    ]) as jni.JObjType<$T>;
+    return $MyStackType(T).fromRef(_of2(obj.reference, obj2.reference).object);
+  }
+
+  static final _push = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("MyStack__push")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void push(T item)
+  void push(
+    $T item,
+  ) {
+    return _push(reference, item.reference).check();
+  }
+
+  static final _pop = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("MyStack__pop")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public T pop()
+  /// The returned object must be released after use, by calling the [release] method.
+  $T pop() {
+    return T.fromRef(_pop(reference).object);
+  }
+
+  static final _size = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("MyStack__size")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public int size()
+  int size() {
+    return _size(reference).integer;
+  }
+}
+
+final class $MyStackType<$T extends jni.JObject>
+    extends jni.JObjType<MyStack<$T>> {
+  final jni.JObjType<$T> T;
+
+  const $MyStackType(
+    this.T,
+  );
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/MyStack;";
+
+  @override
+  MyStack<$T> fromRef(jni.JObjectPtr ref) => MyStack.fromRef(T, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($MyStackType, T);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyStackType<$T>) &&
+        other is $MyStackType<$T> &&
+        T == other.T;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.StringKeyedMap
+class StringKeyedMap<$V extends jni.JObject> extends MyMap<jni.JString, $V> {
+  @override
+  late final jni.JObjType<StringKeyedMap<$V>> $type = type(V);
+
+  final jni.JObjType<$V> V;
+
+  StringKeyedMap.fromRef(
+    this.V,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(const jni.JStringType(), V, ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $StringKeyedMapType<$V> type<$V extends jni.JObject>(
+    jni.JObjType<$V> V,
+  ) {
+    return $StringKeyedMapType(
+      V,
+    );
+  }
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "StringKeyedMap__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory StringKeyedMap({
+    required jni.JObjType<$V> V,
+  }) {
+    return StringKeyedMap.fromRef(V, _new0().object);
+  }
+}
+
+final class $StringKeyedMapType<$V extends jni.JObject>
+    extends jni.JObjType<StringKeyedMap<$V>> {
+  final jni.JObjType<$V> V;
+
+  const $StringKeyedMapType(
+    this.V,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/StringKeyedMap;";
+
+  @override
+  StringKeyedMap<$V> fromRef(jni.JObjectPtr ref) =>
+      StringKeyedMap.fromRef(V, ref);
+
+  @override
+  jni.JObjType get superType => $MyMapType(const jni.JStringType(), V);
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => Object.hash($StringKeyedMapType, V);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($StringKeyedMapType<$V>) &&
+        other is $StringKeyedMapType<$V> &&
+        V == other.V;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.StringMap
+class StringMap extends StringKeyedMap<jni.JString> {
+  @override
+  late final jni.JObjType<StringMap> $type = type;
+
+  StringMap.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(const jni.JStringType(), ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $StringMapType();
+  static final _new0 =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>("StringMap__new0")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory StringMap() {
+    return StringMap.fromRef(_new0().object);
+  }
+}
+
+final class $StringMapType extends jni.JObjType<StringMap> {
+  const $StringMapType();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/StringMap;";
+
+  @override
+  StringMap fromRef(jni.JObjectPtr ref) => StringMap.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const $StringKeyedMapType(jni.JStringType());
+
+  @override
+  final superCount = 3;
+
+  @override
+  int get hashCode => ($StringMapType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($StringMapType) && other is $StringMapType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.StringStack
+class StringStack extends MyStack<jni.JString> {
+  @override
+  late final jni.JObjType<StringStack> $type = type;
+
+  StringStack.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(const jni.JStringType(), ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $StringStackType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "StringStack__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory StringStack() {
+    return StringStack.fromRef(_new0().object);
+  }
+}
+
+final class $StringStackType extends jni.JObjType<StringStack> {
+  const $StringStackType();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/StringStack;";
+
+  @override
+  StringStack fromRef(jni.JObjectPtr ref) => StringStack.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const $MyStackType(jni.JStringType());
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => ($StringStackType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($StringStackType) && other is $StringStackType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.StringValuedMap
+class StringValuedMap<$K extends jni.JObject> extends MyMap<$K, jni.JString> {
+  @override
+  late final jni.JObjType<StringValuedMap<$K>> $type = type(K);
+
+  final jni.JObjType<$K> K;
+
+  StringValuedMap.fromRef(
+    this.K,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(K, const jni.JStringType(), ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $StringValuedMapType<$K> type<$K extends jni.JObject>(
+    jni.JObjType<$K> K,
+  ) {
+    return $StringValuedMapType(
+      K,
+    );
+  }
+
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "StringValuedMap__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory StringValuedMap({
+    required jni.JObjType<$K> K,
+  }) {
+    return StringValuedMap.fromRef(K, _new0().object);
+  }
+}
+
+final class $StringValuedMapType<$K extends jni.JObject>
+    extends jni.JObjType<StringValuedMap<$K>> {
+  final jni.JObjType<$K> K;
+
+  const $StringValuedMapType(
+    this.K,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/StringValuedMap;";
+
+  @override
+  StringValuedMap<$K> fromRef(jni.JObjectPtr ref) =>
+      StringValuedMap.fromRef(K, ref);
+
+  @override
+  jni.JObjType get superType => $MyMapType(K, const jni.JStringType());
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => Object.hash($StringValuedMapType, K);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($StringValuedMapType<$K>) &&
+        other is $StringValuedMapType<$K> &&
+        K == other.K;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyInterface
+class MyInterface<$T extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<MyInterface<$T>> $type = type(T);
+
+  final jni.JObjType<$T> T;
+
+  MyInterface.fromRef(
+    this.T,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static $MyInterfaceType<$T> type<$T extends jni.JObject>(
+    jni.JObjType<$T> T,
+  ) {
+    return $MyInterfaceType(
+      T,
+    );
+  }
+
+  static final _voidCallback = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("MyInterface__voidCallback")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract void voidCallback(java.lang.String s)
+  void voidCallback(
+    jni.JString s,
+  ) {
+    return _voidCallback(reference, s.reference).check();
+  }
+
+  static final _stringCallback = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("MyInterface__stringCallback")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract java.lang.String stringCallback(java.lang.String s)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString stringCallback(
+    jni.JString s,
+  ) {
+    return const jni.JStringType()
+        .fromRef(_stringCallback(reference, s.reference).object);
+  }
+
+  static final _varCallback = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>,
+                  ffi.Pointer<ffi.Void>)>>("MyInterface__varCallback")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract T varCallback(T t)
+  /// The returned object must be released after use, by calling the [release] method.
+  $T varCallback(
+    $T t,
+  ) {
+    return T.fromRef(_varCallback(reference, t.reference).object);
+  }
+
+  static final _manyPrimitives = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                  ffi.Pointer<ffi.Void>,
+                  ffi.Int32,
+                  ffi.Uint8,
+                  ffi.Uint16,
+                  ffi.Double)>>("MyInterface__manyPrimitives")
+      .asFunction<
+          jni.JniResult Function(
+              ffi.Pointer<ffi.Void>, int, int, int, double)>();
+
+  /// from: public abstract long manyPrimitives(int a, boolean b, char c, double d)
+  int manyPrimitives(
+    int a,
+    bool b,
+    int c,
+    double d,
+  ) {
+    return _manyPrimitives(reference, a, b ? 1 : 0, c, d).long;
+  }
+
+  /// Maps a specific port to the implemented interface.
+  static final Map<int, $MyInterfaceImpl> _$impls = {};
+  ReceivePort? _$p;
+
+  static jni.JObjectPtr _$invoke(
+    int port,
+    jni.JObjectPtr descriptor,
+    jni.JObjectPtr args,
+  ) {
+    return _$invokeMethod(
+      port,
+      $MethodInvocation.fromAddresses(
+        0,
+        descriptor.address,
+        args.address,
+      ),
+    );
+  }
+
+  static final ffi.Pointer<
+          ffi.NativeFunction<
+              jni.JObjectPtr Function(
+                  ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>>
+      _$invokePointer = ffi.Pointer.fromFunction(_$invoke);
+
+  static ffi.Pointer<ffi.Void> _$invokeMethod(
+    int $p,
+    $MethodInvocation $i,
+  ) {
+    try {
+      final $d = $i.methodDescriptor.toDartString(releaseOriginal: true);
+      final $a = $i.args;
+      if ($d == r"voidCallback(Ljava/lang/String;)V") {
+        _$impls[$p]!.voidCallback(
+          $a[0].castTo(const jni.JStringType(), releaseOriginal: true),
+        );
+        return jni.nullptr;
+      }
+      if ($d == r"stringCallback(Ljava/lang/String;)Ljava/lang/String;") {
+        final $r = _$impls[$p]!.stringCallback(
+          $a[0].castTo(const jni.JStringType(), releaseOriginal: true),
+        );
+        return ($r as jni.JObject).castTo(const jni.JObjectType()).toPointer();
+      }
+      if ($d == r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;") {
+        final $r = _$impls[$p]!.varCallback(
+          $a[0].castTo(_$impls[$p]!.T, releaseOriginal: true),
+        );
+        return ($r as jni.JObject).castTo(const jni.JObjectType()).toPointer();
+      }
+      if ($d == r"manyPrimitives(IZCD)J") {
+        final $r = _$impls[$p]!.manyPrimitives(
+          $a[0]
+              .castTo(const jni.JIntegerType(), releaseOriginal: true)
+              .intValue(releaseOriginal: true),
+          $a[1]
+              .castTo(const jni.JBooleanType(), releaseOriginal: true)
+              .booleanValue(releaseOriginal: true),
+          $a[2]
+              .castTo(const jni.JCharacterType(), releaseOriginal: true)
+              .charValue(releaseOriginal: true),
+          $a[3]
+              .castTo(const jni.JDoubleType(), releaseOriginal: true)
+              .doubleValue(releaseOriginal: true),
+        );
+        return jni.JLong($r).toPointer();
+      }
+    } catch (e) {
+      return ProtectedJniExtensions.newDartException(e.toString());
+    }
+    return jni.nullptr;
+  }
+
+  factory MyInterface.implement(
+    $MyInterfaceImpl<$T> $impl,
+  ) {
+    final $p = ReceivePort();
+    final $x = MyInterface.fromRef(
+      $impl.T,
+      ProtectedJniExtensions.newPortProxy(
+        r"com.github.dart_lang.jnigen.interfaces.MyInterface",
+        $p,
+        _$invokePointer,
+      ),
+    ).._$p = $p;
+    final $a = $p.sendPort.nativePort;
+    _$impls[$a] = $impl;
+    $p.listen(($m) {
+      if ($m == null) {
+        _$impls.remove($p.sendPort.nativePort);
+        $p.close();
+        return;
+      }
+      final $i = $MethodInvocation.fromMessage($m as List<dynamic>);
+      final $r = _$invokeMethod($p.sendPort.nativePort, $i);
+      ProtectedJniExtensions.returnResult($i.result, $r);
+    });
+    return $x;
+  }
+  static Map<int, $MyInterfaceImpl> get $impls => _$impls;
+}
+
+abstract interface class $MyInterfaceImpl<$T extends jni.JObject> {
+  factory $MyInterfaceImpl({
+    required jni.JObjType<$T> T,
+    required void Function(jni.JString s) voidCallback,
+    required jni.JString Function(jni.JString s) stringCallback,
+    required $T Function($T t) varCallback,
+    required int Function(int a, bool b, int c, double d) manyPrimitives,
+  }) = _$MyInterfaceImpl;
+
+  jni.JObjType<$T> get T;
+
+  void voidCallback(jni.JString s);
+  jni.JString stringCallback(jni.JString s);
+  $T varCallback($T t);
+  int manyPrimitives(int a, bool b, int c, double d);
+}
+
+class _$MyInterfaceImpl<$T extends jni.JObject>
+    implements $MyInterfaceImpl<$T> {
+  _$MyInterfaceImpl({
+    required this.T,
+    required void Function(jni.JString s) voidCallback,
+    required jni.JString Function(jni.JString s) stringCallback,
+    required $T Function($T t) varCallback,
+    required int Function(int a, bool b, int c, double d) manyPrimitives,
+  })  : _voidCallback = voidCallback,
+        _stringCallback = stringCallback,
+        _varCallback = varCallback,
+        _manyPrimitives = manyPrimitives;
+
+  @override
+  final jni.JObjType<$T> T;
+
+  final void Function(jni.JString s) _voidCallback;
+  final jni.JString Function(jni.JString s) _stringCallback;
+  final $T Function($T t) _varCallback;
+  final int Function(int a, bool b, int c, double d) _manyPrimitives;
+
+  void voidCallback(jni.JString s) {
+    return _voidCallback(s);
+  }
+
+  jni.JString stringCallback(jni.JString s) {
+    return _stringCallback(s);
+  }
+
+  $T varCallback($T t) {
+    return _varCallback(t);
+  }
+
+  int manyPrimitives(int a, bool b, int c, double d) {
+    return _manyPrimitives(a, b, c, d);
+  }
+}
+
+final class $MyInterfaceType<$T extends jni.JObject>
+    extends jni.JObjType<MyInterface<$T>> {
+  final jni.JObjType<$T> T;
+
+  const $MyInterfaceType(
+    this.T,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/interfaces/MyInterface;";
+
+  @override
+  MyInterface<$T> fromRef(jni.JObjectPtr ref) => MyInterface.fromRef(T, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($MyInterfaceType, T);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyInterfaceType<$T>) &&
+        other is $MyInterfaceType<$T> &&
+        T == other.T;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyInterfaceConsumer
+class MyInterfaceConsumer extends jni.JObject {
+  @override
+  late final jni.JObjType<MyInterfaceConsumer> $type = type;
+
+  MyInterfaceConsumer.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $MyInterfaceConsumerType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "MyInterfaceConsumer__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyInterfaceConsumer() {
+    return MyInterfaceConsumer.fromRef(_new0().object);
+  }
+
+  static final _consumeOnAnotherThread = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Int32,
+                      ffi.Uint8,
+                      ffi.Uint16,
+                      ffi.Double,
+                      ffi.Pointer<ffi.Void>)>>(
+          "MyInterfaceConsumer__consumeOnAnotherThread")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              int, int, int, double, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public void consumeOnAnotherThread(com.github.dart_lang.jnigen.interfaces.MyInterface<T> myInterface, java.lang.String s, int a, boolean b, char c, double d, T t)
+  static void consumeOnAnotherThread<$T extends jni.JObject>(
+    MyInterface<$T> myInterface,
+    jni.JString s,
+    int a,
+    bool b,
+    int c,
+    double d,
+    $T t, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      t.$type,
+      (myInterface.$type as $MyInterfaceType).T,
+    ]) as jni.JObjType<$T>;
+    return _consumeOnAnotherThread(
+            myInterface.reference, s.reference, a, b ? 1 : 0, c, d, t.reference)
+        .check();
+  }
+
+  static final _consumeOnSameThread = jniLookup<
+              ffi.NativeFunction<
+                  jni.JniResult Function(
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Pointer<ffi.Void>,
+                      ffi.Int32,
+                      ffi.Uint8,
+                      ffi.Uint16,
+                      ffi.Double,
+                      ffi.Pointer<ffi.Void>)>>(
+          "MyInterfaceConsumer__consumeOnSameThread")
+      .asFunction<
+          jni.JniResult Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
+              int, int, int, double, ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public void consumeOnSameThread(com.github.dart_lang.jnigen.interfaces.MyInterface<T> myInterface, java.lang.String s, int a, boolean b, char c, double d, T t)
+  static void consumeOnSameThread<$T extends jni.JObject>(
+    MyInterface<$T> myInterface,
+    jni.JString s,
+    int a,
+    bool b,
+    int c,
+    double d,
+    $T t, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      t.$type,
+      (myInterface.$type as $MyInterfaceType).T,
+    ]) as jni.JObjType<$T>;
+    return _consumeOnSameThread(
+            myInterface.reference, s.reference, a, b ? 1 : 0, c, d, t.reference)
+        .check();
+  }
+}
+
+final class $MyInterfaceConsumerType extends jni.JObjType<MyInterfaceConsumer> {
+  const $MyInterfaceConsumerType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer;";
+
+  @override
+  MyInterfaceConsumer fromRef(jni.JObjectPtr ref) =>
+      MyInterfaceConsumer.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($MyInterfaceConsumerType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyInterfaceConsumerType) &&
+        other is $MyInterfaceConsumerType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyRunnable
+class MyRunnable extends jni.JObject {
+  @override
+  late final jni.JObjType<MyRunnable> $type = type;
+
+  MyRunnable.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $MyRunnableType();
+  static final _run = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(ffi.Pointer<ffi.Void>)>>("MyRunnable__run")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public abstract void run()
+  void run() {
+    return _run(reference).check();
+  }
+
+  /// Maps a specific port to the implemented interface.
+  static final Map<int, $MyRunnableImpl> _$impls = {};
+  ReceivePort? _$p;
+
+  static jni.JObjectPtr _$invoke(
+    int port,
+    jni.JObjectPtr descriptor,
+    jni.JObjectPtr args,
+  ) {
+    return _$invokeMethod(
+      port,
+      $MethodInvocation.fromAddresses(
+        0,
+        descriptor.address,
+        args.address,
+      ),
+    );
+  }
+
+  static final ffi.Pointer<
+          ffi.NativeFunction<
+              jni.JObjectPtr Function(
+                  ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>>
+      _$invokePointer = ffi.Pointer.fromFunction(_$invoke);
+
+  static ffi.Pointer<ffi.Void> _$invokeMethod(
+    int $p,
+    $MethodInvocation $i,
+  ) {
+    try {
+      final $d = $i.methodDescriptor.toDartString(releaseOriginal: true);
+      final $a = $i.args;
+      if ($d == r"run()V") {
+        _$impls[$p]!.run();
+        return jni.nullptr;
+      }
+    } catch (e) {
+      return ProtectedJniExtensions.newDartException(e.toString());
+    }
+    return jni.nullptr;
+  }
+
+  factory MyRunnable.implement(
+    $MyRunnableImpl $impl,
+  ) {
+    final $p = ReceivePort();
+    final $x = MyRunnable.fromRef(
+      ProtectedJniExtensions.newPortProxy(
+        r"com.github.dart_lang.jnigen.interfaces.MyRunnable",
+        $p,
+        _$invokePointer,
+      ),
+    ).._$p = $p;
+    final $a = $p.sendPort.nativePort;
+    _$impls[$a] = $impl;
+    $p.listen(($m) {
+      if ($m == null) {
+        _$impls.remove($p.sendPort.nativePort);
+        $p.close();
+        return;
+      }
+      final $i = $MethodInvocation.fromMessage($m as List<dynamic>);
+      final $r = _$invokeMethod($p.sendPort.nativePort, $i);
+      ProtectedJniExtensions.returnResult($i.result, $r);
+    });
+    return $x;
+  }
+}
+
+abstract interface class $MyRunnableImpl {
+  factory $MyRunnableImpl({
+    required void Function() run,
+  }) = _$MyRunnableImpl;
+
+  void run();
+}
+
+class _$MyRunnableImpl implements $MyRunnableImpl {
+  _$MyRunnableImpl({
+    required void Function() run,
+  }) : _run = run;
+
+  final void Function() _run;
+
+  void run() {
+    return _run();
+  }
+}
+
+final class $MyRunnableType extends jni.JObjType<MyRunnable> {
+  const $MyRunnableType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/interfaces/MyRunnable;";
+
+  @override
+  MyRunnable fromRef(jni.JObjectPtr ref) => MyRunnable.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($MyRunnableType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyRunnableType) && other is $MyRunnableType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyRunnableRunner
+class MyRunnableRunner extends jni.JObject {
+  @override
+  late final jni.JObjType<MyRunnableRunner> $type = type;
+
+  MyRunnableRunner.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $MyRunnableRunnerType();
+  static final _get_error = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(
+                jni.JObjectPtr,
+              )>>("get_MyRunnableRunner__error")
+      .asFunction<
+          jni.JniResult Function(
+            jni.JObjectPtr,
+          )>();
+
+  static final _set_error = jniLookup<
+          ffi.NativeFunction<
+              jni.JniResult Function(jni.JObjectPtr,
+                  ffi.Pointer<ffi.Void>)>>("set_MyRunnableRunner__error")
+      .asFunction<
+          jni.JniResult Function(jni.JObjectPtr, ffi.Pointer<ffi.Void>)>();
+
+  /// from: public java.lang.Throwable error
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject get error =>
+      const jni.JObjectType().fromRef(_get_error(reference).object);
+
+  /// from: public java.lang.Throwable error
+  /// The returned object must be released after use, by calling the [release] method.
+  set error(jni.JObject value) =>
+      _set_error(reference, value.reference).check();
+
+  static final _new0 = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "MyRunnableRunner__new0")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.interfaces.MyRunnable runnable)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyRunnableRunner(
+    MyRunnable runnable,
+  ) {
+    return MyRunnableRunner.fromRef(_new0(runnable.reference).object);
+  }
+
+  static final _runOnSameThread = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "MyRunnableRunner__runOnSameThread")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void runOnSameThread()
+  void runOnSameThread() {
+    return _runOnSameThread(reference).check();
+  }
+
+  static final _runOnAnotherThread = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "MyRunnableRunner__runOnAnotherThread")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: public void runOnAnotherThread()
+  void runOnAnotherThread() {
+    return _runOnAnotherThread(reference).check();
+  }
+}
+
+final class $MyRunnableRunnerType extends jni.JObjType<MyRunnableRunner> {
+  const $MyRunnableRunnerType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/interfaces/MyRunnableRunner;";
+
+  @override
+  MyRunnableRunner fromRef(jni.JObjectPtr ref) => MyRunnableRunner.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($MyRunnableRunnerType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyRunnableRunnerType) &&
+        other is $MyRunnableRunnerType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.annotations.JsonSerializable$Case
+class JsonSerializable_Case extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonSerializable_Case> $type = type;
+
+  JsonSerializable_Case.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonSerializable_CaseType();
+  static final _values =
+      jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+              "JsonSerializable_Case__values")
+          .asFunction<jni.JniResult Function()>();
+
+  /// from: static public com.github.dart_lang.jnigen.annotations.JsonSerializable.Case[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonSerializable_Case> values() {
+    return const jni.JArrayType($JsonSerializable_CaseType())
+        .fromRef(_values().object);
+  }
+
+  static final _valueOf = jniLookup<
+              ffi
+              .NativeFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>>(
+          "JsonSerializable_Case__valueOf")
+      .asFunction<jni.JniResult Function(ffi.Pointer<ffi.Void>)>();
+
+  /// from: static public com.github.dart_lang.jnigen.annotations.JsonSerializable.Case valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonSerializable_Case valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonSerializable_CaseType()
+        .fromRef(_valueOf(name.reference).object);
+  }
+}
+
+final class $JsonSerializable_CaseType
+    extends jni.JObjType<JsonSerializable_Case> {
+  const $JsonSerializable_CaseType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/annotations/JsonSerializable$Case;";
+
+  @override
+  JsonSerializable_Case fromRef(jni.JObjectPtr ref) =>
+      JsonSerializable_Case.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonSerializable_CaseType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonSerializable_CaseType) &&
+        other is $JsonSerializable_CaseType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.annotations.MyDataClass
+class MyDataClass extends jni.JObject {
+  @override
+  late final jni.JObjType<MyDataClass> $type = type;
+
+  MyDataClass.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $MyDataClassType();
+  static final _new0 = jniLookup<ffi.NativeFunction<jni.JniResult Function()>>(
+          "MyDataClass__new0")
+      .asFunction<jni.JniResult Function()>();
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyDataClass() {
+    return MyDataClass.fromRef(_new0().object);
+  }
+}
+
+final class $MyDataClassType extends jni.JObjType<MyDataClass> {
+  const $MyDataClassType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/annotations/MyDataClass;";
+
+  @override
+  MyDataClass fromRef(jni.JObjectPtr ref) => MyDataClass.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($MyDataClassType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyDataClassType) && other is $MyDataClassType;
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart b/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
new file mode 100644
index 0000000..8e0f1ce
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart
@@ -0,0 +1,3806 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Autogenerated by jnigen. DO NOT EDIT!
+
+// ignore_for_file: annotate_overrides
+// ignore_for_file: camel_case_extensions
+// ignore_for_file: camel_case_types
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: doc_directive_unknown
+// ignore_for_file: file_names
+// ignore_for_file: lines_longer_than_80_chars
+// ignore_for_file: no_leading_underscores_for_local_identifiers
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: overridden_fields
+// ignore_for_file: unnecessary_cast
+// ignore_for_file: unused_element
+// ignore_for_file: unused_field
+// ignore_for_file: unused_import
+// ignore_for_file: unused_local_variable
+// ignore_for_file: unused_shown_name
+
+import "dart:isolate" show ReceivePort;
+import "dart:ffi" as ffi;
+import "package:jni/internal_helpers_for_jnigen.dart";
+import "package:jni/jni.dart" as jni;
+
+/// from: com.github.dart_lang.jnigen.simple_package.Color
+class Color extends jni.JObject {
+  @override
+  late final jni.JObjType<Color> $type = type;
+
+  Color.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/simple_package/Color");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $ColorType();
+  static final _id_values = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"values",
+      r"()[Lcom/github/dart_lang/jnigen/simple_package/Color;");
+
+  /// from: static public com.github.dart_lang.jnigen.simple_package.Color[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<Color> values() {
+    return const jni.JArrayType($ColorType()).fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_values,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_valueOf = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"valueOf",
+      r"(Ljava/lang/String;)Lcom/github/dart_lang/jnigen/simple_package/Color;");
+
+  /// from: static public com.github.dart_lang.jnigen.simple_package.Color valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static Color valueOf(
+    jni.JString name,
+  ) {
+    return const $ColorType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_valueOf,
+            jni.JniCallType.objectType, [name.reference]).object);
+  }
+}
+
+final class $ColorType extends jni.JObjType<Color> {
+  const $ColorType();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/simple_package/Color;";
+
+  @override
+  Color fromRef(jni.JObjectPtr ref) => Color.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($ColorType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($ColorType) && other is $ColorType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Example
+class Example extends jni.JObject {
+  @override
+  late final jni.JObjType<Example> $type = type;
+
+  Example.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/simple_package/Example");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $ExampleType();
+
+  /// from: static public final int ON
+  static const ON = 1;
+
+  /// from: static public final int OFF
+  static const OFF = 0;
+
+  /// from: static public final double PI
+  static const PI = 3.14159;
+
+  /// from: static public final char SEMICOLON
+  static const SEMICOLON = 59;
+
+  /// from: static public final java.lang.String SEMICOLON_STRING
+  static const SEMICOLON_STRING = r""";""";
+
+  static final _id_unusedRandom = jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"unusedRandom",
+    r"Ljava/util/Random;",
+  );
+
+  /// from: static public final java.util.Random unusedRandom
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JObject get unusedRandom =>
+      const jni.JObjectType().fromRef(jni.Jni.accessors
+          .getStaticField(
+              _class.reference, _id_unusedRandom, jni.JniCallType.objectType)
+          .object);
+
+  static final _id_protectedField = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"protectedField",
+    r"Ljava/util/Random;",
+  );
+
+  /// from: protected java.util.Random protectedField
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject get protectedField =>
+      const jni.JObjectType().fromRef(jni.Jni.accessors
+          .getField(reference, _id_protectedField, jni.JniCallType.objectType)
+          .object);
+
+  /// from: protected java.util.Random protectedField
+  /// The returned object must be released after use, by calling the [release] method.
+  set protectedField(jni.JObject value) => jni.Jni.env
+      .SetObjectField(reference, _id_protectedField, value.reference);
+
+  static final _id_getAmount = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"getAmount", r"()I");
+
+  /// from: static public int getAmount()
+  static int getAmount() {
+    return jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference, _id_getAmount, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_getPi =
+      jni.Jni.accessors.getStaticMethodIDOf(_class.reference, r"getPi", r"()D");
+
+  /// from: static public double getPi()
+  static double getPi() {
+    return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+        _id_getPi, jni.JniCallType.doubleType, []).doubleFloat;
+  }
+
+  static final _id_getAsterisk = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"getAsterisk", r"()C");
+
+  /// from: static public char getAsterisk()
+  static int getAsterisk() {
+    return jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference, _id_getAsterisk, jni.JniCallType.charType, []).char;
+  }
+
+  static final _id_getName = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"getName", r"()Ljava/lang/String;");
+
+  /// from: static public java.lang.String getName()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString getName() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_getName,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_getNestedInstance = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"getNestedInstance",
+      r"()Lcom/github/dart_lang/jnigen/simple_package/Example$Nested;");
+
+  /// from: static public com.github.dart_lang.jnigen.simple_package.Example.Nested getNestedInstance()
+  /// The returned object must be released after use, by calling the [release] method.
+  static Example_Nested getNestedInstance() {
+    return const $Example_NestedType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_getNestedInstance,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setAmount = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"setAmount", r"(I)V");
+
+  /// from: static public void setAmount(int newAmount)
+  static void setAmount(
+    int newAmount,
+  ) {
+    return jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference,
+        _id_setAmount,
+        jni.JniCallType.voidType,
+        [jni.JValueInt(newAmount)]).check();
+  }
+
+  static final _id_setName = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"setName", r"(Ljava/lang/String;)V");
+
+  /// from: static public void setName(java.lang.String newName)
+  static void setName(
+    jni.JString newName,
+  ) {
+    return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+        _id_setName, jni.JniCallType.voidType, [newName.reference]).check();
+  }
+
+  static final _id_setNestedInstance = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"setNestedInstance",
+      r"(Lcom/github/dart_lang/jnigen/simple_package/Example$Nested;)V");
+
+  /// from: static public void setNestedInstance(com.github.dart_lang.jnigen.simple_package.Example.Nested newNested)
+  static void setNestedInstance(
+    Example_Nested newNested,
+  ) {
+    return jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference,
+        _id_setNestedInstance,
+        jni.JniCallType.voidType,
+        [newNested.reference]).check();
+  }
+
+  static final _id_max4 = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"max4", r"(IIII)I");
+
+  /// from: static public int max4(int a, int b, int c, int d)
+  static int max4(
+    int a,
+    int b,
+    int c,
+    int d,
+  ) {
+    return jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference, _id_max4, jni.JniCallType.intType, [
+      jni.JValueInt(a),
+      jni.JValueInt(b),
+      jni.JValueInt(c),
+      jni.JValueInt(d)
+    ]).integer;
+  }
+
+  static final _id_max8 = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"max8", r"(IIIIIIII)I");
+
+  /// from: static public int max8(int a, int b, int c, int d, int e, int f, int g, int h)
+  static int max8(
+    int a,
+    int b,
+    int c,
+    int d,
+    int e,
+    int f,
+    int g,
+    int h,
+  ) {
+    return jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference, _id_max8, jni.JniCallType.intType, [
+      jni.JValueInt(a),
+      jni.JValueInt(b),
+      jni.JValueInt(c),
+      jni.JValueInt(d),
+      jni.JValueInt(e),
+      jni.JValueInt(f),
+      jni.JValueInt(g),
+      jni.JValueInt(h)
+    ]).integer;
+  }
+
+  static final _id_getNumber =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"getNumber", r"()I");
+
+  /// from: public int getNumber()
+  int getNumber() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getNumber, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_setNumber =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"setNumber", r"(I)V");
+
+  /// from: public void setNumber(int number)
+  void setNumber(
+    int number,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_setNumber,
+        jni.JniCallType.voidType, [jni.JValueInt(number)]).check();
+  }
+
+  static final _id_getIsUp =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"getIsUp", r"()Z");
+
+  /// from: public boolean getIsUp()
+  bool getIsUp() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getIsUp, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_setUp =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"setUp", r"(Z)V");
+
+  /// from: public void setUp(boolean isUp)
+  void setUp(
+    bool isUp,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_setUp, jni.JniCallType.voidType, [isUp ? 1 : 0]).check();
+  }
+
+  static final _id_getCodename = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getCodename", r"()Ljava/lang/String;");
+
+  /// from: public java.lang.String getCodename()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString getCodename() {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getCodename, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setCodename = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"setCodename", r"(Ljava/lang/String;)V");
+
+  /// from: public void setCodename(java.lang.String codename)
+  void setCodename(
+    jni.JString codename,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_setCodename,
+        jni.JniCallType.voidType, [codename.reference]).check();
+  }
+
+  static final _id_getRandom = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getRandom", r"()Ljava/util/Random;");
+
+  /// from: public java.util.Random getRandom()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject getRandom() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getRandom, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_setRandom = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"setRandom", r"(Ljava/util/Random;)V");
+
+  /// from: public void setRandom(java.util.Random random)
+  void setRandom(
+    jni.JObject random,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_setRandom,
+        jni.JniCallType.voidType, [random.reference]).check();
+  }
+
+  static final _id_getRandomLong = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getRandomLong", r"()J");
+
+  /// from: public long getRandomLong()
+  int getRandomLong() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getRandomLong, jni.JniCallType.longType, []).long;
+  }
+
+  static final _id_add4Longs = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"add4Longs", r"(JJJJ)J");
+
+  /// from: public long add4Longs(long a, long b, long c, long d)
+  int add4Longs(
+    int a,
+    int b,
+    int c,
+    int d,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_add4Longs, jni.JniCallType.longType, [a, b, c, d]).long;
+  }
+
+  static final _id_add8Longs = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"add8Longs", r"(JJJJJJJJ)J");
+
+  /// from: public long add8Longs(long a, long b, long c, long d, long e, long f, long g, long h)
+  int add8Longs(
+    int a,
+    int b,
+    int c,
+    int d,
+    int e,
+    int f,
+    int g,
+    int h,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_add8Longs,
+        jni.JniCallType.longType, [a, b, c, d, e, f, g, h]).long;
+  }
+
+  static final _id_getRandomNumericString = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"getRandomNumericString",
+      r"(Ljava/util/Random;)Ljava/lang/String;");
+
+  /// from: public java.lang.String getRandomNumericString(java.util.Random random)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString getRandomNumericString(
+    jni.JObject random,
+  ) {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_getRandomNumericString,
+        jni.JniCallType.objectType,
+        [random.reference]).object);
+  }
+
+  static final _id_protectedMethod = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"protectedMethod",
+      r"(Ljava/lang/String;Ljava/lang/String;)V");
+
+  /// from: protected void protectedMethod(java.lang.String a, java.lang.String b)
+  void protectedMethod(
+    jni.JString a,
+    jni.JString b,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_protectedMethod,
+        jni.JniCallType.voidType, [a.reference, b.reference]).check();
+  }
+
+  static final _id_finalMethod =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"finalMethod", r"()V");
+
+  /// from: public final void finalMethod()
+  void finalMethod() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_finalMethod, jni.JniCallType.voidType, []).check();
+  }
+
+  static final _id_getList = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"getList", r"()Ljava/util/List;");
+
+  /// from: public java.util.List<java.lang.String> getList()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JList<jni.JString> getList() {
+    return const jni.JListType(jni.JStringType()).fromRef(jni.Jni.accessors
+        .callMethodWithArgs(
+            reference, _id_getList, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_joinStrings = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"joinStrings",
+      r"(Ljava/util/List;Ljava/lang/String;)Ljava/lang/String;");
+
+  /// from: public java.lang.String joinStrings(java.util.List<java.lang.String> values, java.lang.String delim)
+  /// The returned object must be released after use, by calling the [release] method.
+  ///
+  /// Joins the strings in the list using the given delimiter.
+  jni.JString joinStrings(
+    jni.JList<jni.JString> values,
+    jni.JString delim,
+  ) {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_joinStrings,
+        jni.JniCallType.objectType,
+        [values.reference, delim.reference]).object);
+  }
+
+  static final _id_methodWithSeveralParams = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"methodWithSeveralParams",
+      r"(CLjava/lang/String;[ILjava/lang/CharSequence;Ljava/util/List;Ljava/util/Map;)V");
+
+  /// from: public void methodWithSeveralParams(char ch, java.lang.String s, int[] a, T t, java.util.List<T> lt, java.util.Map<java.lang.String,? extends java.lang.CharSequence> wm)
+  void methodWithSeveralParams<$T extends jni.JObject>(
+    int ch,
+    jni.JString s,
+    jni.JArray<jni.jint> a,
+    $T t,
+    jni.JList<$T> lt,
+    jni.JMap<jni.JString, jni.JObject> wm, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      (lt.$type as jni.JListType).E,
+      t.$type,
+    ]) as jni.JObjType<$T>;
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_methodWithSeveralParams, jni.JniCallType.voidType, [
+      jni.JValueChar(ch),
+      s.reference,
+      a.reference,
+      t.reference,
+      lt.reference,
+      wm.reference
+    ]).check();
+  }
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example() {
+    return Example.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+
+  static final _id_new1 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(I)V");
+
+  /// from: public void <init>(int number)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example.new1(
+    int number,
+  ) {
+    return Example.fromRef(jni.Jni.accessors.newObjectWithArgs(
+        _class.reference, _id_new1, [jni.JValueInt(number)]).object);
+  }
+
+  static final _id_new2 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(IZ)V");
+
+  /// from: public void <init>(int number, boolean isUp)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example.new2(
+    int number,
+    bool isUp,
+  ) {
+    return Example.fromRef(jni.Jni.accessors.newObjectWithArgs(_class.reference,
+        _id_new2, [jni.JValueInt(number), isUp ? 1 : 0]).object);
+  }
+
+  static final _id_new3 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"<init>", r"(IZLjava/lang/String;)V");
+
+  /// from: public void <init>(int number, boolean isUp, java.lang.String codename)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example.new3(
+    int number,
+    bool isUp,
+    jni.JString codename,
+  ) {
+    return Example.fromRef(jni.Jni.accessors.newObjectWithArgs(
+        _class.reference,
+        _id_new3,
+        [jni.JValueInt(number), isUp ? 1 : 0, codename.reference]).object);
+  }
+
+  static final _id_new4 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"<init>", r"(IIIIIIII)V");
+
+  /// from: public void <init>(int a, int b, int c, int d, int e, int f, int g, int h)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example.new4(
+    int a,
+    int b,
+    int c,
+    int d,
+    int e,
+    int f,
+    int g,
+    int h,
+  ) {
+    return Example.fromRef(
+        jni.Jni.accessors.newObjectWithArgs(_class.reference, _id_new4, [
+      jni.JValueInt(a),
+      jni.JValueInt(b),
+      jni.JValueInt(c),
+      jni.JValueInt(d),
+      jni.JValueInt(e),
+      jni.JValueInt(f),
+      jni.JValueInt(g),
+      jni.JValueInt(h)
+    ]).object);
+  }
+
+  static final _id_whichExample = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"whichExample", r"()I");
+
+  /// from: public int whichExample()
+  int whichExample() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_whichExample, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_addInts = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"addInts", r"(II)I");
+
+  /// from: static public int addInts(int a, int b)
+  static int addInts(
+    int a,
+    int b,
+  ) {
+    return jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference,
+        _id_addInts,
+        jni.JniCallType.intType,
+        [jni.JValueInt(a), jni.JValueInt(b)]).integer;
+  }
+
+  static final _id_getArr = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"getArr", r"()[I");
+
+  /// from: static public int[] getArr()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<jni.jint> getArr() {
+    return const jni.JArrayType(jni.jintType()).fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_getArr,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_addAll = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"addAll", r"([I)I");
+
+  /// from: static public int addAll(int[] arr)
+  static int addAll(
+    jni.JArray<jni.jint> arr,
+  ) {
+    return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+        _id_addAll, jni.JniCallType.intType, [arr.reference]).integer;
+  }
+
+  static final _id_getSelf = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"getSelf", r"()Lcom/github/dart_lang/jnigen/simple_package/Example;");
+
+  /// from: public com.github.dart_lang.jnigen.simple_package.Example getSelf()
+  /// The returned object must be released after use, by calling the [release] method.
+  Example getSelf() {
+    return const $ExampleType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getSelf, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_throwException = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"throwException", r"()V");
+
+  /// from: static public void throwException()
+  static void throwException() {
+    return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+        _id_throwException, jni.JniCallType.voidType, []).check();
+  }
+
+  static final _id_overloaded =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"overloaded", r"()V");
+
+  /// from: public void overloaded()
+  void overloaded() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_overloaded, jni.JniCallType.voidType, []).check();
+  }
+
+  static final _id_overloaded1 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"overloaded", r"(ILjava/lang/String;)V");
+
+  /// from: public void overloaded(int a, java.lang.String b)
+  void overloaded1(
+    int a,
+    jni.JString b,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_overloaded1,
+        jni.JniCallType.voidType, [jni.JValueInt(a), b.reference]).check();
+  }
+
+  static final _id_overloaded2 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"overloaded", r"(I)V");
+
+  /// from: public void overloaded(int a)
+  void overloaded2(
+    int a,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_overloaded2,
+        jni.JniCallType.voidType, [jni.JValueInt(a)]).check();
+  }
+
+  static final _id_overloaded3 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"overloaded",
+      r"(Ljava/util/List;Ljava/lang/String;)V");
+
+  /// from: public void overloaded(java.util.List<java.lang.Integer> a, java.lang.String b)
+  void overloaded3(
+    jni.JList<jni.JInteger> a,
+    jni.JString b,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_overloaded3,
+        jni.JniCallType.voidType, [a.reference, b.reference]).check();
+  }
+
+  static final _id_overloaded4 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"overloaded", r"(Ljava/util/List;)V");
+
+  /// from: public void overloaded(java.util.List<java.lang.Integer> a)
+  void overloaded4(
+    jni.JList<jni.JInteger> a,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_overloaded4,
+        jni.JniCallType.voidType, [a.reference]).check();
+  }
+}
+
+final class $ExampleType extends jni.JObjType<Example> {
+  const $ExampleType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Example;";
+
+  @override
+  Example fromRef(jni.JObjectPtr ref) => Example.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($ExampleType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($ExampleType) && other is $ExampleType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Example$Nested
+class Example_Nested extends jni.JObject {
+  @override
+  late final jni.JObjType<Example_Nested> $type = type;
+
+  Example_Nested.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/simple_package/Example$Nested");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Example_NestedType();
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(Z)V");
+
+  /// from: public void <init>(boolean value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example_Nested(
+    bool value,
+  ) {
+    return Example_Nested.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, [value ? 1 : 0]).object);
+  }
+
+  static final _id_usesAnonymousInnerClass = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"usesAnonymousInnerClass", r"()V");
+
+  /// from: public void usesAnonymousInnerClass()
+  void usesAnonymousInnerClass() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_usesAnonymousInnerClass, jni.JniCallType.voidType, []).check();
+  }
+
+  static final _id_getValue =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"getValue", r"()Z");
+
+  /// from: public boolean getValue()
+  bool getValue() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_getValue, jni.JniCallType.booleanType, []).boolean;
+  }
+
+  static final _id_setValue =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"setValue", r"(Z)V");
+
+  /// from: public void setValue(boolean value)
+  void setValue(
+    bool value,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_setValue,
+        jni.JniCallType.voidType, [value ? 1 : 0]).check();
+  }
+}
+
+final class $Example_NestedType extends jni.JObjType<Example_Nested> {
+  const $Example_NestedType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Example$Nested;";
+
+  @override
+  Example_Nested fromRef(jni.JObjectPtr ref) => Example_Nested.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Example_NestedType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Example_NestedType) &&
+        other is $Example_NestedType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Example$Nested$NestedTwice
+class Example_Nested_NestedTwice extends jni.JObject {
+  @override
+  late final jni.JObjType<Example_Nested_NestedTwice> $type = type;
+
+  Example_Nested_NestedTwice.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Example_Nested_NestedTwiceType();
+  static final _id_ZERO = jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"ZERO",
+    r"I",
+  );
+
+  /// from: static public int ZERO
+  static int get ZERO => jni.Jni.accessors
+      .getStaticField(_class.reference, _id_ZERO, jni.JniCallType.intType)
+      .integer;
+
+  /// from: static public int ZERO
+  static set ZERO(int value) =>
+      jni.Jni.env.SetStaticIntField(_class.reference, _id_ZERO, value);
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example_Nested_NestedTwice() {
+    return Example_Nested_NestedTwice.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $Example_Nested_NestedTwiceType
+    extends jni.JObjType<Example_Nested_NestedTwice> {
+  const $Example_Nested_NestedTwiceType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice;";
+
+  @override
+  Example_Nested_NestedTwice fromRef(jni.JObjectPtr ref) =>
+      Example_Nested_NestedTwice.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Example_Nested_NestedTwiceType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Example_Nested_NestedTwiceType) &&
+        other is $Example_Nested_NestedTwiceType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Example$NonStaticNested
+class Example_NonStaticNested extends jni.JObject {
+  @override
+  late final jni.JObjType<Example_NonStaticNested> $type = type;
+
+  Example_NonStaticNested.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/simple_package/Example$NonStaticNested");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Example_NonStaticNestedType();
+  static final _id_ok = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"ok",
+    r"Z",
+  );
+
+  /// from: public boolean ok
+  bool get ok => jni.Jni.accessors
+      .getField(reference, _id_ok, jni.JniCallType.booleanType)
+      .boolean;
+
+  /// from: public boolean ok
+  set ok(bool value) =>
+      jni.Jni.env.SetBooleanField(reference, _id_ok, value ? 1 : 0);
+
+  static final _id_new0 = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"<init>", r"(Lcom/github/dart_lang/jnigen/simple_package/Example;)V");
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.simple_package.Example $parent)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example_NonStaticNested(
+    Example $parent,
+  ) {
+    return Example_NonStaticNested.fromRef(jni.Jni.accessors.newObjectWithArgs(
+        _class.reference, _id_new0, [$parent.reference]).object);
+  }
+}
+
+final class $Example_NonStaticNestedType
+    extends jni.JObjType<Example_NonStaticNested> {
+  const $Example_NonStaticNestedType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Example$NonStaticNested;";
+
+  @override
+  Example_NonStaticNested fromRef(jni.JObjectPtr ref) =>
+      Example_NonStaticNested.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Example_NonStaticNestedType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Example_NonStaticNestedType) &&
+        other is $Example_NonStaticNestedType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Exceptions
+class Exceptions extends jni.JObject {
+  @override
+  late final jni.JObjType<Exceptions> $type = type;
+
+  Exceptions.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/simple_package/Exceptions");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $ExceptionsType();
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Exceptions() {
+    return Exceptions.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+
+  static final _id_new1 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"(F)V");
+
+  /// from: public void <init>(float x)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Exceptions.new1(
+    double x,
+  ) {
+    return Exceptions.fromRef(jni.Jni.accessors.newObjectWithArgs(
+        _class.reference, _id_new1, [jni.JValueFloat(x)]).object);
+  }
+
+  static final _id_new2 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"<init>", r"(IIIIII)V");
+
+  /// from: public void <init>(int a, int b, int c, int d, int e, int f)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Exceptions.new2(
+    int a,
+    int b,
+    int c,
+    int d,
+    int e,
+    int f,
+  ) {
+    return Exceptions.fromRef(
+        jni.Jni.accessors.newObjectWithArgs(_class.reference, _id_new2, [
+      jni.JValueInt(a),
+      jni.JValueInt(b),
+      jni.JValueInt(c),
+      jni.JValueInt(d),
+      jni.JValueInt(e),
+      jni.JValueInt(f)
+    ]).object);
+  }
+
+  static final _id_staticObjectMethod = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference, r"staticObjectMethod", r"()Ljava/lang/Object;");
+
+  /// from: static public java.lang.Object staticObjectMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JObject staticObjectMethod() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_staticObjectMethod,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_staticIntMethod = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"staticIntMethod", r"()I");
+
+  /// from: static public int staticIntMethod()
+  static int staticIntMethod() {
+    return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+        _id_staticIntMethod, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_staticObjectArrayMethod = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"staticObjectArrayMethod",
+          r"()[Ljava/lang/Object;");
+
+  /// from: static public java.lang.Object[] staticObjectArrayMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<jni.JObject> staticObjectArrayMethod() {
+    return const jni.JArrayType(jni.JObjectType()).fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_staticObjectArrayMethod,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_staticIntArrayMethod = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"staticIntArrayMethod", r"()[I");
+
+  /// from: static public int[] staticIntArrayMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<jni.jint> staticIntArrayMethod() {
+    return const jni.JArrayType(jni.jintType()).fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_staticIntArrayMethod,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_objectMethod = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"objectMethod", r"()Ljava/lang/Object;");
+
+  /// from: public java.lang.Object objectMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject objectMethod() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_objectMethod, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_intMethod =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"intMethod", r"()I");
+
+  /// from: public int intMethod()
+  int intMethod() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_intMethod, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_objectArrayMethod = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"objectArrayMethod", r"()[Ljava/lang/Object;");
+
+  /// from: public java.lang.Object[] objectArrayMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JArray<jni.JObject> objectArrayMethod() {
+    return const jni.JArrayType(jni.JObjectType()).fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_objectArrayMethod,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_intArrayMethod = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"intArrayMethod", r"()[I");
+
+  /// from: public int[] intArrayMethod()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JArray<jni.jint> intArrayMethod() {
+    return const jni.JArrayType(jni.jintType()).fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_intArrayMethod,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_throwNullPointerException = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"throwNullPointerException", r"()I");
+
+  /// from: public int throwNullPointerException()
+  int throwNullPointerException() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_throwNullPointerException, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_throwFileNotFoundException = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"throwFileNotFoundException",
+      r"()Ljava/io/InputStream;");
+
+  /// from: public java.io.InputStream throwFileNotFoundException()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject throwFileNotFoundException() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_throwFileNotFoundException,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_throwClassCastException = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"throwClassCastException",
+      r"()Ljava/io/FileInputStream;");
+
+  /// from: public java.io.FileInputStream throwClassCastException()
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject throwClassCastException() {
+    return const jni.JObjectType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_throwClassCastException,
+        jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_throwArrayIndexException = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"throwArrayIndexException", r"()I");
+
+  /// from: public int throwArrayIndexException()
+  int throwArrayIndexException() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_throwArrayIndexException, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_throwArithmeticException = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"throwArithmeticException", r"()I");
+
+  /// from: public int throwArithmeticException()
+  int throwArithmeticException() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_throwArithmeticException, jni.JniCallType.intType, []).integer;
+  }
+
+  static final _id_throwLoremIpsum = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"throwLoremIpsum", r"()V");
+
+  /// from: static public void throwLoremIpsum()
+  static void throwLoremIpsum() {
+    return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+        _id_throwLoremIpsum, jni.JniCallType.voidType, []).check();
+  }
+}
+
+final class $ExceptionsType extends jni.JObjType<Exceptions> {
+  const $ExceptionsType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Exceptions;";
+
+  @override
+  Exceptions fromRef(jni.JObjectPtr ref) => Exceptions.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($ExceptionsType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($ExceptionsType) && other is $ExceptionsType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Fields
+class Fields extends jni.JObject {
+  @override
+  late final jni.JObjType<Fields> $type = type;
+
+  Fields.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/simple_package/Fields");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $FieldsType();
+  static final _id_amount = jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"amount",
+    r"I",
+  );
+
+  /// from: static public int amount
+  static int get amount => jni.Jni.accessors
+      .getStaticField(_class.reference, _id_amount, jni.JniCallType.intType)
+      .integer;
+
+  /// from: static public int amount
+  static set amount(int value) =>
+      jni.Jni.env.SetStaticIntField(_class.reference, _id_amount, value);
+
+  static final _id_pi = jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"pi",
+    r"D",
+  );
+
+  /// from: static public double pi
+  static double get pi => jni.Jni.accessors
+      .getStaticField(_class.reference, _id_pi, jni.JniCallType.doubleType)
+      .doubleFloat;
+
+  /// from: static public double pi
+  static set pi(double value) =>
+      jni.Jni.env.SetStaticDoubleField(_class.reference, _id_pi, value);
+
+  static final _id_asterisk = jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"asterisk",
+    r"C",
+  );
+
+  /// from: static public char asterisk
+  static int get asterisk => jni.Jni.accessors
+      .getStaticField(_class.reference, _id_asterisk, jni.JniCallType.charType)
+      .char;
+
+  /// from: static public char asterisk
+  static set asterisk(int value) =>
+      jni.Jni.env.SetStaticCharField(_class.reference, _id_asterisk, value);
+
+  static final _id_name = jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"name",
+    r"Ljava/lang/String;",
+  );
+
+  /// from: static public java.lang.String name
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get name => const jni.JStringType().fromRef(jni
+      .Jni.accessors
+      .getStaticField(_class.reference, _id_name, jni.JniCallType.objectType)
+      .object);
+
+  /// from: static public java.lang.String name
+  /// The returned object must be released after use, by calling the [release] method.
+  static set name(jni.JString value) => jni.Jni.env
+      .SetStaticObjectField(_class.reference, _id_name, value.reference);
+
+  static final _id_i = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"i",
+    r"Ljava/lang/Integer;",
+  );
+
+  /// from: public java.lang.Integer i
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JInteger get i => const jni.JIntegerType().fromRef(jni.Jni.accessors
+      .getField(reference, _id_i, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public java.lang.Integer i
+  /// The returned object must be released after use, by calling the [release] method.
+  set i(jni.JInteger value) =>
+      jni.Jni.env.SetObjectField(reference, _id_i, value.reference);
+
+  static final _id_trillion = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"trillion",
+    r"J",
+  );
+
+  /// from: public long trillion
+  int get trillion => jni.Jni.accessors
+      .getField(reference, _id_trillion, jni.JniCallType.longType)
+      .long;
+
+  /// from: public long trillion
+  set trillion(int value) =>
+      jni.Jni.env.SetLongField(reference, _id_trillion, value);
+
+  static final _id_isAchillesDead = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"isAchillesDead",
+    r"Z",
+  );
+
+  /// from: public boolean isAchillesDead
+  bool get isAchillesDead => jni.Jni.accessors
+      .getField(reference, _id_isAchillesDead, jni.JniCallType.booleanType)
+      .boolean;
+
+  /// from: public boolean isAchillesDead
+  set isAchillesDead(bool value) =>
+      jni.Jni.env.SetBooleanField(reference, _id_isAchillesDead, value ? 1 : 0);
+
+  static final _id_bestFighterInGreece = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"bestFighterInGreece",
+    r"Ljava/lang/String;",
+  );
+
+  /// from: public java.lang.String bestFighterInGreece
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString get bestFighterInGreece => const jni.JStringType().fromRef(jni
+      .Jni.accessors
+      .getField(reference, _id_bestFighterInGreece, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public java.lang.String bestFighterInGreece
+  /// The returned object must be released after use, by calling the [release] method.
+  set bestFighterInGreece(jni.JString value) => jni.Jni.env
+      .SetObjectField(reference, _id_bestFighterInGreece, value.reference);
+
+  static final _id_random = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"random",
+    r"Ljava/util/Random;",
+  );
+
+  /// from: public java.util.Random random
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject get random => const jni.JObjectType().fromRef(jni.Jni.accessors
+      .getField(reference, _id_random, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public java.util.Random random
+  /// The returned object must be released after use, by calling the [release] method.
+  set random(jni.JObject value) =>
+      jni.Jni.env.SetObjectField(reference, _id_random, value.reference);
+
+  static final _id_euroSymbol = jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"euroSymbol",
+    r"C",
+  );
+
+  /// from: static public char euroSymbol
+  static int get euroSymbol => jni.Jni.accessors
+      .getStaticField(
+          _class.reference, _id_euroSymbol, jni.JniCallType.charType)
+      .char;
+
+  /// from: static public char euroSymbol
+  static set euroSymbol(int value) =>
+      jni.Jni.env.SetStaticCharField(_class.reference, _id_euroSymbol, value);
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Fields() {
+    return Fields.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $FieldsType extends jni.JObjType<Fields> {
+  const $FieldsType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Fields;";
+
+  @override
+  Fields fromRef(jni.JObjectPtr ref) => Fields.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($FieldsType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($FieldsType) && other is $FieldsType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.simple_package.Fields$Nested
+class Fields_Nested extends jni.JObject {
+  @override
+  late final jni.JObjType<Fields_Nested> $type = type;
+
+  Fields_Nested.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/simple_package/Fields$Nested");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Fields_NestedType();
+  static final _id_hundred = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"hundred",
+    r"J",
+  );
+
+  /// from: public long hundred
+  int get hundred => jni.Jni.accessors
+      .getField(reference, _id_hundred, jni.JniCallType.longType)
+      .long;
+
+  /// from: public long hundred
+  set hundred(int value) =>
+      jni.Jni.env.SetLongField(reference, _id_hundred, value);
+
+  static final _id_BEST_GOD = jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"BEST_GOD",
+    r"Ljava/lang/String;",
+  );
+
+  /// from: static public java.lang.String BEST_GOD
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JString get BEST_GOD =>
+      const jni.JStringType().fromRef(jni.Jni.accessors
+          .getStaticField(
+              _class.reference, _id_BEST_GOD, jni.JniCallType.objectType)
+          .object);
+
+  /// from: static public java.lang.String BEST_GOD
+  /// The returned object must be released after use, by calling the [release] method.
+  static set BEST_GOD(jni.JString value) => jni.Jni.env
+      .SetStaticObjectField(_class.reference, _id_BEST_GOD, value.reference);
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Fields_Nested() {
+    return Fields_Nested.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $Fields_NestedType extends jni.JObjType<Fields_Nested> {
+  const $Fields_NestedType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/simple_package/Fields$Nested;";
+
+  @override
+  Fields_Nested fromRef(jni.JObjectPtr ref) => Fields_Nested.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Fields_NestedType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Fields_NestedType) &&
+        other is $Fields_NestedType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.pkg2.C2
+class C2 extends jni.JObject {
+  @override
+  late final jni.JObjType<C2> $type = type;
+
+  C2.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/pkg2/C2");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $C2Type();
+  static final _id_CONSTANT = jni.Jni.accessors.getStaticFieldIDOf(
+    _class.reference,
+    r"CONSTANT",
+    r"I",
+  );
+
+  /// from: static public int CONSTANT
+  static int get CONSTANT => jni.Jni.accessors
+      .getStaticField(_class.reference, _id_CONSTANT, jni.JniCallType.intType)
+      .integer;
+
+  /// from: static public int CONSTANT
+  static set CONSTANT(int value) =>
+      jni.Jni.env.SetStaticIntField(_class.reference, _id_CONSTANT, value);
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory C2() {
+    return C2.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $C2Type extends jni.JObjType<C2> {
+  const $C2Type();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/pkg2/C2;";
+
+  @override
+  C2 fromRef(jni.JObjectPtr ref) => C2.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($C2Type).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($C2Type) && other is $C2Type;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.pkg2.Example
+class Example1 extends jni.JObject {
+  @override
+  late final jni.JObjType<Example1> $type = type;
+
+  Example1.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/pkg2/Example");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $Example1Type();
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory Example1() {
+    return Example1.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+
+  static final _id_whichExample = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"whichExample", r"()I");
+
+  /// from: public int whichExample()
+  int whichExample() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_whichExample, jni.JniCallType.intType, []).integer;
+  }
+}
+
+final class $Example1Type extends jni.JObjType<Example1> {
+  const $Example1Type();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/pkg2/Example;";
+
+  @override
+  Example1 fromRef(jni.JObjectPtr ref) => Example1.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($Example1Type).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($Example1Type) && other is $Example1Type;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GenericTypeParams
+class GenericTypeParams<$S extends jni.JObject, $K extends jni.JObject>
+    extends jni.JObject {
+  @override
+  late final jni.JObjType<GenericTypeParams<$S, $K>> $type = type(S, K);
+
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$K> K;
+
+  GenericTypeParams.fromRef(
+    this.S,
+    this.K,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/generics/GenericTypeParams");
+
+  /// The type which includes information such as the signature of this class.
+  static $GenericTypeParamsType<$S, $K>
+      type<$S extends jni.JObject, $K extends jni.JObject>(
+    jni.JObjType<$S> S,
+    jni.JObjType<$K> K,
+  ) {
+    return $GenericTypeParamsType(
+      S,
+      K,
+    );
+  }
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GenericTypeParams({
+    required jni.JObjType<$S> S,
+    required jni.JObjType<$K> K,
+  }) {
+    return GenericTypeParams.fromRef(
+        S,
+        K,
+        jni.Jni.accessors
+            .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $GenericTypeParamsType<$S extends jni.JObject,
+    $K extends jni.JObject> extends jni.JObjType<GenericTypeParams<$S, $K>> {
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$K> K;
+
+  const $GenericTypeParamsType(
+    this.S,
+    this.K,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GenericTypeParams;";
+
+  @override
+  GenericTypeParams<$S, $K> fromRef(jni.JObjectPtr ref) =>
+      GenericTypeParams.fromRef(S, K, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GenericTypeParamsType, S, K);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GenericTypeParamsType<$S, $K>) &&
+        other is $GenericTypeParamsType<$S, $K> &&
+        S == other.S &&
+        K == other.K;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent
+class GrandParent<$T extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent<$T>> $type = type(T);
+
+  final jni.JObjType<$T> T;
+
+  GrandParent.fromRef(
+    this.T,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/generics/GrandParent");
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParentType<$T> type<$T extends jni.JObject>(
+    jni.JObjType<$T> T,
+  ) {
+    return $GrandParentType(
+      T,
+    );
+  }
+
+  static final _id_value = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"value",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public T value
+  /// The returned object must be released after use, by calling the [release] method.
+  $T get value => T.fromRef(jni.Jni.accessors
+      .getField(reference, _id_value, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public T value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($T value) =>
+      jni.Jni.env.SetObjectField(reference, _id_value, value.reference);
+
+  static final _id_new0 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"<init>", r"(Ljava/lang/Object;)V");
+
+  /// from: public void <init>(T value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent(
+    $T value, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      value.$type,
+    ]) as jni.JObjType<$T>;
+    return GrandParent.fromRef(
+        T,
+        jni.Jni.accessors.newObjectWithArgs(
+            _class.reference, _id_new0, [value.reference]).object);
+  }
+
+  static final _id_stringParent = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"stringParent",
+      r"()Lcom/github/dart_lang/jnigen/generics/GrandParent$Parent;");
+
+  /// from: public com.github.dart_lang.jnigen.generics.GrandParent<T>.Parent<java.lang.String> stringParent()
+  /// The returned object must be released after use, by calling the [release] method.
+  GrandParent_Parent<jni.JObject, jni.JString> stringParent() {
+    return const $GrandParent_ParentType(jni.JObjectType(), jni.JStringType())
+        .fromRef(jni.Jni.accessors.callMethodWithArgs(reference,
+            _id_stringParent, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_varParent = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"varParent",
+      r"(Ljava/lang/Object;)Lcom/github/dart_lang/jnigen/generics/GrandParent$Parent;");
+
+  /// from: public com.github.dart_lang.jnigen.generics.GrandParent<T>.Parent<S> varParent(S nestedValue)
+  /// The returned object must be released after use, by calling the [release] method.
+  GrandParent_Parent<jni.JObject, $S> varParent<$S extends jni.JObject>(
+    $S nestedValue, {
+    jni.JObjType<$S>? S,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      nestedValue.$type,
+    ]) as jni.JObjType<$S>;
+    return $GrandParent_ParentType(const jni.JObjectType(), S).fromRef(
+        jni.Jni.accessors.callMethodWithArgs(reference, _id_varParent,
+            jni.JniCallType.objectType, [nestedValue.reference]).object);
+  }
+
+  static final _id_stringStaticParent = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"stringStaticParent",
+      r"()Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent;");
+
+  /// from: static public com.github.dart_lang.jnigen.generics.GrandParent.StaticParent<java.lang.String> stringStaticParent()
+  /// The returned object must be released after use, by calling the [release] method.
+  static GrandParent_StaticParent<jni.JString> stringStaticParent() {
+    return const $GrandParent_StaticParentType(jni.JStringType()).fromRef(
+        jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+            _id_stringStaticParent, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_varStaticParent = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"varStaticParent",
+      r"(Ljava/lang/Object;)Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent;");
+
+  /// from: static public com.github.dart_lang.jnigen.generics.GrandParent.StaticParent<S> varStaticParent(S value)
+  /// The returned object must be released after use, by calling the [release] method.
+  static GrandParent_StaticParent<$S> varStaticParent<$S extends jni.JObject>(
+    $S value, {
+    jni.JObjType<$S>? S,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      value.$type,
+    ]) as jni.JObjType<$S>;
+    return $GrandParent_StaticParentType(S).fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_varStaticParent,
+            jni.JniCallType.objectType, [value.reference]).object);
+  }
+
+  static final _id_staticParentWithSameType = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"staticParentWithSameType",
+      r"()Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent;");
+
+  /// from: public com.github.dart_lang.jnigen.generics.GrandParent.StaticParent<T> staticParentWithSameType()
+  /// The returned object must be released after use, by calling the [release] method.
+  GrandParent_StaticParent<$T> staticParentWithSameType() {
+    return $GrandParent_StaticParentType(T).fromRef(jni.Jni.accessors
+        .callMethodWithArgs(reference, _id_staticParentWithSameType,
+            jni.JniCallType.objectType, []).object);
+  }
+}
+
+final class $GrandParentType<$T extends jni.JObject>
+    extends jni.JObjType<GrandParent<$T>> {
+  final jni.JObjType<$T> T;
+
+  const $GrandParentType(
+    this.T,
+  );
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/GrandParent;";
+
+  @override
+  GrandParent<$T> fromRef(jni.JObjectPtr ref) => GrandParent.fromRef(T, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParentType, T);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParentType<$T>) &&
+        other is $GrandParentType<$T> &&
+        T == other.T;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent$Parent
+class GrandParent_Parent<$T extends jni.JObject, $S extends jni.JObject>
+    extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent_Parent<$T, $S>> $type = type(T, S);
+
+  final jni.JObjType<$T> T;
+  final jni.JObjType<$S> S;
+
+  GrandParent_Parent.fromRef(
+    this.T,
+    this.S,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/generics/GrandParent$Parent");
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParent_ParentType<$T, $S>
+      type<$T extends jni.JObject, $S extends jni.JObject>(
+    jni.JObjType<$T> T,
+    jni.JObjType<$S> S,
+  ) {
+    return $GrandParent_ParentType(
+      T,
+      S,
+    );
+  }
+
+  static final _id_parentValue = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"parentValue",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public T parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  $T get parentValue => T.fromRef(jni.Jni.accessors
+      .getField(reference, _id_parentValue, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public T parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  set parentValue($T value) =>
+      jni.Jni.env.SetObjectField(reference, _id_parentValue, value.reference);
+
+  static final _id_value = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"value",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public S value
+  /// The returned object must be released after use, by calling the [release] method.
+  $S get value => S.fromRef(jni.Jni.accessors
+      .getField(reference, _id_value, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public S value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($S value) =>
+      jni.Jni.env.SetObjectField(reference, _id_value, value.reference);
+
+  static final _id_new0 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"<init>",
+      r"(Lcom/github/dart_lang/jnigen/generics/GrandParent;Ljava/lang/Object;)V");
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.generics.GrandParent $parent, S newValue)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent_Parent(
+    GrandParent<$T> $parent,
+    $S newValue, {
+    jni.JObjType<$T>? T,
+    jni.JObjType<$S>? S,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      ($parent.$type as $GrandParentType).T,
+    ]) as jni.JObjType<$T>;
+    S ??= jni.lowestCommonSuperType([
+      newValue.$type,
+    ]) as jni.JObjType<$S>;
+    return GrandParent_Parent.fromRef(
+        T,
+        S,
+        jni.Jni.accessors.newObjectWithArgs(_class.reference, _id_new0,
+            [$parent.reference, newValue.reference]).object);
+  }
+}
+
+final class $GrandParent_ParentType<$T extends jni.JObject,
+    $S extends jni.JObject> extends jni.JObjType<GrandParent_Parent<$T, $S>> {
+  final jni.JObjType<$T> T;
+  final jni.JObjType<$S> S;
+
+  const $GrandParent_ParentType(
+    this.T,
+    this.S,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GrandParent$Parent;";
+
+  @override
+  GrandParent_Parent<$T, $S> fromRef(jni.JObjectPtr ref) =>
+      GrandParent_Parent.fromRef(T, S, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParent_ParentType, T, S);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParent_ParentType<$T, $S>) &&
+        other is $GrandParent_ParentType<$T, $S> &&
+        T == other.T &&
+        S == other.S;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent$Parent$Child
+class GrandParent_Parent_Child<$T extends jni.JObject, $S extends jni.JObject,
+    $U extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent_Parent_Child<$T, $S, $U>> $type =
+      type(T, S, U);
+
+  final jni.JObjType<$T> T;
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$U> U;
+
+  GrandParent_Parent_Child.fromRef(
+    this.T,
+    this.S,
+    this.U,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/generics/GrandParent$Parent$Child");
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParent_Parent_ChildType<$T, $S, $U> type<$T extends jni.JObject,
+      $S extends jni.JObject, $U extends jni.JObject>(
+    jni.JObjType<$T> T,
+    jni.JObjType<$S> S,
+    jni.JObjType<$U> U,
+  ) {
+    return $GrandParent_Parent_ChildType(
+      T,
+      S,
+      U,
+    );
+  }
+
+  static final _id_grandParentValue = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"grandParentValue",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public T grandParentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  $T get grandParentValue => T.fromRef(jni.Jni.accessors
+      .getField(reference, _id_grandParentValue, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public T grandParentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  set grandParentValue($T value) => jni.Jni.env
+      .SetObjectField(reference, _id_grandParentValue, value.reference);
+
+  static final _id_parentValue = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"parentValue",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public S parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  $S get parentValue => S.fromRef(jni.Jni.accessors
+      .getField(reference, _id_parentValue, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public S parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  set parentValue($S value) =>
+      jni.Jni.env.SetObjectField(reference, _id_parentValue, value.reference);
+
+  static final _id_value = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"value",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public U value
+  /// The returned object must be released after use, by calling the [release] method.
+  $U get value => U.fromRef(jni.Jni.accessors
+      .getField(reference, _id_value, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public U value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($U value) =>
+      jni.Jni.env.SetObjectField(reference, _id_value, value.reference);
+
+  static final _id_new0 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"<init>",
+      r"(Lcom/github/dart_lang/jnigen/generics/GrandParent$Parent;Ljava/lang/Object;)V");
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.generics.GrandParent$Parent $parent, U newValue)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent_Parent_Child(
+    GrandParent_Parent<$T, $S> $parent,
+    $U newValue, {
+    jni.JObjType<$T>? T,
+    jni.JObjType<$S>? S,
+    jni.JObjType<$U>? U,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      ($parent.$type as $GrandParent_ParentType).T,
+    ]) as jni.JObjType<$T>;
+    S ??= jni.lowestCommonSuperType([
+      ($parent.$type as $GrandParent_ParentType).S,
+    ]) as jni.JObjType<$S>;
+    U ??= jni.lowestCommonSuperType([
+      newValue.$type,
+    ]) as jni.JObjType<$U>;
+    return GrandParent_Parent_Child.fromRef(
+        T,
+        S,
+        U,
+        jni.Jni.accessors.newObjectWithArgs(_class.reference, _id_new0,
+            [$parent.reference, newValue.reference]).object);
+  }
+}
+
+final class $GrandParent_Parent_ChildType<$T extends jni.JObject,
+        $S extends jni.JObject, $U extends jni.JObject>
+    extends jni.JObjType<GrandParent_Parent_Child<$T, $S, $U>> {
+  final jni.JObjType<$T> T;
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$U> U;
+
+  const $GrandParent_Parent_ChildType(
+    this.T,
+    this.S,
+    this.U,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GrandParent$Parent$Child;";
+
+  @override
+  GrandParent_Parent_Child<$T, $S, $U> fromRef(jni.JObjectPtr ref) =>
+      GrandParent_Parent_Child.fromRef(T, S, U, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParent_Parent_ChildType, T, S, U);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParent_Parent_ChildType<$T, $S, $U>) &&
+        other is $GrandParent_Parent_ChildType<$T, $S, $U> &&
+        T == other.T &&
+        S == other.S &&
+        U == other.U;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent$StaticParent
+class GrandParent_StaticParent<$S extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent_StaticParent<$S>> $type = type(S);
+
+  final jni.JObjType<$S> S;
+
+  GrandParent_StaticParent.fromRef(
+    this.S,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/generics/GrandParent$StaticParent");
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParent_StaticParentType<$S> type<$S extends jni.JObject>(
+    jni.JObjType<$S> S,
+  ) {
+    return $GrandParent_StaticParentType(
+      S,
+    );
+  }
+
+  static final _id_value = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"value",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public S value
+  /// The returned object must be released after use, by calling the [release] method.
+  $S get value => S.fromRef(jni.Jni.accessors
+      .getField(reference, _id_value, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public S value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($S value) =>
+      jni.Jni.env.SetObjectField(reference, _id_value, value.reference);
+
+  static final _id_new0 = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"<init>", r"(Ljava/lang/Object;)V");
+
+  /// from: public void <init>(S value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent_StaticParent(
+    $S value, {
+    jni.JObjType<$S>? S,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      value.$type,
+    ]) as jni.JObjType<$S>;
+    return GrandParent_StaticParent.fromRef(
+        S,
+        jni.Jni.accessors.newObjectWithArgs(
+            _class.reference, _id_new0, [value.reference]).object);
+  }
+}
+
+final class $GrandParent_StaticParentType<$S extends jni.JObject>
+    extends jni.JObjType<GrandParent_StaticParent<$S>> {
+  final jni.JObjType<$S> S;
+
+  const $GrandParent_StaticParentType(
+    this.S,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent;";
+
+  @override
+  GrandParent_StaticParent<$S> fromRef(jni.JObjectPtr ref) =>
+      GrandParent_StaticParent.fromRef(S, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParent_StaticParentType, S);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParent_StaticParentType<$S>) &&
+        other is $GrandParent_StaticParentType<$S> &&
+        S == other.S;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.GrandParent$StaticParent$Child
+class GrandParent_StaticParent_Child<$S extends jni.JObject,
+    $U extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<GrandParent_StaticParent_Child<$S, $U>> $type =
+      type(S, U);
+
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$U> U;
+
+  GrandParent_StaticParent_Child.fromRef(
+    this.S,
+    this.U,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/generics/GrandParent$StaticParent$Child");
+
+  /// The type which includes information such as the signature of this class.
+  static $GrandParent_StaticParent_ChildType<$S, $U>
+      type<$S extends jni.JObject, $U extends jni.JObject>(
+    jni.JObjType<$S> S,
+    jni.JObjType<$U> U,
+  ) {
+    return $GrandParent_StaticParent_ChildType(
+      S,
+      U,
+    );
+  }
+
+  static final _id_parentValue = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"parentValue",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public S parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  $S get parentValue => S.fromRef(jni.Jni.accessors
+      .getField(reference, _id_parentValue, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public S parentValue
+  /// The returned object must be released after use, by calling the [release] method.
+  set parentValue($S value) =>
+      jni.Jni.env.SetObjectField(reference, _id_parentValue, value.reference);
+
+  static final _id_value = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"value",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public U value
+  /// The returned object must be released after use, by calling the [release] method.
+  $U get value => U.fromRef(jni.Jni.accessors
+      .getField(reference, _id_value, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public U value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($U value) =>
+      jni.Jni.env.SetObjectField(reference, _id_value, value.reference);
+
+  static final _id_new0 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"<init>",
+      r"(Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent;Ljava/lang/Object;Ljava/lang/Object;)V");
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.generics.GrandParent$StaticParent $parent, S parentValue, U value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory GrandParent_StaticParent_Child(
+    GrandParent_StaticParent<$S> $parent,
+    $S parentValue,
+    $U value, {
+    jni.JObjType<$S>? S,
+    jni.JObjType<$U>? U,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      parentValue.$type,
+      ($parent.$type as $GrandParent_StaticParentType).S,
+    ]) as jni.JObjType<$S>;
+    U ??= jni.lowestCommonSuperType([
+      value.$type,
+    ]) as jni.JObjType<$U>;
+    return GrandParent_StaticParent_Child.fromRef(
+        S,
+        U,
+        jni.Jni.accessors.newObjectWithArgs(_class.reference, _id_new0, [
+          $parent.reference,
+          parentValue.reference,
+          value.reference
+        ]).object);
+  }
+}
+
+final class $GrandParent_StaticParent_ChildType<$S extends jni.JObject,
+        $U extends jni.JObject>
+    extends jni.JObjType<GrandParent_StaticParent_Child<$S, $U>> {
+  final jni.JObjType<$S> S;
+  final jni.JObjType<$U> U;
+
+  const $GrandParent_StaticParent_ChildType(
+    this.S,
+    this.U,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/GrandParent$StaticParent$Child;";
+
+  @override
+  GrandParent_StaticParent_Child<$S, $U> fromRef(jni.JObjectPtr ref) =>
+      GrandParent_StaticParent_Child.fromRef(S, U, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($GrandParent_StaticParent_ChildType, S, U);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($GrandParent_StaticParent_ChildType<$S, $U>) &&
+        other is $GrandParent_StaticParent_ChildType<$S, $U> &&
+        S == other.S &&
+        U == other.U;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.MyMap
+class MyMap<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObject {
+  @override
+  late final jni.JObjType<MyMap<$K, $V>> $type = type(K, V);
+
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  MyMap.fromRef(
+    this.K,
+    this.V,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/generics/MyMap");
+
+  /// The type which includes information such as the signature of this class.
+  static $MyMapType<$K, $V>
+      type<$K extends jni.JObject, $V extends jni.JObject>(
+    jni.JObjType<$K> K,
+    jni.JObjType<$V> V,
+  ) {
+    return $MyMapType(
+      K,
+      V,
+    );
+  }
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyMap({
+    required jni.JObjType<$K> K,
+    required jni.JObjType<$V> V,
+  }) {
+    return MyMap.fromRef(
+        K,
+        V,
+        jni.Jni.accessors
+            .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+
+  static final _id_get0 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"get", r"(Ljava/lang/Object;)Ljava/lang/Object;");
+
+  /// from: public V get(K key)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V get0(
+    $K key,
+  ) {
+    return V.fromRef(jni.Jni.accessors.callMethodWithArgs(reference, _id_get0,
+        jni.JniCallType.objectType, [key.reference]).object);
+  }
+
+  static final _id_put = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"put", r"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+
+  /// from: public V put(K key, V value)
+  /// The returned object must be released after use, by calling the [release] method.
+  $V put(
+    $K key,
+    $V value,
+  ) {
+    return V.fromRef(jni.Jni.accessors.callMethodWithArgs(reference, _id_put,
+        jni.JniCallType.objectType, [key.reference, value.reference]).object);
+  }
+
+  static final _id_entryStack = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"entryStack",
+      r"()Lcom/github/dart_lang/jnigen/generics/MyStack;");
+
+  /// from: public com.github.dart_lang.jnigen.generics.MyStack<com.github.dart_lang.jnigen.generics.MyMap<K,V>.MyEntry> entryStack()
+  /// The returned object must be released after use, by calling the [release] method.
+  MyStack<MyMap_MyEntry<jni.JObject, jni.JObject>> entryStack() {
+    return const $MyStackType(
+            $MyMap_MyEntryType(jni.JObjectType(), jni.JObjectType()))
+        .fromRef(jni.Jni.accessors.callMethodWithArgs(
+            reference, _id_entryStack, jni.JniCallType.objectType, []).object);
+  }
+}
+
+final class $MyMapType<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObjType<MyMap<$K, $V>> {
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  const $MyMapType(
+    this.K,
+    this.V,
+  );
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/MyMap;";
+
+  @override
+  MyMap<$K, $V> fromRef(jni.JObjectPtr ref) => MyMap.fromRef(K, V, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($MyMapType, K, V);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyMapType<$K, $V>) &&
+        other is $MyMapType<$K, $V> &&
+        K == other.K &&
+        V == other.V;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.MyMap$MyEntry
+class MyMap_MyEntry<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObject {
+  @override
+  late final jni.JObjType<MyMap_MyEntry<$K, $V>> $type = type(K, V);
+
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  MyMap_MyEntry.fromRef(
+    this.K,
+    this.V,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/generics/MyMap$MyEntry");
+
+  /// The type which includes information such as the signature of this class.
+  static $MyMap_MyEntryType<$K, $V>
+      type<$K extends jni.JObject, $V extends jni.JObject>(
+    jni.JObjType<$K> K,
+    jni.JObjType<$V> V,
+  ) {
+    return $MyMap_MyEntryType(
+      K,
+      V,
+    );
+  }
+
+  static final _id_key = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"key",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public K key
+  /// The returned object must be released after use, by calling the [release] method.
+  $K get key => K.fromRef(jni.Jni.accessors
+      .getField(reference, _id_key, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public K key
+  /// The returned object must be released after use, by calling the [release] method.
+  set key($K value) =>
+      jni.Jni.env.SetObjectField(reference, _id_key, value.reference);
+
+  static final _id_value = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"value",
+    r"Ljava/lang/Object;",
+  );
+
+  /// from: public V value
+  /// The returned object must be released after use, by calling the [release] method.
+  $V get value => V.fromRef(jni.Jni.accessors
+      .getField(reference, _id_value, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public V value
+  /// The returned object must be released after use, by calling the [release] method.
+  set value($V value) =>
+      jni.Jni.env.SetObjectField(reference, _id_value, value.reference);
+
+  static final _id_new0 = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"<init>",
+      r"(Lcom/github/dart_lang/jnigen/generics/MyMap;Ljava/lang/Object;Ljava/lang/Object;)V");
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.generics.MyMap $parent, K key, V value)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyMap_MyEntry(
+    MyMap<$K, $V> $parent,
+    $K key,
+    $V value, {
+    jni.JObjType<$K>? K,
+    jni.JObjType<$V>? V,
+  }) {
+    K ??= jni.lowestCommonSuperType([
+      key.$type,
+      ($parent.$type as $MyMapType).K,
+    ]) as jni.JObjType<$K>;
+    V ??= jni.lowestCommonSuperType([
+      value.$type,
+      ($parent.$type as $MyMapType).V,
+    ]) as jni.JObjType<$V>;
+    return MyMap_MyEntry.fromRef(
+        K,
+        V,
+        jni.Jni.accessors.newObjectWithArgs(_class.reference, _id_new0,
+            [$parent.reference, key.reference, value.reference]).object);
+  }
+}
+
+final class $MyMap_MyEntryType<$K extends jni.JObject, $V extends jni.JObject>
+    extends jni.JObjType<MyMap_MyEntry<$K, $V>> {
+  final jni.JObjType<$K> K;
+  final jni.JObjType<$V> V;
+
+  const $MyMap_MyEntryType(
+    this.K,
+    this.V,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/MyMap$MyEntry;";
+
+  @override
+  MyMap_MyEntry<$K, $V> fromRef(jni.JObjectPtr ref) =>
+      MyMap_MyEntry.fromRef(K, V, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($MyMap_MyEntryType, K, V);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyMap_MyEntryType<$K, $V>) &&
+        other is $MyMap_MyEntryType<$K, $V> &&
+        K == other.K &&
+        V == other.V;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.MyStack
+class MyStack<$T extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<MyStack<$T>> $type = type(T);
+
+  final jni.JObjType<$T> T;
+
+  MyStack.fromRef(
+    this.T,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/generics/MyStack");
+
+  /// The type which includes information such as the signature of this class.
+  static $MyStackType<$T> type<$T extends jni.JObject>(
+    jni.JObjType<$T> T,
+  ) {
+    return $MyStackType(
+      T,
+    );
+  }
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyStack({
+    required jni.JObjType<$T> T,
+  }) {
+    return MyStack.fromRef(
+        T,
+        jni.Jni.accessors
+            .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+
+  static final _id_fromArray = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"fromArray",
+      r"([Ljava/lang/Object;)Lcom/github/dart_lang/jnigen/generics/MyStack;");
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<T> fromArray(T[] arr)
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$T> fromArray<$T extends jni.JObject>(
+    jni.JArray<$T> arr, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      ((arr.$type as jni.JArrayType).elementType as jni.JObjType),
+    ]) as jni.JObjType<$T>;
+    return $MyStackType(T).fromRef(jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference,
+        _id_fromArray,
+        jni.JniCallType.objectType,
+        [arr.reference]).object);
+  }
+
+  static final _id_fromArrayOfArrayOfGrandParents = jni.Jni.accessors
+      .getStaticMethodIDOf(_class.reference, r"fromArrayOfArrayOfGrandParents",
+          r"([[Lcom/github/dart_lang/jnigen/generics/GrandParent;)Lcom/github/dart_lang/jnigen/generics/MyStack;");
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<S> fromArrayOfArrayOfGrandParents(com.github.dart_lang.jnigen.generics.GrandParent<S>[][] arr)
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$S> fromArrayOfArrayOfGrandParents<$S extends jni.JObject>(
+    jni.JArray<jni.JArray<GrandParent<$S>>> arr, {
+    jni.JObjType<$S>? S,
+  }) {
+    S ??= jni.lowestCommonSuperType([
+      (((((arr.$type as jni.JArrayType).elementType as jni.JObjType)
+                  as jni.JArrayType)
+              .elementType as jni.JObjType) as $GrandParentType)
+          .T,
+    ]) as jni.JObjType<$S>;
+    return $MyStackType(S).fromRef(jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference,
+        _id_fromArrayOfArrayOfGrandParents,
+        jni.JniCallType.objectType,
+        [arr.reference]).object);
+  }
+
+  static final _id_of = jni.Jni.accessors.getStaticMethodIDOf(_class.reference,
+      r"of", r"()Lcom/github/dart_lang/jnigen/generics/MyStack;");
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<T> of()
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$T> of<$T extends jni.JObject>({
+    required jni.JObjType<$T> T,
+  }) {
+    return $MyStackType(T).fromRef(jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference, _id_of, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_of1 = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"of",
+      r"(Ljava/lang/Object;)Lcom/github/dart_lang/jnigen/generics/MyStack;");
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<T> of(T obj)
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$T> of1<$T extends jni.JObject>(
+    $T obj, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      obj.$type,
+    ]) as jni.JObjType<$T>;
+    return $MyStackType(T).fromRef(jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference,
+        _id_of1,
+        jni.JniCallType.objectType,
+        [obj.reference]).object);
+  }
+
+  static final _id_of2 = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"of",
+      r"(Ljava/lang/Object;Ljava/lang/Object;)Lcom/github/dart_lang/jnigen/generics/MyStack;");
+
+  /// from: static public com.github.dart_lang.jnigen.generics.MyStack<T> of(T obj, T obj2)
+  /// The returned object must be released after use, by calling the [release] method.
+  static MyStack<$T> of2<$T extends jni.JObject>(
+    $T obj,
+    $T obj2, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      obj2.$type,
+      obj.$type,
+    ]) as jni.JObjType<$T>;
+    return $MyStackType(T).fromRef(jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference,
+        _id_of2,
+        jni.JniCallType.objectType,
+        [obj.reference, obj2.reference]).object);
+  }
+
+  static final _id_push = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"push", r"(Ljava/lang/Object;)V");
+
+  /// from: public void push(T item)
+  void push(
+    $T item,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_push,
+        jni.JniCallType.voidType, [item.reference]).check();
+  }
+
+  static final _id_pop = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"pop", r"()Ljava/lang/Object;");
+
+  /// from: public T pop()
+  /// The returned object must be released after use, by calling the [release] method.
+  $T pop() {
+    return T.fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_pop, jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_size =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"size", r"()I");
+
+  /// from: public int size()
+  int size() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_size, jni.JniCallType.intType, []).integer;
+  }
+}
+
+final class $MyStackType<$T extends jni.JObject>
+    extends jni.JObjType<MyStack<$T>> {
+  final jni.JObjType<$T> T;
+
+  const $MyStackType(
+    this.T,
+  );
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/MyStack;";
+
+  @override
+  MyStack<$T> fromRef(jni.JObjectPtr ref) => MyStack.fromRef(T, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($MyStackType, T);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyStackType<$T>) &&
+        other is $MyStackType<$T> &&
+        T == other.T;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.StringKeyedMap
+class StringKeyedMap<$V extends jni.JObject> extends MyMap<jni.JString, $V> {
+  @override
+  late final jni.JObjType<StringKeyedMap<$V>> $type = type(V);
+
+  final jni.JObjType<$V> V;
+
+  StringKeyedMap.fromRef(
+    this.V,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(const jni.JStringType(), V, ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/generics/StringKeyedMap");
+
+  /// The type which includes information such as the signature of this class.
+  static $StringKeyedMapType<$V> type<$V extends jni.JObject>(
+    jni.JObjType<$V> V,
+  ) {
+    return $StringKeyedMapType(
+      V,
+    );
+  }
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory StringKeyedMap({
+    required jni.JObjType<$V> V,
+  }) {
+    return StringKeyedMap.fromRef(
+        V,
+        jni.Jni.accessors
+            .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $StringKeyedMapType<$V extends jni.JObject>
+    extends jni.JObjType<StringKeyedMap<$V>> {
+  final jni.JObjType<$V> V;
+
+  const $StringKeyedMapType(
+    this.V,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/StringKeyedMap;";
+
+  @override
+  StringKeyedMap<$V> fromRef(jni.JObjectPtr ref) =>
+      StringKeyedMap.fromRef(V, ref);
+
+  @override
+  jni.JObjType get superType => $MyMapType(const jni.JStringType(), V);
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => Object.hash($StringKeyedMapType, V);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($StringKeyedMapType<$V>) &&
+        other is $StringKeyedMapType<$V> &&
+        V == other.V;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.StringMap
+class StringMap extends StringKeyedMap<jni.JString> {
+  @override
+  late final jni.JObjType<StringMap> $type = type;
+
+  StringMap.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(const jni.JStringType(), ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/generics/StringMap");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $StringMapType();
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory StringMap() {
+    return StringMap.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $StringMapType extends jni.JObjType<StringMap> {
+  const $StringMapType();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/StringMap;";
+
+  @override
+  StringMap fromRef(jni.JObjectPtr ref) => StringMap.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const $StringKeyedMapType(jni.JStringType());
+
+  @override
+  final superCount = 3;
+
+  @override
+  int get hashCode => ($StringMapType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($StringMapType) && other is $StringMapType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.StringStack
+class StringStack extends MyStack<jni.JString> {
+  @override
+  late final jni.JObjType<StringStack> $type = type;
+
+  StringStack.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(const jni.JStringType(), ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/generics/StringStack");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $StringStackType();
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory StringStack() {
+    return StringStack.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $StringStackType extends jni.JObjType<StringStack> {
+  const $StringStackType();
+
+  @override
+  String get signature => r"Lcom/github/dart_lang/jnigen/generics/StringStack;";
+
+  @override
+  StringStack fromRef(jni.JObjectPtr ref) => StringStack.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const $MyStackType(jni.JStringType());
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => ($StringStackType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($StringStackType) && other is $StringStackType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.generics.StringValuedMap
+class StringValuedMap<$K extends jni.JObject> extends MyMap<$K, jni.JString> {
+  @override
+  late final jni.JObjType<StringValuedMap<$K>> $type = type(K);
+
+  final jni.JObjType<$K> K;
+
+  StringValuedMap.fromRef(
+    this.K,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(K, const jni.JStringType(), ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/generics/StringValuedMap");
+
+  /// The type which includes information such as the signature of this class.
+  static $StringValuedMapType<$K> type<$K extends jni.JObject>(
+    jni.JObjType<$K> K,
+  ) {
+    return $StringValuedMapType(
+      K,
+    );
+  }
+
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory StringValuedMap({
+    required jni.JObjType<$K> K,
+  }) {
+    return StringValuedMap.fromRef(
+        K,
+        jni.Jni.accessors
+            .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $StringValuedMapType<$K extends jni.JObject>
+    extends jni.JObjType<StringValuedMap<$K>> {
+  final jni.JObjType<$K> K;
+
+  const $StringValuedMapType(
+    this.K,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/generics/StringValuedMap;";
+
+  @override
+  StringValuedMap<$K> fromRef(jni.JObjectPtr ref) =>
+      StringValuedMap.fromRef(K, ref);
+
+  @override
+  jni.JObjType get superType => $MyMapType(K, const jni.JStringType());
+
+  @override
+  final superCount = 2;
+
+  @override
+  int get hashCode => Object.hash($StringValuedMapType, K);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($StringValuedMapType<$K>) &&
+        other is $StringValuedMapType<$K> &&
+        K == other.K;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyInterface
+class MyInterface<$T extends jni.JObject> extends jni.JObject {
+  @override
+  late final jni.JObjType<MyInterface<$T>> $type = type(T);
+
+  final jni.JObjType<$T> T;
+
+  MyInterface.fromRef(
+    this.T,
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/interfaces/MyInterface");
+
+  /// The type which includes information such as the signature of this class.
+  static $MyInterfaceType<$T> type<$T extends jni.JObject>(
+    jni.JObjType<$T> T,
+  ) {
+    return $MyInterfaceType(
+      T,
+    );
+  }
+
+  static final _id_voidCallback = jni.Jni.accessors.getMethodIDOf(
+      _class.reference, r"voidCallback", r"(Ljava/lang/String;)V");
+
+  /// from: public abstract void voidCallback(java.lang.String s)
+  void voidCallback(
+    jni.JString s,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(reference, _id_voidCallback,
+        jni.JniCallType.voidType, [s.reference]).check();
+  }
+
+  static final _id_stringCallback = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"stringCallback",
+      r"(Ljava/lang/String;)Ljava/lang/String;");
+
+  /// from: public abstract java.lang.String stringCallback(java.lang.String s)
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JString stringCallback(
+    jni.JString s,
+  ) {
+    return const jni.JStringType().fromRef(jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_stringCallback,
+        jni.JniCallType.objectType,
+        [s.reference]).object);
+  }
+
+  static final _id_varCallback = jni.Jni.accessors.getMethodIDOf(
+      _class.reference,
+      r"varCallback",
+      r"(Ljava/lang/Object;)Ljava/lang/Object;");
+
+  /// from: public abstract T varCallback(T t)
+  /// The returned object must be released after use, by calling the [release] method.
+  $T varCallback(
+    $T t,
+  ) {
+    return T.fromRef(jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_varCallback, jni.JniCallType.objectType, [t.reference]).object);
+  }
+
+  static final _id_manyPrimitives = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"manyPrimitives", r"(IZCD)J");
+
+  /// from: public abstract long manyPrimitives(int a, boolean b, char c, double d)
+  int manyPrimitives(
+    int a,
+    bool b,
+    int c,
+    double d,
+  ) {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference,
+        _id_manyPrimitives,
+        jni.JniCallType.longType,
+        [jni.JValueInt(a), b ? 1 : 0, jni.JValueChar(c), d]).long;
+  }
+
+  /// Maps a specific port to the implemented interface.
+  static final Map<int, $MyInterfaceImpl> _$impls = {};
+  ReceivePort? _$p;
+
+  static jni.JObjectPtr _$invoke(
+    int port,
+    jni.JObjectPtr descriptor,
+    jni.JObjectPtr args,
+  ) {
+    return _$invokeMethod(
+      port,
+      $MethodInvocation.fromAddresses(
+        0,
+        descriptor.address,
+        args.address,
+      ),
+    );
+  }
+
+  static final ffi.Pointer<
+          ffi.NativeFunction<
+              jni.JObjectPtr Function(
+                  ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>>
+      _$invokePointer = ffi.Pointer.fromFunction(_$invoke);
+
+  static ffi.Pointer<ffi.Void> _$invokeMethod(
+    int $p,
+    $MethodInvocation $i,
+  ) {
+    try {
+      final $d = $i.methodDescriptor.toDartString(releaseOriginal: true);
+      final $a = $i.args;
+      if ($d == r"voidCallback(Ljava/lang/String;)V") {
+        _$impls[$p]!.voidCallback(
+          $a[0].castTo(const jni.JStringType(), releaseOriginal: true),
+        );
+        return jni.nullptr;
+      }
+      if ($d == r"stringCallback(Ljava/lang/String;)Ljava/lang/String;") {
+        final $r = _$impls[$p]!.stringCallback(
+          $a[0].castTo(const jni.JStringType(), releaseOriginal: true),
+        );
+        return ($r as jni.JObject).castTo(const jni.JObjectType()).toPointer();
+      }
+      if ($d == r"varCallback(Ljava/lang/Object;)Ljava/lang/Object;") {
+        final $r = _$impls[$p]!.varCallback(
+          $a[0].castTo(_$impls[$p]!.T, releaseOriginal: true),
+        );
+        return ($r as jni.JObject).castTo(const jni.JObjectType()).toPointer();
+      }
+      if ($d == r"manyPrimitives(IZCD)J") {
+        final $r = _$impls[$p]!.manyPrimitives(
+          $a[0]
+              .castTo(const jni.JIntegerType(), releaseOriginal: true)
+              .intValue(releaseOriginal: true),
+          $a[1]
+              .castTo(const jni.JBooleanType(), releaseOriginal: true)
+              .booleanValue(releaseOriginal: true),
+          $a[2]
+              .castTo(const jni.JCharacterType(), releaseOriginal: true)
+              .charValue(releaseOriginal: true),
+          $a[3]
+              .castTo(const jni.JDoubleType(), releaseOriginal: true)
+              .doubleValue(releaseOriginal: true),
+        );
+        return jni.JLong($r).toPointer();
+      }
+    } catch (e) {
+      return ProtectedJniExtensions.newDartException(e.toString());
+    }
+    return jni.nullptr;
+  }
+
+  factory MyInterface.implement(
+    $MyInterfaceImpl<$T> $impl,
+  ) {
+    final $p = ReceivePort();
+    final $x = MyInterface.fromRef(
+      $impl.T,
+      ProtectedJniExtensions.newPortProxy(
+        r"com.github.dart_lang.jnigen.interfaces.MyInterface",
+        $p,
+        _$invokePointer,
+      ),
+    ).._$p = $p;
+    final $a = $p.sendPort.nativePort;
+    _$impls[$a] = $impl;
+    $p.listen(($m) {
+      if ($m == null) {
+        _$impls.remove($p.sendPort.nativePort);
+        $p.close();
+        return;
+      }
+      final $i = $MethodInvocation.fromMessage($m as List<dynamic>);
+      final $r = _$invokeMethod($p.sendPort.nativePort, $i);
+      ProtectedJniExtensions.returnResult($i.result, $r);
+    });
+    return $x;
+  }
+  static Map<int, $MyInterfaceImpl> get $impls => _$impls;
+}
+
+abstract interface class $MyInterfaceImpl<$T extends jni.JObject> {
+  factory $MyInterfaceImpl({
+    required jni.JObjType<$T> T,
+    required void Function(jni.JString s) voidCallback,
+    required jni.JString Function(jni.JString s) stringCallback,
+    required $T Function($T t) varCallback,
+    required int Function(int a, bool b, int c, double d) manyPrimitives,
+  }) = _$MyInterfaceImpl;
+
+  jni.JObjType<$T> get T;
+
+  void voidCallback(jni.JString s);
+  jni.JString stringCallback(jni.JString s);
+  $T varCallback($T t);
+  int manyPrimitives(int a, bool b, int c, double d);
+}
+
+class _$MyInterfaceImpl<$T extends jni.JObject>
+    implements $MyInterfaceImpl<$T> {
+  _$MyInterfaceImpl({
+    required this.T,
+    required void Function(jni.JString s) voidCallback,
+    required jni.JString Function(jni.JString s) stringCallback,
+    required $T Function($T t) varCallback,
+    required int Function(int a, bool b, int c, double d) manyPrimitives,
+  })  : _voidCallback = voidCallback,
+        _stringCallback = stringCallback,
+        _varCallback = varCallback,
+        _manyPrimitives = manyPrimitives;
+
+  @override
+  final jni.JObjType<$T> T;
+
+  final void Function(jni.JString s) _voidCallback;
+  final jni.JString Function(jni.JString s) _stringCallback;
+  final $T Function($T t) _varCallback;
+  final int Function(int a, bool b, int c, double d) _manyPrimitives;
+
+  void voidCallback(jni.JString s) {
+    return _voidCallback(s);
+  }
+
+  jni.JString stringCallback(jni.JString s) {
+    return _stringCallback(s);
+  }
+
+  $T varCallback($T t) {
+    return _varCallback(t);
+  }
+
+  int manyPrimitives(int a, bool b, int c, double d) {
+    return _manyPrimitives(a, b, c, d);
+  }
+}
+
+final class $MyInterfaceType<$T extends jni.JObject>
+    extends jni.JObjType<MyInterface<$T>> {
+  final jni.JObjType<$T> T;
+
+  const $MyInterfaceType(
+    this.T,
+  );
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/interfaces/MyInterface;";
+
+  @override
+  MyInterface<$T> fromRef(jni.JObjectPtr ref) => MyInterface.fromRef(T, ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => Object.hash($MyInterfaceType, T);
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyInterfaceType<$T>) &&
+        other is $MyInterfaceType<$T> &&
+        T == other.T;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyInterfaceConsumer
+class MyInterfaceConsumer extends jni.JObject {
+  @override
+  late final jni.JObjType<MyInterfaceConsumer> $type = type;
+
+  MyInterfaceConsumer.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $MyInterfaceConsumerType();
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyInterfaceConsumer() {
+    return MyInterfaceConsumer.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+
+  static final _id_consumeOnAnotherThread = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"consumeOnAnotherThread",
+      r"(Lcom/github/dart_lang/jnigen/interfaces/MyInterface;Ljava/lang/String;IZCDLjava/lang/Object;)V");
+
+  /// from: static public void consumeOnAnotherThread(com.github.dart_lang.jnigen.interfaces.MyInterface<T> myInterface, java.lang.String s, int a, boolean b, char c, double d, T t)
+  static void consumeOnAnotherThread<$T extends jni.JObject>(
+    MyInterface<$T> myInterface,
+    jni.JString s,
+    int a,
+    bool b,
+    int c,
+    double d,
+    $T t, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      t.$type,
+      (myInterface.$type as $MyInterfaceType).T,
+    ]) as jni.JObjType<$T>;
+    return jni.Jni.accessors.callStaticMethodWithArgs(_class.reference,
+        _id_consumeOnAnotherThread, jni.JniCallType.voidType, [
+      myInterface.reference,
+      s.reference,
+      jni.JValueInt(a),
+      b ? 1 : 0,
+      jni.JValueChar(c),
+      d,
+      t.reference
+    ]).check();
+  }
+
+  static final _id_consumeOnSameThread = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"consumeOnSameThread",
+      r"(Lcom/github/dart_lang/jnigen/interfaces/MyInterface;Ljava/lang/String;IZCDLjava/lang/Object;)V");
+
+  /// from: static public void consumeOnSameThread(com.github.dart_lang.jnigen.interfaces.MyInterface<T> myInterface, java.lang.String s, int a, boolean b, char c, double d, T t)
+  static void consumeOnSameThread<$T extends jni.JObject>(
+    MyInterface<$T> myInterface,
+    jni.JString s,
+    int a,
+    bool b,
+    int c,
+    double d,
+    $T t, {
+    jni.JObjType<$T>? T,
+  }) {
+    T ??= jni.lowestCommonSuperType([
+      t.$type,
+      (myInterface.$type as $MyInterfaceType).T,
+    ]) as jni.JObjType<$T>;
+    return jni.Jni.accessors.callStaticMethodWithArgs(
+        _class.reference, _id_consumeOnSameThread, jni.JniCallType.voidType, [
+      myInterface.reference,
+      s.reference,
+      jni.JValueInt(a),
+      b ? 1 : 0,
+      jni.JValueChar(c),
+      d,
+      t.reference
+    ]).check();
+  }
+}
+
+final class $MyInterfaceConsumerType extends jni.JObjType<MyInterfaceConsumer> {
+  const $MyInterfaceConsumerType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer;";
+
+  @override
+  MyInterfaceConsumer fromRef(jni.JObjectPtr ref) =>
+      MyInterfaceConsumer.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($MyInterfaceConsumerType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyInterfaceConsumerType) &&
+        other is $MyInterfaceConsumerType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyRunnable
+class MyRunnable extends jni.JObject {
+  @override
+  late final jni.JObjType<MyRunnable> $type = type;
+
+  MyRunnable.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class =
+      jni.Jni.findJClass(r"com/github/dart_lang/jnigen/interfaces/MyRunnable");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $MyRunnableType();
+  static final _id_run =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"run", r"()V");
+
+  /// from: public abstract void run()
+  void run() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_run, jni.JniCallType.voidType, []).check();
+  }
+
+  /// Maps a specific port to the implemented interface.
+  static final Map<int, $MyRunnableImpl> _$impls = {};
+  ReceivePort? _$p;
+
+  static jni.JObjectPtr _$invoke(
+    int port,
+    jni.JObjectPtr descriptor,
+    jni.JObjectPtr args,
+  ) {
+    return _$invokeMethod(
+      port,
+      $MethodInvocation.fromAddresses(
+        0,
+        descriptor.address,
+        args.address,
+      ),
+    );
+  }
+
+  static final ffi.Pointer<
+          ffi.NativeFunction<
+              jni.JObjectPtr Function(
+                  ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>>
+      _$invokePointer = ffi.Pointer.fromFunction(_$invoke);
+
+  static ffi.Pointer<ffi.Void> _$invokeMethod(
+    int $p,
+    $MethodInvocation $i,
+  ) {
+    try {
+      final $d = $i.methodDescriptor.toDartString(releaseOriginal: true);
+      final $a = $i.args;
+      if ($d == r"run()V") {
+        _$impls[$p]!.run();
+        return jni.nullptr;
+      }
+    } catch (e) {
+      return ProtectedJniExtensions.newDartException(e.toString());
+    }
+    return jni.nullptr;
+  }
+
+  factory MyRunnable.implement(
+    $MyRunnableImpl $impl,
+  ) {
+    final $p = ReceivePort();
+    final $x = MyRunnable.fromRef(
+      ProtectedJniExtensions.newPortProxy(
+        r"com.github.dart_lang.jnigen.interfaces.MyRunnable",
+        $p,
+        _$invokePointer,
+      ),
+    ).._$p = $p;
+    final $a = $p.sendPort.nativePort;
+    _$impls[$a] = $impl;
+    $p.listen(($m) {
+      if ($m == null) {
+        _$impls.remove($p.sendPort.nativePort);
+        $p.close();
+        return;
+      }
+      final $i = $MethodInvocation.fromMessage($m as List<dynamic>);
+      final $r = _$invokeMethod($p.sendPort.nativePort, $i);
+      ProtectedJniExtensions.returnResult($i.result, $r);
+    });
+    return $x;
+  }
+}
+
+abstract interface class $MyRunnableImpl {
+  factory $MyRunnableImpl({
+    required void Function() run,
+  }) = _$MyRunnableImpl;
+
+  void run();
+}
+
+class _$MyRunnableImpl implements $MyRunnableImpl {
+  _$MyRunnableImpl({
+    required void Function() run,
+  }) : _run = run;
+
+  final void Function() _run;
+
+  void run() {
+    return _run();
+  }
+}
+
+final class $MyRunnableType extends jni.JObjType<MyRunnable> {
+  const $MyRunnableType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/interfaces/MyRunnable;";
+
+  @override
+  MyRunnable fromRef(jni.JObjectPtr ref) => MyRunnable.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($MyRunnableType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyRunnableType) && other is $MyRunnableType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.interfaces.MyRunnableRunner
+class MyRunnableRunner extends jni.JObject {
+  @override
+  late final jni.JObjType<MyRunnableRunner> $type = type;
+
+  MyRunnableRunner.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/interfaces/MyRunnableRunner");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $MyRunnableRunnerType();
+  static final _id_error = jni.Jni.accessors.getFieldIDOf(
+    _class.reference,
+    r"error",
+    r"Ljava/lang/Throwable;",
+  );
+
+  /// from: public java.lang.Throwable error
+  /// The returned object must be released after use, by calling the [release] method.
+  jni.JObject get error => const jni.JObjectType().fromRef(jni.Jni.accessors
+      .getField(reference, _id_error, jni.JniCallType.objectType)
+      .object);
+
+  /// from: public java.lang.Throwable error
+  /// The returned object must be released after use, by calling the [release] method.
+  set error(jni.JObject value) =>
+      jni.Jni.env.SetObjectField(reference, _id_error, value.reference);
+
+  static final _id_new0 = jni.Jni.accessors.getMethodIDOf(_class.reference,
+      r"<init>", r"(Lcom/github/dart_lang/jnigen/interfaces/MyRunnable;)V");
+
+  /// from: public void <init>(com.github.dart_lang.jnigen.interfaces.MyRunnable runnable)
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyRunnableRunner(
+    MyRunnable runnable,
+  ) {
+    return MyRunnableRunner.fromRef(jni.Jni.accessors.newObjectWithArgs(
+        _class.reference, _id_new0, [runnable.reference]).object);
+  }
+
+  static final _id_runOnSameThread = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"runOnSameThread", r"()V");
+
+  /// from: public void runOnSameThread()
+  void runOnSameThread() {
+    return jni.Jni.accessors.callMethodWithArgs(
+        reference, _id_runOnSameThread, jni.JniCallType.voidType, []).check();
+  }
+
+  static final _id_runOnAnotherThread = jni.Jni.accessors
+      .getMethodIDOf(_class.reference, r"runOnAnotherThread", r"()V");
+
+  /// from: public void runOnAnotherThread()
+  void runOnAnotherThread() {
+    return jni.Jni.accessors.callMethodWithArgs(reference,
+        _id_runOnAnotherThread, jni.JniCallType.voidType, []).check();
+  }
+}
+
+final class $MyRunnableRunnerType extends jni.JObjType<MyRunnableRunner> {
+  const $MyRunnableRunnerType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/interfaces/MyRunnableRunner;";
+
+  @override
+  MyRunnableRunner fromRef(jni.JObjectPtr ref) => MyRunnableRunner.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($MyRunnableRunnerType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyRunnableRunnerType) &&
+        other is $MyRunnableRunnerType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.annotations.JsonSerializable$Case
+class JsonSerializable_Case extends jni.JObject {
+  @override
+  late final jni.JObjType<JsonSerializable_Case> $type = type;
+
+  JsonSerializable_Case.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/annotations/JsonSerializable$Case");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $JsonSerializable_CaseType();
+  static final _id_values = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"values",
+      r"()[Lcom/github/dart_lang/jnigen/annotations/JsonSerializable$Case;");
+
+  /// from: static public com.github.dart_lang.jnigen.annotations.JsonSerializable.Case[] values()
+  /// The returned object must be released after use, by calling the [release] method.
+  static jni.JArray<JsonSerializable_Case> values() {
+    return const jni.JArrayType($JsonSerializable_CaseType()).fromRef(
+        jni.Jni.accessors.callStaticMethodWithArgs(_class.reference, _id_values,
+            jni.JniCallType.objectType, []).object);
+  }
+
+  static final _id_valueOf = jni.Jni.accessors.getStaticMethodIDOf(
+      _class.reference,
+      r"valueOf",
+      r"(Ljava/lang/String;)Lcom/github/dart_lang/jnigen/annotations/JsonSerializable$Case;");
+
+  /// from: static public com.github.dart_lang.jnigen.annotations.JsonSerializable.Case valueOf(java.lang.String name)
+  /// The returned object must be released after use, by calling the [release] method.
+  static JsonSerializable_Case valueOf(
+    jni.JString name,
+  ) {
+    return const $JsonSerializable_CaseType().fromRef(jni.Jni.accessors
+        .callStaticMethodWithArgs(_class.reference, _id_valueOf,
+            jni.JniCallType.objectType, [name.reference]).object);
+  }
+}
+
+final class $JsonSerializable_CaseType
+    extends jni.JObjType<JsonSerializable_Case> {
+  const $JsonSerializable_CaseType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/annotations/JsonSerializable$Case;";
+
+  @override
+  JsonSerializable_Case fromRef(jni.JObjectPtr ref) =>
+      JsonSerializable_Case.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($JsonSerializable_CaseType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($JsonSerializable_CaseType) &&
+        other is $JsonSerializable_CaseType;
+  }
+}
+
+/// from: com.github.dart_lang.jnigen.annotations.MyDataClass
+class MyDataClass extends jni.JObject {
+  @override
+  late final jni.JObjType<MyDataClass> $type = type;
+
+  MyDataClass.fromRef(
+    jni.JObjectPtr ref,
+  ) : super.fromRef(ref);
+
+  static final _class = jni.Jni.findJClass(
+      r"com/github/dart_lang/jnigen/annotations/MyDataClass");
+
+  /// The type which includes information such as the signature of this class.
+  static const type = $MyDataClassType();
+  static final _id_new0 =
+      jni.Jni.accessors.getMethodIDOf(_class.reference, r"<init>", r"()V");
+
+  /// from: public void <init>()
+  /// The returned object must be released after use, by calling the [release] method.
+  factory MyDataClass() {
+    return MyDataClass.fromRef(jni.Jni.accessors
+        .newObjectWithArgs(_class.reference, _id_new0, []).object);
+  }
+}
+
+final class $MyDataClassType extends jni.JObjType<MyDataClass> {
+  const $MyDataClassType();
+
+  @override
+  String get signature =>
+      r"Lcom/github/dart_lang/jnigen/annotations/MyDataClass;";
+
+  @override
+  MyDataClass fromRef(jni.JObjectPtr ref) => MyDataClass.fromRef(ref);
+
+  @override
+  jni.JObjType get superType => const jni.JObjectType();
+
+  @override
+  final superCount = 1;
+
+  @override
+  int get hashCode => ($MyDataClassType).hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    return other.runtimeType == ($MyDataClassType) && other is $MyDataClassType;
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/generate.dart b/pkgs/jnigen/test/simple_package_test/generate.dart
new file mode 100644
index 0000000..9f59888
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/generate.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jnigen/src/config/experiments.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart';
+import 'package:jnigen/jnigen.dart';
+
+const testName = 'simple_package_test';
+final testRoot = join('test', testName);
+final javaPath = join(testRoot, 'java');
+
+const preamble = '''
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+''';
+
+var javaPrefix = join('com', 'github', 'dart_lang', 'jnigen');
+
+var javaFiles = [
+  join(javaPrefix, 'simple_package', 'Example.java'),
+  join(javaPrefix, 'pkg2', 'C2.java'),
+  join(javaPrefix, 'pkg2', 'Example.java'),
+  join(javaPrefix, 'generics', 'MyStack.java'),
+  join(javaPrefix, 'generics', 'MyMap.java'),
+  join(javaPrefix, 'generics', 'GrandParent.java'),
+  join(javaPrefix, 'generics', 'StringStack.java'),
+  join(javaPrefix, 'generics', 'StringValuedMap.java'),
+  join(javaPrefix, 'generics', 'StringKeyedMap.java'),
+  join(javaPrefix, 'interfaces', 'MyRunnable.java'),
+  join(javaPrefix, 'interfaces', 'MyRunnableRunner.java'),
+  join(javaPrefix, 'interfaces', 'MyInterface.java'),
+  join(javaPrefix, 'interfaces', 'MyInterfaceConsumer.java'),
+  join(javaPrefix, 'annotations', 'JsonSerializable.java'),
+  join(javaPrefix, 'annotations', 'MyDataClass.java'),
+];
+
+void compileJavaSources(String workingDir, List<String> files) async {
+  final procRes = Process.runSync('javac', files, workingDirectory: workingDir);
+  if (procRes.exitCode != 0) {
+    throw "javac exited with ${procRes.exitCode}\n"
+        "${procRes.stderr}";
+  }
+}
+
+Config getConfig([BindingsType bindingsType = BindingsType.cBased]) {
+  compileJavaSources(javaPath, javaFiles);
+  final typeDir = bindingsType.getConfigString();
+  final cWrapperDir = Uri.directory(join(testRoot, typeDir, "c_bindings"));
+  final dartWrappersRoot = Uri.directory(
+    join(testRoot, typeDir, "dart_bindings"),
+  );
+  final config = Config(
+    sourcePath: [Uri.directory(javaPath)],
+    classPath: [Uri.directory(javaPath)],
+    classes: [
+      'com.github.dart_lang.jnigen.simple_package',
+      'com.github.dart_lang.jnigen.pkg2',
+      'com.github.dart_lang.jnigen.generics',
+      'com.github.dart_lang.jnigen.interfaces',
+      'com.github.dart_lang.jnigen.annotations',
+    ],
+    logLevel: Level.INFO,
+    customClassBody: {
+      'com.github.dart_lang.jnigen.interfaces.MyInterface': r'''
+  static Map<int, $MyInterfaceImpl> get $impls => _$impls;
+'''
+    },
+    outputConfig: OutputConfig(
+      bindingsType: bindingsType,
+      cConfig: CCodeOutputConfig(
+        path: cWrapperDir,
+        libraryName: 'simple_package',
+      ),
+      dartConfig: DartCodeOutputConfig(
+        path: dartWrappersRoot.resolve('simple_package.dart'),
+        structure: OutputStructure.singleFile,
+      ),
+    ),
+    preamble: preamble,
+    experiments: {Experiment.interfaceImplementation},
+  );
+  return config;
+}
+
+void main() async {
+  await generateJniBindings(getConfig(BindingsType.cBased));
+  await generateJniBindings(getConfig(BindingsType.dartOnly));
+}
diff --git a/pkgs/jnigen/test/simple_package_test/generated_files_test.dart b/pkgs/jnigen/test/simple_package_test/generated_files_test.dart
new file mode 100644
index 0000000..0f595d2
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/generated_files_test.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:jnigen/jnigen.dart';
+import 'package:test/test.dart';
+
+import 'generate.dart';
+import '../test_util/test_util.dart';
+
+void main() async {
+  await checkLocallyBuiltDependencies();
+
+  generateAndCompareBothModes(
+    'Generate and compare bindings for simple_package java files',
+    getConfig(BindingsType.cBased),
+    getConfig(BindingsType.dartOnly),
+  );
+
+  test("Generate and analyze bindings for simple_package - pure dart",
+      () async {
+    await generateAndAnalyzeBindings(
+      getConfig(BindingsType.dartOnly),
+    );
+  }); // test if generated file == expected file
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/annotations/JsonSerializable.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/annotations/JsonSerializable.java
new file mode 100644
index 0000000..af7071e
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/annotations/JsonSerializable.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.annotations;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface JsonSerializable {
+  public static enum Case {
+    SNAKE_CASE,
+    KEBAB_CASE,
+    CAMEL_CASE
+  }
+
+  public Case value();
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/annotations/MyDataClass.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/annotations/MyDataClass.java
new file mode 100644
index 0000000..ff71b69
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/annotations/MyDataClass.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.annotations;
+
+@JsonSerializable(JsonSerializable.Case.CAMEL_CASE)
+public class MyDataClass {}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/GenericTypeParams.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/GenericTypeParams.java
new file mode 100644
index 0000000..2a41875
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/GenericTypeParams.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.generics;
+
+public class GenericTypeParams<S extends CharSequence, K extends GenericTypeParams<S, K>> {}
+
+// TODO: Add more cases of generic parameters
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/GrandParent.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/GrandParent.java
new file mode 100644
index 0000000..b703403
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/GrandParent.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.generics;
+
+public class GrandParent<T> {
+  public T value;
+
+  public GrandParent(T value) {
+    this.value = value;
+  }
+
+  public Parent<String> stringParent() {
+    return new Parent<>("Hello");
+  }
+
+  public <S> Parent<S> varParent(S nestedValue) {
+    return new Parent<>(nestedValue);
+  }
+
+  public static StaticParent<String> stringStaticParent() {
+    return new StaticParent<>("Hello");
+  }
+
+  public static <S> StaticParent<S> varStaticParent(S value) {
+    return new StaticParent<>(value);
+  }
+
+  public StaticParent<T> staticParentWithSameType() {
+    return new StaticParent<>(value);
+  }
+
+  // This doesn't have access to T
+  public static class StaticParent<S> {
+    public S value;
+
+    public StaticParent(S value) {
+      this.value = value;
+    }
+
+    public class Child<U> {
+      public S parentValue;
+      public U value;
+
+      public Child(S parentValue, U value) {
+        this.parentValue = parentValue;
+        this.value = value;
+      }
+    }
+  }
+
+  public class Parent<S> {
+    public T parentValue;
+    public S value;
+
+    public Parent(S newValue) {
+      parentValue = GrandParent.this.value;
+      value = newValue;
+    }
+
+    public class Child<U> {
+      public T grandParentValue;
+      public S parentValue;
+      public U value;
+
+      public Child(U newValue) {
+        this.grandParentValue = GrandParent.this.value;
+        this.parentValue = Parent.this.value;
+        this.value = newValue;
+      }
+    }
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/MyMap.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/MyMap.java
new file mode 100644
index 0000000..21392ae
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/MyMap.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.generics;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MyMap<K, V> {
+  public class MyEntry {
+    public K key;
+    public V value;
+
+    public MyEntry(K key, V value) {
+      this.key = key;
+      this.value = value;
+    }
+  }
+
+  private Map<K, V> map;
+
+  public MyMap() {
+    map = new HashMap<>();
+  }
+
+  public V get(K key) {
+    return map.get(key);
+  }
+
+  public V put(K key, V value) {
+    return map.put(key, value);
+  }
+
+  public MyStack<MyEntry> entryStack() {
+    var stack = new MyStack<MyEntry>();
+    map.entrySet().stream()
+        .map(e -> new MyEntry(e.getKey(), e.getValue()))
+        .forEach(e -> stack.push(e));
+    return stack;
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/MyStack.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/MyStack.java
new file mode 100644
index 0000000..c4e2d06
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/MyStack.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.generics;
+
+import java.util.Stack;
+
+public class MyStack<T> {
+  private Stack<T> stack;
+
+  public MyStack() {
+    stack = new Stack<>();
+  }
+
+  public static <T> MyStack<T> fromArray(T[] arr) {
+    var stack = new MyStack<T>();
+    for (int i = 0; i < arr.length; ++i) {
+      stack.push(arr[i]);
+    }
+    return stack;
+  }
+
+  public static <S> MyStack<S> fromArrayOfArrayOfGrandParents(GrandParent<S>[][] arr) {
+    // just for testing
+    var stack = new MyStack<S>();
+    stack.push(arr[0][0].value);
+    return stack;
+  }
+
+  public static <T> MyStack<T> of() {
+    return new MyStack<T>();
+  }
+
+  public static <T> MyStack<T> of(T obj) {
+    var stack = new MyStack<T>();
+    stack.push(obj);
+    return stack;
+  }
+
+  public static <T> MyStack<T> of(T obj, T obj2) {
+    var stack = new MyStack<T>();
+    stack.push(obj);
+    stack.push(obj2);
+    return stack;
+  }
+
+  public void push(T item) {
+    stack.push(item);
+  }
+
+  public T pop() {
+    return stack.pop();
+  }
+
+  public int size() {
+    return stack.size();
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringKeyedMap.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringKeyedMap.java
new file mode 100644
index 0000000..1ca40aa
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringKeyedMap.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.generics;
+
+public class StringKeyedMap<V> extends MyMap<String, V> {}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringMap.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringMap.java
new file mode 100644
index 0000000..05f9365
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringMap.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.generics;
+
+public class StringMap extends StringKeyedMap<String> {}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringStack.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringStack.java
new file mode 100644
index 0000000..87fd041
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringStack.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.generics;
+
+public class StringStack extends MyStack<String> {}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringValuedMap.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringValuedMap.java
new file mode 100644
index 0000000..c005ae0
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/generics/StringValuedMap.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.generics;
+
+public class StringValuedMap<K> extends MyMap<K, String> {}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/inheritance/BaseClass.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/inheritance/BaseClass.java
new file mode 100644
index 0000000..f9157e4
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/inheritance/BaseClass.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.inheritance;
+
+public class BaseClass<T extends CharSequence> {}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/inheritance/GenericDerivedClass.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/inheritance/GenericDerivedClass.java
new file mode 100644
index 0000000..5829657
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/inheritance/GenericDerivedClass.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.inheritance;
+
+public class GenericDerivedClass<T extends CharSequence> extends BaseClass<T> {}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/inheritance/SpecificDerivedClass.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/inheritance/SpecificDerivedClass.java
new file mode 100644
index 0000000..6c33e83
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/inheritance/SpecificDerivedClass.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.inheritance;
+
+public class SpecificDerivedClass extends BaseClass<String> {}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterface.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterface.java
new file mode 100644
index 0000000..97b243e
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterface.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.interfaces;
+
+public interface MyInterface<T> {
+  void voidCallback(String s);
+
+  String stringCallback(String s);
+
+  T varCallback(T t);
+
+  long manyPrimitives(int a, boolean b, char c, double d);
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer.java
new file mode 100644
index 0000000..b4eeb2f
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyInterfaceConsumer.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.interfaces;
+
+public class MyInterfaceConsumer {
+  public static <T> void consumeOnAnotherThread(
+      MyInterface<T> myInterface, String s, int a, boolean b, char c, double d, T t) {
+    var thread = new Thread(() -> consumeOnSameThread(myInterface, s, a, b, c, d, t));
+    thread.start();
+  }
+
+  public static <T> void consumeOnSameThread(
+      MyInterface<T> myInterface, String s, int a, boolean b, char c, double d, T t) {
+    String result = myInterface.stringCallback(s);
+    myInterface.voidCallback(result);
+    myInterface.manyPrimitives(a, b, c, d);
+    myInterface.varCallback(t);
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyRunnable.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyRunnable.java
new file mode 100644
index 0000000..4a39f81
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyRunnable.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.interfaces;
+
+public interface MyRunnable {
+  void run();
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyRunnableRunner.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyRunnableRunner.java
new file mode 100644
index 0000000..2903f37
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/interfaces/MyRunnableRunner.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.interfaces;
+
+public class MyRunnableRunner {
+  final MyRunnable runnable;
+
+  public Throwable error;
+
+  public MyRunnableRunner(MyRunnable runnable) {
+    this.runnable = runnable;
+  }
+
+  public void runOnSameThread() {
+    try {
+      runnable.run();
+    } catch (Throwable e) {
+      error = e;
+    }
+  }
+
+  public void runOnAnotherThread() {
+    var thread = new Thread(this::runOnSameThread);
+    thread.start();
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/pkg2/C2.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/pkg2/C2.java
new file mode 100644
index 0000000..fde1e6d
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/pkg2/C2.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.pkg2;
+
+public class C2 {
+  public static int CONSTANT = 12;
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/pkg2/Example.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/pkg2/Example.java
new file mode 100644
index 0000000..6cce455
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/pkg2/Example.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This class is named the same as `Example` in other package to verify the renaming works.
+
+package com.github.dart_lang.jnigen.pkg2;
+
+public class Example {
+  public int whichExample() {
+    return 1;
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Color.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Color.java
new file mode 100644
index 0000000..efb96c6
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Color.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.simple_package;
+
+public enum Color {
+  RED,
+  BLUE,
+  BLACK,
+  GREEN,
+  YELLOW,
+  LIME
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Example.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Example.java
new file mode 100644
index 0000000..b96156e
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Example.java
@@ -0,0 +1,238 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.simple_package;
+
+import java.util.*;
+
+public class Example {
+  // static fields - primitive & string
+  public static final int ON = 1;
+  public static final int OFF = 0;
+  public static final double PI = 3.14159;
+  public static final char SEMICOLON = ';';
+  public static final String SEMICOLON_STRING = ";";
+
+  public static final Random unusedRandom = new Random();
+
+  private static int amount = 500;
+  private static double pi = 3.14159;
+  private static char asterisk = '*';
+  private static String name = "Ragnar Lothbrok";
+
+  // Static fields - object
+  private static Nested nested = new Nested(true);
+
+  // static methods
+  public static int getAmount() {
+    return amount;
+  }
+
+  public static double getPi() {
+    return pi;
+  }
+
+  public static char getAsterisk() {
+    return asterisk;
+  }
+
+  public static String getName() {
+    return name;
+  }
+
+  public static Nested getNestedInstance() {
+    return nested;
+  }
+
+  // void functions with 1 parameter
+  public static void setAmount(int newAmount) {
+    amount = newAmount;
+  }
+
+  public static void setName(String newName) {
+    name = newName;
+  }
+
+  public static void setNestedInstance(Nested newNested) {
+    nested = newNested;
+  }
+
+  // void functions with many parameters
+  public static int max4(int a, int b, int c, int d) {
+    return Integer.max(Integer.max(a, b), Integer.max(c, d));
+  }
+
+  public static int max8(int a, int b, int c, int d, int e, int f, int g, int h) {
+    return Integer.max(max4(a, b, c, d), max4(e, f, g, h));
+  }
+
+  // Instance fields - primitive and string
+  private int number = 0;
+  private boolean isUp = false;
+  private String codename = "achilles";
+
+  // Instance fields - object
+  private Random random = new Random();
+
+  // Instance methods
+  public int getNumber() {
+    return number;
+  }
+
+  public void setNumber(int number) {
+    this.number = number;
+  }
+
+  public boolean getIsUp() {
+    return isUp;
+  }
+
+  public void setUp(boolean isUp) {
+    this.isUp = isUp;
+  }
+
+  public String getCodename() {
+    return codename;
+  }
+
+  public void setCodename(String codename) {
+    this.codename = codename;
+  }
+
+  public Random getRandom() {
+    return random;
+  }
+
+  public void setRandom(Random random) {
+    this.random = random;
+  }
+
+  public long getRandomLong() {
+    return random.nextLong();
+  }
+
+  public long add4Longs(long a, long b, long c, long d) {
+    return a + b + c + d;
+  }
+
+  public long add8Longs(long a, long b, long c, long d, long e, long f, long g, long h) {
+    return a + b + c + d + e + f + g + h;
+  }
+
+  public String getRandomNumericString(Random random) {
+    return String.format(
+        "%d%d%d%d", random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10));
+  }
+
+  private void privateMethod(String a, String b) {}
+
+  protected void protectedMethod(String a, String b) {}
+
+  protected Random protectedField;
+
+  public final void finalMethod() {}
+
+  public List<String> getList() {
+    return null;
+  }
+
+  /** Joins the strings in the list using the given delimiter. */
+  public String joinStrings(List<String> values, String delim) {
+    return null;
+  }
+
+  public <T extends CharSequence> void methodWithSeveralParams(
+      char ch, String s, int[] a, T t, List<T> lt, Map<String, ? extends CharSequence> wm) {}
+
+  public Example() {
+    this(0);
+  }
+
+  public Example(int number) {
+    this(number, true);
+  }
+
+  public Example(int number, boolean isUp) {
+    this(number, isUp, "achilles");
+  }
+
+  public Example(int number, boolean isUp, String codename) {
+    this.number = number;
+    this.isUp = isUp;
+    this.codename = codename;
+  }
+
+  public Example(int a, int b, int c, int d, int e, int f, int g, int h) {
+    this(a + b + c + d + e + f + g + h);
+  }
+
+  public int whichExample() {
+    return 0;
+  }
+
+  public static int addInts(int a, int b) {
+    return a + b;
+  }
+
+  public static int[] getArr() {
+    return new int[] {1, 2, 3};
+  }
+
+  public static int addAll(int[] arr) {
+    return Arrays.stream(arr).sum();
+  }
+
+  public Example getSelf() {
+    return this;
+  }
+
+  public static void throwException() {
+    throw new RuntimeException("Hello");
+  }
+
+  public void overloaded() {}
+
+  public void overloaded(int a, String b) {}
+
+  public void overloaded(int a) {}
+
+  public void overloaded(List<Integer> a, String b) {}
+
+  public void overloaded(List<Integer> a) {}
+
+  public class NonStaticNested {
+    public boolean ok;
+  }
+
+  public static class Nested {
+    private boolean value;
+
+    public Nested(boolean value) {
+      this.value = value;
+    }
+
+    public void usesAnonymousInnerClass() {
+      new Thread(
+              new Runnable() {
+                @Override
+                public void run() {
+                  System.out.println("2982157093127690243");
+                }
+              })
+          .start();
+    }
+
+    public boolean getValue() {
+      return value;
+    }
+
+    public void setValue(boolean value) {
+      this.value = value;
+    }
+
+    public static class NestedTwice {
+      public static int ZERO = 0;
+    }
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Exceptions.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Exceptions.java
new file mode 100644
index 0000000..e0e79b3
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Exceptions.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.simple_package;
+
+import java.io.*;
+import java.util.*;
+
+public class Exceptions {
+  public Exceptions() {}
+
+  // constructor throwing exception
+  public Exceptions(float x) {
+    throw new IllegalArgumentException("Float is not a serious type");
+  }
+
+  public Exceptions(int a, int b, int c, int d, int e, int f) {
+    throw new IllegalArgumentException("Too many arguments, but none in your favor");
+  }
+
+  public static Object staticObjectMethod() {
+    throw new RuntimeException(":-/");
+  }
+
+  public static int staticIntMethod() {
+    throw new RuntimeException("\\-:");
+  }
+
+  public static Object[] staticObjectArrayMethod() {
+    throw new RuntimeException(":-/[]");
+  }
+
+  public static int[] staticIntArrayMethod() {
+    throw new RuntimeException("\\-:[]");
+  }
+
+  public Object objectMethod() {
+    throw new RuntimeException("['--']");
+  }
+
+  public int intMethod() {
+    throw new RuntimeException("[-_-]");
+  }
+
+  public Object[] objectArrayMethod() {
+    throw new RuntimeException(":-/[]");
+  }
+
+  public int[] intArrayMethod() {
+    throw new RuntimeException("\\-:[]");
+  }
+
+  public int throwNullPointerException() {
+    Random random = null;
+    return random.nextInt();
+  }
+
+  public InputStream throwFileNotFoundException() throws IOException {
+    return new FileInputStream("/dev/nulll/59613/287");
+  }
+
+  public FileInputStream throwClassCastException() {
+    InputStream x = System.in;
+    return (FileInputStream) x;
+  }
+
+  public int throwArrayIndexException() {
+    int[] nums = {1, 2};
+    return nums[4];
+  }
+
+  public int throwArithmeticException() {
+    int x = 10;
+    int y = 100 - 100 + 1 - 1;
+    return x / y;
+  }
+
+  public static void throwLoremIpsum() {
+    throw new RuntimeException("Lorem Ipsum");
+  }
+}
diff --git a/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Fields.java b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Fields.java
new file mode 100644
index 0000000..face69c
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Fields.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.github.dart_lang.jnigen.simple_package;
+
+import java.util.*;
+
+public class Fields {
+  // static fields - non-final primitive & string
+  public static int amount = 500;
+  public static double pi = 3.14159;
+  public static char asterisk = '*';
+  public static String name = "Earl Haraldson";
+
+  // Static fields - object
+  public Integer i = 100;
+
+  // Instance fields - primitive and string
+  public long trillion = 1024L * 1024L * 1024L * 1024L;
+  public boolean isAchillesDead = false;
+  public String bestFighterInGreece = "Achilles";
+
+  // Instance fields - object
+  public Random random = new Random();
+
+  // Static and instance fields in nested class.
+  public static class Nested {
+    public long hundred = 100L;
+    public static String BEST_GOD = "Pallas Athena";
+  }
+
+  public static char euroSymbol = '\u20ac';
+}
diff --git a/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart
new file mode 100644
index 0000000..3791f9e
--- /dev/null
+++ b/pkgs/jnigen/test/simple_package_test/runtime_test_registrant.dart
@@ -0,0 +1,741 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import 'package:jni/jni.dart';
+
+import '../test_util/callback_types.dart';
+
+import 'c_based/dart_bindings/simple_package.dart';
+
+const pi = 3.14159;
+const fpDelta = 0.001;
+const trillion = 1024 * 1024 * 1024 * 1024;
+
+void _runJavaGC() {
+  final managementFactory =
+      Jni.findJClass('java/lang/management/ManagementFactory');
+  final bean = managementFactory.callStaticMethodByName<JObject>(
+      'getRuntimeMXBean', '()Ljava/lang/management/RuntimeMXBean;', []);
+  final pid = bean.callMethodByName<int>('getPid', '()J', []);
+  ProcessResult result;
+  do {
+    result = Process.runSync('jcmd', [pid.toString(), 'GC.run']);
+    sleep(const Duration(milliseconds: 100));
+  } while (result.exitCode != 0);
+}
+
+void registerTests(String groupName, TestRunnerCallback test) {
+  group(groupName, () {
+    test('static final fields - int', () {
+      expect(Example.ON, equals(1));
+      expect(Example.OFF, equals(0));
+      expect(Example.PI, closeTo(pi, fpDelta));
+      expect(Example.SEMICOLON, equals(';'.codeUnitAt(0)));
+      expect(Example.SEMICOLON_STRING, equals(';'));
+    });
+
+    test('Static methods - primitive', () {
+      // same test can be run at a replicated (dart-only) test, check for both
+      // possible values.
+      expect(Example.getAmount(), isIn([1012, 500]));
+      Example.setAmount(1012);
+      expect(Example.getAmount(), equals(1012));
+      expect(Example.getAsterisk(), equals('*'.codeUnitAt(0)));
+      expect(C2.CONSTANT, equals(12));
+    });
+
+    test('Static fields & methods - string', () {
+      expect(
+        Example.getName().toDartString(releaseOriginal: true),
+        isIn(["Ragnar Lothbrok", "Theseus"]),
+      );
+      Example.setName("Theseus".toJString());
+      expect(
+        Example.getName().toDartString(releaseOriginal: true),
+        equals("Theseus"),
+      );
+    });
+
+    test('Static fields and methods - Object', () {
+      final nested = Example.getNestedInstance();
+      expect(nested.getValue(), isIn([true, false]));
+      nested.setValue(false);
+      expect(nested.getValue(), isFalse);
+    });
+
+    test('static methods with several arguments', () {
+      expect(Example.addInts(10, 15), equals(25));
+      expect(Example.max4(-1, 15, 30, 12), equals(30));
+      expect(Example.max8(1, 4, 8, 2, 4, 10, 8, 6), equals(10));
+    });
+
+    test('Instance methods (getters & setters)', () {
+      final e = Example();
+      expect(e.getNumber(), equals(0));
+      expect(e.getIsUp(), true);
+      expect(e.getCodename().toDartString(), equals("achilles"));
+      e.setNumber(1);
+      e.setUp(false);
+      e.setCodename("spartan".toJString());
+      expect(e.getIsUp(), false);
+      expect(e.getNumber(), 1);
+      expect(e.getCodename().toDartString(), equals("spartan"));
+      e.release();
+    });
+
+    test('Instance methods with several arguments', () {
+      final e = Example();
+      expect(e.add4Longs(1, 2, 3, 4), equals(10));
+      expect(e.add8Longs(1, 1, 2, 2, 3, 3, 12, 24), equals(48));
+      expect(
+        e.add4Longs(trillion, trillion, trillion, trillion),
+        equals(4 * trillion),
+      );
+      expect(
+        e.add8Longs(trillion, -trillion, trillion, -trillion, trillion,
+            -trillion, -trillion, -trillion),
+        equals(2 * -trillion),
+      );
+      e.release();
+    });
+
+    test('Misc. instance methods', () {
+      final e = Example();
+      final rand = e.getRandom();
+      expect(rand.isNull, isFalse);
+      final _ = e.getRandomLong();
+      final id =
+          e.getRandomNumericString(rand).toDartString(releaseOriginal: true);
+      expect(int.parse(id), lessThan(10000));
+      e.setNumber(145);
+      expect(
+        e.getSelf().getSelf().getSelf().getSelf().getNumber(),
+        equals(145),
+      );
+      e.release();
+    });
+
+    test('Constructors', () {
+      final e0 = Example();
+      expect(e0.getNumber(), 0);
+      expect(e0.getIsUp(), true);
+      expect(e0.getCodename().toDartString(), equals('achilles'));
+      final e1 = Example.new1(111);
+      expect(e1.getNumber(), equals(111));
+      expect(e1.getIsUp(), true);
+      expect(e1.getCodename().toDartString(), "achilles");
+      final e2 = Example.new2(122, false);
+      expect(e2.getNumber(), equals(122));
+      expect(e2.getIsUp(), false);
+      expect(e2.getCodename().toDartString(), "achilles");
+      final e3 = Example.new3(133, false, "spartan".toJString());
+      expect(e3.getNumber(), equals(133));
+      expect(e3.getIsUp(), false);
+      expect(e3.getCodename().toDartString(), "spartan");
+    });
+
+    test('Static (non-final) fields', () {
+      // Other replica test may already have modified this, so assert both
+      // values.
+      expect(Fields.amount, isIn([500, 101]));
+      Fields.amount = 101;
+      expect(Fields.amount, equals(101));
+
+      expect(Fields.asterisk, equals('*'.codeUnitAt(0)));
+
+      expect(
+        Fields.name.toDartString(),
+        isIn(["Earl Haraldson", "Ragnar Lothbrok"]),
+      );
+
+      Fields.name = "Ragnar Lothbrok".toJString();
+      expect(Fields.name.toDartString(), equals("Ragnar Lothbrok"));
+
+      expect(Fields.pi, closeTo(pi, fpDelta));
+    });
+
+    test('Instance fields', () {
+      final f = Fields();
+      expect(f.trillion, equals(trillion));
+
+      expect(f.isAchillesDead, isFalse);
+      expect(f.bestFighterInGreece.toDartString(), equals("Achilles"));
+      // "For your glory walks hand-in-hand with your doom." - Thetis.
+      f.isAchillesDead = true;
+      // I don't know much Greek mythology. But Troy was released in 2004,
+      // and 300 was released in 2006, so it's Leonidas I.
+      f.bestFighterInGreece = "Leonidas I".toJString();
+      expect(f.isAchillesDead, isTrue);
+      expect(f.bestFighterInGreece.toDartString(), "Leonidas I");
+    });
+
+    test('Fields from nested class', () {
+      expect(Fields_Nested().hundred, equals(100));
+      // Hector of Troy may disagree.
+      expect(Fields_Nested.BEST_GOD.toDartString(), equals('Pallas Athena'));
+    });
+
+    test('static methods arrays', () {
+      final array = Example.getArr();
+      expect(array[0], 1);
+      expect(array[1], 2);
+      expect(array[2], 3);
+      expect(Example.addAll(array), 6);
+      array[0] = 4;
+      expect(Example.addAll(array), 9);
+    });
+
+    test('array of the class', () {
+      final ex1 = Example();
+      final ex2 = Example();
+      ex1.setNumber(1);
+      ex2.setNumber(2);
+      final array = JArray(Example.type, 2);
+      array[0] = ex1;
+      array[1] = ex2;
+      expect(array[0].getNumber(), 1);
+      expect(array[1].getNumber(), 2);
+      array.release();
+      ex1.release();
+      ex2.release();
+    });
+
+    test("Check bindings for same-named classes", () {
+      expect(Example().whichExample(), 0);
+      expect(Example1().whichExample(), 1);
+    });
+
+    test('Unicode char', () {
+      expect(Fields.euroSymbol, equals('\u20AC'.codeUnitAt(0)));
+    });
+
+    group('exception tests', () {
+      void throwsException(void Function() f) {
+        expect(f, throwsA(isA<JniException>()));
+      }
+
+      test('Example throw exception', () {
+        throwsException(Example.throwException);
+      });
+
+      test('Exception from method returning Object', () {
+        throwsException(Exceptions.staticObjectMethod);
+        throwsException(Exceptions.staticObjectArrayMethod);
+        final x = Exceptions();
+        throwsException(x.objectMethod);
+        throwsException(x.objectArrayMethod);
+      });
+
+      test('Exception from method returning int', () {
+        throwsException(Exceptions.staticIntMethod);
+        throwsException(Exceptions.staticIntArrayMethod);
+        final x = Exceptions();
+        throwsException(x.intMethod);
+        throwsException(x.intArrayMethod);
+      });
+
+      test('Exception from constructor', () {
+        throwsException(() => Exceptions.new1(6.8));
+        throwsException(() => Exceptions.new2(1, 2, 3, 4, 5, 6));
+      });
+
+      test('Exception contains error message & stack trace', () {
+        try {
+          Exceptions.throwLoremIpsum();
+        } on JniException catch (e) {
+          expect(e.message, stringContainsInOrder(["Lorem Ipsum"]));
+          expect(
+            e.toString(),
+            stringContainsInOrder(["Lorem Ipsum", "throwLoremIpsum"]),
+          );
+          return;
+        }
+        throw AssertionError("No exception was thrown");
+      });
+    });
+
+    group('generics', () {
+      test('GrandParent constructor', () {
+        using((arena) {
+          final grandParent =
+              GrandParent('Hello'.toJString()..releasedBy(arena))
+                ..releasedBy(arena);
+          expect(grandParent, isA<GrandParent<JString>>());
+          expect(grandParent.$type, isA<$GrandParentType<JString>>());
+          expect(
+              grandParent.value.toDartString(releaseOriginal: true), 'Hello');
+        });
+      });
+      test('MyStack<T>', () {
+        using((arena) {
+          final stack = MyStack(T: JString.type)..releasedBy(arena);
+          stack.push('Hello'.toJString()..releasedBy(arena));
+          stack.push('World'.toJString()..releasedBy(arena));
+          expect(stack.pop().toDartString(releaseOriginal: true), 'World');
+          expect(stack.pop().toDartString(releaseOriginal: true), 'Hello');
+        });
+      });
+      test('Different stacks have different types, same stacks have same types',
+          () {
+        using((arena) {
+          final aStringStack = MyStack(T: JString.type)..releasedBy(arena);
+          final anotherStringStack = MyStack(T: JString.type)
+            ..releasedBy(arena);
+          final anObjectStack = MyStack(T: JObject.type)..releasedBy(arena);
+          expect(aStringStack.$type, anotherStringStack.$type);
+          expect(
+            aStringStack.$type.hashCode,
+            anotherStringStack.$type.hashCode,
+          );
+          expect(aStringStack.$type, isNot(anObjectStack.$type));
+          expect(
+            aStringStack.$type.hashCode,
+            isNot(anObjectStack.$type.hashCode),
+          );
+        });
+      });
+      test('MyMap<K, V>', () {
+        using((arena) {
+          final map = MyMap(K: JString.type, V: Example.type)
+            ..releasedBy(arena);
+          final helloExample = Example.new1(1)..releasedBy(arena);
+          final worldExample = Example.new1(2)..releasedBy(arena);
+          map.put('Hello'.toJString()..releasedBy(arena), helloExample);
+          map.put('World'.toJString()..releasedBy(arena), worldExample);
+          expect(
+            (map.get0('Hello'.toJString()..releasedBy(arena))
+                  ..releasedBy(arena))
+                .getNumber(),
+            1,
+          );
+          expect(
+            (map.get0('World'.toJString()..releasedBy(arena))
+                  ..releasedBy(arena))
+                .getNumber(),
+            2,
+          );
+          expect(
+            ((map.entryStack()..releasedBy(arena)).pop()..releasedBy(arena))
+                .key
+                .castTo(JString.type, releaseOriginal: true)
+                .toDartString(releaseOriginal: true),
+            anyOf('Hello', 'World'),
+          );
+        });
+      });
+      group('classes extending generics', () {
+        test('StringStack', () {
+          using((arena) {
+            final stringStack = StringStack()..releasedBy(arena);
+            stringStack.push('Hello'.toJString()..releasedBy(arena));
+            expect(
+                stringStack.pop().toDartString(releaseOriginal: true), 'Hello');
+          });
+        });
+        test('StringKeyedMap', () {
+          using((arena) {
+            final map = StringKeyedMap(V: Example.type)..releasedBy(arena);
+            final example = Example()..releasedBy(arena);
+            map.put('Hello'.toJString()..releasedBy(arena), example);
+            expect(
+              (map.get0('Hello'.toJString()..releasedBy(arena))
+                    ..releasedBy(arena))
+                  .getNumber(),
+              0,
+            );
+          });
+        });
+        test('StringValuedMap', () {
+          using((arena) {
+            final map = StringValuedMap(K: Example.type)..releasedBy(arena);
+            final example = Example()..releasedBy(arena);
+            map.put(example, 'Hello'.toJString()..releasedBy(arena));
+            expect(
+              map.get0(example).toDartString(releaseOriginal: true),
+              'Hello',
+            );
+          });
+        });
+        test('StringMap', () {
+          using((arena) {
+            final map = StringMap()..releasedBy(arena);
+            map.put('hello'.toJString()..releasedBy(arena),
+                'world'.toJString()..releasedBy(arena));
+            expect(
+              map
+                  .get0('hello'.toJString()..releasedBy(arena))
+                  .toDartString(releaseOriginal: true),
+              'world',
+            );
+          });
+        });
+      });
+      test('superclass count', () {
+        expect(JObject.type.superCount, 0);
+        expect(MyMap.type(JObject.type, JObject.type).superCount, 1);
+        expect(StringKeyedMap.type(JObject.type).superCount, 2);
+        expect(StringValuedMap.type(JObject.type).superCount, 2);
+        expect(StringMap.type.superCount, 3);
+      });
+      test('nested generics', () {
+        using((arena) {
+          final grandParent =
+              GrandParent(T: JString.type, "!".toJString()..releasedBy(arena))
+                ..releasedBy(arena);
+          expect(
+            grandParent.value.toDartString(releaseOriginal: true),
+            "!",
+          );
+
+          final strStaticParent = GrandParent.stringStaticParent()
+            ..releasedBy(arena);
+          expect(
+            strStaticParent.value.toDartString(releaseOriginal: true),
+            "Hello",
+          );
+
+          final exampleStaticParent = GrandParent.varStaticParent(
+              S: Example.type, Example()..releasedBy(arena))
+            ..releasedBy(arena);
+          expect(
+            (exampleStaticParent.value..releasedBy(arena)).getNumber(),
+            0,
+          );
+
+          final strParent = grandParent.stringParent()..releasedBy(arena);
+          expect(
+            strParent.parentValue
+                .castTo(JString.type, releaseOriginal: true)
+                .toDartString(releaseOriginal: true),
+            "!",
+          );
+          expect(
+            strParent.value.toDartString(releaseOriginal: true),
+            "Hello",
+          );
+
+          final exampleParent = grandParent.varParent(
+              S: Example.type, Example()..releasedBy(arena))
+            ..releasedBy(arena);
+          expect(
+            exampleParent.parentValue
+                .castTo(JString.type, releaseOriginal: true)
+                .toDartString(releaseOriginal: true),
+            "!",
+          );
+          expect(
+            (exampleParent.value..releasedBy(arena)).getNumber(),
+            0,
+          );
+          // TODO(#139): test constructing Child, currently does not work due
+          // to a problem with C-bindings.
+        });
+      });
+    });
+    test('Constructing non-static nested classes', () {
+      using((arena) {
+        final grandParent = GrandParent(1.toJInteger())..releasedBy(arena);
+        final parent = GrandParent_Parent(grandParent, 2.toJInteger())
+          ..releasedBy(arena);
+        final child = GrandParent_Parent_Child(parent, 3.toJInteger())
+          ..releasedBy(arena);
+        expect(grandParent.value.intValue(releaseOriginal: true), 1);
+        expect(parent.parentValue.intValue(releaseOriginal: true), 1);
+        expect(parent.value.intValue(releaseOriginal: true), 2);
+        expect(child.grandParentValue.intValue(releaseOriginal: true), 1);
+        expect(child.parentValue.intValue(releaseOriginal: true), 2);
+        expect(child.value.intValue(releaseOriginal: true), 3);
+      });
+    });
+
+    group('Generic type inference', () {
+      test('MyStack.of1', () {
+        using((arena) {
+          final emptyStack = MyStack(T: JString.type)..releasedBy(arena);
+          expect(emptyStack.size(), 0);
+          final stack = MyStack.of1(
+            "Hello".toJString()..releasedBy(arena),
+          )..releasedBy(arena);
+          expect(stack, isA<MyStack<JString>>());
+          expect(stack.$type, isA<$MyStackType<JString>>());
+          expect(
+            stack.pop().toDartString(releaseOriginal: true),
+            "Hello",
+          );
+        });
+      });
+      test('MyStack.of 2 strings', () {
+        using((arena) {
+          final stack = MyStack.of2(
+            "Hello".toJString()..releasedBy(arena),
+            "World".toJString()..releasedBy(arena),
+          )..releasedBy(arena);
+          expect(stack, isA<MyStack<JString>>());
+          expect(stack.$type, isA<$MyStackType<JString>>());
+          expect(
+            stack.pop().toDartString(releaseOriginal: true),
+            "World",
+          );
+          expect(
+            stack.pop().toDartString(releaseOriginal: true),
+            "Hello",
+          );
+        });
+      });
+      test('MyStack.of a string and an array', () {
+        using((arena) {
+          final array = JArray.filled(1, "World".toJString()..releasedBy(arena))
+            ..releasedBy(arena);
+          final stack = MyStack.of2(
+            "Hello".toJString()..releasedBy(arena),
+            array,
+          )..releasedBy(arena);
+          expect(stack, isA<MyStack<JObject>>());
+          expect(stack.$type, isA<$MyStackType<JObject>>());
+          expect(
+            stack
+                .pop()
+                .castTo(JArray.type(JString.type), releaseOriginal: true)[0]
+                .toDartString(releaseOriginal: true),
+            "World",
+          );
+          expect(
+            stack
+                .pop()
+                .castTo(JString.type, releaseOriginal: true)
+                .toDartString(releaseOriginal: true),
+            "Hello",
+          );
+        });
+      });
+      test('MyStack.from array of string', () {
+        using((arena) {
+          final array = JArray.filled(1, "Hello".toJString()..releasedBy(arena))
+            ..releasedBy(arena);
+          final stack = MyStack.fromArray(array)..releasedBy(arena);
+          expect(stack, isA<MyStack<JString>>());
+          expect(stack.$type, isA<$MyStackType<JString>>());
+          expect(
+            stack.pop().toDartString(releaseOriginal: true),
+            "Hello",
+          );
+        });
+      });
+      test('MyStack.fromArrayOfArrayOfGrandParents', () {
+        using((arena) {
+          final firstDimention = JArray.filled(
+            1,
+            GrandParent("Hello".toJString()..releasedBy(arena))
+              ..releasedBy(arena),
+          )..releasedBy(arena);
+          final twoDimentionalArray = JArray.filled(1, firstDimention)
+            ..releasedBy(arena);
+          final stack =
+              MyStack.fromArrayOfArrayOfGrandParents(twoDimentionalArray)
+                ..releasedBy(arena);
+          expect(stack, isA<MyStack<JString>>());
+          expect(stack.$type, isA<$MyStackType<JString>>());
+          expect(
+            stack.pop().toDartString(releaseOriginal: true),
+            "Hello",
+          );
+        });
+      });
+    });
+  });
+
+  group('interface implementation', () {
+    for (final (threading, consume) in [
+      ('another thread', MyInterfaceConsumer.consumeOnAnotherThread),
+      ('the same thread', MyInterfaceConsumer.consumeOnSameThread),
+    ]) {
+      test('MyInterface.implement on $threading', () async {
+        final voidCallbackResult = Completer<JString>();
+        final varCallbackResult = Completer<JInteger>();
+        final manyPrimitivesResult = Completer<int>();
+        // We can use this trick to access self, instead of generating a `thiz`
+        // or `self` argument for each one of the callbacks.
+        late final MyInterface<JInteger> myInterface;
+        myInterface = MyInterface.implement(
+          $MyInterfaceImpl(
+            voidCallback: (s) {
+              voidCallbackResult.complete(s);
+            },
+            stringCallback: (s) {
+              return (s.toDartString(releaseOriginal: true) * 2).toJString();
+            },
+            varCallback: (JInteger t) {
+              final result =
+                  (t.intValue(releaseOriginal: true) * 2).toJInteger();
+              varCallbackResult.complete(result);
+              return result;
+            },
+            manyPrimitives: (a, b, c, d) {
+              if (b) {
+                final result = a + c + d.toInt();
+                manyPrimitivesResult.complete(result);
+                return result;
+              } else {
+                // Call self, add to [a] when [b] is false and change b to true.
+                return myInterface.manyPrimitives(a + 1, true, c, d);
+              }
+            },
+            T: JInteger.type,
+          ),
+        );
+        // [stringCallback] is going to be called first using [s].
+        // The result of it is going to be used as the argument for
+        // [voidCallback].
+        // The other two methods will be called individually using the passed
+        // arguments afterwards.
+        consume(
+          myInterface,
+          // For stringCallback:
+          'hello'.toJString(),
+          // For manyPrimitives:
+          -1,
+          false,
+          3,
+          3.14,
+          // For varCallback
+          7.toJInteger(),
+        );
+        final voidCallback = await voidCallbackResult.future;
+        expect(voidCallback.toDartString(releaseOriginal: true), 'hellohello');
+
+        final varCallback = await varCallbackResult.future;
+        expect(varCallback.intValue(), 14);
+
+        final manyPrimitives = await manyPrimitivesResult.future;
+        expect(manyPrimitives, -1 + 3 + 3.14.toInt() + 1);
+
+        // Currently we have one implementation of the interface.
+        expect(MyInterface.$impls, hasLength(1));
+        myInterface.release();
+        // Running System.gc() and waiting.
+        _runJavaGC();
+        for (var i = 0; i < 8; ++i) {
+          await Future<void>.delayed(Duration(milliseconds: (1 << i) * 100));
+          if (MyInterface.$impls.isEmpty) {
+            break;
+          }
+        }
+        // Since the interface is now deleted, the cleaner must signal to Dart
+        // to clean up.
+        expect(MyInterface.$impls, isEmpty);
+      });
+    }
+    group('Dart exceptions are handled', () {
+      for (final exception in [UnimplementedError(), 'Hello!']) {
+        for (final sameThread in [true, false]) {
+          test(
+              'on ${sameThread ? 'the same thread' : 'another thread'}'
+              ' throwing $exception', () async {
+            final runnable = MyRunnable.implement(
+              $MyRunnableImpl(
+                run: () {
+                  throw exception;
+                },
+              ),
+            );
+            final runner = MyRunnableRunner(runnable);
+            if (sameThread) {
+              runner.runOnSameThread();
+            } else {
+              runner.runOnAnotherThread();
+            }
+            while (runner.error.isNull) {
+              await Future<void>.delayed(const Duration(milliseconds: 100));
+            }
+            expect(
+              Jni.env.IsInstanceOf(
+                runner.error.reference,
+                Jni.findClass('java/lang/reflect/UndeclaredThrowableException'),
+              ),
+              isTrue,
+            );
+            final cause = runner.error.callMethodByName<JObject>(
+                'getCause', '()Ljava/lang/Throwable;', []);
+            expect(
+              Jni.env.IsInstanceOf(
+                cause.reference,
+                Jni.findClass(
+                    'com/github/dart_lang/jni/PortProxy\$DartException'),
+              ),
+              isTrue,
+            );
+            expect(cause.toString(), contains(exception.toString()));
+          });
+        }
+      }
+    });
+  });
+
+  group('$groupName (load tests)', () {
+    const k4 = 4 * 1024; // This is a round number, unlike say 4000
+    const k256 = 256 * 1024;
+    test('Create large number of JNI references without deleting', () {
+      for (int i = 0; i < k4; i++) {
+        final e = Example.new1(i);
+        expect(e.getNumber(), equals(i));
+      }
+    });
+    test('Create many JNI refs with scoped deletion', () {
+      for (int i = 0; i < k256; i++) {
+        using((arena) {
+          final e = Example.new1(i)..releasedBy(arena);
+          expect(e.getNumber(), equals(i));
+        });
+      }
+    });
+    test('Create many JNI refs with scoped deletion, in batches', () {
+      for (int i = 0; i < 256; i++) {
+        using((arena) {
+          for (int i = 0; i < 1024; i++) {
+            final e = Example.new1(i)..releasedBy(arena);
+            expect(e.getNumber(), equals(i));
+          }
+        });
+      }
+    });
+    test('Create large number of JNI refs with manual delete', () {
+      for (int i = 0; i < k256; i++) {
+        final e = Example.new1(i);
+        expect(e.getNumber(), equals(i));
+        e.release();
+      }
+    });
+    test('Method returning primitive type does not create references', () {
+      using((arena) {
+        final e = Example.new1(64)..releasedBy(arena);
+        for (int i = 0; i < k256; i++) {
+          expect(e.getNumber(), equals(64));
+        }
+      });
+    });
+    test('Class references are cached', () {
+      final asterisk = '*'.codeUnitAt(0);
+      for (int i = 0; i < k256; i++) {
+        expect(Fields.asterisk, equals(asterisk));
+      }
+    });
+    void testPassageOfTime(int n) {
+      test('Refs are not inadvertently deleted after $n seconds', () {
+        final f = Fields();
+        expect(f.trillion, equals(trillion));
+        sleep(Duration(seconds: n));
+        expect(f.trillion, equals(trillion));
+      });
+    }
+
+    if (!Platform.isAndroid) {
+      testPassageOfTime(1);
+      testPassageOfTime(4);
+    }
+  });
+}
diff --git a/pkgs/jnigen/test/summary_generation_test.dart b/pkgs/jnigen/test/summary_generation_test.dart
new file mode 100644
index 0000000..62eee3b
--- /dev/null
+++ b/pkgs/jnigen/test/summary_generation_test.dart
@@ -0,0 +1,174 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// These tests validate summary generation in various scenarios.
+// Currently, no validation of the summary content itself is done.
+
+@Tags(['summarizer_test'])
+
+import 'dart:math';
+
+import 'package:jnigen/src/config/config.dart';
+import 'package:jnigen/src/elements/elements.dart';
+import 'package:jnigen/src/summary/summary.dart';
+
+import 'package:path/path.dart' hide equals;
+import 'package:test/test.dart';
+
+import 'test_util/summary_util.dart';
+import 'test_util/test_util.dart';
+
+const nestedClasses = [
+  'com.github.dart_lang.jnigen.simple_package.Example\$Nested',
+  'com.github.dart_lang.jnigen.simple_package.Example\$Nested\$NestedTwice',
+  'com.github.dart_lang.jnigen.generics.GrandParent\$StaticParent',
+  'com.github.dart_lang.jnigen.generics.GrandParent\$StaticParent\$Child',
+  'com.github.dart_lang.jnigen.generics.GrandParent\$Parent',
+  'com.github.dart_lang.jnigen.generics.GrandParent\$Parent\$Child',
+];
+
+void expectSummaryHasAllClasses(Classes? classes) {
+  expect(classes, isNotNull);
+  final decls = classes!.decls;
+  expect(decls.entries.length, greaterThanOrEqualTo(javaFiles.length));
+  final declNames = decls.keys.toSet();
+  final expectedClasses =
+      javaClasses.where((name) => !name.contains("annotations.")).toList();
+  // Nested classes should be included automatically with parent class.
+  // change this part if you change this behavior intentionally.
+  expectedClasses.addAll(nestedClasses);
+  expect(declNames, containsAll(expectedClasses));
+}
+
+/// Packs files indicated by [artifacts], each relative to [artifactDir] into
+/// a JAR file at [jarPath].
+Future<void> createJar({
+  required String artifactDir,
+  required List<String> artifacts,
+  required String jarPath,
+}) async {
+  await runCommand(
+    'jar',
+    ['cf', relative(jarPath, from: artifactDir), ...artifacts],
+    workingDirectory: artifactDir,
+  );
+}
+
+final random = Random.secure();
+
+void testSuccessCase(String description, Config config) {
+  config.classes = summarizerClassesSpec;
+  test(description, () async {
+    final classes = await getSummary(config);
+    expectSummaryHasAllClasses(classes);
+  });
+}
+
+void testFailureCase(
+    String description, Config config, String nonExistingClass) {
+  test(description, () async {
+    final insertPosition = random.nextInt(config.classes.length + 1);
+    config.classes = summarizerClassesSpec.sublist(0, insertPosition) +
+        [nonExistingClass] +
+        summarizerClassesSpec.sublist(insertPosition);
+    try {
+      await getSummary(config);
+    } on SummaryParseException catch (e) {
+      expect(e.stderr, isNotNull);
+      expect(e.stderr!, stringContainsInOrder(["Not found", nonExistingClass]));
+      return;
+    }
+    throw AssertionError("No exception was caught");
+  });
+}
+
+void testAllCases({
+  List<String>? sourcePath,
+  List<String>? classPath,
+}) {
+  testSuccessCase(
+    '- valid config',
+    getSummaryGenerationConfig(sourcePath: sourcePath, classPath: classPath),
+  );
+  testFailureCase(
+    '- should fail with non-existing class',
+    getSummaryGenerationConfig(sourcePath: sourcePath, classPath: classPath),
+    'com.github.dart_lang.jnigen.DoesNotExist',
+  );
+  testFailureCase(
+    '- should fail with non-existing package',
+    getSummaryGenerationConfig(sourcePath: sourcePath, classPath: classPath),
+    'com.github.dart_lang.notexist',
+  );
+}
+
+void main() async {
+  await checkLocallyBuiltDependencies();
+  final tempDir = getTempDir("jnigen_summary_tests_");
+
+  group('Test summary generation from compiled JAR', () {
+    final targetDir = tempDir.createTempSync("compiled_jar_test_");
+    final jarPath = join(targetDir.absolute.path, 'classes.jar');
+    setUpAll(() async {
+      await compileJavaFiles(simplePackageDir, targetDir);
+      final classFiles = findFilesWithSuffix(targetDir, '.class');
+      await createJar(
+          artifactDir: targetDir.path, artifacts: classFiles, jarPath: jarPath);
+    });
+    testAllCases(classPath: [jarPath]);
+  });
+
+  group('Test summary generation from source JAR', () {
+    final targetDir = tempDir.createTempSync("source_jar_test_");
+    final jarPath = join(targetDir.path, 'sources.jar');
+    setUpAll(() async {
+      await createJar(
+          artifactDir: simplePackageDir.path,
+          artifacts: javaFiles,
+          jarPath: jarPath);
+    });
+    testAllCases(sourcePath: [jarPath]);
+  });
+
+  group('Test summary generation from source folder', () {
+    testAllCases(sourcePath: [simplePackagePath]);
+  });
+
+  group('Test summary generation from compiled classes in directory', () {
+    final targetDir = tempDir.createTempSync("compiled_classes_test_");
+    setUpAll(() => compileJavaFiles(simplePackageDir, targetDir));
+    testAllCases(classPath: [targetDir.path]);
+  });
+
+  // Test summary generation from combination of a source and class path
+  group('Test summary generation from combination', () {
+    final targetDir = tempDir.createTempSync("combination_test_");
+    final classesJarPath = join(targetDir.path, 'classes.jar');
+    // remove a class from source files and create a source JAR
+    final sourceFiles = javaFiles.toList();
+    sourceFiles.removeLast();
+    final sourceJarPath = join(targetDir.path, 'sources.jar');
+    setUpAll(() async {
+      await createJar(
+        artifactDir: simplePackageDir.path,
+        artifacts: sourceFiles,
+        jarPath: sourceJarPath,
+      );
+
+      await compileJavaFiles(simplePackageDir, targetDir);
+      final classFiles = findFilesWithSuffix(targetDir, '.class');
+      await createJar(
+        artifactDir: targetDir.path,
+        artifacts: classFiles,
+        jarPath: classesJarPath,
+      );
+    });
+    testAllCases(
+      classPath: [classesJarPath],
+      sourcePath: [sourceJarPath],
+    );
+  });
+
+  tearDownAll(() => deleteTempDirWithDelay(tempDir));
+}
diff --git a/pkgs/jnigen/test/summary_test.dart b/pkgs/jnigen/test/summary_test.dart
new file mode 100644
index 0000000..c83e798
--- /dev/null
+++ b/pkgs/jnigen/test/summary_test.dart
@@ -0,0 +1,323 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// These tests validate individual characteristics in summary
+// For example, the values of methods, arguments, types, generic params etc...
+@Tags(['summarizer_test'])
+
+import 'package:jnigen/src/elements/elements.dart';
+import 'package:jnigen/src/summary/summary.dart';
+import 'package:test/test.dart';
+
+import 'test_util/summary_util.dart';
+import 'test_util/test_util.dart';
+
+const jnigenPackage = 'com.github.dart_lang.jnigen';
+const simplePackage = "$jnigenPackage.simple_package";
+
+extension on Classes {
+  String _getSimpleName(ClassDecl c) {
+    return c.binaryName.split(".").last;
+  }
+
+  ClassDecl getClassBySimpleName(String simpleName) {
+    return decls.values.firstWhere((c) => _getSimpleName(c) == simpleName);
+  }
+
+  ClassDecl getClass(String dirName, String className) {
+    return decls['$jnigenPackage.$dirName.$className']!;
+  }
+
+  ClassDecl getExampleClass() {
+    return getClass('simple_package', 'Example');
+  }
+}
+
+extension on ClassDecl {
+  Method getMethod(String name) => methods.firstWhere((m) => m.name == name);
+  Field getField(String name) => fields.firstWhere((f) => f.name == name);
+}
+
+void registerCommonTests(Classes classes) {
+  test('static modifier', () {
+    final example = classes.getExampleClass();
+    final containsStatic = contains("static");
+    final notContainsStatic = isNot(containsStatic);
+    expect(example.getMethod("max4").modifiers, containsStatic);
+    expect(example.getMethod("getCodename").modifiers, notContainsStatic);
+    expect(example.getField("ON").modifiers, containsStatic);
+    expect(example.getField("codename").modifiers, notContainsStatic);
+    final nested = classes.getClassBySimpleName("Example\$Nested");
+    expect(nested.modifiers, containsStatic);
+    final nonStaticNested =
+        classes.getClassBySimpleName("Example\$NonStaticNested");
+    expect(nonStaticNested.modifiers, notContainsStatic);
+  });
+
+  test('Public, protected and private modifiers', () {
+    final example = classes.getExampleClass();
+    final hasPrivate = contains("private");
+    final hasProtected = contains("protected");
+    final hasPublic = contains("public");
+    final isPrivate = allOf(hasPrivate, isNot(hasProtected), isNot(hasPublic));
+    final isProtected =
+        allOf(isNot(hasPrivate), hasProtected, isNot(hasPublic));
+    final isPublic = allOf(isNot(hasPrivate), isNot(hasProtected), hasPublic);
+    expect(example.getMethod("getNumber").modifiers, isPublic);
+    expect(example.getMethod("privateMethod").modifiers, isPrivate);
+    expect(example.getMethod("protectedMethod").modifiers, isProtected);
+    expect(example.getField("OFF").modifiers, isPublic);
+    expect(example.getField("number").modifiers, isPrivate);
+    expect(example.getField("protectedField").modifiers, isProtected);
+  });
+
+  test('final modifier', () {
+    final example = classes.getExampleClass();
+    final isFinal = contains('final');
+    expect(example.getField("PI").modifiers, isFinal);
+    expect(example.getField("unusedRandom").modifiers, isFinal);
+    expect(example.getField("number").modifiers, isNot(isFinal));
+    expect(example.getMethod("finalMethod").modifiers, isFinal);
+  });
+
+  void assertToBeStringListType(TypeUsage listType) {
+    expect(listType.kind, equals(Kind.declared));
+    final listClassType = listType.type as DeclaredType;
+    expect(listClassType.binaryName, equals('java.util.List'));
+    expect(listClassType.params, hasLength(1));
+    final listTypeParam = listClassType.params[0];
+    expect(listTypeParam.kind, equals(Kind.declared));
+    expect(listTypeParam.type.name, equals('java.lang.String'));
+  }
+
+  test('return types', () {
+    final example = classes.getExampleClass();
+    expect(example.getMethod("getNumber").returnType.shorthand, equals("int"));
+    expect(example.getMethod("getName").returnType.shorthand,
+        equals("java.lang.String"));
+    expect(example.getMethod("getNestedInstance").returnType.name,
+        equals("$simplePackage.Example\$Nested"));
+    final listType = example.getMethod("getList").returnType;
+    assertToBeStringListType(listType);
+  });
+
+  test('parameter types', () {
+    final example = classes.getExampleClass();
+    final joinStrings = example.getMethod('joinStrings');
+    final listType = joinStrings.params[0].type;
+    assertToBeStringListType(listType);
+    final stringType = joinStrings.params[1].type;
+    expect(stringType.kind, Kind.declared);
+    expect((stringType.type as DeclaredType).binaryName, 'java.lang.String');
+  });
+
+  test('Parameters of several types', () {
+    final example = classes.getExampleClass();
+    final method = example.getMethod('methodWithSeveralParams');
+    expect(method.typeParams, hasLength(1));
+    expect(method.typeParams[0].name, 'T');
+    expect(method.typeParams[0].bounds[0].name, 'java.lang.CharSequence');
+
+    final charParam = method.params[0];
+    expect(charParam.type.kind, equals(Kind.primitive));
+    expect(charParam.type.name, equals('char'));
+
+    final stringParam = method.params[1];
+    expect(stringParam.type.kind, equals(Kind.declared));
+    expect((stringParam.type.type as DeclaredType).binaryName,
+        equals('java.lang.String'));
+
+    final arrayParam = method.params[2];
+    expect(arrayParam.type.kind, equals(Kind.array));
+    expect((arrayParam.type.type as ArrayType).type.name, equals('int'));
+
+    final typeVarParam = method.params[3];
+    expect(typeVarParam.type.kind, equals(Kind.typeVariable));
+    expect((typeVarParam.type.type as TypeVar).name, equals('T'));
+
+    final listParam = method.params[4];
+    expect(listParam.type.kind, equals(Kind.declared));
+    final listType = (listParam.type.type as DeclaredType);
+    expect(listType.binaryName, equals('java.util.List'));
+    expect(listType.params, hasLength(1));
+    final tType = listType.params[0];
+    expect(tType.kind, Kind.typeVariable);
+    expect((tType.type as TypeVar).name, equals('T'));
+
+    final wildcardMapParam = method.params[5];
+    expect(wildcardMapParam.type.kind, equals(Kind.declared));
+    final mapType = (wildcardMapParam.type.type as DeclaredType);
+    expect(mapType.binaryName, equals('java.util.Map'));
+    expect(mapType.params, hasLength(2));
+    final strType = mapType.params[0];
+    expect(strType.name, 'java.lang.String');
+    // TODO(#141): Wildcard implementation.
+    /*
+    final wildcardType = mapType.params[1];
+    expect(wildcardType.kind, equals(Kind.wildcard));
+    expect((wildcardType.type as Wildcard).extendsBound?.name,
+        equals('java.lang.CharSequence'));
+    */
+  });
+
+  test('superclass', () {
+    final baseClass = classes.getClass('inheritance', 'BaseClass');
+    expect(baseClass.typeParams, hasLength(1));
+    final typeParam = baseClass.typeParams.single;
+    expect(typeParam.bounds.map((b) => b.name).toList(),
+        ['java.lang.CharSequence']);
+
+    final specific = classes.getClass('inheritance', 'SpecificDerivedClass');
+    expect(specific.typeParams, hasLength(0));
+    expect(specific.superclass, isNotNull);
+    final specificSuper = specific.superclass!.type as DeclaredType;
+    expect(specificSuper.params[0].type, isA<DeclaredType>());
+    expect(specificSuper.params[0].type.name, equals('java.lang.String'));
+
+    final generic = classes.getClass('inheritance', 'GenericDerivedClass');
+    expect(generic.typeParams, hasLength(1));
+    expect(generic.typeParams[0].name, equals('T'));
+    expect(generic.typeParams[0].bounds.map((b) => b.name).toList(),
+        ['java.lang.CharSequence']);
+    expect(generic.superclass, isNotNull);
+    final genericSuper = generic.superclass!.type as DeclaredType;
+    expect(genericSuper.params[0].type, isA<TypeVar>());
+    expect(genericSuper.params[0].type.name, equals('T'));
+  });
+
+  test('constructor is included', () {
+    final example = classes.getExampleClass();
+    void assertOneCtorExistsWithArity(List<String> paramTypes) {
+      final arityCtors = example.methods
+          .where(
+              (m) => m.name == '<init>' && m.params.length == paramTypes.length)
+          .toList();
+      expect(arityCtors, hasLength(1));
+      final ctor = arityCtors[0];
+      expect(ctor.params.map((p) => p.type.name), equals(paramTypes));
+    }
+
+    assertOneCtorExistsWithArity([]);
+    assertOneCtorExistsWithArity(['int']);
+    assertOneCtorExistsWithArity(['int', 'boolean']);
+    assertOneCtorExistsWithArity(['int', 'boolean', 'java.lang.String']);
+  });
+
+  test('Overloaded methods', () {
+    final methods = classes
+        .getExampleClass()
+        .methods
+        .where((m) => m.name == 'overloaded')
+        .toList();
+    final signatures =
+        methods.map((m) => m.params.map((p) => p.type.name).toList()).toSet();
+    expect(
+      signatures,
+      equals({
+        <String>[],
+        ['int'],
+        ['int', 'java.lang.String'],
+        ['java.util.List'],
+        ['java.util.List', 'java.lang.String'],
+      }),
+    );
+  });
+
+  test('Declaration type (class vs interface vs enum)', () {
+    final example = classes.getExampleClass();
+    expect(example.declKind, DeclKind.classKind);
+    final myInterface = classes.getClass('interfaces', 'MyInterface');
+    expect(myInterface.declKind, DeclKind.interfaceKind);
+    final color = classes.getClass('simple_package', 'Color');
+    expect(color.declKind, DeclKind.enumKind);
+  });
+
+  test('Enum values', () {
+    final example = classes.getExampleClass();
+    expect(example.values, anyOf(isNull, isEmpty));
+    final color = classes.getClass('simple_package', 'Color');
+    const expectedEnumValues = {
+      'RED',
+      'BLUE',
+      'BLACK',
+      'GREEN',
+      'YELLOW',
+      'LIME'
+    };
+    expect(color.values?.toSet(), expectedEnumValues);
+  });
+
+  test('Static final field values', () {
+    final example = classes.getExampleClass();
+    expect(example.getField("ON").defaultValue, equals(1));
+    expect(example.getField("OFF").defaultValue, equals(0));
+    expect(example.getField("PI").defaultValue, closeTo(3.14159, 0.001));
+    expect(
+        example.getField("SEMICOLON").defaultValue, equals(';'.codeUnitAt(0)));
+    expect(example.getField("SEMICOLON_STRING").defaultValue, equals(';'));
+  });
+
+  test('self referencing generic parameters', () {
+    final gp = classes.getClass('generics', 'GenericTypeParams');
+    final typeParams = gp.typeParams;
+    expect(typeParams[0].name, equals('S'));
+    expect(typeParams[0].bounds.map((e) => e.name), ['java.lang.CharSequence']);
+    expect(typeParams[1].name, equals('K'));
+    final selfBound = typeParams[1].bounds[0];
+    expect(selfBound.kind, Kind.declared);
+    expect(selfBound.name,
+        equals('com.github.dart_lang.jnigen.generics.GenericTypeParams'));
+    final selfBoundType = selfBound.type as DeclaredType;
+    expect(selfBoundType.params, hasLength(2));
+    expect(selfBoundType.params.map((e) => e.name), ['S', 'K']);
+    expect(selfBoundType.params.map((e) => e.kind),
+        [Kind.typeVariable, Kind.typeVariable]);
+  });
+}
+
+void main() async {
+  await checkLocallyBuiltDependencies();
+
+  final tempDir = getTempDir("jnigen_summary_tests_");
+
+  final sourceConfig =
+      getSummaryGenerationConfig(sourcePath: [simplePackagePath]);
+  final parsedFromSource = await getSummary(sourceConfig);
+
+  final targetDir = tempDir.createTempSync("compiled_classes_test_");
+  await compileJavaFiles(simplePackageDir, targetDir);
+  final classConfig = getSummaryGenerationConfig(classPath: [targetDir.path]);
+  final parsedFromClasses = await getSummary(classConfig);
+
+  group('source summary', () {
+    registerCommonTests(parsedFromSource);
+  });
+
+  group('compiled summary', () {
+    registerCommonTests(parsedFromClasses);
+  });
+
+  group('source-based summary features', () {
+    final classes = parsedFromSource;
+    test('Parameter names', () {
+      final example = classes.getExampleClass();
+      final joinStrings = example.getMethod('joinStrings');
+      expect(
+          joinStrings.params.map((p) => p.name).toList(), ['values', 'delim']);
+      final methodWithSeveralParams =
+          example.getMethod("methodWithSeveralParams");
+      expect(methodWithSeveralParams.params.map((p) => p.name).toList(),
+          ['ch', 's', 'a', 't', 'lt', 'wm']);
+    });
+
+    test('Javadoc comment', () {
+      final example = classes.getExampleClass();
+      final joinStrings = example.getMethod('joinStrings');
+      expect(joinStrings.javadoc?.comment,
+          contains("Joins the strings in the list using the given delimiter."));
+    });
+  });
+
+  tearDownAll(() => tempDir.deleteSync(recursive: true));
+}
diff --git a/pkgs/jnigen/test/test_util/bindings_test_setup.dart b/pkgs/jnigen/test/test_util/bindings_test_setup.dart
new file mode 100644
index 0000000..c493ad1
--- /dev/null
+++ b/pkgs/jnigen/test/test_util/bindings_test_setup.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Tests on generated code.
+//
+// Both the simple java example & jackson core classes example have tests in
+// same file, because the test runner will reuse the process, which leads to
+// reuse of the old JVM with old classpath if we have separate tests with
+// different classpaths.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:path/path.dart' hide equals;
+
+import 'test_util.dart';
+
+final simplePackageTest = join('test', 'simple_package_test');
+final jacksonCoreTest = join('test', 'jackson_core_test');
+final kotlinTest = join('test', 'kotlin_test');
+final jniJar = join('build', 'jni_libs', 'jni.jar');
+
+final simplePackageTestJava = join(simplePackageTest, 'java');
+final kotlinTestKotlin = join(kotlinTest, 'kotlin');
+
+late Directory tempClassDir;
+
+Future<void> bindingsTestSetup() async {
+  await runCommand('dart', [
+    'run',
+    'jni:setup',
+    '-p',
+    'jni',
+    '-s',
+    join(simplePackageTest, 'c_based', 'c_bindings'),
+    '-s',
+    join(kotlinTest, 'c_based', 'c_bindings'),
+    '-s',
+    join(jacksonCoreTest, 'third_party', 'c_based', 'c_bindings'),
+  ]);
+  tempClassDir =
+      Directory.current.createTempSync("jnigen_runtime_test_classpath_");
+  await compileJavaFiles(Directory(simplePackageTestJava), tempClassDir);
+  await runCommand('dart', [
+    'run',
+    'jnigen:download_maven_jars',
+    '--config',
+    join(jacksonCoreTest, 'jnigen.yaml')
+  ]);
+
+  final jacksonJars = await getJarPaths(join(jacksonCoreTest, 'third_party'));
+
+  await runCommand(
+    'mvn',
+    ['package'],
+    workingDirectory: kotlinTestKotlin,
+    runInShell: true,
+  );
+  // Jar including Kotlin runtime and dependencies.
+  final kotlinTestJar =
+      join(kotlinTestKotlin, 'target', 'kotlin_test-jar-with-dependencies.jar');
+
+  if (!Platform.isAndroid) {
+    Jni.spawn(dylibDir: join('build', 'jni_libs'), classPath: [
+      jniJar,
+      tempClassDir.path,
+      ...jacksonJars,
+      kotlinTestJar,
+    ]);
+  }
+  Jni.initDLApi();
+}
+
+void bindingsTestTeardown() {
+  tempClassDir.deleteSync(recursive: true);
+}
diff --git a/pkgs/jnigen/test/test_util/callback_types.dart b/pkgs/jnigen/test/test_util/callback_types.dart
new file mode 100644
index 0000000..3b3356e
--- /dev/null
+++ b/pkgs/jnigen/test/test_util/callback_types.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// These definitions could be in `test_util` but these are imported by android
+// integration tests, and we test_util imports several parts of package:jnigen.
+
+typedef TestCaseCallback = void Function();
+typedef TestRunnerCallback = void Function(
+  String description,
+  TestCaseCallback test,
+);
diff --git a/pkgs/jnigen/test/test_util/summary_util.dart b/pkgs/jnigen/test/test_util/summary_util.dart
new file mode 100644
index 0000000..0d44583
--- /dev/null
+++ b/pkgs/jnigen/test/test_util/summary_util.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jnigen/src/config/config.dart';
+import 'package:jnigen/src/logging/logging.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart';
+
+import 'test_util.dart';
+
+String getClassNameFromPath(String path) {
+  if (!path.endsWith('.java')) {
+    throw ArgumentError('Filename must end with java');
+  }
+  return path
+      .replaceAll('/', '.')
+      .replaceAll('\\', '.')
+      .substring(0, path.length - 5);
+}
+
+/// test/simple_package_test/java
+final simplePackagePath = join('test', 'simple_package_test', 'java');
+
+/// Directory(test/simple_package_test/java)
+final simplePackageDir = Directory(simplePackagePath);
+
+/// All Java files in simple_package_test/java
+final javaFiles = findFilesWithSuffix(simplePackageDir, '.java');
+
+/// All Java classes in simple_package_test/java
+final javaClasses = javaFiles.map(getClassNameFromPath).toList();
+
+// Remove individual class listings from one package,
+// and add the package name instead, for testing.
+
+const removalPackageForSummaryTests = 'com.github.dart_lang.jnigen.pkg2';
+
+/// List of FQNs passed to summarizer for simple_package_test.
+final summarizerClassesSpec = [
+  ...javaClasses.where((e) => !e.startsWith('$removalPackageForSummaryTests.')),
+  removalPackageForSummaryTests,
+];
+
+Config getSummaryGenerationConfig(
+    {List<String>? sourcePath, List<String>? classPath}) {
+  return Config(
+    outputConfig: OutputConfig(
+      bindingsType: BindingsType.dartOnly,
+      dartConfig: DartCodeOutputConfig(
+        path: Uri.file('unused.dart'),
+        structure: OutputStructure.singleFile,
+      ),
+    ),
+    // Make a defensive copy of class list, if some test mutates the list...
+    classes: summarizerClassesSpec.toList(),
+    sourcePath: sourcePath?.map((e) => Uri.file(e)).toList(),
+    classPath: classPath?.map((e) => Uri.file(e)).toList(),
+    logLevel: Level.WARNING,
+  );
+}
+
+void deleteTempDirWithDelay(Directory directory) {
+  try {
+    if (Platform.isWindows) {
+      // This appears to avoid "file used by another process" errors.
+      sleep(const Duration(seconds: 1));
+    }
+    directory.deleteSync(recursive: true);
+  } on FileSystemException catch (e) {
+    log.warning("Cannot delete directory: $e");
+  }
+}
diff --git a/pkgs/jnigen/test/test_util/test_util.dart b/pkgs/jnigen/test/test_util/test_util.dart
new file mode 100644
index 0000000..8fbc7ce
--- /dev/null
+++ b/pkgs/jnigen/test/test_util/test_util.dart
@@ -0,0 +1,250 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jnigen/jnigen.dart';
+import 'package:jnigen/src/util/find_package.dart';
+import 'package:path/path.dart' hide equals;
+import 'package:test/test.dart';
+import 'package:logging/logging.dart' show Level;
+
+import 'package:jnigen/src/logging/logging.dart';
+
+final _currentDirectory = Directory(".");
+
+// If changing these constants, grep for these values. In some places, test
+// package expects string literals.
+const largeTestTag = 'large_test';
+const summarizerTestTag = 'summarizer_test';
+
+Directory getTempDir(String prefix) {
+  return _currentDirectory.createTempSync(prefix);
+}
+
+Future<bool> isEmptyOrNotExistDir(String path) async {
+  final dir = Directory(path);
+  return (!await dir.exists()) || (await dir.list().length == 0);
+}
+
+/// Runs command, and prints output only if the exit status is non-zero.
+Future<int> runCommandReturningStatus(String exec, List<String> args,
+    {String? workingDirectory, bool runInShell = false}) async {
+  final proc = await Process.run(exec, args,
+      workingDirectory: workingDirectory, runInShell: runInShell);
+  if (proc.exitCode != 0) {
+    printError('command exited with exit status ${proc.exitCode}:\n'
+        '$exec ${args.join(" ")}\n');
+    printError(proc.stdout);
+    printError(proc.stderr);
+  }
+  return proc.exitCode;
+}
+
+Future<void> runCommand(
+  String exec,
+  List<String> args, {
+  String? workingDirectory,
+  bool runInShell = false,
+  String? messageOnFailure,
+}) async {
+  final status = await runCommandReturningStatus(
+    exec,
+    args,
+    workingDirectory: workingDirectory,
+    runInShell: runInShell,
+  );
+  if (status != 0) {
+    final message = messageOnFailure ?? 'Failed to execute $exec';
+    throw Exception('$message: Command exited with return code $status');
+  }
+}
+
+/// List all JAR files in [testRoot]/jar
+Future<List<String>> getJarPaths(String testRoot) async {
+  final jarPath = join(testRoot, 'jar');
+  if (!await Directory(jarPath).exists()) {
+    return [];
+  }
+  return Directory(jarPath)
+      .list()
+      .map((entry) => entry.path)
+      .where((path) => path.endsWith('.jar'))
+      .toList();
+}
+
+/// Read file normalizing CRLF to LF.
+String readFile(File file) => file.readAsStringSync().replaceAll('\r\n', '\n');
+
+/// Compares 2 hierarchies using `git diff --no-index`.
+void comparePaths(String path1, String path2) {
+  final diffCommand = [
+    "diff",
+    "--no-index",
+    if (stderr.supportsAnsiEscapes) "--color=always",
+  ];
+  final diffProc = Process.runSync("git", [...diffCommand, path1, path2]);
+  if (diffProc.exitCode != 0) {
+    final originalDiff = diffProc.stdout;
+    log.warning(
+        "Paths $path1 and $path2 differ, Running dart format on $path1.");
+    Process.runSync('dart', ['format', path1]);
+    final fallbackDiffProc =
+        Process.runSync("git", [...diffCommand, path1, path2]);
+    if (fallbackDiffProc.exitCode != 0) {
+      stderr.writeln(originalDiff);
+      throw Exception("Paths $path1 and $path2 differ");
+    }
+  }
+}
+
+Future<void> _generateTempBindings(Config config, Directory tempDir) async {
+  final tempSrc = tempDir.uri.resolve("src/");
+  final singleFile =
+      config.outputConfig.dartConfig.structure == OutputStructure.singleFile;
+  final tempLib = singleFile
+      ? tempDir.uri.resolve("generated.dart")
+      : tempDir.uri.resolve("lib/");
+  if (config.outputConfig.bindingsType == BindingsType.cBased) {
+    config.outputConfig.cConfig!.path = tempSrc;
+  }
+  config.outputConfig.dartConfig.path = tempLib;
+  config.logLevel = Level.WARNING;
+  await generateJniBindings(config);
+}
+
+/// Generates and compares bindings with reference bindings.
+///
+/// [dartReferenceBindings] can be directory or file depending on output
+/// configuration.
+///
+/// If the config generates C code, [cReferenceBindings] must be a non-null
+/// directory path.
+Future<void> generateAndCompareBindings(Config config) async {
+  final dartReferenceBindings =
+      config.outputConfig.dartConfig.path.toFilePath();
+  final cReferenceBindings = config.outputConfig.cConfig?.path.toFilePath();
+  final currentDir = Directory.current;
+  final tempDir = currentDir.createTempSync("jnigen_test_temp");
+  final tempSrc = tempDir.uri.resolve("src/");
+  final singleFile =
+      config.outputConfig.dartConfig.structure == OutputStructure.singleFile;
+  final tempLib = singleFile
+      ? tempDir.uri.resolve("generated.dart")
+      : tempDir.uri.resolve("lib/");
+  try {
+    await _generateTempBindings(config, tempDir);
+    comparePaths(dartReferenceBindings, tempLib.toFilePath());
+    if (config.outputConfig.bindingsType == BindingsType.cBased) {
+      comparePaths(cReferenceBindings!, tempSrc.toFilePath());
+    }
+  } finally {
+    tempDir.deleteSync(recursive: true);
+  }
+}
+
+Future<void> generateAndAnalyzeBindings(Config config) async {
+  final tempDir = Directory.current.createTempSync("jnigen_test_temp");
+  try {
+    await _generateTempBindings(config, tempDir);
+    final analyzeResult = Process.runSync("dart", ["analyze", tempDir.path]);
+    if (analyzeResult.exitCode != 0) {
+      stderr.write(analyzeResult.stdout);
+      fail('Analyzer exited with non-zero status (${analyzeResult.exitCode})');
+    }
+  } finally {
+    tempDir.deleteSync(recursive: true);
+  }
+}
+
+final summarizerJar = join('.', '.dart_tool', 'jnigen', 'ApiSummarizer.jar');
+
+Future<void> failIfSummarizerNotBuilt() async {
+  final jarExists = await File(summarizerJar).exists();
+  if (!jarExists) {
+    stderr.writeln();
+    log.fatal('Please build summarizer by running '
+        '`dart run jnigen:setup` and try again');
+  }
+  final isJarStale = jarExists &&
+      await isPackageModifiedAfter(
+          'jnigen', await File(summarizerJar).lastModified(), 'java/');
+  if (isJarStale) {
+    stderr.writeln();
+    log.fatal('Summarizer is not rebuilt after recent changes. '
+        'Please run `dart run jnigen:setup` and try again.');
+  }
+}
+
+const bindingTests = [
+  'jackson_core_test',
+  'simple_package_test',
+  'kotlin_test',
+];
+
+const registrantName = 'runtime_test_registrant.dart';
+const replicaName = 'runtime_test_registrant_dartonly_generated.dart';
+
+void warnIfRuntimeTestsAreOutdated() {
+  final runtimeTests = join('test', 'generated_runtime_test.dart');
+  if (!File(runtimeTests).existsSync()) {
+    log.fatal('Runtime test files not found. To run binding '
+        'runtime tests, please generate them by running '
+        '`dart run tool/generate_runtime_tests.dart`');
+  }
+  const regenInstr = 'Please run `dart run tool/generate_runtime_tests.dart` '
+      'and try again.';
+  for (var testName in bindingTests) {
+    final registrant = File(join('test', testName, registrantName));
+    final replica = File(join('test', testName, replicaName));
+    if (!replica.existsSync()) {
+      log.fatal(
+        'One or more generated runtime tests do not exist. $regenInstr',
+      );
+    }
+    if (replica.lastModifiedSync().isBefore(registrant.lastModifiedSync())) {
+      log.fatal(
+        'One or more generated runtime tests are not up-to-date. $regenInstr',
+      );
+    }
+  }
+}
+
+/// Verifies if locally built dependencies (currently `ApiSummarizer`)
+/// are up-to-date.
+Future<void> checkLocallyBuiltDependencies() async {
+  await failIfSummarizerNotBuilt();
+  warnIfRuntimeTestsAreOutdated();
+}
+
+void generateAndCompareBothModes(
+  String description,
+  Config cBasedConfig,
+  Config dartOnlyConfig,
+) {
+  test('$description (cBased)', () async {
+    await generateAndCompareBindings(cBasedConfig);
+  });
+  test('$description (dartOnly)', () async {
+    await generateAndCompareBindings(dartOnlyConfig);
+  });
+}
+
+List<String> findFilesWithSuffix(Directory dir, String suffix) {
+  return dir
+      .listSync(recursive: true)
+      .map((entry) => relative(entry.path, from: dir.path))
+      .where((path) => path.endsWith(suffix))
+      .toList();
+}
+
+Future<void> compileJavaFiles(Directory root, Directory target) async {
+  final javaFiles = findFilesWithSuffix(root, '.java');
+  await runCommand(
+    'javac',
+    ['-d', target.absolute.path, ...javaFiles],
+    workingDirectory: root.path,
+    messageOnFailure: 'Cannot compile java sources',
+  );
+}
diff --git a/pkgs/jnigen/tool/command_runner.dart b/pkgs/jnigen/tool/command_runner.dart
new file mode 100644
index 0000000..7bf925e
--- /dev/null
+++ b/pkgs/jnigen/tool/command_runner.dart
@@ -0,0 +1,109 @@
+import 'dart:io';
+
+const ansiRed = '\x1b[31m';
+const ansiDefault = '\x1b[39;49m';
+
+void printError(Object? message) {
+  if (stderr.supportsAnsiEscapes) {
+    message = '$ansiRed$message$ansiDefault';
+  }
+  stderr.writeln(message);
+}
+
+class StepFailure implements Exception {
+  StepFailure(this.name);
+  String name;
+  @override
+  String toString() => 'step failed: $name';
+}
+
+abstract class Step {
+  /// Runs this step, raises an exception if something fails.
+  Future<void> run();
+}
+
+class Callback implements Step {
+  Callback(this.name, this.function);
+  String name;
+  Future<void> Function() function;
+  @override
+  Future<void> run() => function();
+}
+
+class Command implements Step {
+  Command(this.exec, this.args, this.workingDirectory);
+  final String exec;
+  final List<String> args;
+  final Uri workingDirectory;
+
+  @override
+  Future<void> run() async {
+    final result = await Process.run(
+      exec,
+      args,
+      workingDirectory: workingDirectory.toFilePath(),
+      runInShell: true,
+    );
+    if (result.exitCode != 0) {
+      printError(result.stdout);
+      printError(result.stderr);
+      final commandString = "$exec ${args.join(" ")}";
+      stderr.writeln("failure executing command: $commandString");
+      throw StepFailure(commandString);
+    }
+  }
+}
+
+class Runner {
+  static final gitRoot = getRepositoryRoot();
+  Runner(this.name, this.defaultWorkingDir);
+  String name;
+  Uri defaultWorkingDir;
+  final steps = <Step>[];
+  final cleanupSteps = <Step>[];
+
+  void chainCommand(String exec, List<String> args, {Uri? workingDirectory}) =>
+      _addCommand(steps, exec, args, workingDirectory: workingDirectory);
+
+  void chainCleanupCommand(String exec, List<String> args,
+          {Uri? workingDirectory}) =>
+      _addCommand(cleanupSteps, exec, args, workingDirectory: workingDirectory);
+
+  void _addCommand(List<Step> list, String exec, List<String> args,
+      {Uri? workingDirectory}) {
+    list.add(Command(exec, args, (workingDirectory ?? defaultWorkingDir)));
+  }
+
+  void chainCallback(String name, Future<void> Function() callback) {
+    steps.add(Callback(name, callback));
+  }
+
+  Future<void> run() async {
+    stderr.writeln("started: $name");
+    var error = false;
+    for (var step in steps) {
+      try {
+        await step.run();
+      } on StepFailure catch (e) {
+        stderr.writeln(e);
+        error = true;
+        exitCode = 1;
+        break;
+      }
+    }
+    stderr.writeln('${error ? "failed" : "complete"}: $name');
+    for (var step in cleanupSteps) {
+      try {
+        await step.run();
+      } on Exception catch (e) {
+        printError("ERROR: $e");
+      }
+    }
+  }
+}
+
+Uri getRepositoryRoot() {
+  final gitCommand = Process.runSync("git", ["rev-parse", "--show-toplevel"]);
+  final output = gitCommand.stdout as String;
+  return Uri.directory(output.trim());
+}
diff --git a/pkgs/jnigen/tool/generate_runtime_tests.dart b/pkgs/jnigen/tool/generate_runtime_tests.dart
new file mode 100644
index 0000000..75a1ea2
--- /dev/null
+++ b/pkgs/jnigen/tool/generate_runtime_tests.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:path/path.dart';
+import 'package:args/args.dart';
+
+import 'package:jnigen/src/logging/logging.dart';
+
+final lineBreak = Platform.isWindows ? '\r\n' : '\n';
+
+void runCommand(String exec, List<String> args) {
+  final proc = Process.runSync(exec, args, runInShell: true);
+  log.info('Execute $exec ${args.join(" ")}');
+  if (proc.exitCode != 0) {
+    exitCode = proc.exitCode;
+    printError(proc.stdout);
+    printError(proc.stderr);
+    throw Exception('Command failed: $exec ${args.join(" ")}');
+  }
+}
+
+const testPath = 'test';
+const registrantFileName = 'runtime_test_registrant.dart';
+const dartOnlyRegistrantFileName =
+    'runtime_test_registrant_dartonly_generated.dart';
+
+// Paths of generated files, should not be checked in.
+// If you change this, add the corresponding entry to .gitignore as well.
+const replicaSuffix = '_dartonly_generated.dart';
+final runnerFilePath = join(testPath, 'generated_runtime_test.dart');
+final androidRunnerFilePath =
+    join('android_test_runner', 'integration_test', 'runtime_test.dart');
+
+final generatedComment =
+    '// Generated file. Do not edit or check-in to version control.$lineBreak';
+const copyright = '''
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+''';
+
+const bindingTests = [
+  'jackson_core_test',
+  'simple_package_test',
+  'kotlin_test',
+];
+
+const hasThirdPartyDir = {'jackson_core_test'};
+
+final _generatedFiles = <String>[
+  for (var testName in bindingTests)
+    join(testPath, testName, dartOnlyRegistrantFileName),
+  runnerFilePath,
+  androidRunnerFilePath,
+];
+
+void generateReplicasAndRunner() {
+  final imports = <String, String>{};
+  for (var testName in bindingTests) {
+    final registrant = join(testName, registrantFileName);
+    final registrantFile = File(join(testPath, registrant));
+    final contents = registrantFile
+        .readAsStringSync()
+        .replaceAll('c_based/dart_bindings/', 'dart_only/dart_bindings/');
+
+    final replica = registrant.replaceAll('.dart', replicaSuffix);
+    final replicaFile = File(join(testPath, replica));
+    replicaFile.writeAsStringSync('$generatedComment$lineBreak$contents');
+    log.info('Generated $replica');
+    imports['${testName}_c_based'] =
+        Uri.file(registrant).toFilePath(windows: false);
+    imports['${testName}_dart_only'] =
+        Uri.file(replica).toFilePath(windows: false);
+  }
+  final importStrings = imports.entries
+      .map((e) => 'import "${e.value}" as ${e.key};')
+      .join(lineBreak);
+  final androidImportStrings = imports.entries
+      .map((e) => 'import "../../test/${e.value}" as ${e.key};')
+      .join(lineBreak);
+  final runStrings = imports.keys
+      .map((name) => '$name.registerTests("$name", test);')
+      .join('$lineBreak  ');
+  final runnerProgram = '''
+$generatedComment
+$copyright
+import 'package:test/test.dart';
+import 'test_util/bindings_test_setup.dart' as setup;
+
+$importStrings
+
+void main() {
+  setUpAll(setup.bindingsTestSetup);
+  $runStrings
+  tearDownAll(setup.bindingsTestTeardown);
+}
+''';
+  final runnerFile = File(runnerFilePath);
+  runnerFile.writeAsStringSync(runnerProgram);
+  log.info('Generated runner $runnerFilePath');
+
+  final androidRunnerProgram = '''
+$generatedComment
+$copyright
+import "package:flutter_test/flutter_test.dart";
+import "package:jni/jni.dart";
+
+$androidImportStrings
+
+typedef TestCaseCallback = void Function();
+
+void test(String description, TestCaseCallback testCase) {
+  testWidgets(description, (widgetTester) async => testCase());
+}
+
+void main() {
+  Jni.initDLApi();
+  $runStrings
+}
+''';
+  File(androidRunnerFilePath).writeAsStringSync(androidRunnerProgram);
+  log.info('Generated android runner: $androidRunnerFilePath');
+
+  final cMakePath =
+      join('android_test_runner', 'android', 'app', 'CMakeLists.txt');
+
+  final cmakeSubdirs = bindingTests.map((testName) {
+    final indirect = hasThirdPartyDir.contains(testName) ? '/third_party' : '';
+    return 'add_subdirectory'
+        '(../../../test/$testName$indirect/c_based/c_bindings '
+        '${testName}_build)';
+  }).join(lineBreak);
+  final cMakeConfig = '''
+## Parent CMake for Android native build target. This will build
+## all C bindings from tests.
+
+cmake_minimum_required(VERSION 3.10)
+
+project(simple_package VERSION 0.0.1 LANGUAGES C)
+
+$cmakeSubdirs
+''';
+  File(cMakePath).writeAsStringSync(cMakeConfig);
+  log.info('Wrote Android CMake file: $cMakePath');
+}
+
+void cleanup() {
+  for (var path in _generatedFiles) {
+    File(path).deleteSync();
+    log.info('Deleted $path');
+  }
+}
+
+void main(List<String> args) async {
+  final parser = ArgParser()
+    ..addFlag(
+      'help',
+      abbr: 'h',
+      help: 'show help',
+      negatable: false,
+    )
+    ..addFlag(
+      'clean',
+      abbr: 'c',
+      help: 'clear generated files',
+      negatable: false,
+    );
+  final argResults = parser.parse(args);
+  if (argResults['help']) {
+    stderr.writeln(
+        'Generates runtime tests for both Dart-only and C based bindings.');
+    stderr.writeln(parser.usage);
+    return;
+  } else if (argResults['clean']) {
+    cleanup();
+  } else {
+    generateReplicasAndRunner();
+    runCommand('dart', ['format', ..._generatedFiles]);
+  }
+}
diff --git a/pkgs/jnigen/tool/pr_checks.dart b/pkgs/jnigen/tool/pr_checks.dart
new file mode 100644
index 0000000..3e983c2
--- /dev/null
+++ b/pkgs/jnigen/tool/pr_checks.dart
@@ -0,0 +1,180 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This is a script to run a subset of CI checks on development machine before
+// submitting PR. This prevents from accidentally forgetting to format or
+// remove an unused import.
+
+// This is rough around the edges. This script may give false positives such
+// as some gitignore'd temporary files failing the comparison.
+
+// On windows, please install 'diffutils' using your favorite package installer
+// for this script to work correctly. Also ensure that clang-format is on your
+// PATH.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+
+import 'command_runner.dart';
+
+// Flags
+const _clone = 'clone';
+
+void main(List<String> arguments) async {
+  final parser = ArgParser()
+    ..addFlag(
+      _clone,
+      help: 'Run checks in a cloned copy of the project.',
+      defaultsTo: true,
+      negatable: true,
+    );
+  final argResults = parser.parse(arguments);
+  final shouldClone = argResults[_clone] as bool;
+  final gitRoot = getRepositoryRoot();
+
+  // change to project root
+  Directory.current = gitRoot.toFilePath();
+
+  var tempDir = Directory.current;
+  if (shouldClone) {
+    tempDir = Directory.current.createTempSync('jnigen_checks_clone_');
+    final gitClone = Runner("Clone jni", Directory.current.uri)
+      ..chainCommand('git', ['clone', '.', tempDir.path]);
+    await gitClone.run();
+  }
+
+  final jniPath = tempDir.uri.resolve("jni/");
+  final jnigenPath = tempDir.uri.resolve("jnigen/");
+  final pubGet = Runner("Pub get", tempDir.uri)
+    ..chainCommand("flutter", ["pub", "get", "--offline"],
+        workingDirectory: jniPath)
+    ..chainCommand("dart", ["pub", "get", "--offline"],
+        workingDirectory: jnigenPath);
+  await pubGet.run();
+
+  final jniAnalyze = Runner("Analyze JNI", jniPath);
+  jniAnalyze
+    ..chainCommand("dart", ["analyze", "--fatal-infos"])
+    ..chainCommand(
+        "dart", ["format", "--output=none", "--set-exit-if-changed", "."])
+    ..chainCommand(
+        "clang-format",
+        [
+          "--dry-run",
+          "-Werror",
+          "dartjni.c",
+          "dartjni.h",
+          "third_party/global_jni_env.c",
+          "third_party/global_jni_env.h",
+        ],
+        workingDirectory: jniPath.resolve("src/"));
+  final jniTest = Runner("Test JNI", jniPath)
+    ..chainCommand("dart", ["run", "jni:setup"])
+    ..chainCommand("dart", ["test", "-j", "1"]);
+  unawaited(jniAnalyze.run().then((f) => jniTest.run()));
+
+  List<String> getJavaFiles(String relativePath) =>
+      Directory.fromUri(jnigenPath.resolve(relativePath))
+          .listSync(recursive: true)
+          .where((file) => file.path.endsWith(".java"))
+          .map((file) => file.path)
+          .toList();
+
+  final javaFiles = getJavaFiles("java/") +
+      getJavaFiles("test/simple_package_test") +
+      getJavaFiles("example/in_app_java") +
+      getJavaFiles("example/notification_plugin");
+
+  final checkJavaFormat = Runner("Check java format", jnigenPath)
+    ..chainCommand(
+        "google-java-format", ["-n", "--set-exit-if-changed", ...javaFiles]);
+
+  unawaited(checkJavaFormat.run());
+
+  final ffigenBindingsPath = getRepositoryRoot()
+      .resolve("jni/lib/src/third_party/jni_bindings_generated.dart");
+  final ffigenBindings = File.fromUri(ffigenBindingsPath);
+  final oldBindingsText = ffigenBindings.readAsStringSync();
+  final ffigenCompare = Runner("Generate & Compare FFIGEN bindings", jniPath)
+    ..chainCommand("dart", ["run", "ffigen", "--config", "ffigen.yaml"])
+    ..chainCallback("compare bindings", () async {
+      final newBindingsText = await ffigenBindings.readAsString();
+      if (newBindingsText != oldBindingsText) {
+        await ffigenBindings.writeAsString(oldBindingsText);
+        throw "new JNI.h bindings differ from old bindings";
+      }
+    });
+  unawaited(ffigenCompare.run());
+
+  final jnigenAnalyze = Runner("Analyze jnigen", jnigenPath)
+    ..chainCommand("dart", ["analyze", "--fatal-infos"])
+    ..chainCommand(
+        "dart", ["format", "--output=none", "--set-exit-if-changed", "."])
+    ..chainCommand("dart", ["run", "jnigen:setup"]);
+
+  // Tests may need more time when running on systems with less cores.
+  // So '--timeout 2x' is specified.
+  final jnigenTest = Runner("Test jnigen", gitRoot.resolve("jnigen/"))
+    ..chainCommand("dart", ["test", "--timeout", "2x"]);
+
+  // Note: Running in_app_java and notification_plugin checks on source dir
+  // itself, because running flutter build in cloned dir will take time.
+  final compareInAppJavaBindings = Runner(
+      "Generate & compare InAppJava bindings",
+      gitRoot.resolve("jnigen/example/in_app_java"))
+    ..chainCommand("dart", [
+      "run",
+      "jnigen",
+      "--config",
+      "jnigen.yaml",
+      "-Doutput.c.path=src_temp/",
+      "-Doutput.dart.path=_temp.dart",
+    ])
+    ..chainCommand("diff", ["lib/android_utils.dart", "_temp.dart"])
+    ..chainCommand("diff", ["-qr", "src/android_utils/", "src_temp/"])
+    ..chainCleanupCommand("rm", ["-r", "_temp.dart", "src_temp"]);
+
+  final comparePdfboxBindings = Runner("Generate & compare PdfBox Bindings",
+      gitRoot.resolve("jnigen/example/pdfbox_plugin"))
+    ..chainCommand("dart", [
+      "run",
+      "jnigen",
+      "--config",
+      "jnigen.yaml",
+      "-Doutput.c.path=src_temp/",
+      "-Doutput.dart.path=lib_temp/",
+    ])
+    ..chainCommand("diff", ["-qr", "lib/src/third_party/", "lib_temp/"])
+    ..chainCommand("diff", ["-qr", "src/", "src_temp/"])
+    ..chainCleanupCommand("rm", ["-r", "lib_temp", "src_temp"]);
+
+  final compareNotificationPluginBindings = Runner(
+      "Generate & compare NotificationPlugin Bindings",
+      gitRoot.resolve("jnigen/example/notification_plugin"))
+    ..chainCommand("dart", [
+      "run",
+      "jnigen",
+      "--config",
+      "jnigen.yaml",
+      "-Doutput.c.path=src_temp/",
+      "-Doutput.dart.path=_temp.dart",
+    ])
+    ..chainCommand("diff", ["lib/notifications.dart", "_temp.dart"])
+    ..chainCommand("diff", ["-qr", "src/", "src_temp/"])
+    ..chainCleanupCommand("rm", ["-r", "_temp.dart", "src_temp"]);
+
+  unawaited(jnigenAnalyze.run().then((_) {
+    final test = jnigenTest.run();
+    final inAppJava = compareInAppJavaBindings.run();
+    final pdfBox = comparePdfboxBindings.run();
+    final notificationPlugin = compareNotificationPluginBindings.run();
+    return Future.wait([test, inAppJava, pdfBox, notificationPlugin]);
+  }).then((_) {
+    if (shouldClone) {
+      tempDir.deleteSync(recursive: true);
+    }
+  }));
+}
diff --git a/pkgs/jnigen/tool/regenerate_all_bindings.dart b/pkgs/jnigen/tool/regenerate_all_bindings.dart
new file mode 100644
index 0000000..19b7642
--- /dev/null
+++ b/pkgs/jnigen/tool/regenerate_all_bindings.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Run this script after any change which affects generated bindings.
+//
+// This will update all generated bindings in the whole repository.
+
+import 'dart:io';
+
+import 'command_runner.dart';
+
+const scripts = [
+  "test/jackson_core_test/generate.dart",
+  "test/simple_package_test/generate.dart",
+  "test/kotlin_test/generate.dart",
+];
+
+const yamlBasedExamples = [
+  "example/in_app_java",
+  "example/pdfbox_plugin",
+  "example/notification_plugin",
+  "example/kotlin_plugin",
+];
+
+void main() async {
+  final runners = <Runner>[];
+  final current = Directory.current.uri;
+  for (var script in scripts) {
+    runners.add(Runner("Run generate script: $script", current)
+      ..chainCommand("dart", ["run", script]));
+  }
+
+  for (var yamlDir in yamlBasedExamples) {
+    runners.add(
+        Runner("Regenerate bindings in $yamlDir", current.resolve(yamlDir))
+          ..chainCommand("dart", ["run", "jnigen", "--config", "jnigen.yaml"]));
+  }
+
+  await Future.wait(runners.map((runner) => runner.run()).toList());
+}