diff --git a/.github/workflows/dep_build_v3.yml b/.github/workflows/dep_build_v3.yml index d2cadb3d6..ad09e93e9 100644 --- a/.github/workflows/dep_build_v3.yml +++ b/.github/workflows/dep_build_v3.yml @@ -15,13 +15,26 @@ jobs: strategy: fail-fast: false matrix: - java_version: ['17', '21'] + # 17, 21: all modules except Blackbird (profile-gated to JDK 25+). + # 25: only the Blackbird modules, which the other two leave out. + java_version: ['17', '21', '25'] env: JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: 3.x + # Blackbird builds only on JDK 25+, but its jar must run on 17-24 too + # (module discovery skips it there); blackbird-tests starts those JDKs. + # Keep in sync with main.yml. + - name: Set up older JDKs for Blackbird runtime tests + if: ${{ matrix.java_version == '25' }} + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 + with: + distribution: 'temurin' + java-version: | + 17 + 21 - name: Set up JDK uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: @@ -29,6 +42,16 @@ jobs: java-version: ${{ matrix.java_version }} cache: 'maven' - name: Build and test - run: ./mvnw -B -ff -ntp clean verify + run: | + BUILD_ARGS="" + if [ "${{ matrix.java_version }}" = "25" ]; then + if [ -z "${JAVA_HOME_17_X64:-}" ] || [ -z "${JAVA_HOME_21_X64:-}" ]; then + echo "::error::JDK 17/21 homes not found; Blackbird old-JDK tests would be skipped" + exit 1 + fi + BUILD_ARGS="-pl blackbird,blackbird-tests,blackbird-jpms-tests -am" + BUILD_ARGS="${BUILD_ARGS} -Dblackbird.test.oldJdks=${JAVA_HOME_17_X64},${JAVA_HOME_21_X64}" + fi + ./mvnw -B -ff -ntp clean verify ${BUILD_ARGS} # No recursive rebuild (yet?) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fad8c7fd5..05ef7b445 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,12 +20,24 @@ jobs: matrix: java_version: ['17', '21', '24', '25'] include: - - java_version: '17' + # Release/deploy runs on 25 so the blackbird module (profile-gated + # to JDK 25+) is part of the deployed reactor. + - java_version: '25' release_build: 'R' env: JAVA_OPTS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + # Blackbird builds only on JDK 25+, but its jar must run on 17-24 too + # (module discovery skips it there); blackbird-tests starts those JDKs. + - name: Set up older JDKs for Blackbird runtime tests + if: ${{ matrix.java_version == '25' }} + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 + with: + distribution: 'temurin' + java-version: | + 17 + 21 - name: Set up JDK uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: @@ -39,7 +51,16 @@ jobs: # gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import # gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase - name: Build - run: ./mvnw -B -ff -ntp clean verify + run: | + OLD_JDKS="" + if [ "${{ matrix.java_version }}" = "25" ]; then + if [ -z "${JAVA_HOME_17_X64:-}" ] || [ -z "${JAVA_HOME_21_X64:-}" ]; then + echo "::error::JDK 17/21 homes not found; Blackbird old-JDK tests would be skipped" + exit 1 + fi + OLD_JDKS="-Dblackbird.test.oldJdks=${JAVA_HOME_17_X64},${JAVA_HOME_21_X64}" + fi + ./mvnw -B -ff -ntp clean verify ${OLD_JDKS} - name: Extract project Maven version id: projectVersion run: echo "version=$(./mvnw org.apache.maven.plugins:maven-help-plugin:3.5.1:evaluate -DforceStdout -Dexpression=project.version -q)" >> $GITHUB_OUTPUT diff --git a/CLAUDE.md b/CLAUDE.md index 3a44da006..7a47e0538 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,9 @@ Jackson modules-base is a multi-module Maven project containing 11 foundational (paranamer is not part of 3.x — it only appears commented out in the root `pom.xml`.) -There are also two test-only, never-published modules: `afterburner-tests` and `blackbird-tests`. They have no `module-info.java`, so their test POJOs live in the unnamed module and exercise the bytecode-generation paths that are disabled for beans in named JPMS modules (see each module's `README.md`). +There are also three test-only, never-published modules: `afterburner-tests` and `blackbird-tests` have no `module-info.java`, so their test POJOs live in the unnamed module, as in most applications (see each module's `README.md`). For Afterburner this covers the bytecode-generation paths it disables for beans in named JPMS modules; Blackbird's in-tree tests run inside Blackbird's own named module, and `blackbird-tests` also holds the tests that need the packaged jar or other JDKs; `blackbird-jpms-tests` is a foreign named module that requires blackbird and runs on the module path. + +**Blackbird builds only on JDK 25+:** `blackbird`, `blackbird-tests` and `blackbird-jpms-tests` are in the root pom's `blackbird-jdk25` profile (activated on JDK 25+). On an older JDK the full build silently leaves them out, and `-pl blackbird` fails with "Could not find the selected project in the reactor"; use a JDK 25 (`JAVA_HOME=... ./mvnw ...`) for Blackbird work. Maven coordinates: groupId `tools.jackson.module`, parent `tools.jackson:jackson-base`; depends on `tools.jackson.core:jackson-databind`. @@ -27,7 +29,10 @@ Maven coordinates: groupId `tools.jackson.module`, parent `tools.jackson:jackson ./mvnw -B -ff -ntp test # Run tests for a single module -./mvnw -B -ff -ntp test -pl blackbird +./mvnw -B -ff -ntp test -pl mrbean + +# Blackbird modules need JDK 25+ (not in the reactor on older JDKs) +JAVA_HOME=/path/to/jdk25 ./mvnw -B -ff -ntp test -pl blackbird # Run a single test class ./mvnw -B -ff -ntp test -pl afterburner -Dtest=TestSimpleSerialize @@ -36,7 +41,7 @@ Maven coordinates: groupId `tools.jackson.module`, parent `tools.jackson:jackson ./mvnw -B -ff -ntp test -pl afterburner -Dtest=BasicDeserializeTest#testIntMethod ``` -Java 17 is the minimum (`javac.src.version` 17); CI tests against 17, 21, 24, 25. osgi and afterburner-tests add `--add-opens java.base/java.lang=ALL-UNNAMED` to their test `argLine`. +Java 17 is the minimum (`javac.src.version` 17); CI (`main.yml`) tests against 17, 21, 24, 25; Blackbird builds only in the 25 job, which also runs its runtime tests on 17 and 21 through `-Dblackbird.test.oldJdks`. The databind-push rebuild (`dep_build_v3.yml`) builds all other modules on 17 and 21 and only the Blackbird modules on 25, with the same old-JDK tests; keep the old-JDK wiring of the two workflows in sync. osgi and afterburner-tests add `--add-opens java.base/java.lang=ALL-UNNAMED` to their test `argLine`. ## Architecture @@ -44,12 +49,12 @@ Every module follows the same pattern: - A `*Module` class (e.g., `AfterburnerModule`, `BlackbirdModule`) extends Jackson's `tools.jackson.databind.JacksonModule`, registered on a mapper builder (e.g., `JsonMapper.builder().addModule(...)`) - Package: `tools.jackson.module.` (hyphens dropped or turned into sub-packages: `androidrecord`, `noctordeser`, `spisubtypes`, `jakarta.xmlbind`) - `PackageVersion.java` is generated from `PackageVersion.java.in` by the replacer Maven plugin — do not edit manually -- JPMS module descriptors are real sources: `/src/main/java/module-info.java` (and `src/test/java/module-info.java` for tests). Moditect is no longer used; stray `src/moditect/` dirs in blackbird and android-record are leftovers +- JPMS module descriptors are real sources: `/src/main/java/module-info.java` (and `src/test/java/module-info.java` for tests). Moditect is no longer used; the stray `src/moditect/` dir in android-record is a leftover - SPI registration: `src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule` (all modules except guice, guice7, osgi) ### Key module categories -- **Performance (bytecode generation):** afterburner (Byte Buddy-based), blackbird (LambdaMetafactory-based, modern replacement) — both disable themselves automatically in GraalVM native images via `NativeImageUtil.isInNativeImage()` check in `setupModule` +- **Performance (bytecode generation):** afterburner (Byte Buddy-based), blackbird (generated per-bean codecs through the JDK ClassFile API; a multi-release jar that builds on JDK 25+ and is active only on 25+; on Java 17-24 module discovery skips it and explicit `addModule` throws `IllegalStateException`, see `blackbird/README.md`) — both disable themselves automatically in GraalVM native images via `NativeImageUtil.isInNativeImage()` check in `setupModule` - **DI integration:** guice (javax.inject), guice7 (jakarta.inject) — note: guice's `ObjectMapperModule` implements Guice's `com.google.inject.Module`, not Jackson's `JacksonModule` - **Type materialization:** mrbean (generates implementations for interfaces/abstract classes, Byte Buddy-based) - **Annotation support:** jaxb (javax.xml.bind), jakarta-xmlbind (jakarta.xml.bind) diff --git a/blackbird-jpms-tests/pom.xml b/blackbird-jpms-tests/pom.xml new file mode 100644 index 000000000..547df8d60 --- /dev/null +++ b/blackbird-jpms-tests/pom.xml @@ -0,0 +1,177 @@ + + + 4.0.0 + + tools.jackson.module + jackson-modules-base + 3.3.0-SNAPSHOT + + jackson-module-blackbird-jpms-tests + Jackson module: Blackbird JPMS integration tests + jar + Module-path integration tests: a foreign named module that +requires blackbird, exercising codec generation for its own non-public +classes. Not published. + + + 25 + 25 + 25 + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + default-jar + none + + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + org.sonatype.central + central-publishing-maven-plugin + + + injected-central-publishing + none + + + + + org.apache.felix + maven-bundle-plugin + + + bundle-manifest + none + + + default-install + none + + + + + org.cyclonedx + cyclonedx-maven-plugin + + true + + + + org.gradlex + gradle-module-metadata-maven-plugin + + + default + none + + + + + org.jacoco + jacoco-maven-plugin + + + report + none + + + + + + org.apache.maven.plugins + maven-source-plugin + + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + + + + org.apache.maven.plugins + maven-gpg-plugin + + true + + + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + + true + + true + + + + + + + + + tools.jackson.module + jackson-module-blackbird + ${project.version} + + + org.junit.jupiter + junit-jupiter + test + + + diff --git a/blackbird-jpms-tests/src/main/java/blackbird/jpms/test/beans/Beans.java b/blackbird-jpms-tests/src/main/java/blackbird/jpms/test/beans/Beans.java new file mode 100644 index 000000000..d83f90e4c --- /dev/null +++ b/blackbird-jpms-tests/src/main/java/blackbird/jpms/test/beans/Beans.java @@ -0,0 +1,16 @@ +package blackbird.jpms.test.beans; + +import java.lang.invoke.MethodHandles; + +/** Hands tests this module's lookup and its non-public bean type. */ +public final class Beans { + private Beans() {} + + public static MethodHandles.Lookup lookup() { + return MethodHandles.lookup(); + } + + public static Class pkgBeanClass() { + return PkgBean.class; + } +} diff --git a/blackbird-jpms-tests/src/main/java/blackbird/jpms/test/beans/PkgBean.java b/blackbird-jpms-tests/src/main/java/blackbird/jpms/test/beans/PkgBean.java new file mode 100644 index 000000000..45846385a --- /dev/null +++ b/blackbird-jpms-tests/src/main/java/blackbird/jpms/test/beans/PkgBean.java @@ -0,0 +1,15 @@ +package blackbird.jpms.test.beans; + +// Deliberately package-private, in a foreign named module: the codec +// defines in blackbird's own module context and reaches the members only +// through handles databind's fixAccess made accessible (see +// JpmsPackagePrivateTest). +class PkgBean { + private int count; + private String name; + + public int getCount() { return count; } + public void setCount(int v) { count = v; } + public String getName() { return name; } + public void setName(String v) { name = v; } +} diff --git a/blackbird-jpms-tests/src/main/java/module-info.java b/blackbird-jpms-tests/src/main/java/module-info.java new file mode 100644 index 000000000..9e9471324 --- /dev/null +++ b/blackbird-jpms-tests/src/main/java/module-info.java @@ -0,0 +1,15 @@ +// A foreign named module from Blackbird's point of view: it requires the +// blackbird module and holds non-public beans plus a lookup of its own, the +// exact arrangement a modular application has. +module blackbird.jpms.test +{ + requires tools.jackson.databind; + requires tools.jackson.module.blackbird; + + exports blackbird.jpms.test.beans; + // What a modular application must do for stock databind to construct + // non-public beans reflectively - and the ONLY requirement blackbird + // adds: none. Deliberately NOT opened to blackbird: the accelerated path + // rides databind's fixAccess through unreflected constant handles. + opens blackbird.jpms.test.beans to tools.jackson.databind; +} diff --git a/blackbird-jpms-tests/src/test/java/blackbird/jpms/test/beans/JpmsPackagePrivateTest.java b/blackbird-jpms-tests/src/test/java/blackbird/jpms/test/beans/JpmsPackagePrivateTest.java new file mode 100644 index 000000000..a6bbc7e27 --- /dev/null +++ b/blackbird-jpms-tests/src/test/java/blackbird/jpms/test/beans/JpmsPackagePrivateTest.java @@ -0,0 +1,94 @@ +package blackbird.jpms.test.beans; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.module.blackbird.BlackbirdModule; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Runs on the module path as the named module blackbird.jpms.test, which + * requires blackbird: the arrangement a modular application has. A + * package-private bean of this module accelerates with no user lookup at all: + * the only JPMS requirement is the {@code opens ... to tools.jackson.databind} + * that stock databind already needs. Databind's fixAccess marks the members + * accessible, the codec reaches them through unreflected constant handles, + * and the generated class defines in blackbird's own module context. The + * lookup-supplying constructor remains a supported (now redundant) API. + * + * Engagement is asserted through the blackbird.debug.codegen diagnostic + * stream: byte-identical output makes generated and stock otherwise + * indistinguishable from out here. + */ +public class JpmsPackagePrivateTest +{ + private static final String DOC = "{\"count\":7,\"name\":\"x\"}"; + + // The lookup API is a released contract and must keep working even though + // acceleration no longer needs it. + @Test + public void packagePrivateBeanAcceleratesWithModuleLookup() throws Exception { + ObjectMapper mapper = JsonMapper.builder() + .addModule(new BlackbirdModule() { + private static final long serialVersionUID = 1L; + @Override + @SuppressWarnings("deprecation") + protected Supplier findLookupSupplier() { + return Beans::lookup; + } + }) + .build(); + String err = captureErr(() -> { + PkgBean bean = mapper.readValue(DOC, PkgBean.class); + assertEquals(7, bean.getCount()); + assertEquals("x", bean.getName()); + }); + assertTrue(err.contains("BBReader_PkgBean"), + "expected a generated codec for PkgBean; diagnostics:\n" + err); + assertFalse(err.contains("null (gated)"), + "PkgBean was gated instead of accelerated; diagnostics:\n" + err); + } + + @Test + public void acceleratesWithoutUserLookup() throws Exception { + // Widened from a demotion pin: no lookup is required any more. The + // opens-to-databind that stock needs is the whole contract. + ObjectMapper mapper = JsonMapper.builder() + .addModule(new BlackbirdModule()) + .build(); + String err = captureErr(() -> { + PkgBean bean = mapper.readValue(DOC, PkgBean.class); + assertEquals(7, bean.getCount()); + assertEquals("x", bean.getName()); + }); + assertTrue(err.contains("BBReader_PkgBean"), + "expected a generated codec for PkgBean with no user lookup; diagnostics:\n" + + err); + assertFalse(err.contains("null (gated)"), + "PkgBean was gated instead of accelerated; diagnostics:\n" + err); + } + + private interface Body { + void run() throws Exception; + } + + private static synchronized String captureErr(Body body) throws Exception { + PrintStream original = System.err; + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + System.setErr(new PrintStream(buffer, true, StandardCharsets.UTF_8)); + try { + body.run(); + } finally { + System.setErr(original); + } + return buffer.toString(StandardCharsets.UTF_8); + } +} diff --git a/blackbird-tests/README.md b/blackbird-tests/README.md index 545450afb..a1569c124 100644 --- a/blackbird-tests/README.md +++ b/blackbird-tests/README.md @@ -5,50 +5,130 @@ test-only and is never published.** ## Why this module exists -This module is the Blackbird counterpart to -[`afterburner-tests`](../afterburner-tests/README.md). The coverage gap it -addresses is narrower than Afterburner's, because Blackbird's optimizer -mechanism is structurally different — and better aligned with JPMS — than -Afterburner's. - -**Afterburner:** uses ByteBuddy + `ClassLoader.defineClass` + reflective -access to `java.lang.ClassLoader` methods. On Java 9+, reflective access to -`ClassLoader` is gated by JPMS, and in the JPMS-named test module every test -POJO ends up in a sealed package. That combination silently disables the -optimizer in the in-tree afterburner test suite. See the `afterburner-tests` -README for the full story. - -**Blackbird:** uses `MethodHandles.Lookup.defineClass` (a JDK 9+ supported -API) + `MethodHandles.privateLookupIn`. Neither needs reflective access to -`ClassLoader` methods, neither cares whether the target package is "sealed by -module", and `privateLookupIn` works across the JPMS module boundary whenever -the target module opens its package to the caller (or lives in the unnamed -module). As a result, Blackbird's in-tree tests **do** exercise the optimizer -for setter-based POJOs. - -What's NOT exercised by Blackbird's in-tree tests, and is covered here: - -- **Classpath / unnamed-module POJOs.** A real-world POJO loaded by the - system class loader rather than as part of the Blackbird JPMS module. - This module's test POJOs live in the unnamed module (no `module-info.java` - anywhere), so the optimizer has to cross the module boundary. -- **The documented limitations of Blackbird's optimizer** — specifically - that direct public-field access is **not** optimized on either the - deserializer or serializer side. Blackbird's - `BBDeserializerModifier.nextProperty` and - `BBSerializerModifier.createProperty` both skip non-method members. -- **The `CrossLoaderAccess` fast-path behavior.** Blackbird's - `CrossLoaderAccess` contains a slow-path that defines a - `$$JacksonBlackbirdAccess` companion class via `Lookup.defineClass(byte[])` - to upgrade a partial-privilege lookup. `CrossLoaderAccessTest` pins the - current behavior that on JDK 9+ with an unnamed-module bean, the fast - path always wins and the companion class is never defined. +Blackbird's in-tree tests run as Blackbird's own named JPMS module, so their +test beans live in that module. This module has no `module-info.java`, and its +tests run on the classpath: the test beans live in the unnamed module, as in +most applications. It also holds the tests that need the packaged jar or other +JDKs. + +## What is covered + +- `inject`: generated codecs engage for setter-based and field-backed POJOs + in the unnamed module, on the reader and the writer side. +- `diff`: differential tests. Each scenario runs through a stock mapper and a + mapper with Blackbird, built from the same configuration, and the two must + give the same value or the same exception (class and message, with the + reference path). Each scenario then asserts which path Blackbird took: the + generated codec, a stock property inside it, or the stock (de)serializer for + the whole bean. The checks look at the (de)serializer that the mapper uses, + not at the placeholder that a modifier sees. The scenarios cover polymorphic + values, non-standard creation, property wrappers, ignoral sets, absent + record components, construction and setter failures, builder setters that + return null, null serializers, self-references, failures with their + reference paths, and wide beans. +- `jdk`: the packaged jar. `JdkCompatibilityTest` runs a small program + against the jar in child JVMs, on the build JDK and on each Java 17-24 home + listed in `-Dblackbird.test.oldJdks=[,]`: module discovery on the + class path and module path, with a second module provider on the path, and + explicit registration with discovery before it, after it and without it. + Without that property, the old-JDK cases are skipped. On the build JDK it + also runs discovery and explicit registration with a `jackson-core` that + does not have a class the generators load. `PackagingTest` + checks the module descriptor, the OSGi exports and the release guard of the + root POM. `IsolatedLoaderTest` loads Jackson and Blackbird in a class loader + that the system class loader cannot see, as an application server or OSGi + does. + +Some checks need the packaged jar (`mvn verify`); a build that stops before the +package phase reads `blackbird/target/classes`, which is not a multi-release +jar, and those checks are skipped. + +## Databind test corpus (manual) + +The `corpus` directory runs the jackson-databind test suite with +`BlackbirdModule` registered in every mapper that the tests build. Each +failure is a difference between Blackbird and stock databind, or one of the 7 +expected failures that the harness itself causes. The build does not run the +corpus. Run it manually before you submit a change to Blackbird. + +The corpus is not part of the build for two reasons. Databind publishes no +test jar, so the run needs a databind source tree. Also, the hook is a patch +to databind main code, which a databind change can break. Thus a databind +change that breaks Blackbird shows only at the next manual run. + +### Files + +- `databind-register-blackbird.patch`: the only source change to databind. +- `corpus.sh`: the runner. It also compares the failures with the expected + list. +- `stripmod.py`: copies the Blackbird jar without `module-info.class`. +- `failures.py`: lists the failed tests and the tests with errors from the + surefire reports. +- `expected-failures.txt`: the 7 expected failures and their cause. + +### How to run it + +1. Get jackson-databind at commit `f75dc9039fe00f84cf3098c005bbfbb1c5c0bc2d` + (3.x, 2026-10-14), for example from + `https://github.com/FasterXML/jackson-databind/archive/f75dc9039fe00f84cf3098c005bbfbb1c5c0bc2d.tar.gz`. + A later databind commit can also work, but its result can be different. +2. In the databind tree, apply the patch: + `git apply /blackbird-tests/corpus/databind-register-blackbird.patch`. +3. In jackson-modules-base, build the Blackbird jar on JDK 25: + `./mvnw -B -pl blackbird -am package -DskipTests`. +4. Run the corpus on JDK 25 or later: + + ```sh + JAVA_HOME= blackbird-tests/corpus/corpus.sh + ``` + +The runner uses the jar in `blackbird/target` of the same checkout. To use a +different jar, give its path as a third argument. To get the baseline +without Blackbird, give `none` as the third argument. + +### Expected result + +The runner prints `Result: only the expected failures.` and exits with 0. +With databind `f75dc9039fe0`, the suite has 7476 tests: 7468 pass, 1 is +skipped (also without Blackbird), and the 7 expected failures fail. These 7 +tests assert the exact list or number of registered modules, and the harness +adds one module. The baseline without Blackbird has 0 failures. + +The runner exits with 1 when the failures are not the expected ones, and it +lists each difference. Each unexpected failure is a gap in Blackbird: fix the +codec, or add a gate that keeps the bean on the stock path. The full report +of each failure is in `/failures.txt` and `/reports`. + +### What the harness changes in the databind build + +The patch adds 10 lines at the start of the `ObjectMapper(MapperBuilder)` +constructor. When the system property `blackbird.corpus` is `true`, the +constructor creates `tools.jackson.module.blackbird.BlackbirdModule` through +reflection and adds it to the builder. Every mapper goes through this +constructor: the public constructors and `build()` (also after `rebuild()`) +call it. Without the property, databind is unchanged. + +The runner changes no POM. It gives these settings to Maven: + +- `-Dmaven.test.additionalClasspath` adds the Blackbird jar. The runner + removes `module-info.class` from the jar first, so the jar is the + automatic module `blackbird.corpus` in the test run. +- `-DargLine` keeps the `--add-opens` of the databind POM, and adds + `--add-exports` for `deser.impl`, `ser.impl` and `ext.javatime.ser.key`. + The test descriptor of databind does not have these exports of the main + descriptor. +- `-Dblackbird.corpus=true` turns on the registration. +- `-Dtools.jackson.module.blackbird.failOnCodegenError=true` makes a failure + of code generation fail the test, instead of a silent fallback to stock. +- `-Dmaven.test.failure.ignore=true` lets the run continue to the report. + +To see the generation decisions, set +`CORPUS_EXTRA_ARGS=-Dblackbird.debug.codegen=true`. ## Do not add a `module-info.java` here -Same reason as `afterburner-tests`: adding a module descriptor would put the -test POJOs back into a named module and change how `privateLookupIn` -resolves them, silently undermining the classpath-coverage purpose. +Adding a module descriptor would put the test beans back into a named module +and remove the classpath coverage. ## Do not publish this module @@ -57,41 +137,3 @@ that `mvn install` on this module produces no jar, no SBOM, no Gradle module metadata, and no OSGi bundle. See `afterburner-tests/pom.xml` for the same pattern with extended commentary; this module's `pom.xml` shares the same shape. - -## What is covered - -- `BBDeserializerModifier` — setter specializations (int / long / boolean / - String / Object) in `SetterOptimizationTest`. -- `BBSerializerModifier` — getter-based writer specializations in - `SerializerInjectionTest`. -- Field-access negative cases (known design limitation) on both sides, in - `FieldAccessNotOptimizedTest` and `SerializerInjectionTest`. -- `CrossLoaderAccess` fast-path short-circuit for unnamed-module beans, in - `CrossLoaderAccessTest`. - -## What is *not* covered - -- Private-class guard (`Modifier.isPrivate(beanClass)` in both modifiers). -- The slow-path `CrossLoaderAccess.accessClassIn` companion-class definition - — that code is effectively dead for classpath POJOs on JDK 9+; see - `CrossLoaderAccessTest` javadoc for the explanation. -- `CreatorOptimizer` — Blackbird has one; a follow-up could mirror - `afterburner-tests/CreatorOptimizerTest`. -- Concurrent deserializer construction. -- Bean classes in a JPMS module other than Blackbird's own. Testing that - would require a third submodule with its own `module-info.java` that - `opens` its package to Blackbird, which adds complexity for unclear - additional value. -- Lambda-classloader growth as a function of mapper count. Blackbird's - fundamental design creates a new anonymous lambda class per - `LambdaMetafactory.metafactory` invocation; this isn't a bug and isn't - caching-related, so there's nothing to assert. - -## References - -- PR that introduced this module — see git history -- [`afterburner-tests/README.md`](../afterburner-tests/README.md) for the - sibling module and the broader rationale -- Issue #348 — analogous (but different) afterburner classloader caching - issue. Does not apply to Blackbird: Blackbird uses `Lookup.defineClass`, - not reflective `ClassLoader.defineClass`, so it is structurally immune. diff --git a/blackbird-tests/corpus/corpus.sh b/blackbird-tests/corpus/corpus.sh new file mode 100755 index 000000000..e400f86ee --- /dev/null +++ b/blackbird-tests/corpus/corpus.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# Runs the jackson-databind test suite with Blackbird registered in every +# mapper, and compares the failures with expected-failures.txt. See +# ../README.md ("Databind test corpus"). +# +# Usage: corpus.sh [ | none] +# databind-tree a jackson-databind checkout with databind-register-blackbird.patch +# out-dir receives build.log, reports/ and failures.txt (emptied first) +# blackbird-jar default: the jar in blackbird/target of this checkout +# none runs the suite without Blackbird (the baseline) +# JAVA_HOME must be a JDK 25 or later. CORPUS_EXTRA_ARGS adds JVM arguments to +# the forked test JVM (for example -Dblackbird.debug.codegen=true). +# Exit status: 0 when the failures are exactly the expected ones, 1 when they +# differ, 2 or more when the run could not complete. +set -u +if [ $# -lt 2 ] || [ $# -gt 3 ]; then + sed -n '6,10p' "$0" >&2 + exit 2 +fi +HERE=$(cd "$(dirname "$0")" && pwd) +REPO=$(cd "$HERE/../.." && pwd) +TREE=$(cd "$1" && pwd) || exit 2 +OUT=$2 +JAR=${3:-} +if [ -z "$JAR" ]; then + for f in "$REPO"/blackbird/target/jackson-module-blackbird-*.jar; do + case "$f" in + *-sources.jar|*-javadoc.jar|*-tests.jar) ;; + *) [ -f "$f" ] && JAR=$f ;; + esac + done + if [ -z "$JAR" ]; then + echo "No Blackbird jar in $REPO/blackbird/target: run" >&2 + echo " ./mvnw -B -pl blackbird -am package -DskipTests" >&2 + exit 2 + fi +fi +if ! grep -q 'blackbird.corpus' "$TREE/src/main/java/tools/jackson/databind/ObjectMapper.java"; then + echo "$TREE does not have the corpus hook: in that tree, run" >&2 + echo " git apply $HERE/databind-register-blackbird.patch" >&2 + exit 2 +fi +rm -rf "$OUT" +mkdir -p "$OUT" +OUT=$(cd "$OUT" && pwd) + +# The same opens that databind's own surefire configuration passes. +BASEARG="--add-opens=java.base/java.lang=tools.jackson.databind --add-opens=java.base/java.util=tools.jackson.databind --add-opens=tools.jackson.databind/tools.jackson.databind.jdk21=org.junit.platform.commons" +if [ "$JAR" = none ]; then + EXTRA=("-Dcorpus.none=true") + ARGLINE="$BASEARG" +else + echo "Blackbird jar: $JAR" + # Without module-info, the jar joins the test run as the automatic module + # "blackbird.corpus". Databind's test descriptor leaves out three exports of + # the real descriptor that Blackbird uses, so they are added here. + python3 -I "$HERE/stripmod.py" "$JAR" "$OUT/blackbird-corpus.jar" || exit 3 + EXTRA=("-Dmaven.test.additionalClasspath=$OUT/blackbird-corpus.jar") + EXPORTS="" + for p in tools.jackson.databind.deser.impl tools.jackson.databind.ser.impl tools.jackson.databind.ext.javatime.ser.key; do + EXPORTS="$EXPORTS --add-exports=tools.jackson.databind/$p=blackbird.corpus" + done + ARGLINE="$BASEARG $EXPORTS -Dblackbird.corpus=true -Dtools.jackson.module.blackbird.failOnCodegenError=true ${CORPUS_EXTRA_ARGS:-}" +fi +cd "$TREE" || exit 2 +rm -rf target/surefire-reports +mvn -B -ntp test -Djacoco.skip=true \ + -Dmaven.test.failure.ignore=true \ + "${EXTRA[@]}" "-DargLine=$ARGLINE" > "$OUT/build.log" 2>&1 +STATUS=$? +if [ $STATUS -ne 0 ] || [ ! -d target/surefire-reports ]; then + echo "The databind build failed (exit $STATUS): see $OUT/build.log" >&2 + exit 3 +fi +cp -r target/surefire-reports "$OUT/reports" +grep -E 'Tests run:.*Fail' "$OUT/build.log" | tail -1 +python3 -I "$HERE/failures.py" "$OUT/reports" > "$OUT/failures.txt" || exit 3 +tail -1 "$OUT/failures.txt" + +if [ "$JAR" = none ]; then + EXPECTED=/dev/null +else + EXPECTED="$HERE/expected-failures.txt" +fi +grep -E '^[FE] ' "$OUT/failures.txt" | awk '{print $2}' | sort > "$OUT/failed-names.txt" +grep -v -E '^#|^$' "$EXPECTED" | awk '{print $1}' | sort > "$OUT/expected-names.txt" +UNEXPECTED=$(comm -23 "$OUT/failed-names.txt" "$OUT/expected-names.txt") +MISSING=$(comm -13 "$OUT/failed-names.txt" "$OUT/expected-names.txt") +if [ -z "$UNEXPECTED" ] && [ -z "$MISSING" ]; then + echo "Result: only the expected failures." + exit 0 +fi +if [ -n "$UNEXPECTED" ]; then + echo "Unexpected failures (details in $OUT/failures.txt):" + echo "$UNEXPECTED" +fi +if [ -n "$MISSING" ]; then + echo "Expected failures that did not fail (update expected-failures.txt):" + echo "$MISSING" +fi +exit 1 diff --git a/blackbird-tests/corpus/databind-register-blackbird.patch b/blackbird-tests/corpus/databind-register-blackbird.patch new file mode 100644 index 000000000..e9d113204 --- /dev/null +++ b/blackbird-tests/corpus/databind-register-blackbird.patch @@ -0,0 +1,20 @@ +diff -ru a/src/main/java/tools/jackson/databind/ObjectMapper.java b/src/main/java/tools/jackson/databind/ObjectMapper.java +--- a/src/main/java/tools/jackson/databind/ObjectMapper.java ++++ b/src/main/java/tools/jackson/databind/ObjectMapper.java +@@ -299,6 +299,16 @@ + */ + protected ObjectMapper(MapperBuilder builder) + { ++ // CORPUS HARNESS (not upstream): register Blackbird in every mapper. ++ if (Boolean.getBoolean("blackbird.corpus")) { ++ try { ++ builder.addModule((JacksonModule) Class.forName( ++ "tools.jackson.module.blackbird.BlackbirdModule") ++ .getDeclaredConstructor().newInstance()); ++ } catch (ReflectiveOperationException e) { ++ throw new IllegalStateException(e); ++ } ++ } + // First things first: finalize building process. Saved state + // consists of snapshots and is safe to keep references to; used + // for rebuild()ing mapper instances diff --git a/blackbird-tests/corpus/expected-failures.txt b/blackbird-tests/corpus/expected-failures.txt new file mode 100644 index 000000000..c3c6430d0 --- /dev/null +++ b/blackbird-tests/corpus/expected-failures.txt @@ -0,0 +1,11 @@ +# The 7 failures that the harness itself causes. The harness adds +# BlackbirdModule to every mapper, so a test that asserts the exact list or +# number of registered modules sees one module more. None of them is a +# Blackbird behavior difference. Format: class#method :: assertion. +tools.jackson.databind.ObjectMapperTest#testRegisterDependentModules :: expected: <[dep1, dep2, main]> but was: <[dep1, dep2, main, tools.jackson.module.blackbird.BlackbirdModule]> +tools.jackson.databind.ObjectMapperTest#testRegisterTransitiveModuleDependencies :: expected: <[A, B, C]> but was: <[A, B, C, tools.jackson.module.blackbird.BlackbirdModule]> +tools.jackson.databind.module.BuilderModuleReuse5481Test#testBuilderReuseWithDifferentModules :: expected: <[ModuleA, ModuleB]> but was: <[ModuleA, ModuleB, BlackbirdModule]> +tools.jackson.databind.module.SimpleModuleTest#testGetRegisteredModules :: expected: <2> but was: <3> +tools.jackson.databind.module.SimpleModuleTest#testMultipleSimpleModules :: expected: <2> but was: <3> +tools.jackson.databind.module.SimpleModuleTest#testAutoDiscovery :: expected: <0> but was: <1> +tools.jackson.databind.module.SimpleModuleTest#testDuplicateModules5063 :: expected: <2> but was: <3> diff --git a/blackbird-tests/corpus/failures.py b/blackbird-tests/corpus/failures.py new file mode 100644 index 000000000..9a687b521 --- /dev/null +++ b/blackbird-tests/corpus/failures.py @@ -0,0 +1,28 @@ +"""List failing/erroring test cases from surefire XML reports. + +Usage: failures.py [--detail] +""" +import glob +import os +import sys +import xml.etree.ElementTree as ET + +reports = sys.argv[1] +detail = "--detail" in sys.argv +rows = [] +for path in sorted(glob.glob(os.path.join(reports, "TEST-*.xml"))): + root = ET.parse(path).getroot() + for tc in root.iter("testcase"): + for kind in ("failure", "error"): + el = tc.find(kind) + if el is not None: + msg = (el.get("message") or "").replace("\n", " ")[:300] + typ = el.get("type") or "" + rows.append((tc.get("classname"), tc.get("name"), kind, typ, msg, + (el.text or ""))) +for cls, name, kind, typ, msg, text in rows: + print(f"{kind[0].upper()} {cls}#{name} :: {typ} :: {msg}") + if detail: + lines = [l for l in text.splitlines() if l.strip()][:25] + print(" " + "\n ".join(lines)) +print(f"total {len(rows)}") diff --git a/blackbird-tests/corpus/stripmod.py b/blackbird-tests/corpus/stripmod.py new file mode 100644 index 000000000..87d20c3a9 --- /dev/null +++ b/blackbird-tests/corpus/stripmod.py @@ -0,0 +1,10 @@ +"""Copy a jar without any module-info.class entries (base or versioned).""" +import sys +import zipfile + +src, dst = sys.argv[1], sys.argv[2] +with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout: + for item in zin.infolist(): + if item.filename.endswith("module-info.class"): + continue + zout.writestr(item, zin.read(item.filename)) diff --git a/blackbird-tests/pom.xml b/blackbird-tests/pom.xml index 51c382b19..112273bda 100644 --- a/blackbird-tests/pom.xml +++ b/blackbird-tests/pom.xml @@ -10,16 +10,18 @@ Jackson module: Blackbird integration tests jar - Classpath-mode integration tests for jackson-module-blackbird. Unlike -jackson-module-afterburner-tests, Blackbird's in-tree tests already exercise most of -the optimizer because Blackbird uses the JDK 9+ supported MethodHandles.Lookup API -rather than reflective ClassLoader access. What this module adds is coverage of the -classpath/unnamed-module path: when a POJO lives outside Blackbird's own JPMS module, -Blackbird's CrossLoaderAccess has to define a $$JacksonBlackbirdAccess companion class -via Lookup.defineClass to upgrade the caller lookup to one with full privilege access -over the target package. That path is not exercised by in-tree tests (caller and bean -share a module, so privateLookupIn already has full privilege). This module is test-only -and is never published. + Classpath-mode integration tests for jackson-module-blackbird: test beans in +the unnamed module, differential tests against stock databind, and runs of the packaged +jar on older JDKs. This module is test-only and is never published. + + + + 25 + 25 + 25 + + + @@ -150,14 +152,21 @@ and is never published. + so tests must run on the classpath (unnamed module): Blackbird's + in-tree tests run as Blackbird's own named module instead. --> org.apache.maven.plugins maven-surefire-plugin false + + + ${blackbird.test.oldJdks} + + true + diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/DifferentialTestBase.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/DifferentialTestBase.java new file mode 100644 index 000000000..36a611e71 --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/DifferentialTestBase.java @@ -0,0 +1,180 @@ +package tools.jackson.module.blackbird.diff; + +import java.util.function.UnaryOperator; + +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.module.blackbird.BlackbirdModule; +import tools.jackson.module.blackbird.internal.GeneratedBuilderBase; +import tools.jackson.module.blackbird.internal.GeneratedReaderBase; +import tools.jackson.module.blackbird.internal.GeneratedWriterBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Differential tests: every scenario runs through two mappers built from the + * same configuration, one of them with Blackbird, and must give the same + * outcome - the same value (class and stock serialization), or the same + * exception (class and message, which includes the reference path). + * Scenarios then assert which path Blackbird took: the generated codec, a + * stock arm inside it for one property, or the stock (de)serializer for the + * whole bean. The assertions look at the (de)serializer the mapper actually + * uses, not at what a modifier saw. + */ +public abstract class DifferentialTestBase +{ + static { + // Strict mode: a codec-generation failure fails the test instead of + // demoting to the stock path (see CodegenFallbacks). Surefire sets + // this for every test class. Outside Maven (an IDE) this takes + // effect only if it runs before Blackbird's codegen classes first + // load, which read the property once. + System.setProperty("tools.jackson.module.blackbird.failOnCodegenError", "true"); + } + + // Describes values; never has Blackbird. + private static final ObjectMapper DESCRIBER = JsonMapper.builder().build(); + + /** Two mappers with one configuration; only the second has Blackbird. */ + protected record Mappers(JsonMapper stock, JsonMapper blackbird) {} + + protected static Mappers mappers() { + return mappers(b -> b); + } + + protected static Mappers mappers(UnaryOperator config) { + return new Mappers(config.apply(JsonMapper.builder()).build(), + config.apply(JsonMapper.builder()).addModule(new BlackbirdModule()).build()); + } + + /** The result of one operation: a value, or the exception it threw. */ + protected record Outcome(Object value, Throwable error) { + String describe() { + if (error != null) { + return "threw " + error.getClass().getName() + ": " + error.getMessage(); + } + if (value == null) { + return "null"; + } + if (value instanceof String s) { + return "string " + s; + } + try { + return value.getClass().getName() + " " + DESCRIBER.writeValueAsString(value); + } catch (RuntimeException e) { + return value.getClass().getName() + " " + value; + } + } + + @SuppressWarnings("unchecked") + T get() { + assertNull(error, () -> "expected a value, got " + describe()); + return (T) value; + } + + T error(Class type) { + assertTrue(type.isInstance(error), () -> "expected " + type.getName() + ", got " + + describe()); + return type.cast(error); + } + } + + protected static Outcome read(ObjectMapper mapper, String json, Class type) { + try { + return new Outcome(mapper.readValue(json, type), null); + } catch (RuntimeException | StackOverflowError e) { + return new Outcome(null, e); + } + } + + protected static Outcome write(ObjectMapper mapper, Object value) { + try { + return new Outcome(mapper.writeValueAsString(value), null); + } catch (RuntimeException | StackOverflowError e) { + return new Outcome(null, e); + } + } + + /** Reads with both mappers; returns the Blackbird outcome once it matches stock. */ + protected static Outcome assertSameRead(Mappers m, String json, Class type) { + Outcome stock = read(m.stock(), json, type); + Outcome blackbird = read(m.blackbird(), json, type); + assertEquals(stock.describe(), blackbird.describe(), + "Blackbird and stock differ reading " + type.getSimpleName() + " from " + json); + return blackbird; + } + + /** Writes with both mappers; returns the Blackbird outcome once it matches stock. */ + protected static Outcome assertSameWrite(Mappers m, Object value) { + Outcome stock = write(m.stock(), value); + Outcome blackbird = write(m.blackbird(), value); + assertEquals(stock.describe(), blackbird.describe(), + "Blackbird and stock differ writing " + value.getClass().getSimpleName()); + return blackbird; + } + + /* + /********************************************************************** + /* Which path Blackbird took + /********************************************************************** + */ + + @SuppressWarnings("unchecked") + protected static ValueDeserializer rootReader(ObjectMapper mapper, Class type) { + DeserializationContext ctxt = mapper._deserializationContext(); + // Contextual but not type-wrapped: under default typing a root value + // is read through a TypeWrappedDeserializer around this one. + return (ValueDeserializer) ctxt.findContextualValueDeserializer( + ctxt.constructType(type), null); + } + + protected static ValueSerializer rootWriter(ObjectMapper mapper, Class type) { + SerializationContext ctxt = mapper._serializationContext(); + return ctxt.handleRootContextualization(ctxt.findValueSerializer(type)); + } + + /** Asserts that a generated reader handles the type; returns its "name=KIND" layout. */ + protected static String assertGeneratedReader(ObjectMapper mapper, Class type) { + ValueDeserializer d = rootReader(mapper, type); + if (d instanceof GeneratedReaderBase r) { + return r.codecLayout(); + } + if (d instanceof GeneratedBuilderBase b) { + return b.codecLayout(); + } + return fail(type.getSimpleName() + " should use a generated reader, uses " + d); + } + + /** Asserts that the stock deserializer handles the whole type. */ + protected static void assertStockReader(ObjectMapper mapper, Class type) { + ValueDeserializer d = rootReader(mapper, type); + assertFalse(d instanceof GeneratedReaderBase || d instanceof GeneratedBuilderBase, + type.getSimpleName() + " should keep the stock deserializer, uses " + d); + } + + /** Asserts that a generated writer handles the type; returns its "name=KIND" layout. */ + protected static String assertGeneratedWriter(ObjectMapper mapper, Class type) { + ValueSerializer s = rootWriter(mapper, type); + if (s instanceof GeneratedWriterBase w) { + return w.codecLayout(); + } + return fail(type.getSimpleName() + " should use a generated writer, uses " + s); + } + + /** Asserts that the stock serializer handles the whole type. */ + protected static void assertStockWriter(ObjectMapper mapper, Class type) { + ValueSerializer s = rootWriter(mapper, type); + assertFalse(s instanceof GeneratedWriterBase, + type.getSimpleName() + " should keep the stock serializer, uses " + s); + } + + /** Asserts that the layout has the arm, e.g. "pet=STOCK". */ + protected static void assertArm(String layout, String arm) { + assertTrue(("," + layout + ",").contains("," + arm + ","), + "expected arm " + arm + " in layout " + layout); + } +} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/ReaderErrorsDiffTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/ReaderErrorsDiffTest.java new file mode 100644 index 000000000..26f3793a2 --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/ReaderErrorsDiffTest.java @@ -0,0 +1,294 @@ +package tools.jackson.module.blackbird.diff; + +import java.lang.reflect.Field; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonPOJOBuilder; +import tools.jackson.databind.deser.DeserializationProblemHandler; +import tools.jackson.databind.exc.ValueInstantiationException; +import tools.jackson.module.blackbird.deser.BBDeserializerModifier; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Failures inside generated POJO and builder codecs: constructors, build + * methods and setters fail with the exception class, message and reference + * path of stock, with and without WRAP_EXCEPTIONS. A builder setter that + * returns null keeps the current builder, as in stock. Every scenario's bean + * keeps the generated codec. + */ +public class ReaderErrorsDiffTest extends DifferentialTestBase +{ + /* + /********************************************************************** + /* Constructor and build() failures + /********************************************************************** + */ + + public static class ThrowingCtor { + static final ThreadLocal FAIL = ThreadLocal.withInitial(() -> true); + + public String a; + + public ThrowingCtor() { + if (FAIL.get()) { + throw new IllegalStateException("no default instance"); + } + } + + static ThrowingCtor created() { + FAIL.set(false); + try { + return new ThrowingCtor(); + } finally { + FAIL.remove(); + } + } + } + + @Test + public void constructorFailureIsValueInstantiationException() { + Mappers m = mappers(); + ValueInstantiationException e = assertSameRead(m, "{\"a\":\"x\"}", ThrowingCtor.class) + .error(ValueInstantiationException.class); + assertTrue(e.getMessage().contains("no default instance"), e.getMessage()); + assertSameRead(mappers(b -> b.disable(DeserializationFeature.WRAP_EXCEPTIONS)), + "{\"a\":\"x\"}", ThrowingCtor.class); + assertGeneratedReader(m.blackbird(), ThrowingCtor.class); + } + + static final class CtorHandler extends DeserializationProblemHandler { + @Override + public Object handleInstantiationProblem(DeserializationContext ctxt, Class instClass, + Object argument, Throwable t) { + return (instClass == ThrowingCtor.class) ? ThrowingCtor.created() : NOT_HANDLED; + } + } + + @Test + public void problemHandlerReplacesFailedConstruction() { + Mappers m = mappers(b -> b.addHandler(new CtorHandler())); + ThrowingCtor t = assertSameRead(m, "{\"a\":\"x\"}", ThrowingCtor.class).get(); + assertEquals("x", t.a); + assertGeneratedReader(m.blackbird(), ThrowingCtor.class); + } + + @JsonDeserialize(builder = Immutable.Builder.class) + public static class Immutable { + public final String name; + public final int count; + + Immutable(String name, int count) { + this.name = name; + this.count = count; + } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + private String name; + private int count; + + public Builder name(String n) { name = n; return this; } + public Builder count(int c) { count = c; return this; } + + public Immutable build() { + if (name == null) { + throw new IllegalStateException( + "Cannot build Immutable, some of required attributes are not set [name]"); + } + return new Immutable(name, count); + } + } + } + + @Test + public void buildFailureIsValueInstantiationException() { + Mappers m = mappers(); + assertEquals("a", assertSameRead(m, "{\"name\":\"a\",\"count\":1}", Immutable.class) + .get().name); + ValueInstantiationException e = assertSameRead(m, "{\"count\":1}", Immutable.class) + .error(ValueInstantiationException.class); + assertTrue(e.getMessage().contains("required attributes"), e.getMessage()); + assertSameRead(mappers(b -> b.disable(DeserializationFeature.WRAP_EXCEPTIONS)), + "{\"count\":1}", Immutable.class); + assertGeneratedReader(m.blackbird(), Immutable.class); + } + + /* + /********************************************************************** + /* Setter failures + /********************************************************************** + */ + + public static class Child { + public String name; + } + + public static class Guarded { + private int age; + private String code; + private Child child; + public String other; + + public int getAge() { return age; } + + public void setAge(int age) { + if (age < 0) { + throw new IllegalArgumentException("age must be >= 0"); + } + this.age = age; + } + + public String getCode() { return code; } + + public void setCode(String code) { + if (code.isEmpty()) { + throw new IllegalStateException("empty code"); + } + this.code = code; + } + + public Child getChild() { return child; } + + public void setChild(Child child) { + if (child.name == null) { + throw new IllegalArgumentException(null, null); + } + this.child = child; + } + } + + @Test + public void setterIllegalArgument() { + Mappers m = mappers(); + DatabindException e = assertSameRead(m, "{\"other\":\"o\",\"age\":-1}", Guarded.class) + .error(DatabindException.class); + assertTrue(e.getMessage().startsWith("Problem deserializing property 'age'"), + e.getMessage()); + // Stock turns the IllegalArgumentException into a DatabindException + // even without wrapping. + assertSameRead(mappers(b -> b.disable(DeserializationFeature.WRAP_EXCEPTIONS)), + "{\"age\":-1}", Guarded.class).error(DatabindException.class); + // A child value: the message names its class; no message from the setter. + assertSameRead(m, "{\"child\":{}}", Guarded.class).error(DatabindException.class); + String layout = assertGeneratedReader(m.blackbird(), Guarded.class); + assertArm(layout, "age=INT"); + assertArm(layout, "child=CHILD"); + } + + @Test + public void setterOtherRuntimeException() { + Mappers m = mappers(); + assertSameRead(m, "{\"code\":\"\"}", Guarded.class).error(DatabindException.class); + assertSameRead(mappers(b -> b.disable(DeserializationFeature.WRAP_EXCEPTIONS)), + "{\"code\":\"\"}", Guarded.class).error(IllegalStateException.class); + assertArm(assertGeneratedReader(m.blackbird(), Guarded.class), "code=STRING"); + } + + @JsonDeserialize(builder = GuardedBuilt.Builder.class) + public static class GuardedBuilt { + public final int age; + + GuardedBuilt(int age) { + this.age = age; + } + + @JsonPOJOBuilder(withPrefix = "with") + public static class Builder { + private int age; + + public Builder withAge(int a) { + if (a < 0) { + throw new IllegalArgumentException("age must be >= 0"); + } + age = a; + return this; + } + + public GuardedBuilt build() { return new GuardedBuilt(age); } + } + } + + @Test + public void builderSetterIllegalArgument() { + Mappers m = mappers(); + assertSameRead(m, "{\"age\":-1}", GuardedBuilt.class).error(DatabindException.class); + assertArm(assertGeneratedReader(m.blackbird(), GuardedBuilt.class), "age=INT"); + } + + /* + /********************************************************************** + /* Builder setters that return null + /********************************************************************** + */ + + // A setter declared to return the builder may return null; stock + // deserializeSetAndReturn then keeps the current builder. + @JsonDeserialize(builder = NullFluent.Builder.class) + public static class NullFluent { + public final String a; + public final int n; + public final Child child; + public final String b; + + NullFluent(String a, int n, Child child, String b) { + this.a = a; + this.n = n; + this.child = child; + this.b = b; + } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + private String a; + private int n; + private Child child; + private String b; + + public Builder a(String v) { a = v; return null; } + public Builder n(int v) { n = v; return null; } + public Builder child(Child v) { child = v; return null; } + public Builder b(String v) { b = v; return this; } + + public NullFluent build() { return new NullFluent(a, n, child, b); } + } + } + + @Test + public void builderSetterReturningNullKeepsBuilder() { + Mappers m = mappers(); + NullFluent v = assertSameRead(m, + "{\"a\":\"x\",\"n\":3,\"child\":{\"name\":\"c\"},\"b\":\"y\"}", NullFluent.class) + .get(); + assertEquals("x", v.a); + assertEquals(3, v.n); + assertEquals("c", v.child.name); + assertEquals("y", v.b); + String layout = assertGeneratedReader(m.blackbird(), NullFluent.class); + assertArm(layout, "a=STRING"); + assertArm(layout, "n=INT"); + assertArm(layout, "child=CHILD"); + assertArm(layout, "b=STRING"); + } + + /* + /********************************************************************** + /* No thread-local hand-off of the build method + /********************************************************************** + */ + + @Test + public void modifierKeepsNoThreadLocalState() { + for (Field f : BBDeserializerModifier.class.getDeclaredFields()) { + assertFalse(ThreadLocal.class.isAssignableFrom(f.getType()), f.toString()); + } + Mappers m = mappers(); + assertSameRead(m, "{\"name\":\"a\",\"count\":1}", Immutable.class); + assertGeneratedReader(m.blackbird(), Immutable.class); + } +} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/ReaderGatesDiffTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/ReaderGatesDiffTest.java new file mode 100644 index 000000000..3ffaecc46 --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/ReaderGatesDiffTest.java @@ -0,0 +1,348 @@ +package tools.jackson.module.blackbird.diff; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIgnoreType; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.BeanDescription; +import tools.jackson.databind.DefaultTyping; +import tools.jackson.databind.DeserializationConfig; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.deser.BeanDeserializerBuilder; +import tools.jackson.databind.deser.SettableBeanProperty; +import tools.jackson.databind.deser.ValueDeserializerModifier; +import tools.jackson.databind.exc.UnrecognizedPropertyException; +import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import tools.jackson.databind.module.SimpleModule; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Reader features that the generated loop must leave to stock: polymorphic + * property values, non-standard creation (external type ids, delegating + * creators), property wrappers, and the ignoral sets stock computes. Each + * scenario has a plain control bean that must keep the generated codec. + */ +public class ReaderGatesDiffTest extends DifferentialTestBase +{ + public static class Plain { + public String name; + public int count; + } + + /* + /********************************************************************** + /* Polymorphic property values + /********************************************************************** + */ + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@t") + @JsonSubTypes({ @JsonSubTypes.Type(value = Dog.class, name = "dog") }) + public static class Animal { + public String name; + } + + public static class Dog extends Animal { + public boolean bark; + } + + public static class Owner { + public String name; + public Animal pet; + } + + @Test + public void polymorphicPropertyKeepsSubtype() { + Mappers m = mappers(); + Owner owner = assertSameRead(m, + "{\"name\":\"o\",\"pet\":{\"@t\":\"dog\",\"name\":\"rex\",\"bark\":true}}", + Owner.class).get(); + assertEquals(Dog.class, owner.pet.getClass()); + assertTrue(((Dog) owner.pet).bark); + String layout = assertGeneratedReader(m.blackbird(), Owner.class); + assertArm(layout, "pet=STOCK"); + assertArm(layout, "name=STRING"); + } + + public static class Pet { + public String name; + } + + public static class Cat extends Pet { + public int lives; + } + + public static class PetHolder { + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) + @JsonSubTypes({ @JsonSubTypes.Type(value = Cat.class, name = "cat") }) + public Pet pet; + public String tag; + } + + @Test + public void propertyLevelTypeInfoKeepsSubtype() { + Mappers m = mappers(); + PetHolder h = assertSameRead(m, + "{\"pet\":{\"@type\":\"cat\",\"name\":\"c\",\"lives\":9},\"tag\":\"t\"}", + PetHolder.class).get(); + assertEquals(Cat.class, h.pet.getClass()); + String layout = assertGeneratedReader(m.blackbird(), PetHolder.class); + assertArm(layout, "pet=STOCK"); + assertArm(layout, "tag=STRING"); + } + + public static class DefaultTyped { + public Pet pet; + public String tag; + } + + @Test + public void defaultTypingKeepsSubtype() { + // As a property, the type id sits inside the value object. + Mappers m = mappers(b -> b.activateDefaultTypingAsProperty( + BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build(), + DefaultTyping.NON_FINAL, "@class")); + DefaultTyped value = new DefaultTyped(); + Cat cat = new Cat(); + cat.name = "c"; + cat.lives = 7; + value.pet = cat; + value.tag = "t"; + String json = m.stock().writeValueAsString(value); + DefaultTyped read = assertSameRead(m, json, DefaultTyped.class).get(); + assertEquals(Cat.class, read.pet.getClass()); + // Default typing types the property; the bean itself is non-final, + // so it is read through the typed path, which stays stock. + assertArm(assertGeneratedReader(m.blackbird(), DefaultTyped.class), "pet=STOCK"); + } + + /* + /********************************************************************** + /* Non-standard creation + /********************************************************************** + */ + + public static class ExtBase { + public String v; + } + + public static class ExtImpl extends ExtBase { + public int n; + } + + public static class ExtHolder { + public String note; + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, + property = "kind") + @JsonSubTypes({ @JsonSubTypes.Type(value = ExtImpl.class, name = "impl") }) + public ExtBase payload; + } + + @Test + public void externalTypeIdKeepsStockBean() { + Mappers m = mappers(); + ExtHolder h = assertSameRead(m, + "{\"note\":\"x\",\"kind\":\"impl\",\"payload\":{\"v\":\"a\",\"n\":3}}", + ExtHolder.class).get(); + assertEquals(ExtImpl.class, h.payload.getClass()); + assertEquals(3, ((ExtImpl) h.payload).n); + assertStockReader(m.blackbird(), ExtHolder.class); + assertGeneratedReader(m.blackbird(), Plain.class); + } + + public static class Delegating { + public String v; + + public Delegating() { } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static Delegating of(Map m) { + Delegating d = new Delegating(); + d.v = "delegated:" + m.get("v"); + return d; + } + } + + @Test + public void delegatingCreatorBesideDefaultConstructorKeepsStockBean() { + Mappers m = mappers(); + Delegating d = assertSameRead(m, "{\"v\":\"x\",\"w\":1}", Delegating.class).get(); + assertEquals("delegated:x", d.v); + assertStockReader(m.blackbird(), Delegating.class); + assertGeneratedReader(m.blackbird(), Plain.class); + } + + /* + /********************************************************************** + /* Property wrappers + /********************************************************************** + */ + + public static class Parent { + public String name; + @JsonManagedReference + public Kid kid; + } + + public static class Kid { + public String name; + @JsonBackReference + public Parent parent; + } + + @Test + public void managedReferenceSetsBackReference() { + Mappers m = mappers(); + Parent p = assertSameRead(m, "{\"name\":\"p\",\"kid\":{\"name\":\"k\"}}", + Parent.class).get(); + assertSame(p, p.kid.parent); + Parent stock = m.stock().readValue("{\"name\":\"p\",\"kid\":{\"name\":\"k\"}}", + Parent.class); + assertSame(stock, stock.kid.parent); + String layout = assertGeneratedReader(m.blackbird(), Parent.class); + assertArm(layout, "kid=STOCK"); + assertArm(layout, "name=STRING"); + } + + // A user wrapper: upper-cases every String property value. + static final class UpperCaseProperty extends SettableBeanProperty.Delegating { + private static final long serialVersionUID = 1L; + + UpperCaseProperty(SettableBeanProperty d) { + super(d); + } + + @Override + protected SettableBeanProperty withDelegate(SettableBeanProperty d) { + return new UpperCaseProperty(d); + } + + @Override + public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, + Object instance) { + delegate.set(ctxt, instance, p.getString().toUpperCase()); + } + + @Override + public Object deserializeSetAndReturn(JsonParser p, DeserializationContext ctxt, + Object instance) { + return delegate.setAndReturn(ctxt, instance, p.getString().toUpperCase()); + } + } + + public static class Wrapped { + public String text; + public int count; + } + + private static SimpleModule upperCaseModule() { + return new SimpleModule("upper").setDeserializerModifier(new ValueDeserializerModifier() { + private static final long serialVersionUID = 1L; + + @Override + public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, + BeanDescription.Supplier beanDescRef, BeanDeserializerBuilder builder) { + if (beanDescRef.getBeanClass() == Wrapped.class) { + SettableBeanProperty text = builder.findProperty( + tools.jackson.databind.PropertyName.construct("text")); + builder.addOrReplaceProperty(new UpperCaseProperty(text), true); + } + return builder; + } + }); + } + + @Test + public void userPropertyWrapperRuns() { + Mappers m = mappers(b -> b.addModule(upperCaseModule())); + Wrapped w = assertSameRead(m, "{\"text\":\"xy\",\"count\":2}", Wrapped.class).get(); + assertEquals("XY", w.text); + String layout = assertGeneratedReader(m.blackbird(), Wrapped.class); + assertArm(layout, "text=STOCK"); + assertArm(layout, "count=INT"); + } + + /* + /********************************************************************** + /* Ignoral sets as stock computes them + /********************************************************************** + */ + + @JsonIgnoreType + public static class Secret { + public String s; + } + + public static class HasSecret { + public String name; + public Secret secret; + } + + @Test + public void ignoredTypePropertyIsIgnoredNotUnknown() { + Mappers m = mappers(b -> b.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + HasSecret h = assertSameRead(m, "{\"name\":\"n\",\"secret\":{\"s\":\"x\"}}", + HasSecret.class).get(); + assertEquals("n", h.name); + assertNull(h.secret); + assertGeneratedReader(m.blackbird(), HasSecret.class); + } + + @JsonIgnoreProperties(value = "x", allowSetters = true) + public static class AllowSetters { + public String name; + public String x; + } + + @Test + public void allowSettersStillReadsProperty() { + Mappers m = mappers(); + AllowSetters a = assertSameRead(m, "{\"name\":\"n\",\"x\":\"set\"}", + AllowSetters.class).get(); + assertEquals("set", a.x); + assertGeneratedReader(m.blackbird(), AllowSetters.class); + } + + @JsonIgnoreProperties(value = "y", allowSetters = true) + public static class AllowSettersUnknown { + public String name; + } + + @Test + public void allowSettersNameWithoutSetterIsUnknown() { + Mappers m = mappers(b -> b.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + UnrecognizedPropertyException e = assertSameRead(m, "{\"name\":\"n\",\"y\":1}", + AllowSettersUnknown.class).error(UnrecognizedPropertyException.class); + assertEquals("y", e.getPropertyName()); + assertGeneratedReader(m.blackbird(), AllowSettersUnknown.class); + } + + public record IgnoredComponent(int id, @JsonIgnore String name) {} + + @Test + public void ignoredRecordComponentKeepsAbsentValue() { + Mappers m = mappers(); + IgnoredComponent r = assertSameRead(m, "{\"id\":123,\"name\":\"Bob\"}", + IgnoredComponent.class).get(); + assertEquals(new IgnoredComponent(123, null), r); + assertGeneratedReader(m.blackbird(), IgnoredComponent.class); + } + + @Test + public void ignoredRecordComponentFailsWhenIgnoredPropertiesFail() { + Mappers m = mappers(b -> b.enable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES)); + assertSameRead(m, "{\"id\":123,\"name\":\"Bob\"}", IgnoredComponent.class) + .error(tools.jackson.databind.exc.IgnoredPropertyException.class); + } +} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/RecordsDiffTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/RecordsDiffTest.java new file mode 100644 index 000000000..fdb35ce19 --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/RecordsDiffTest.java @@ -0,0 +1,300 @@ +package tools.jackson.module.blackbird.diff; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.deser.DeserializationProblemHandler; +import tools.jackson.databind.exc.InvalidNullException; +import tools.jackson.databind.exc.MismatchedInputException; +import tools.jackson.databind.exc.ValueInstantiationException; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Record codecs: absent components get the stock absent value, the + * canonical constructor's failures are reported as stock reports them, and + * unknown names are reported against the record once it exists. Every + * scenario's record keeps the generated codec. + */ +public class RecordsDiffTest extends DifferentialTestBase +{ + /* + /********************************************************************** + /* Absent components + /********************************************************************** + */ + + public record OptRec(String a, Optional b) {} + + @Test + public void absentOptionalIsEmpty() { + Mappers m = mappers(); + OptRec r = assertSameRead(m, "{\"a\":\"1\"}", OptRec.class).get(); + assertEquals(new OptRec("1", Optional.empty()), r); + assertGeneratedReader(m.blackbird(), OptRec.class); + } + + @Test + public void absentOptionalPassesFailOnNullCreatorProperties() { + Mappers m = mappers(b -> b.enable(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES)); + OptRec r = assertSameRead(m, "{\"a\":\"1\"}", OptRec.class).get(); + assertEquals(new OptRec("1", Optional.empty()), r); + // An absent String has no non-null absent value: the null fails. + assertSameRead(m, "{\"b\":\"x\"}", OptRec.class).error(MismatchedInputException.class); + assertGeneratedReader(m.blackbird(), OptRec.class); + } + + public record AsEmptyRec(String a, @JsonSetter(nulls = Nulls.AS_EMPTY) String b) {} + + @Test + public void absentAsEmptyComponentIsEmpty() { + Mappers m = mappers(); + AsEmptyRec r = assertSameRead(m, "{\"a\":\"1\"}", AsEmptyRec.class).get(); + assertEquals(new AsEmptyRec("1", ""), r); + assertGeneratedReader(m.blackbird(), AsEmptyRec.class); + } + + public record FailNullsRec(String a, @JsonSetter(nulls = Nulls.FAIL) List b) {} + + @Test + public void absentFailNullsComponentFails() { + Mappers m = mappers(); + assertSameRead(m, "{\"a\":\"1\"}", FailNullsRec.class).error(InvalidNullException.class); + assertGeneratedReader(m.blackbird(), FailNullsRec.class); + } + + public record RequiredRec(@JsonProperty(required = true) String a, int b) {} + + @Test + public void missingRequiredAndFailOnMissing() { + Mappers m = mappers(); + assertSameRead(m, "{\"b\":1}", RequiredRec.class).error(MismatchedInputException.class); + Mappers strict = mappers(b -> + b.enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES)); + assertSameRead(strict, "{\"a\":\"x\"}", RequiredRec.class) + .error(MismatchedInputException.class); + assertGeneratedReader(m.blackbird(), RequiredRec.class); + } + + public record Prims(int i, long l, boolean z, double d, String s) {} + + @Test + public void absentPrimitivesAreDefaults() { + Mappers m = mappers(); + assertEquals(new Prims(0, 0L, false, 0.0, null), + assertSameRead(m, "{}", Prims.class).get()); + Mappers strict = mappers(b -> + b.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)); + assertSameRead(strict, "{\"s\":\"x\"}", Prims.class); + assertSameRead(strict, "{\"i\":null}", Prims.class); + assertGeneratedReader(m.blackbird(), Prims.class); + } + + public static class NullInt extends ValueDeserializer { + @Override + public Integer deserialize(JsonParser p, DeserializationContext ctxt) { + p.skipChildren(); + return null; + } + } + + public record NullPrimitiveRec(@JsonDeserialize(using = NullInt.class) int n, String s) {} + + @Test + public void nullFromCustomDeserializerForPrimitiveComponent() { + Mappers m = mappers(); + assertSameRead(m, "{\"n\":5,\"s\":\"x\"}", NullPrimitiveRec.class); + assertSameRead(m, "{\"n\":5,\"n\":6,\"s\":\"x\"}", NullPrimitiveRec.class); + assertGeneratedReader(m.blackbird(), NullPrimitiveRec.class); + } + + /* + /********************************************************************** + /* Constructor failures + /********************************************************************** + */ + + public record Validated(int n, String s) { + public Validated { + if (n < 0) { + throw new IllegalArgumentException("n must be >= 0"); + } + } + } + + @Test + public void constructorFailureIsValueInstantiationException() { + Mappers m = mappers(); + ValueInstantiationException e = assertSameRead(m, "{\"n\":-1,\"s\":\"x\"}", + Validated.class).error(ValueInstantiationException.class); + assertTrue(e.getMessage().contains("n must be >= 0"), e.getMessage()); + // Also when a component is absent (the stock creator constructs). + assertSameRead(m, "{\"n\":-1}", Validated.class) + .error(ValueInstantiationException.class); + assertGeneratedReader(m.blackbird(), Validated.class); + } + + @Test + public void constructorFailureWithoutWrapping() { + Mappers m = mappers(b -> b.disable(DeserializationFeature.WRAP_EXCEPTIONS)); + assertSameRead(m, "{\"n\":-1,\"s\":\"x\"}", Validated.class); + assertSameRead(m, "{\"n\":-1}", Validated.class); + } + + static final class ReplacingHandler extends DeserializationProblemHandler { + @Override + public Object handleInstantiationProblem(DeserializationContext ctxt, Class instClass, + Object argument, Throwable t) { + return (instClass == Validated.class) ? new Validated(0, "replaced") : NOT_HANDLED; + } + } + + @Test + public void problemHandlerReplacesFailedConstruction() { + Mappers m = mappers(b -> b.addHandler(new ReplacingHandler())); + Validated v = assertSameRead(m, "{\"n\":-1,\"s\":\"x\"}", Validated.class).get(); + assertEquals(new Validated(0, "replaced"), v); + assertGeneratedReader(m.blackbird(), Validated.class); + } + + /* + /********************************************************************** + /* JSON names that are not JVM names + /********************************************************************** + */ + + public record Dotted(@JsonProperty("user.name") String userName, + @JsonProperty("a;b/c[d]") int n) {} + + @Test + public void namesThatAreNotJvmNames() { + Mappers m = mappers(); + Dotted d = assertSameRead(m, "{\"user.name\":\"a\",\"a;b/c[d]\":1}", Dotted.class) + .get(); + assertEquals(new Dotted("a", 1), d); + assertGeneratedReader(m.blackbird(), Dotted.class); + } + + /* + /********************************************************************** + /* Unknown names are reported against the record built + /********************************************************************** + */ + + @Test + public void unknownNameReportedAfterConstruction() { + Mappers m = mappers(b -> b.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + // The constructor fails first, as in stock. + assertSameRead(m, "{\"zzz\":1,\"n\":-1,\"s\":\"x\"}", Validated.class) + .error(ValueInstantiationException.class); + // A valid record: the unknown name fails, against the instance. + assertSameRead(m, "{\"zzz\":1,\"n\":1,\"s\":\"x\"}", Validated.class) + .error(tools.jackson.databind.exc.UnrecognizedPropertyException.class); + assertGeneratedReader(m.blackbird(), Validated.class); + } + + static final class RecordingHandler extends DeserializationProblemHandler { + final List seen = new ArrayList<>(); + + @Override + public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, + ValueDeserializer deserializer, Object beanOrClass, String propertyName) { + seen.add(beanOrClass); + p.skipChildren(); + return true; + } + } + + @Test + public void problemHandlerSeesRecordInstance() { + RecordingHandler stock = new RecordingHandler(); + RecordingHandler blackbird = new RecordingHandler(); + Mappers m = new Mappers( + tools.jackson.databind.json.JsonMapper.builder().addHandler(stock).build(), + tools.jackson.databind.json.JsonMapper.builder().addHandler(blackbird) + .addModule(new tools.jackson.module.blackbird.BlackbirdModule()).build()); + Validated v = assertSameRead(m, "{\"zzz\":1,\"n\":1,\"s\":\"x\"}", Validated.class).get(); + assertEquals(List.of(new Validated(1, "x")), stock.seen); + assertEquals(stock.seen, blackbird.seen); + assertEquals(new Validated(1, "x"), v); + assertGeneratedReader(m.blackbird(), Validated.class); + } + + /* + /********************************************************************** + /* Nulls.AS_EMPTY for a record-valued property + /********************************************************************** + */ + + public record Inner(String a, int b) {} + + public record Names(String a, String b) {} + + public static class AsEmptyHolder { + @JsonSetter(nulls = Nulls.AS_EMPTY) + public Names names; + @JsonSetter(nulls = Nulls.AS_EMPTY) + public Inner inner; + public String name; + } + + // The empty value of a record comes from its creator with null + // arguments, which a primitive component cannot take: stock fails then. + @Test + public void asEmptyRecordProperty() { + Mappers m = mappers(); + AsEmptyHolder h = assertSameRead(m, "{\"names\":null,\"name\":\"n\"}", + AsEmptyHolder.class).get(); + assertEquals(new Names(null, null), h.names); + assertSameRead(m, "{\"inner\":null}", AsEmptyHolder.class) + .error(ValueInstantiationException.class); + assertGeneratedReader(m.blackbird(), AsEmptyHolder.class); + assertGeneratedReader(m.blackbird(), Names.class); + assertGeneratedReader(m.blackbird(), Inner.class); + } + + /* + /********************************************************************** + /* Names as stock reads them through the creator + /********************************************************************** + */ + + public record CiRec(@JsonAlias("nm") String name, int count) {} + + @Test + public void caseInsensitiveNamesAndAliases() { + Mappers m = mappers(b -> b.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)); + assertEquals(new CiRec("a", 1), + assertSameRead(m, "{\"NAME\":\"a\",\"Count\":1}", CiRec.class).get()); + assertEquals(new CiRec("b", 2), + assertSameRead(m, "{\"NM\":\"b\",\"count\":2}", CiRec.class).get()); + assertGeneratedReader(m.blackbird(), CiRec.class); + } + + public record AnyCreatorRec(String name, + @com.fasterxml.jackson.annotation.JsonAnySetter java.util.Map rest) {} + + @Test + public void anySetterCreatorParameterKeepsStockBean() { + Mappers m = mappers(); + AnyCreatorRec r = assertSameRead(m, "{\"name\":\"n\",\"x\":1}", AnyCreatorRec.class) + .get(); + assertEquals(java.util.Map.of("x", 1), r.rest()); + assertStockReader(m.blackbird(), AnyCreatorRec.class); + assertGeneratedReader(m.blackbird(), Inner.class); + } +} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/WideBeanTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/WideBeanTest.java new file mode 100644 index 000000000..adb12c669 --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/WideBeanTest.java @@ -0,0 +1,140 @@ +package tools.jackson.module.blackbird.diff; + +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.MethodModel; +import java.lang.classfile.attribute.CodeAttribute; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.module.blackbird.codegen.ClassFileEmitters; +import tools.jackson.module.blackbird.internal.GeneratedReaderBase; +import tools.jackson.module.blackbird.internal.GeneratedWriterBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * HotSpot does not JIT-compile a method over 8000 bytes, so a bean whose + * generated method would be larger keeps the stock (de)serializer. The test + * builds beans of growing width, dumps every generated class, and checks + * that each generated method stays within + * {@link ClassFileEmitters#MAX_METHOD_BYTES}, that wide beans do generate up + * to near that limit, and that wider ones are demoted. + */ +public class WideBeanTest extends DifferentialTestBase +{ + @TempDir + static Path dumpDir; + + private static int beans; + + // A public class with n public String fields f0..f(n-1), defined next to + // this test (databind reads and writes public fields). + private static Class wideBean(int n) throws IllegalAccessException { + String name = WideBeanTest.class.getPackageName() + ".Wide" + n + "_" + (beans++); + byte[] bytes = ClassFile.of().build(ClassDesc.of(name), clb -> { + clb.withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_SUPER); + for (int i = 0; i < n; i++) { + clb.withField("f" + i, ConstantDescs.CD_String, ClassFile.ACC_PUBLIC); + } + clb.withMethod(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, + mb -> mb.withCode(cob -> cob.aload(0) + .invokespecial(ConstantDescs.CD_Object, ConstantDescs.INIT_NAME, + ConstantDescs.MTD_void) + .return_())); + }); + return MethodHandles.lookup().defineClass(bytes); + } + + private static String document(int n) { + StringBuilder sb = new StringBuilder("{"); + for (int i = 0; i < n; i++) { + sb.append(i == 0 ? "" : ",").append("\"f").append(i).append("\":\"v").append(i) + .append('"'); + } + return sb.append('}').toString(); + } + + private static int largestMethod(Path classFile) throws Exception { + int largest = 0; + for (MethodModel m : ClassFile.of().parse(Files.readAllBytes(classFile)).methods()) { + CodeAttribute code = m.findAttribute(Attributes.code()).orElse(null); + if (code != null) { + largest = Math.max(largest, code.codeLength()); + } + } + return largest; + } + + @Test + public void generatedMethodsStayWithinTheLimit() throws Exception { + // Restored afterwards, so a dump directory given to the whole run + // keeps working for later tests. + String oldDumpDir = System.setProperty("blackbird.debug.dumpDir", dumpDir.toString()); + List readers = new ArrayList<>(); + int widestReader = 0; + int largestReader = 0; + int largestWriter = 0; + try { + for (int n = 20; n <= 260; n += 20) { + Class type = wideBean(n); + Mappers m = mappers(); + String json = document(n); + Object value = assertSameRead(m, json, type).get(); + assertSameWrite(m, value); + + ValueDeserializer reader = rootReader(m.blackbird(), type); + Path readerDump = dumpDir.resolve(type.getName() + "-reader.class"); + if (reader instanceof GeneratedReaderBase) { + int size = largestMethod(readerDump); + assertTrue(size <= ClassFileEmitters.MAX_METHOD_BYTES, n + ": " + size); + widestReader = n; + largestReader = Math.max(largestReader, size); + readers.add(n + "=" + size); + } else { + assertFalse(Files.exists(readerDump), "a demoted reader is not dumped"); + } + ValueSerializer writer = rootWriter(m.blackbird(), type); + if (writer instanceof GeneratedWriterBase) { + int size = largestMethod(dumpDir.resolve(type.getName() + "-writer.class")); + assertTrue(size <= ClassFileEmitters.MAX_METHOD_BYTES, n + ": " + size); + largestWriter = Math.max(largestWriter, size); + } + } + } finally { + if (oldDumpDir == null) { + System.clearProperty("blackbird.debug.dumpDir"); + } else { + System.setProperty("blackbird.debug.dumpDir", oldDumpDir); + } + } + // Wide beans do generate, up to near the limit, and the widest ones + // keep the stock reader. + assertTrue(widestReader >= 60, "readers: " + readers); + assertTrue(widestReader < 260, "readers: " + readers); + assertTrue(largestReader > ClassFileEmitters.MAX_METHOD_BYTES - 1000, + "readers: " + readers); + assertTrue(largestWriter > 0); + } + + @Test + public void wideBeanKeepsStockReader() throws Exception { + Class type = wideBean(260); + Mappers m = mappers(); + assertSameRead(m, document(260), type); + assertStockReader(m.blackbird(), type); + Class narrow = wideBean(20); + assertSameRead(m, document(20), narrow); + assertGeneratedReader(m.blackbird(), narrow); + } +} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/WriterDiffTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/WriterDiffTest.java new file mode 100644 index 000000000..cd88c5c0f --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/diff/WriterDiffTest.java @@ -0,0 +1,310 @@ +package tools.jackson.module.blackbird.diff; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonApplyView; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonView; + +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.StreamWriteConstraints; +import tools.jackson.core.exc.StreamConstraintsException; +import tools.jackson.core.json.JsonFactory; +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.DefaultTyping; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.exc.InvalidDefinitionException; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import tools.jackson.databind.module.SimpleModule; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Generated writers: values with type serializers and non-default null + * serializers stay with the stock property writer, a value that is the bean + * itself gets stock self-reference handling, applied views apply, and + * failures carry the stock exception, message and reference path. + */ +public class WriterDiffTest extends DifferentialTestBase +{ + public static class Plain { + public String name = "p"; + public int count = 1; + } + + /* + /********************************************************************** + /* Type serializers, null serializers, applied views + /********************************************************************** + */ + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@t") + @JsonSubTypes({ @JsonSubTypes.Type(value = TypedRec.class, name = "rec") }) + public record TypedRec(String v) {} + + public static class HoldsTypedRec { + public String name = "h"; + public TypedRec rec = new TypedRec("x"); + } + + @Test + public void childWithTypeInfoWritesTypeId() { + Mappers m = mappers(); + String json = assertSameWrite(m, new HoldsTypedRec()).get(); + assertTrue(json.contains("\"@t\":\"rec\""), json); + String layout = assertGeneratedWriter(m.blackbird(), HoldsTypedRec.class); + assertArm(layout, "rec=STOCK"); + assertArm(layout, "name=STRING"); + } + + public record PlainRec(String v) {} + + public static class HoldsRecord { + public PlainRec rec = new PlainRec("x"); + public String name = "h"; + } + + @Test + public void defaultTypingWritesTypeIds() { + Mappers m = mappers(b -> b.activateDefaultTypingAsProperty( + BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build(), + DefaultTyping.NON_FINAL_AND_RECORDS, "@class")); + String json = assertSameWrite(m, new HoldsRecord()).get(); + assertTrue(json.contains("\"@class\""), json); + assertArm(assertGeneratedWriter(m.blackbird(), HoldsRecord.class), "rec=STOCK"); + } + + public static class EmptyForNull extends ValueSerializer { + @Override + public void serialize(Object value, JsonGenerator g, SerializationContext ctxt) { + g.writeString(""); + } + } + + public static class NullsUsing { + @JsonSerialize(nullsUsing = EmptyForNull.class) + public String a; + public String b; + } + + @Test + public void propertyNullSerializer() { + Mappers m = mappers(); + assertEquals("{\"a\":\"\",\"b\":null}", assertSameWrite(m, new NullsUsing()).get()); + String layout = assertGeneratedWriter(m.blackbird(), NullsUsing.class); + assertArm(layout, "a=STOCK"); + assertArm(layout, "b=STRING"); + } + + // Final value types get statically assigned serializers, so without the + // null-serializer gate a generated child arm would write them. + public static final class FinalKid { + public String name = "k"; + } + + public static class HoldsChild { + public FinalKid kid; + public String s; + public int n = 3; + } + + @Test + public void defaultNullSerializer() { + Mappers m = mappers(b -> b.addModule(new SimpleModule("nulls") + .setDefaultNullValueSerializer(new EmptyForNull()))); + assertEquals("{\"kid\":\"\",\"n\":3,\"s\":\"\"}", + assertSameWrite(m, new HoldsChild()).get()); + String layout = assertGeneratedWriter(m.blackbird(), HoldsChild.class); + assertArm(layout, "kid=STOCK"); + assertArm(layout, "s=STOCK"); + // Primitives are never null: they keep the generated write. + assertArm(layout, "n=INT"); + } + + public static class ViewA { } + public static class ViewB { } + + public static final class Viewed { + @JsonView(ViewA.class) + public String a = "1"; + @JsonView(ViewB.class) + public String b = "2"; + } + + public static class AppliesView { + @JsonView(ViewA.class) + @JsonApplyView(ViewB.class) + public Viewed inner = new Viewed(); + @JsonView(ViewA.class) + public Viewed plain = new Viewed(); + } + + @Test + public void appliedViewApplies() { + Mappers m = mappers(); + Outcome stock = new Outcome(m.stock().writerWithView(ViewA.class) + .writeValueAsString(new AppliesView()), null); + Outcome blackbird = new Outcome(m.blackbird().writerWithView(ViewA.class) + .writeValueAsString(new AppliesView()), null); + assertEquals(stock.describe(), blackbird.describe()); + assertEquals("{\"inner\":{\"b\":\"2\"},\"plain\":{\"a\":\"1\"}}", blackbird.get()); + String layout = assertGeneratedWriter(m.blackbird(), AppliesView.class); + assertArm(layout, "inner=STOCK"); + assertArm(layout, "plain=CHILD"); + } + + /* + /********************************************************************** + /* Failures and the reference path + /********************************************************************** + */ + + public static final class ThrowingGetter { + public String a = "x"; + + public String getB() { + throw new IllegalStateException("getter boom"); + } + } + + public static final class Inner { + public ThrowingGetter tg = new ThrowingGetter(); + } + + public static class Outer { + public Inner inner = new Inner(); + } + + @Test + public void getterFailureCarriesReferencePath() { + Mappers m = mappers(); + DatabindException e = assertSameWrite(m, new ThrowingGetter()) + .error(DatabindException.class); + assertTrue(e.getMessage().contains("getter boom"), e.getMessage()); + assertTrue(e.getMessage().contains("ThrowingGetter[\"b\"]"), e.getMessage()); + DatabindException nested = assertSameWrite(m, new Outer()) + .error(DatabindException.class); + assertTrue(nested.getMessage().contains("Outer[\"inner\"]->"), nested.getMessage()); + assertGeneratedWriter(m.blackbird(), ThrowingGetter.class); + assertArm(assertGeneratedWriter(m.blackbird(), Outer.class), "inner=CHILD"); + } + + @Test + public void getterFailureWithoutWrapping() { + Mappers m = mappers(b -> b.disable(SerializationFeature.WRAP_EXCEPTIONS)); + assertSameWrite(m, new ThrowingGetter()).error(IllegalStateException.class); + assertSameWrite(m, new Outer()).error(IllegalStateException.class); + } + + public static class CycA { + public String name = "a"; + public CycB b; + } + + public static class CycB { + public String name = "b"; + public CycA a; + } + + private static CycA cycle() { + CycA a = new CycA(); + CycB b = new CycB(); + a.b = b; + b.a = a; + return a; + } + + @Test + public void cycleHitsNestingLimitWithStockPath() { + Mappers m = mappers(); + StreamConstraintsException e = assertSameWrite(m, cycle()) + .error(StreamConstraintsException.class); + assertTrue(e.getMessage().contains("CycA[\"b\"]->"), e.getMessage()); + assertTrue(e.getMessage().contains("CycB[\"a\"]->"), e.getMessage()); + assertArm(assertGeneratedWriter(m.blackbird(), CycA.class), "name=STRING"); + } + + @Test + public void infiniteRecursion() { + // A class first initialized near the overflow would fail to + // initialize for good, so the exception paths run once up front. + assertSameWrite(mappers(), new ThrowingGetter()); + assertSameWrite(mappers(), cycle()); + // No nesting limit: the stack overflows first. + JsonFactory unlimited = JsonFactory.builder() + .streamWriteConstraints(StreamWriteConstraints.builder() + .maxNestingDepth(Integer.MAX_VALUE).build()) + .build(); + Mappers m = new Mappers(JsonMapper.builder(unlimited).build(), + JsonMapper.builder(unlimited) + .addModule(new tools.jackson.module.blackbird.BlackbirdModule()).build()); + Outcome stock = write(m.stock(), cycle()); + Outcome blackbird = write(m.blackbird(), cycle()); + DatabindException se = stock.error(DatabindException.class); + DatabindException be = blackbird.error(DatabindException.class); + assertTrue(se.getMessage().startsWith("Infinite recursion (StackOverflowError)"), + se.getMessage()); + // The overflow depth differs between the paths, and databind keeps a + // bounded number of path entries, so the chains are cut at different + // points of the same alternation. + assertEquals(head(se.getMessage()), head(be.getMessage())); + assertEquals(cycleEntries(), Set.copyOf(chain(se.getMessage()))); + assertEquals(cycleEntries(), Set.copyOf(chain(be.getMessage()))); + } + + private static final String CHAIN_START = "(through reference chain: "; + + private static String head(String message) { + return message.substring(0, message.indexOf(CHAIN_START)); + } + + private static List chain(String message) { + String chain = message.substring(message.indexOf(CHAIN_START) + CHAIN_START.length(), + message.lastIndexOf(')')); + return Arrays.stream(chain.split("->")).filter(e -> !e.equals("...")).toList(); + } + + private static Set cycleEntries() { + return Set.of(CycA.class.getName() + "[\"b\"]", CycB.class.getName() + "[\"a\"]"); + } + + /* + /********************************************************************** + /* Self-references + /********************************************************************** + */ + + public static class Selfie { + public String name = "s"; + + public Selfie getSelf() { + return this; + } + } + + @Test + public void directSelfReferenceFails() { + Mappers m = mappers(); + InvalidDefinitionException e = assertSameWrite(m, new Selfie()) + .error(InvalidDefinitionException.class); + assertTrue(e.getMessage().startsWith("Direct self-reference leading to cycle"), + e.getMessage()); + assertGeneratedWriter(m.blackbird(), Selfie.class); + } + + @Test + public void directSelfReferenceAsNull() { + Mappers m = mappers(b -> b.disable(SerializationFeature.FAIL_ON_SELF_REFERENCES) + .enable(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL)); + assertEquals("{\"name\":\"s\",\"self\":null}", assertSameWrite(m, new Selfie()).get()); + } +} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/BlackbirdInjectionTestBase.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/BlackbirdInjectionTestBase.java index 1f3575043..1a1d2790f 100644 --- a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/BlackbirdInjectionTestBase.java +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/BlackbirdInjectionTestBase.java @@ -1,180 +1,58 @@ package tools.jackson.module.blackbird.inject; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import tools.jackson.databind.BeanDescription; -import tools.jackson.databind.DeserializationConfig; -import tools.jackson.databind.SerializationConfig; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.SerializationContext; import tools.jackson.databind.ValueDeserializer; import tools.jackson.databind.ValueSerializer; -import tools.jackson.databind.deser.SettableBeanProperty; -import tools.jackson.databind.deser.ValueDeserializerModifier; -import tools.jackson.databind.deser.bean.BeanDeserializer; import tools.jackson.databind.json.JsonMapper; -import tools.jackson.databind.module.SimpleModule; -import tools.jackson.databind.ser.BeanPropertyWriter; -import tools.jackson.databind.ser.ValueSerializerModifier; import tools.jackson.module.blackbird.BlackbirdModule; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -// Test utilities for verifying that Blackbird's lambda-based injection pipeline -// actually ran on a given POJO. Because Blackbird's optimized property and writer -// classes (SettableIntProperty, etc.; IntPropertyWriter, etc.) are package-private -// inside the blackbird module, these checks rely on reflection and simple-name -// matching rather than compile-time type references. Mirrors the afterburner-tests -// harness; see that module's README for the broader rationale. +import tools.jackson.module.blackbird.internal.GeneratedBuilderBase; +import tools.jackson.module.blackbird.internal.GeneratedReaderBase; +import tools.jackson.module.blackbird.internal.GeneratedWriterBase; + +// Test utilities for verifying that Blackbird's codec generation engaged for a +// given POJO loaded from the unnamed module (classpath). The checks look at +// the (de)serializer the mapper uses for the type: a generated codec, or the +// stock one when a gate demoted the bean. (A modifier would only see the +// placeholder that stands in for either until resolution.) abstract class BlackbirdInjectionTestBase { + static { + // Strict mode: a codec-generation failure fails the test instead of + // demoting to the stock path (see CodegenFallbacks). Surefire sets + // this for every test class. Outside Maven (an IDE) this takes + // effect only if it runs before Blackbird's codegen classes first + // load, which read the property once. + System.setProperty("tools.jackson.module.blackbird.failOnCodegenError", "true"); + } + protected static Harness newHarness() { return new Harness(); } protected static final class Harness { - private final ConcurrentMap, ValueDeserializer> desers = new ConcurrentHashMap<>(); - private final ConcurrentMap, ValueSerializer> sers = new ConcurrentHashMap<>(); - final JsonMapper mapper; - - Harness() { - SimpleModule capture = new SimpleModule("capture") { - private static final long serialVersionUID = 1L; - @Override - public void setupModule(SetupContext ctxt) { - super.setupModule(ctxt); - ctxt.addDeserializerModifier(new ValueDeserializerModifier() { - private static final long serialVersionUID = 1L; - @Override - public ValueDeserializer modifyDeserializer( - DeserializationConfig cfg, BeanDescription.Supplier ref, - ValueDeserializer d) { - desers.put(ref.getBeanClass(), d); - return d; - } - }); - ctxt.addSerializerModifier(new ValueSerializerModifier() { - private static final long serialVersionUID = 1L; - @Override - public ValueSerializer modifySerializer( - SerializationConfig cfg, BeanDescription.Supplier ref, - ValueSerializer s) { - sers.put(ref.getBeanClass(), s); - return s; - } - }); - } - }; - this.mapper = JsonMapper.builder() - .addModule(new BlackbirdModule()) - .addModule(capture) - .build(); - } + final JsonMapper mapper = JsonMapper.builder() + .addModule(new BlackbirdModule()) + .build(); ValueDeserializer deserFor(Class cls) { - ValueDeserializer d = desers.get(cls); - assertNotNull(d, "no deserializer captured for " + cls.getName()); - return d; + DeserializationContext ctxt = mapper._deserializationContext(); + return ctxt.findContextualValueDeserializer(ctxt.constructType(cls), null); } ValueSerializer serFor(Class cls) { - ValueSerializer s = sers.get(cls); - assertNotNull(s, "no serializer captured for " + cls.getName()); - return s; - } - } - - /** Returns the `_propsByIndex` array from a bean deserializer. */ - protected static SettableBeanProperty[] propsOf(ValueDeserializer deser) { - if (!(deser instanceof BeanDeserializer)) { - throw new AssertionError("not a BeanDeserializer: " + deser.getClass().getName()); + SerializationContext ctxt = mapper._serializationContext(); + return ctxt.handleRootContextualization(ctxt.findValueSerializer(cls)); } - return (SettableBeanProperty[]) reflectField(deser, "_propsByIndex"); } - /** Returns the BeanPropertyWriter[] from a bean serializer, as a list. */ - protected static List writersOf(ValueSerializer ser) { - BeanPropertyWriter[] arr = (BeanPropertyWriter[]) reflectField(ser, "_props"); - List out = new ArrayList<>(arr.length); - for (BeanPropertyWriter w : arr) { - out.add(w); - } - return out; + /** True if the deserializer is a generated Blackbird codec. */ + protected static boolean isBlackbirdReader(ValueDeserializer deser) { + return (deser instanceof GeneratedReaderBase) || (deser instanceof GeneratedBuilderBase); } - /** Walks the class chain of {@code instance} looking for a declared field - * named {@code fieldName}. Picks the error message hypothesis based on the - * class's package — databind/blackbird fields usually mean a rename, - * anything else means the caller passed the wrong receiver. */ - protected static Object reflectField(Object instance, String fieldName) { - Class origClass = instance.getClass(); - Class c = origClass; - while (c != null) { - try { - Field f = c.getDeclaredField(fieldName); - f.setAccessible(true); - return f.get(instance); - } catch (NoSuchFieldException ignore) { - c = c.getSuperclass(); - } catch (IllegalAccessException e) { - throw new AssertionError("cannot read field '" + fieldName + "' on " - + origClass.getName(), e); - } - } - String pkg = origClass.getPackageName(); - String hint; - if (pkg.startsWith("tools.jackson.databind") - || pkg.startsWith("tools.jackson.module.blackbird")) { - hint = "databind or blackbird may have renamed or removed it;" - + " update " + BlackbirdInjectionTestBase.class.getSimpleName() - + " to match."; - } else { - hint = "this looks like the wrong receiver type — '" + fieldName - + "' is an internal Jackson field and the caller passed an" - + " instance of " + origClass.getName() + "."; - } - throw new AssertionError("field '" + fieldName + "' not found on " - + origClass.getName() + " (walked up full class chain) — " + hint); - } - - /** True if `prop`'s class chain contains Blackbird's OptimizedSettableBeanProperty. */ - protected static boolean isOptimizedProperty(SettableBeanProperty prop) { - return blackbirdClassChainIncludes(prop.getClass(), "OptimizedSettableBeanProperty"); - } - - /** True if `writer`'s class chain contains Blackbird's OptimizedBeanPropertyWriter. */ - protected static boolean isOptimizedWriter(BeanPropertyWriter writer) { - return blackbirdClassChainIncludes(writer.getClass(), "OptimizedBeanPropertyWriter"); - } - - /** Walks the superclass chain of {@code cls} looking for a class whose simple - * name is {@code simpleName}. Used to recognize Blackbird's package-private - * optimized types without importing them. */ - protected static boolean classChainIncludes(Class cls, String simpleName) { - Class c = cls; - while (c != null) { - if (simpleName.equals(c.getSimpleName())) { - return true; - } - c = c.getSuperclass(); - } - return false; - } - - /** Like {@link #classChainIncludes} but additionally requires the matched - * class to live inside a blackbird package. Guards against false positives - * from unrelated classes that happen to share a simple name. */ - protected static boolean blackbirdClassChainIncludes(Class cls, String simpleName) { - Class c = cls; - while (c != null) { - if (simpleName.equals(c.getSimpleName()) - && c.getPackageName().startsWith("tools.jackson.module.blackbird")) { - return true; - } - c = c.getSuperclass(); - } - return false; + /** True if the serializer is a generated Blackbird writer. */ + protected static boolean isBlackbirdWriter(ValueSerializer ser) { + return ser instanceof GeneratedWriterBase; } } diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/CrossLoaderAccessTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/CrossLoaderAccessTest.java deleted file mode 100644 index b775e7599..000000000 --- a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/CrossLoaderAccessTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package tools.jackson.module.blackbird.inject; - -import org.junit.jupiter.api.Test; - -import tools.jackson.databind.deser.SettableBeanProperty; - -import static org.junit.jupiter.api.Assertions.*; - -// Documents a finding about Blackbird's CrossLoaderAccess code path: -// on JDK 9+ with a bean in the unnamed module (classpath), the companion -// `$$JacksonBlackbirdAccess` class is **never** defined. -// -// Why: BBDeserializerModifier.updateBuilder obtains a lookup via -// MethodHandles.lookup() inside BlackbirdModule (full privilege on blackbird's -// own class), then calls ReflectionHack.privateLookupIn(beanClass, lookup). -// For an unnamed-module bean, privateLookupIn always succeeds and returns a -// lookup with hasFullPrivilegeAccess() == true over the bean's package. Then -// CrossLoaderAccess.grantAccess short-circuits on `hasFullAccess(lookup)` and -// returns the lookup unchanged, skipping the slow path that would define a -// $$JacksonBlackbirdAccess companion class via Lookup.defineClass(byte[]). -// -// That slow path exists for historical / edge-case scenarios (JDK 8 when -// DEFINE_CLASS is null, or a future JDK change that lowers privileges on -// privateLookupIn). It is not exercised by either Blackbird's in-tree test -// suite (same-module case is also full-privilege) nor by this classpath -// test module. -// -// This test pins the current behavior: the fast path must win, and the -// companion class must not be introduced as a side effect of deserialization. -// If Blackbird's CrossLoaderAccess logic changes such that the slow path -// starts firing for classpath POJOs, this test will catch it — at which -// point the maintainer should decide whether that's intended (and update the -// assertion) or accidental (and revert). -public class CrossLoaderAccessTest extends BlackbirdInjectionTestBase -{ - public static class XLoaderBean { - private int a; - private String b; - public int getA() { return a; } - public void setA(int a) { this.a = a; } - public String getB() { return b; } - public void setB(String b) { this.b = b; } - } - - private static final String COMPANION_CLASS_NAME = - XLoaderBean.class.getPackage().getName() + ".$$JacksonBlackbirdAccess"; - - @Test - public void testFastPathWins_NoCompanionClassDefined() throws Exception - { - Harness h = newHarness(); - - // Trigger Blackbird's modifier chain for XLoaderBean. - XLoaderBean bean = h.mapper.readValue("{\"a\":1,\"b\":\"hi\"}", XLoaderBean.class); - assertEquals(1, bean.getA()); - assertEquals("hi", bean.getB()); - - // Optimization still ran — setters are Blackbird-optimized. - SettableBeanProperty[] props = propsOf(h.deserFor(XLoaderBean.class)); - assertEquals(2, props.length); - for (SettableBeanProperty p : props) { - assertTrue(isOptimizedProperty(p), - "XLoaderBean." + p.getName() + " not optimized: " - + p.getClass().getName()); - } - - // But the $$JacksonBlackbirdAccess companion class must NOT have been - // defined — CrossLoaderAccess.grantAccess short-circuits for a lookup - // with hasFullPrivilegeAccess() == true, which is what privateLookupIn - // returns for an unnamed-module target. - assertFalse(companionClassExists(), - "CrossLoaderAccess unexpectedly defined " + COMPANION_CLASS_NAME - + " for an unnamed-module bean — the grantAccess fast path" - + " should have short-circuited. If Blackbird has started" - + " taking the slow path (e.g. because privateLookupIn" - + " semantics changed), update this test."); - } - - private static boolean companionClassExists() { - try { - Class.forName(COMPANION_CLASS_NAME, false, - XLoaderBean.class.getClassLoader()); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } -} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/FieldAccessNotOptimizedTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/FieldAccessNotOptimizedTest.java deleted file mode 100644 index f36ee1cf9..000000000 --- a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/FieldAccessNotOptimizedTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package tools.jackson.module.blackbird.inject; - -import org.junit.jupiter.api.Test; - -import tools.jackson.databind.deser.SettableBeanProperty; - -import static org.junit.jupiter.api.Assertions.*; - -// Documents a known design limitation of Blackbird: direct public-field access -// is NOT optimized. BBDeserializerModifier.nextProperty only handles properties -// whose backing JDK member is a Method (setter); if it's a Field, the method -// returns early and the property is left as a plain FieldProperty. Afterburner -// optimizes both setter and field access; Blackbird deliberately doesn't. -// -// The point of this test is to pin that contract. If Blackbird ever grows -// field-access support, this test will start failing — which is the correct -// signal to update it. -public class FieldAccessNotOptimizedTest extends BlackbirdInjectionTestBase -{ - public static class FieldOnlyBean { - public int intField; - public long longField; - public boolean boolField; - public String stringField; - } - - private final Harness h = newHarness(); - - @Test - public void testFieldPropsAreNotReplacedWithOptimizedVersions() throws Exception - { - // End-to-end deserialization must still work — Blackbird just delegates - // to databind's plain FieldProperty for these. - FieldOnlyBean bean = h.mapper.readValue( - "{\"intField\":1,\"longField\":2,\"boolField\":true,\"stringField\":\"x\"}", - FieldOnlyBean.class); - assertEquals(1, bean.intField); - assertEquals(2L, bean.longField); - assertTrue(bean.boolField); - assertEquals("x", bean.stringField); - - // None of the properties should be Blackbird-optimized. - SettableBeanProperty[] props = propsOf(h.deserFor(FieldOnlyBean.class)); - assertEquals(4, props.length); - for (SettableBeanProperty p : props) { - assertFalse(isOptimizedProperty(p), - "Blackbird unexpectedly optimized field property '" + p.getName() - + "' (is " + p.getClass().getName() + "). If Blackbird" - + " has grown field-access support, update this test to" - + " assert the positive case instead."); - } - } -} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/FieldAccessTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/FieldAccessTest.java new file mode 100644 index 000000000..91365b579 --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/FieldAccessTest.java @@ -0,0 +1,37 @@ +package tools.jackson.module.blackbird.inject; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +// Public-field access is generated: a bean whose properties are all public +// fields engages a Blackbird codec that stores through putfield (read side) and +// getfield (write side), the same as a setter/getter POJO. Values must match +// stock databind. +public class FieldAccessTest extends BlackbirdInjectionTestBase +{ + public static class FieldOnlyBean { + public int intField; + public long longField; + public boolean boolField; + public String stringField; + } + + private final Harness h = newHarness(); + + @Test + public void testFieldBackedBeanEngagesCodec() throws Exception + { + FieldOnlyBean bean = h.mapper.readValue( + "{\"intField\":1,\"longField\":2,\"boolField\":true,\"stringField\":\"x\"}", + FieldOnlyBean.class); + assertEquals(1, bean.intField); + assertEquals(2L, bean.longField); + assertTrue(bean.boolField); + assertEquals("x", bean.stringField); + + assertTrue(isBlackbirdReader(h.deserFor(FieldOnlyBean.class)), + "FieldOnlyBean did not engage a Blackbird codec: " + + h.deserFor(FieldOnlyBean.class).getClass().getName()); + } +} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/SerializerInjectionTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/SerializerInjectionTest.java index 41f6ada69..431480fab 100644 --- a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/SerializerInjectionTest.java +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/SerializerInjectionTest.java @@ -1,19 +1,15 @@ package tools.jackson.module.blackbird.inject; -import java.util.List; - import org.junit.jupiter.api.Test; -import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.json.JsonMapper; import static org.junit.jupiter.api.Assertions.*; -// Verifies Blackbird's serializer-side optimization runs end-to-end for all -// accessor-method specializations, and documents the parallel limitation with -// the deserializer side: direct public-field writers are not optimized either. -// BBSerializerModifier.createProperty skips properties whose backing JDK -// member isn't an AnnotatedMethod (see BBSerializerModifier.java lines -// 103-109), so public fields stay as plain BeanPropertyWriter instances. +// Verifies Blackbird's serializer codec generation engages end-to-end on the +// classpath and produces output identical to stock databind, for a getter POJO +// and for a public-field POJO (whose values are read through a generated +// getfield). public class SerializerInjectionTest extends BlackbirdInjectionTestBase { public static class GetterSerPojo { @@ -44,44 +40,32 @@ public FieldSerPojo() { } } @Test - public void testAllGetterBasedWritersOptimized() throws Exception + public void testGetterPojoEngagesGeneratedWriter() throws Exception { Harness h = newHarness(); + GetterSerPojo pojo = new GetterSerPojo(1, 2L, true, "x", java.util.Arrays.asList("a", "b")); - String json = h.mapper.writeValueAsString( - new GetterSerPojo(1, 2L, true, "x", java.util.Arrays.asList("a", "b"))); - assertTrue(json.contains("\"intProp\":1"), json); - assertTrue(json.contains("\"longProp\":2"), json); - assertTrue(json.contains("\"boolProp\":true"), json); - assertTrue(json.contains("\"stringProp\":\"x\""), json); - assertTrue(json.contains("\"objectProp\":[\"a\",\"b\"]"), json); + String json = h.mapper.writeValueAsString(pojo); + assertEquals(new JsonMapper().writeValueAsString(pojo), json, + "generated writer output differs from stock databind"); - List writers = writersOf(h.serFor(GetterSerPojo.class)); - assertEquals(5, writers.size(), "expected 5 writers, got " + writers); - for (BeanPropertyWriter w : writers) { - assertTrue(isOptimizedWriter(w), - "ser writer '" + w.getName() + "' not optimized (is " - + w.getClass().getName() + "); BBSerializerModifier" - + " did not replace it with a Blackbird-generated writer"); - } + assertTrue(isBlackbirdWriter(h.serFor(GetterSerPojo.class)), + "GetterSerPojo did not engage a Blackbird writer codec: " + + h.serFor(GetterSerPojo.class).getClass().getName()); } @Test - public void testFieldBackedWriterNotOptimized() throws Exception + public void testFieldBackedWriterEngagesCodec() throws Exception { Harness h = newHarness(); + FieldSerPojo pojo = new FieldSerPojo(42); - // Serialization still works end-to-end, just not via a Blackbird-generated accessor. - String json = h.mapper.writeValueAsString(new FieldSerPojo(42)); - assertTrue(json.contains("\"value\":42"), json); + String json = h.mapper.writeValueAsString(pojo); + assertEquals(new JsonMapper().writeValueAsString(pojo), json, + "field-backed output differs from stock databind"); - List writers = writersOf(h.serFor(FieldSerPojo.class)); - assertEquals(1, writers.size()); - BeanPropertyWriter w = writers.get(0); - assertFalse(isOptimizedWriter(w), - "Blackbird unexpectedly optimized a field-backed writer '" + w.getName() - + "' (is " + w.getClass().getName() + "). If BBSerializerModifier" - + " has grown field-access support, update this test to assert" - + " the positive case instead."); + assertTrue(isBlackbirdWriter(h.serFor(FieldSerPojo.class)), + "FieldSerPojo did not engage a Blackbird writer codec: " + + h.serFor(FieldSerPojo.class).getClass().getName()); } } diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/SetterOptimizationTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/SetterOptimizationTest.java index 93db3d552..05fa1f8d1 100644 --- a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/SetterOptimizationTest.java +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/inject/SetterOptimizationTest.java @@ -2,17 +2,12 @@ import org.junit.jupiter.api.Test; -import tools.jackson.databind.deser.SettableBeanProperty; - import static org.junit.jupiter.api.Assertions.*; -// End-to-end verification that Blackbird's setter optimization runs on POJOs -// loaded from the unnamed module (classpath). The specializations tested here -// correspond to BBDeserializerModifier's four primitive branches (int, long, -// boolean, object/String) plus the object/non-String branch. -// -// Note: Blackbird does NOT optimize direct public-field access — only setter -// methods. See FieldAccessNotOptimizedTest for the documented negative case. +// End-to-end verification that Blackbird's codec generation engages for setter +// POJOs loaded from the unnamed module (classpath). The beans cover the +// generator's scalar kinds (int, long, boolean, String) plus a non-scalar +// setter that rides the codec's stock-property arm. public class SetterOptimizationTest extends BlackbirdInjectionTestBase { public static class IntSetterBean { @@ -47,28 +42,28 @@ public static class ObjectSetterBean { public void testIntSetter() throws Exception { IntSetterBean b = h.mapper.readValue("{\"value\":42}", IntSetterBean.class); assertEquals(42, b.getValue()); - assertAllPropsOptimized(IntSetterBean.class); + assertCodecEngaged(IntSetterBean.class); } @Test public void testLongSetter() throws Exception { LongSetterBean b = h.mapper.readValue("{\"value\":9999999999}", LongSetterBean.class); assertEquals(9999999999L, b.getValue()); - assertAllPropsOptimized(LongSetterBean.class); + assertCodecEngaged(LongSetterBean.class); } @Test public void testBooleanSetter() throws Exception { BooleanSetterBean b = h.mapper.readValue("{\"value\":true}", BooleanSetterBean.class); assertTrue(b.isValue()); - assertAllPropsOptimized(BooleanSetterBean.class); + assertCodecEngaged(BooleanSetterBean.class); } @Test public void testStringSetter() throws Exception { StringSetterBean b = h.mapper.readValue("{\"value\":\"hi\"}", StringSetterBean.class); assertEquals("hi", b.getValue()); - assertAllPropsOptimized(StringSetterBean.class); + assertCodecEngaged(StringSetterBean.class); } @Test @@ -76,17 +71,12 @@ public void testObjectSetter() throws Exception { ObjectSetterBean b = h.mapper.readValue("{\"value\":[\"a\",\"b\"]}", ObjectSetterBean.class); assertEquals(2, b.getValue().size()); assertEquals("a", b.getValue().get(0)); - assertAllPropsOptimized(ObjectSetterBean.class); + assertCodecEngaged(ObjectSetterBean.class); } - private void assertAllPropsOptimized(Class cls) { - SettableBeanProperty[] props = propsOf(h.deserFor(cls)); - assertTrue(props.length > 0, "no properties for " + cls.getSimpleName()); - for (SettableBeanProperty p : props) { - assertTrue(isOptimizedProperty(p), - cls.getSimpleName() + "." + p.getName() + " not optimized: " - + p.getClass().getName() - + " — BBDeserializerModifier did not install a SettableXProperty"); - } + private void assertCodecEngaged(Class cls) { + assertTrue(isBlackbirdReader(h.deserFor(cls)), + cls.getSimpleName() + " did not engage a Blackbird codec: " + + h.deserFor(cls).getClass().getName()); } } diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/jdk/IsolatedLoaderTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/jdk/IsolatedLoaderTest.java new file mode 100644 index 000000000..eebc7e69e --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/jdk/IsolatedLoaderTest.java @@ -0,0 +1,182 @@ +package tools.jackson.module.blackbird.jdk; + +import java.lang.classfile.ClassFile; +import java.lang.classfile.Label; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.Field; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.deser.bean.BeanDeserializer; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.module.blackbird.BlackbirdModule; +import tools.jackson.module.blackbird.codegen.CodegenFallbacks; +import tools.jackson.module.blackbird.internal.GeneratedReaderBase; +import tools.jackson.module.blackbird.internal.GeneratedWriterBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * The ClassFile API computes stack map frames with a class hierarchy + * resolver. Its default resolver reads class files through the system class + * loader, which does not see Jackson when an application server, a plugin + * host or OSGi loads Jackson and Blackbird in a loader of their own. This + * test runs a child JVM whose class path has only the test classes, and + * loads Jackson and Blackbird there in a loader whose parent is the platform + * loader. + */ +public class IsolatedLoaderTest +{ + public static class Child { + public String name; + public int rank; + } + + public static class Parent { + public String id; + public Child child; + public List children; + public long count; + public boolean flag; + } + + public record Rec(String name, int count, Child child) {} + + @TempDir + Path dir; + + /** Runs inside the isolated loader. */ + public static class Probe { + public static List run() throws Exception { + System.setProperty(CodegenFallbacks.FAIL_ON_ERROR_PROPERTY, "true"); + List out = new ArrayList<>(); + out.add("isolated " + (JsonMapper.class.getClassLoader() == Probe.class.getClassLoader())); + Field field = Class.forName("tools.jackson.module.blackbird.codegen.BeanReaderGenerator") + .getDeclaredField("CLASS_FILE"); + field.setAccessible(true); + out.add("default resolver " + mergeFrame(ClassFile.of()) + + ", generators " + mergeFrame((ClassFile) field.get(null))); + + ObjectMapper mapper = JsonMapper.builder().addModule(new BlackbirdModule()).build(); + Parent p = mapper.readValue("{\"id\":\"p\",\"child\":{\"name\":\"c\",\"rank\":2}," + + "\"children\":[{\"name\":\"d\",\"rank\":3}],\"count\":4,\"flag\":true}", + Parent.class); + out.add(mapper.writeValueAsString(p)); + Rec r = mapper.readValue("{\"name\":\"r\",\"count\":5,\"child\":{\"name\":\"e\"}}", + Rec.class); + out.add(mapper.writeValueAsString(r)); + DeserializationContext dc = mapper._deserializationContext(); + SerializationContext sc = mapper._serializationContext(); + for (Class type : List.of(Parent.class, Child.class, Rec.class)) { + boolean reader = dc.findRootValueDeserializer(dc.constructType(type)) + instanceof GeneratedReaderBase; + boolean writer = sc.handleRootContextualization(sc.findValueSerializer(type)) + instanceof GeneratedWriterBase; + out.add(type.getSimpleName() + " reader=" + reader + " writer=" + writer); + } + return out; + } + + // Two branches leave a GeneratedReaderBase and a BeanDeserializer on + // the stack. The frame where they join needs their common superclass, + // so the resolver must find both classes. + private static String mergeFrame(ClassFile classFile) { + ClassDesc reader = ClassDesc.of(GeneratedReaderBase.class.getName()); + ClassDesc stock = ClassDesc.of(BeanDeserializer.class.getName()); + try { + classFile.build(ClassDesc.of("Merge"), clb -> clb.withMethod("pick", + MethodTypeDesc.of(ConstantDescs.CD_Object, ConstantDescs.CD_boolean, + reader, stock), + ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, + mb -> mb.withCode(cob -> { + Label second = cob.newLabel(); + Label join = cob.newLabel(); + cob.iload(0).ifeq(second) + .aload(1).goto_(join) + .labelBinding(second).aload(2) + .labelBinding(join).areturn(); + }))); + return "resolves"; + } catch (IllegalArgumentException e) { + return "fails"; + } + } + } + + /** The main class of the child JVM: it sees only the test classes. */ + public static class Launcher { + public static void main(String[] args) throws Exception { + URL[] urls = new URL[args.length]; + for (int i = 0; i < args.length; i++) { + urls[i] = Path.of(args[i]).toUri().toURL(); + } + try (URLClassLoader loader = new URLClassLoader("isolated", urls, + ClassLoader.getPlatformClassLoader())) { + Class probe = Class.forName(Launcher.class.getName() + .replace("$Launcher", "$Probe"), true, loader); + for (Object line : (List) probe.getMethod("run").invoke(null)) { + System.out.println(line); + } + } + } + } + + private static String location(Class type) throws URISyntaxException { + return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI()) + .toString(); + } + + @Test + public void codecsGenerateWhenTheSystemLoaderCannotSeeJackson() throws Exception { + String testClasses = location(IsolatedLoaderTest.class); + List cmd = List.of( + Path.of(System.getProperty("java.home"), "bin", "java").toString(), + "--class-path", testClasses, + Launcher.class.getName(), + location(BlackbirdModule.class), + location(ObjectMapper.class), + location(JsonParser.class), + location(JsonProperty.class), + testClasses); + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.environment().remove("JAVA_TOOL_OPTIONS"); + pb.environment().remove("JDK_JAVA_OPTIONS"); + pb.redirectOutput(dir.resolve("out.txt").toFile()); + pb.redirectError(dir.resolve("err.txt").toFile()); + Process process = pb.start(); + if (!process.waitFor(120, TimeUnit.SECONDS)) { + process.destroyForcibly(); + fail("child JVM timed out: " + cmd); + } + String err = Files.readString(dir.resolve("err.txt")); + assertEquals(0, process.exitValue(), err); + assertEquals(List.of( + "isolated true", + "default resolver fails, generators resolves", + "{\"child\":{\"name\":\"c\",\"rank\":2}," + + "\"children\":[{\"name\":\"d\",\"rank\":3}],\"count\":4,\"flag\":true," + + "\"id\":\"p\"}", + "{\"name\":\"r\",\"count\":5,\"child\":{\"name\":\"e\",\"rank\":0}}", + "Parent reader=true writer=true", + "Child reader=true writer=true", + "Rec reader=true writer=true"), + Files.readAllLines(dir.resolve("out.txt")), err); + } +} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/jdk/JdkCompatibilityTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/jdk/JdkCompatibilityTest.java new file mode 100644 index 000000000..144b017b2 --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/jdk/JdkCompatibilityTest.java @@ -0,0 +1,460 @@ +package tools.jackson.module.blackbird.jdk; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.tools.ToolProvider; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.JacksonModule; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.module.blackbird.BlackbirdModule; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * Runs a small program against the packaged Blackbird jar in child JVMs: on + * the JDK that runs the build (25 or later), and on each Java 17-24 home + * listed (comma-separated) in the {@value #OLD_JDKS_PROPERTY} system property. + * The jar is multi-release: its base classes are class file 61 and only the + * two code generators are release 25 classes under META-INF/versions/25. On + * Java 17-24, module discovery must skip Blackbird with one warning and still + * register every other module, and an explicit registration must fail with a + * clear message, also when discovery runs before or after it; on 25+, both + * must engage the generated codecs. A second module provider, compiled at + * release 17, is on the path of every run. The program is a single source + * file that the child's launcher compiles (JEP 330), so it runs on every JDK + * no matter what release this module compiles at. + */ +public class JdkCompatibilityTest +{ + static final String OLD_JDKS_PROPERTY = "blackbird.test.oldJdks"; + + private static final String INACTIVE_WARNING = + "found through module discovery, is inactive"; + + // The one class that the emitters' initializers load and stock databind + // does not load for the probe's beans. + private static final String MISSING_CORE_CLASS = "tools/jackson/core/type/WritableTypeId"; + + private static final String MISSING_CORE_CLASS_ERROR = + "java.lang.NoClassDefFoundError: " + MISSING_CORE_CLASS; + + private static final String PROBE = """ + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; + + import tools.jackson.databind.JacksonModule; + import tools.jackson.databind.ObjectMapper; + import tools.jackson.databind.json.JsonMapper; + + // Names Blackbird only reflectively, like an application that + // gets it through module discovery. + public class BlackbirdProbe { + public static class Bean { + public int x; + public String s; + } + + public static void main(String[] args) throws Exception { + System.out.println("runtime " + Runtime.version().feature() + + " " + Runtime.version()); + if (args[0].equals("discover")) { + for (int i = 0; i < 2; i++) { + ObjectMapper mapper = JsonMapper.builder().findAndAddModules().build(); + boolean found = false; + for (JacksonModule m : mapper.registeredModules()) { + found |= m.getClass().getName() + .startsWith("tools.jackson.module.blackbird."); + } + Bean bean = mapper.readValue("{\\"x\\":1,\\"s\\":\\"a\\"}", Bean.class); + System.out.println("discovered " + found + " modules " + names(mapper) + + " roundtrip " + mapper.writeValueAsString(bean)); + } + return; + } + JacksonModule module = (JacksonModule) Class.forName( + "tools.jackson.module.blackbird.BlackbirdModule") + .getDeclaredConstructor().newInstance(); + JsonMapper.Builder builder = JsonMapper.builder(); + switch (args[0]) { + case "explicit" -> builder.addModule(module); + case "explicit-discover" -> builder.addModule(module).findAndAddModules(); + case "discover-explicit" -> builder.findAndAddModules().addModule(module); + default -> throw new IllegalArgumentException(args[0]); + } + try { + System.out.println("registered modules " + names(builder.build())); + } catch (IllegalStateException e) { + System.out.println("rejected " + e.getMessage()); + } + } + + private static List names(ObjectMapper mapper) { + List names = new ArrayList<>(); + for (JacksonModule m : mapper.registeredModules()) { + names.add(m.getModuleName()); + } + Collections.sort(names); + return names; + } + } + """; + + // The module provider of another library, for the class path + // (META-INF/services) and the module path (module-info). + private static final String OTHER_MODULE_NAME = "OtherProbeModule"; + + private static final String OTHER_MODULE_INFO = """ + module blackbird.probe.other { + requires tools.jackson.databind; + provides tools.jackson.databind.JacksonModule with probe.other.OtherProbeModule; + } + """; + + private static final String OTHER_MODULE = """ + package probe.other; + + import tools.jackson.core.Version; + import tools.jackson.databind.JacksonModule; + + public final class OtherProbeModule extends JacksonModule { + @Override + public String getModuleName() { + return "OtherProbeModule"; + } + + @Override + public Version version() { + return Version.unknownVersion(); + } + + @Override + public void setupModule(SetupContext context) { + } + } + """; + + @TempDir + static Path probeDir; + + static Stream javaHomes() { + List homes = new ArrayList<>(); + homes.add(Path.of(System.getProperty("java.home"))); + homes.addAll(oldJdkHomes()); + return homes.stream(); + } + + private static List oldJdkHomes() { + String prop = System.getProperty(OLD_JDKS_PROPERTY, "").trim(); + if (prop.isEmpty()) { + return List.of(); + } + return Arrays.stream(prop.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(Path::of) + .collect(Collectors.toList()); + } + + @Test + public void oldJdksAreConfigured() { + assumeTrue(!oldJdkHomes().isEmpty(), "No Java 17-24 home configured: set -D" + + OLD_JDKS_PROPERTY + "=[,] to run the old-runtime cases"); + for (Path home : oldJdkHomes()) { + assertTrue(Files.isExecutable(home.resolve("bin/java")), "not a JDK home: " + home); + } + } + + @ParameterizedTest + @MethodSource("javaHomes") + public void discoveryOnClassPath(Path javaHome) throws Exception { + assertDiscovery(run(javaHome, false, "discover")); + } + + @ParameterizedTest + @MethodSource("javaHomes") + public void discoveryOnModulePath(Path javaHome) throws Exception { + assertDiscovery(run(javaHome, true, "discover")); + } + + @ParameterizedTest + @MethodSource("javaHomes") + public void explicitRegistration(Path javaHome) throws Exception { + assertExplicit(run(javaHome, false, "explicit"), List.of("BlackbirdModule")); + } + + // Databind keeps the module added last for each registration id, so the + // discovery provider must not replace an explicit registration. + @ParameterizedTest + @MethodSource("javaHomes") + public void explicitRegistrationThenDiscovery(Path javaHome) throws Exception { + assertExplicit(run(javaHome, false, "explicit-discover"), + List.of("BlackbirdModule", OTHER_MODULE_NAME)); + } + + @ParameterizedTest + @MethodSource("javaHomes") + public void discoveryThenExplicitRegistration(Path javaHome) throws Exception { + assertExplicit(run(javaHome, false, "discover-explicit"), + List.of("BlackbirdModule", OTHER_MODULE_NAME)); + } + + // The packaged jar is what users get; target/classes (used by a reactor + // build that stops before the package phase) is not multi-release. + @Test + public void jarIsMultiReleaseWithJava17Base() throws Exception { + Path jar = blackbirdJar(); + try (JarFile jf = new JarFile(jar.toFile())) { + Attributes main = jf.getManifest().getMainAttributes(); + assertEquals("true", main.getValue("Multi-Release")); + assertEquals("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=17))\"", + main.getValue("Require-Capability")); + List versioned = new ArrayList<>(); + for (JarEntry e : jf.stream().toList()) { + if (!e.getName().endsWith(".class")) { + continue; + } + int major = classMajor(jf, e); + if (e.getName().startsWith("META-INF/versions/25/")) { + assertEquals(69, major, e.getName()); + versioned.add(e.getName()); + } else { + assertEquals(61, major, e.getName()); + } + } + assertTrue(versioned.contains("META-INF/versions/25/tools/jackson/module/blackbird/codegen/BeanReaderGenerator.class"), + "versioned entries: " + versioned); + assertTrue(versioned.contains("META-INF/versions/25/tools/jackson/module/blackbird/codegen/BeanWriterGenerator.class"), + "versioned entries: " + versioned); + } + } + + // A jackson-core without a class that the emitters need stands for a + // jackson-core or jackson-databind of another version. The emitters then + // fail to initialize, and Blackbird must behave as on Java 24. + @Test + public void discoveryWithMismatchedJacksonCore() throws Exception { + Result r = run(Path.of(System.getProperty("java.home")), false, "discover", + mismatchedJacksonCore()); + assertDiscovery(r, false); + assertTrue(r.stderr.stream().anyMatch(l -> l.contains(INACTIVE_WARNING) + && l.contains(MISSING_CORE_CLASS_ERROR)), r.toString()); + } + + @Test + public void explicitRegistrationWithMismatchedJacksonCore() throws Exception { + Result r = run(Path.of(System.getProperty("java.home")), false, "explicit", + mismatchedJacksonCore()); + assertEquals(0, r.exitCode, r.toString()); + assertEquals(2, r.stdout.size(), r.toString()); + assertTrue(r.stdout.get(1).startsWith("rejected Cannot register BlackbirdModule: its Java 25" + + " classes failed to initialize (" + MISSING_CORE_CLASS_ERROR + ")"), r.toString()); + } + + private static void assertDiscovery(Result r) { + assertDiscovery(r, r.feature >= 25); + } + + private static void assertDiscovery(Result r, boolean active) { + assertEquals(0, r.exitCode, r.toString()); + String discovered = "discovered true modules [BlackbirdModule, " + OTHER_MODULE_NAME + + "] roundtrip {\"s\":\"a\",\"x\":1}"; + assertEquals(List.of(r.stdout.get(0), discovered, discovered), r.stdout, r.toString()); + long warnings = r.stderr.stream().filter(l -> l.contains(INACTIVE_WARNING)).count(); + boolean engaged = r.stderr.stream().anyMatch(l -> l.contains("BBReader_BlackbirdProbe")) + && r.stderr.stream().anyMatch(l -> l.contains("BBWriter_BlackbirdProbe")); + if (active) { + assertEquals(0, warnings, r.toString()); + assertTrue(engaged, "expected generated codecs: " + r); + } else { + assertEquals(1, warnings, "expected exactly one warning: " + r); + assertFalse(engaged, r.toString()); + } + } + + private static void assertExplicit(Result r, List modules) { + assertEquals(0, r.exitCode, r.toString()); + assertEquals(2, r.stdout.size(), r.toString()); + if (r.feature >= 25) { + assertEquals("registered modules " + modules, r.stdout.get(1), r.toString()); + } else { + assertEquals("rejected Cannot register BlackbirdModule: it requires Java 25 or" + + " later, and this runtime is Java " + r.version, r.stdout.get(1) + .replaceFirst("\\. Remove the registration.*$", ""), + r.toString()); + } + } + + private static Result run(Path javaHome, boolean modulePath, String mode) + throws IOException, InterruptedException, URISyntaxException { + return run(javaHome, modulePath, mode, codeSource(JsonParser.class)); + } + + private static Result run(Path javaHome, boolean modulePath, String mode, Path jacksonCore) + throws IOException, InterruptedException, URISyntaxException { + Path probe = probeDir.resolve("BlackbirdProbe.java"); + if (!Files.exists(probe)) { + Files.writeString(probe, PROBE); + } + String path = Stream.of(blackbirdJar(), otherModuleJar(), codeSource(ObjectMapper.class), + jacksonCore, codeSource(JsonProperty.class)) + .map(Path::toString) + .collect(Collectors.joining(java.io.File.pathSeparator)); + List cmd = new ArrayList<>(); + cmd.add(javaHome.resolve("bin/java").toString()); + cmd.add("-Dblackbird.debug.codegen=true"); + if (modulePath) { + cmd.addAll(List.of("--module-path", path, "--add-modules", "ALL-MODULE-PATH")); + } else { + cmd.addAll(List.of("--class-path", path)); + } + cmd.add(probe.toString()); + cmd.add(mode); + ProcessBuilder pb = new ProcessBuilder(cmd); + Map env = pb.environment(); + env.remove("JAVA_TOOL_OPTIONS"); + env.remove("JDK_JAVA_OPTIONS"); + Path out = probeDir.resolve(mode + "-" + modulePath + "-out.txt"); + Path err = probeDir.resolve(mode + "-" + modulePath + "-err.txt"); + pb.redirectOutput(out.toFile()); + pb.redirectError(err.toFile()); + Process process = pb.start(); + if (!process.waitFor(120, TimeUnit.SECONDS)) { + process.destroyForcibly(); + fail("probe timed out: " + cmd); + } + Result r = new Result(cmd, process.exitValue(), + Files.readAllLines(out, StandardCharsets.UTF_8), + Files.readAllLines(err, StandardCharsets.UTF_8)); + assertFalse(r.stdout.isEmpty(), r.toString()); + return r; + } + + private static Path otherModuleJar() throws IOException, URISyntaxException { + Path jar = probeDir.resolve("other-probe-module.jar"); + if (Files.exists(jar)) { + return jar; + } + Path src = probeDir.resolve("other-src"); + Path classes = probeDir.resolve("other-classes"); + Files.createDirectories(src.resolve("probe/other")); + Files.writeString(src.resolve("module-info.java"), OTHER_MODULE_INFO); + Files.writeString(src.resolve("probe/other/OtherProbeModule.java"), OTHER_MODULE); + String modulePath = Stream.of(codeSource(ObjectMapper.class), + codeSource(JsonParser.class), codeSource(JsonProperty.class)) + .map(Path::toString) + .collect(Collectors.joining(java.io.File.pathSeparator)); + ByteArrayOutputStream diagnostics = new ByteArrayOutputStream(); + int rc = ToolProvider.getSystemJavaCompiler().run(null, diagnostics, diagnostics, + "--release", "17", "--module-path", modulePath, "-d", classes.toString(), + src.resolve("module-info.java").toString(), + src.resolve("probe/other/OtherProbeModule.java").toString()); + assertEquals(0, rc, diagnostics.toString(StandardCharsets.UTF_8)); + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(jar), manifest)) { + for (String name : List.of("module-info.class", "probe/other/OtherProbeModule.class")) { + out.putNextEntry(new JarEntry(name)); + out.write(Files.readAllBytes(classes.resolve(name))); + out.closeEntry(); + } + out.putNextEntry(new JarEntry("META-INF/services/" + JacksonModule.class.getName())); + out.write("probe.other.OtherProbeModule\n".getBytes(StandardCharsets.UTF_8)); + out.closeEntry(); + } + return jar; + } + + private static Path mismatchedJacksonCore() throws IOException, URISyntaxException { + Path jar = probeDir.resolve("jackson-core-mismatched.jar"); + if (Files.exists(jar)) { + return jar; + } + try (JarFile in = new JarFile(codeSource(JsonParser.class).toFile()); + JarOutputStream out = new JarOutputStream(Files.newOutputStream(jar))) { + for (JarEntry e : in.stream().toList()) { + if (e.getName().startsWith(MISSING_CORE_CLASS)) { + continue; + } + out.putNextEntry(new JarEntry(e.getName())); + try (InputStream data = in.getInputStream(e)) { + data.transferTo(out); + } + out.closeEntry(); + } + } + return jar; + } + + private static Path blackbirdJar() throws URISyntaxException { + Path location = codeSource(BlackbirdModule.class); + assumeTrue(Files.isRegularFile(location), "Needs the packaged Blackbird jar (mvn verify);" + + " this run reads " + location + ", which is not multi-release"); + return location; + } + + private static Path codeSource(Class cls) throws URISyntaxException { + return Path.of(cls.getProtectionDomain().getCodeSource().getLocation().toURI()); + } + + private static int classMajor(JarFile jf, JarEntry e) throws IOException { + try (InputStream in = jf.getInputStream(e)) { + byte[] head = in.readNBytes(8); + return ((head[6] & 0xFF) << 8) | (head[7] & 0xFF); + } + } + + private static final class Result { + final List cmd; + final int exitCode; + final List stdout; + final List stderr; + final int feature; + final String version; + + Result(List cmd, int exitCode, List stdout, List stderr) { + this.cmd = cmd; + this.exitCode = exitCode; + this.stdout = stdout; + this.stderr = stderr; + String first = stdout.isEmpty() ? "" : stdout.get(0); + // "runtime " + String[] parts = first.split(" "); + this.feature = (parts.length == 3) ? Integer.parseInt(parts[1]) : -1; + this.version = (parts.length == 3) ? parts[2] : "?"; + } + + @Override + public String toString() { + return "command " + cmd + "\nexit " + exitCode + "\nstdout:\n " + + String.join("\n ", stdout) + "\nstderr:\n " + String.join("\n ", stderr); + } + } +} diff --git a/blackbird-tests/src/test/java/tools/jackson/module/blackbird/jdk/PackagingTest.java b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/jdk/PackagingTest.java new file mode 100644 index 000000000..33ea9a3c9 --- /dev/null +++ b/blackbird-tests/src/test/java/tools/jackson/module/blackbird/jdk/PackagingTest.java @@ -0,0 +1,155 @@ +package tools.jackson.module.blackbird.jdk; + +import java.io.InputStream; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.constantpool.MethodRefEntry; +import java.lang.module.ModuleDescriptor; +import java.lang.reflect.Method; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; + +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.JacksonModule; +import tools.jackson.databind.deser.bean.BeanDeserializer; +import tools.jackson.module.blackbird.BlackbirdModule; +import tools.jackson.module.blackbird.codegen.MemberHandles; +import tools.jackson.module.blackbird.internal.DiscoveredBlackbirdModule; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * Structural checks of the published module: what it exports (JPMS and + * OSGi) and requires, what its helpers can reach, what it needs from + * jackson-core, and the release guard of the build. + */ +public class PackagingTest +{ + private static Path blackbirdLocation() throws URISyntaxException { + return Path.of(BlackbirdModule.class.getProtectionDomain().getCodeSource() + .getLocation().toURI()); + } + + private static ModuleDescriptor descriptor() throws Exception { + Path location = blackbirdLocation(); + if (Files.isDirectory(location)) { + try (InputStream in = Files.newInputStream(location.resolve("module-info.class"))) { + return ModuleDescriptor.read(in); + } + } + try (JarFile jar = new JarFile(location.toFile()); + InputStream in = jar.getInputStream(jar.getEntry("module-info.class"))) { + return ModuleDescriptor.read(in); + } + } + + // The internal package is not exported: generated codecs define in + // Blackbird's own module, so no other module links to it. + @Test + public void moduleExportsOnlyTheModulePackage() throws Exception { + ModuleDescriptor d = descriptor(); + assertEquals(Set.of("tools.jackson.module.blackbird"), d.exports().stream() + .map(ModuleDescriptor.Exports::source).collect(Collectors.toSet())); + assertTrue(d.exports().stream().noneMatch(ModuleDescriptor.Exports::isQualified)); + assertTrue(d.opens().isEmpty()); + } + + // Logging goes through System.Logger; no java.logging dependency. + @Test + public void moduleRequiresNoJavaLogging() throws Exception { + Set requires = descriptor().requires().stream() + .map(ModuleDescriptor.Requires::name).collect(Collectors.toSet()); + assertFalse(requires.contains("java.logging"), requires.toString()); + assertEquals(Set.of("java.base", "tools.jackson.core", "tools.jackson.databind"), + requires); + } + + @Test + public void moduleProvidesTheDiscoveryProvider() throws Exception { + ModuleDescriptor.Provides provides = descriptor().provides().stream() + .filter(p -> p.service().equals(JacksonModule.class.getName())) + .findFirst().orElseThrow(); + assertEquals(java.util.List.of(DiscoveredBlackbirdModule.class.getName()), + provides.providers()); + } + + // OSGi exports match the module descriptor. + @Test + public void bundleExportsOnlyTheModulePackage() throws Exception { + Path location = blackbirdLocation(); + assumeTrue(Files.isRegularFile(location), "Needs the packaged Blackbird jar (mvn verify);" + + " this run reads " + location); + try (JarFile jar = new JarFile(location.toFile())) { + String exports = jar.getManifest().getMainAttributes().getValue("Export-Package"); + assertTrue(exports.startsWith("tools.jackson.module.blackbird;"), exports); + assertFalse(exports.contains("tools.jackson.module.blackbird."), exports); + } + } + + // MemberHandles grants no access of its own: a member nobody made + // accessible unreflects only when public. Blackbird's own full-privilege + // lookup would also reach Blackbird's package-private members. + @Test + public void memberHandlesGrantNoPrivilege() throws Exception { + // A package-private static method of a package-private class in the + // package of MemberHandles. + Class descs = Class.forName("tools.jackson.module.blackbird.codegen.Descs"); + Method internal = descs.getDeclaredMethod("of", Class.class); + assertThrows(IllegalAccessException.class, () -> MemberHandles.unary(internal)); + Method open = descs.getDeclaredMethod("of", Class.class); + open.setAccessible(true); + assertNotNull(MemberHandles.unary(open)); + } + + // The generated readers call JsonParser.nextNameMatchAndToken (new + // in jackson-core 3.3), and so does stock BeanDeserializer: Blackbird + // needs no newer jackson-core than the databind it runs with. + @Test + public void stockDatabindAlreadyNeedsFusedNameMatch() throws Exception { + assertNotNull(JsonParser.class.getMethod("nextNameMatchAndToken", + tools.jackson.core.sym.PropertyNameMatcher.class)); + ClassModel stock; + try (InputStream in = BeanDeserializer.class.getResourceAsStream( + "BeanDeserializer.class")) { + stock = ClassFile.of().parse(in.readAllBytes()); + } + boolean calls = false; + for (var entry : stock.constantPool()) { + if (entry instanceof MethodRefEntry ref + && ref.name().equalsString("nextNameMatchAndToken")) { + calls = true; + } + } + assertTrue(calls, "stock BeanDeserializer no longer calls nextNameMatchAndToken"); + } + + // A release built on JDK < 25 would leave Blackbird out. The release profile (maven-release-plugin passes -Prelease) + // requires JDK 25; a manual check confirmed that + // "mvn -N -Prelease validate" fails on JDK 21 and passes on JDK 25. + @Test + public void releaseProfileRequiresJdk25() throws Exception { + Path rootPom = Path.of(System.getProperty("user.dir")).resolveSibling("pom.xml"); + assumeTrue(Files.isRegularFile(rootPom), "root pom.xml not found from " + + System.getProperty("user.dir")); + Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(rootPom.toFile()); + String version = (String) XPathFactory.newInstance().newXPath().evaluate( + "/*[local-name()='project']/*[local-name()='profiles']" + + "/*[local-name()='profile'][*[local-name()='id']='release']" + + "//*[local-name()='requireJavaVersion']/*[local-name()='version']", + doc, XPathConstants.STRING); + assertEquals("[25,)", version.trim()); + } +} diff --git a/blackbird/GenBlackbirdAccess.java b/blackbird/GenBlackbirdAccess.java deleted file mode 100644 index 802cf38e7..000000000 --- a/blackbird/GenBlackbirdAccess.java +++ /dev/null @@ -1,276 +0,0 @@ -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.tools.Diagnostic; -import javax.tools.DiagnosticListener; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import javax.tools.FileObject; -import javax.tools.JavaFileManager; -import javax.tools.JavaFileObject; -import javax.tools.JavaFileObject.Kind; -import javax.tools.SimpleJavaFileObject; -import javax.tools.StandardJavaFileManager; -import javax.tools.StandardLocation; - -/** - * The following code produces the class file stored in the data arrays in CrossLoaderAccess. - * Loading the Java compiler is extremely expensive so we inline the byte data instead. - * - * This code is not compiled or shipped with Blackbird, just here for maintainer reference. - */ -class GrantAccess { - private static MethodHandles.Lookup grantAccessSlow(final MethodHandles.Lookup lookup, final Package pkg) throws IOException, ReflectiveOperationException { - final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - final BlackbirdDiagnosticListener diagnostics = new BlackbirdDiagnosticListener<>(); - final StandardJavaFileManager delegateFileManager = compiler.getStandardFileManager(diagnostics, Locale.ROOT, StandardCharsets.UTF_8); - final String myPackage = pkg.getName(); - final BlackbirdFileManager fileManager = new BlackbirdFileManager(delegateFileManager, myPackage); - final JavaFileObject file = new BlackbirdFileManager.BlackbirdInputJavaFileObject("BlackbirdAccess", "package " + myPackage + "; public class BlackbirdAccess { public static final java.lang.invoke.MethodHandles.Lookup LOOKUP = java.lang.invoke.MethodHandles.lookup(); }"); - compiler - .getTask(null, fileManager, diagnostics, Arrays.asList("--release", "8"), null, Arrays.asList(file)) - .call(); - diagnostics.assertSuccess(); - final FileObject accessClassFile = fileManager.getFileForInput(StandardLocation.CLASS_PATH, myPackage, "BlackbirdAccess"); - final byte[] accessClassBytes = accessClassFile.openInputStream().readAllBytes(); - final Class accessClass = lookup.defineClass(accessClassBytes); - return (MethodHandles.Lookup) accessClass.getField("LOOKUP").get(null); - } -} - -class BlackbirdDiagnosticListener implements DiagnosticListener { - private static final Set FAILURES; - - static { - FAILURES = Collections.unmodifiableSet( - EnumSet.of(Diagnostic.Kind.ERROR, Diagnostic.Kind.WARNING, Diagnostic.Kind.MANDATORY_WARNING)); - } - - private final List> failures = new ArrayList<>(); - - @Override - public void report(final Diagnostic diagnostic) { - if (FAILURES.contains(diagnostic.getKind())) { - failures.add(diagnostic); - } - } - - public void assertSuccess() { - if (!failures.isEmpty()) { - throw new RuntimeException("Compilation failed:\n" - + failures.stream() - .map(Object::toString) - .collect(Collectors.joining("\n"))); - } - } -} - - -class BlackbirdFileManager implements JavaFileManager { - private final StandardJavaFileManager delegate; - private final Map createdFiles = new HashMap<>(); - private final String myPackage; - - public BlackbirdFileManager(final StandardJavaFileManager delegate, final String myPackage) { - this.delegate = delegate; - this.myPackage = myPackage; - } - - @Override - public ClassLoader getClassLoader(final Location location) { - return delegate.getClassLoader(location); - } - - @Override - public Iterable list(final Location location, final String packageName, final Set kinds, final boolean recurse) throws IOException { - final Iterable stdList = delegate.list(location, packageName, kinds, recurse); - if (StandardLocation.CLASS_PATH.equals(location)) { - return () -> new Iterator() { - boolean stdDone; - Iterator iter; - - @Override - public boolean hasNext() { - if (iter == null) { - iter = stdList.iterator(); - } - if (iter.hasNext()) { - return true; - } - if (stdDone) { - return false; - } - stdDone = true; - iter = createdFiles.values().iterator(); - return iter.hasNext(); - } - - @Override - public JavaFileObject next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - return iter.next(); - } - }; - } - return stdList; - } - - @Override - public String inferBinaryName(final Location location, final JavaFileObject file) { - if (file instanceof BlackbirdOutputJavaFileObject) { - return file.getName(); - } - return delegate.inferBinaryName(location, file); - } - - @Override - public boolean handleOption(final String current, final Iterator remaining) { - return delegate.handleOption(current, remaining); - } - - @Override - public boolean hasLocation(final Location location) { - return delegate.hasLocation(location); - } - - @Override - public JavaFileObject getJavaFileForInput(final Location location, final String className, final Kind kind) throws IOException { - return delegate.getJavaFileForInput(location, className, kind); - } - - @Override - public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) throws IOException { - final BlackbirdOutputJavaFileObject out = new BlackbirdOutputJavaFileObject(className, kind); - createdFiles.put(className, out); - return out; - } - - @Override - public FileObject getFileForInput(final Location location, final String packageName, final String relativeName) throws IOException { - if (StandardLocation.CLASS_PATH.equals(location) && myPackage.equals(packageName)) { - final BlackbirdOutputJavaFileObject file = createdFiles.get(packageName + "." + relativeName); - if (file != null) { - return file; - } - } - return delegate.getFileForInput(location, packageName, relativeName); - } - - @Override - public FileObject getFileForOutput(final Location location, final String packageName, final String relativeName, final FileObject sibling) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public void flush() throws IOException {} - - @Override - public void close() throws IOException {} - - @Override - public int isSupportedOption(final String option) { - return delegate.isSupportedOption(option); - } - - @Override - public boolean isSameFile(final FileObject a, final FileObject b) { - return delegate.isSameFile(a, b); - } - - @Override - public Location getLocationForModule(final Location location, final String moduleName) throws IOException { - return delegate.getLocationForModule(location, moduleName); - } - - @Override - public Location getLocationForModule(final Location location, final JavaFileObject fo) throws IOException { - return delegate.getLocationForModule(location, fo); - } - - @Override - public String inferModuleName(final Location location) throws IOException { - return delegate.inferModuleName(location); - } - - @Override - public Iterable> listLocationsForModules(final Location location) throws IOException { - return delegate.listLocationsForModules(location); - } - - @Override - public boolean contains(final Location location, final FileObject file) throws IOException { - return delegate.contains(location, file); - } - - public static class BlackbirdJavaFileObject extends SimpleJavaFileObject { - - protected BlackbirdJavaFileObject(final String name, final Kind kind) { - super(URI.create("string:///" + name.replace('.', '/') - + kind.extension), kind); - } - } - - public static class BlackbirdInputJavaFileObject extends BlackbirdJavaFileObject { - private final String contents; - - protected BlackbirdInputJavaFileObject(final String name, final String contents) { - super(name, Kind.SOURCE); - this.contents = contents; - } - - @Override - public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException { - return contents; - } - } - - public static class BlackbirdOutputJavaFileObject extends BlackbirdJavaFileObject { - private final String name; - private ByteArrayOutputStream baos = new ByteArrayOutputStream(); - private byte[] bytes; - - protected BlackbirdOutputJavaFileObject(final String name, final Kind kind) { - super(name, kind); - this.name = name; - } - - @Override - public String getName() { - return name; - } - - public byte[] getBytes() { - if (bytes == null) { - bytes = baos.toByteArray(); - baos = null; - } - return bytes; - } - - @Override - public OutputStream openOutputStream() throws IOException { - return baos; - } - - @Override - public InputStream openInputStream() throws IOException { - return new ByteArrayInputStream(getBytes()); - } - } -} - diff --git a/blackbird/README.md b/blackbird/README.md index 1d1c26ddc..b4420ec1b 100644 --- a/blackbird/README.md +++ b/blackbird/README.md @@ -1,38 +1,50 @@ # Jackson Blackbird -_Upgrade your Afterburner for your Java 11+ Environment 🚀_ - -The [Afterburner](https://github.com/FasterXML/jackson-modules-base/tree/master/afterburner) -has long been your engine of choice for maximum Jackson performance. -But in the brave new Java 11 world, the trusty Afterburner is showing its age. -It uses horrifying bytecode manipulation and cracks `Unsafe.defineClass` which will -[stop working soon](https://github.com/FasterXML/jackson-modules-base/issues/37). - -Blackbird is Afterburner Mk II - powered by 100% renewable Lambda based fuel. - -The [LambdaMetafactory](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/invoke/LambdaMetafactory.html) -introduces a standard Java API for dynamically instantiating function objects. -The current OpenJDK implementation generates anonymous classes in a somewhat similar fashion -to the classic Afterburner. While the metafactory cannot generate comparably specialized -implementations, we can write needed adapters as simple Java code and use the metafactory -to create distinct call sites for every needed access path. This should allow each accessor to -have a monomorphic call profile and easily inline for maximum performance. - -> **Warning** -> The Java implementation of lambdas associates each generated lambda object strongly with the -> ClassLoader of the target class. This means that each Blackbird instance will add dynamic accessors -> to your classes that cannot be unloaded until the entire ClassLoader is garbage collected. -> Therefore, Blackbird is not appropriate if you instantiate many ObjectMappers, since -> you will eventually run out of class space and OOM. +Blackbird accelerates Jackson databind with generated per-bean codecs. For +each bean type it defines one hidden class with the JDK ClassFile API +(JEP 484) and installs it as that bean's deserializer. The generated codec +matches property names with the parser's `PropertyNameMatcher`, dispatches on +the match index with a `tableswitch`, and reads each property with direct, +monomorphic code. The JIT sees one inlinable region per bean type instead of +the stock dispatch chain. + +This is the second Blackbird engine. The first (Jackson 2.x, and 3.x before +this version) built accessor lambdas with `LambdaMetafactory` inside the stock +deserializer loop. The current engine replaces the loop itself. Blackbird +2.x continues to serve Jackson 2.x users. + +## Requirements + +- Jackson 3.x, with `jackson-core` and `jackson-databind` of the same version. + If the release-25 classes fail to initialize, for example because a + `jackson-core` or `jackson-databind` of another version does not have a + class that they need, Blackbird behaves as on Java 24. The warning or the + exception names the error. +- Java 25 or later at run time. The code generators use the ClassFile API + (final in Java 24) and compile at release 25, the current LTS release. The + jar is a multi-release jar: the generators are under + `META-INF/versions/25`, and all other classes are release 17. On Java 17 + to 24, module discovery (`MapperBuilder.findAndAddModules()`) skips + Blackbird with one logged warning, and the mapper works without it. An + explicit `addModule(new BlackbirdModule())` throws + `IllegalStateException`. +- If you repackage Blackbird into an uber jar (for example with the Maven + Shade plugin), keep `Multi-Release: true` in the manifest of that jar. + Without it, the JVM does not see the release-25 classes, and Blackbird + behaves as on Java 24. +- Building the module needs JDK 25 or later. On an older JDK the reactor + leaves the Blackbird modules out, and a release build (`-Prelease`) fails. ## Status [![Maven Central](https://img.shields.io/maven-central/v/tools.jackson.module/jackson-module-blackbird.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/tools.jackson.module/jackson-module-blackbird) -Blackbird is new and not as mature as Afterburner, but has been tested and runs well. -The code is written to fail-fast and explode on the tarmac rather than later at runtime. +The module passes the full Blackbird test suite. -Blackbird passes all the original Afterburner tests (except a couple that didn't make sense anymore). +Generated codecs unload with the mapper: the hidden classes are anchored only +by the codec instances in the mapper's caches. The old engine's lambdas stayed +alive with the target ClassLoader, so applications that created many mappers +ran out of metaspace. That limitation is gone. ## Usage @@ -47,9 +59,9 @@ Blackbird is available on Maven Central: ``` -### Registering module +### Registering the module -To use the Module in Jackson, simply register it with the ObjectMapper instance: +Register the module with the mapper: ```java ObjectMapper mapper = JsonMapper.builder() @@ -57,46 +69,147 @@ ObjectMapper mapper = JsonMapper.builder() .build(); ``` -after which you just do data-binding as usual: +then do data binding as usual: ```java Value val = mapper.readValue(jsonSource, Value.class); mapper.writeValue(new File("result.json"), val); ``` -If you're really brave and running with a modulepath, you may need to grant Blackbird access to your classes. -This is done by calling the caller sensitive `MethodHandles.lookup()`. The module constructor allows you to customize -which lookup instances are used for what classes. - -### What is optimized? - -Following things are optimized: - -* For serialization (POJOs to JSON): - * Getter methods are inlined using generated lambdas instead of reflection - * Serializers for small number of 'primitive' types (`int`, `long`, `boolean`, `String`) are replaced with lambda-specialized generators, instead of getting delegated to `JsonSerializer`s -* For deserialization (JSON to POJOs): - * Calls to default (no-argument) constructors are lambda-fied instead of using reflection - * Calls to @JsonCreate factory methods and constructors with arguments get a slightly less efficient lambda based implementation - - This is new in Blackbird; Afterburner never did this - * Setter methods are called using lambdas instead of reflection - * Deserializers for small number of 'primitive' types (`int`, `long`, `boolean`, `String`) are replaced with lambda-specialized parsers, instead of getting delegated to `JsonDeserializer`s - -### ... and what is not? - -* Any sort of direct field access. This may be possible to support someday but requires additional JDK support. Use methods instead. -* Streaming parser/generator access -* Tree Model: there isn't much that can be done at this level +No access setup is required beyond what stock databind already needs. On the +module path, that is `opens your.package to tools.jackson.databind` for +non-public classes and members; Blackbird adds no requirement of its own. +Generated codecs reach members through `MethodHandle` constants unreflected +after databind's own access checks, so everything stock databind can read or +write - private members, non-public classes, records, foreign classloaders - +accelerates. A member databind cannot open falls back to the stock path, +which fails (or succeeds) exactly as it would without Blackbird. + +The `BlackbirdModule` constructors and overrides that supply a +`MethodHandles.Lookup` (`findLookup()`, `findLookupSupplier()`) remain for +compatibility, but Blackbird no longer uses the lookup: no acceleration +needs it. The module exports only `tools.jackson.module.blackbird`. +Blackbird defines the generated codecs in its own module. + +## What is optimized? + +Blackbird generates a codec for a bean only where the generated code does +what stock databind does. The codec is a copy of the stock deserializer or +serializer whose main read or write method is generated. Inputs and features +that the generated code does not handle go to the stock code. Other modifiers +and databind's own checks therefore see the stock `BeanDeserializer`, +`BuilderBasedDeserializer` or `BeanSerializer` types. + +Deserialization (JSON to POJOs): + +- POJOs that stock builds with a no-arg constructor: construction and + stores through constant `MethodHandle`s, which the JIT inlines like direct + calls. Non-public setters and constructors accelerate the same way public + ones do. Non-final fields (any visibility) store through constant field + handles. Final fields keep stock behavior. +- Records with their canonical constructor: typed locals in + canonical-constructor order and a direct constructor call. When the + document leaves out a component, the stock creator completes the + arguments (absent values such as `Optional.empty()`, required and + `FAIL_ON_MISSING_CREATOR_PROPERTIES` checks). +- Builder-based beans (for example Immutables types): fluent setter calls + through constant handles, and the build method called through a constant + handle. +- `String`, `int`, `long` and `boolean` values are read with direct parser + calls, and nested beans that have a generated codec are read by that codec. + All other values, and any value with an unexpected token (such as `null` or + a string for a number), go through the stock property. + +Serialization (POJOs to JSON): straight-line writers with pre-encoded name +constants and direct getter or field reads. Property writes go through one +generated class of write helpers that all writers share. The helpers are +sized to compile as standalone units, which avoids a C2 code-quality penalty +for inlined copies of hot jackson-core methods inside looping writer bodies, +and one shared copy keeps the compiled code small. + +A property keeps the stock property implementation inside the generated +codec when it has polymorphic type handling (`@JsonTypeInfo` or default +typing), a custom deserializer or serializer, a property wrapper (managed and +back references, or wrappers from a `ValueDeserializerModifier`), a +non-default null serializer, an applied view, or value suppression. A bean +keeps the stock deserializer or serializer when it has Object Ids, a +delegating or property-based creator (other than the canonical constructor +of a record), an external type id, unwrapped or merging properties, or, for +records, an any-setter or injectable values. A bean whose own shape is +`@JsonFormat(shape = JsonFormat.Shape.ARRAY)` (by annotation or config +override) keeps the stock deserializer and serializer: stock reads and +writes it with its own as-array code. A bean also keeps the stock code +when one of its generated methods would be larger than 7000 bytes of +bytecode: HotSpot does not compile methods over 8000 bytes. + +## What is not? + +- Streaming parser and generator access. +- The tree model. + +## Debugging + +With `-Dblackbird.debug.codegen=true`, the module prints each generation +decision (engaged, or which gate demoted the bean) to standard error. With +`-Dblackbird.debug.dumpDir=`, the module writes each generated class's +bytes to that directory as `-reader.class` or +`-writer.class`, for inspection with javap or a decompiler. + +When code generation fails for a bean, the bean keeps the stock path, and +the module logs the failure at `DEBUG` (`FINE` in java.util.logging) through +the `System.Logger` named `tools.jackson.module.blackbird`. One case is not +reported anywhere: when generation runs out of stack, deep in the resolution +of a deeply nested type, the bean keeps the stock path silently (logging on +that stack could fail again). The codegen trace then shows the bean's +`reader resolve` or `writer resolve` line with no outcome after it. ## Performance -Blackbird has been lightly performance tested using `jmh` on openjdk 11 and 12. -For reading and writing a moderately complex bean, Blackbird performance seems -to be almost exactly on par with Afterburner - up to 20% better than vanilla Jackson in some cases. -At that point the method invocation overhead disappears behind the heat generated -from the long parser and UTF8 decoder. - -I used [jitwatch](https://github.com/AdoptOpenJDK/jitwatch) with `hsdis` to verify the generated code at a -superficial level. The generated call sites do seem to be good targets for inlining, although unfortunately -[jitwatch currently can't analyze lambdas very well](https://github.com/AdoptOpenJDK/jitwatch/issues/282) so -more advanced analysis will have to come later. +The table gives throughput relative to stock databind. Each ratio is from +one JMH run that measured both engines: the classic column from a run of +the classic jar, and the new column from a run of this version. "Classic" +is Blackbird 3.2.2, the last release of the LambdaMetafactory engine, with +the same databind. + +| Benchmark | Classic | This version | +|---------------------------------------------|--------:|-------------:| +| Record read (nested record, list of 3) | 1.40x | 2.32x | +| Record write | 1.08x | 1.22x | +| Builder bean read (8 properties) | 1.01x | 1.26x | +| Builder bean write | 1.21x | 1.44x | +| Setter POJO read (media item) | 0.97x | 1.22x | +| Setter POJO write (media item) | 1.01x | 1.19x | +| 1000 beans, 8 `int` properties, read | 1.07x | 1.34x | +| 1000 beans, 8 `int` properties, write | 1.14x | 1.59x | +| 1000 beans, 5 properties, read | 1.06x | 1.20x | +| 1000 beans, 5 properties, write | 1.01x | 1.22x | +| Same beans through a `@JsonCreator`, read | 1.26x | 1.02x | +| 40-property bean, read | 1.06x | 1.36x | +| 40-property bean, write | 1.25x | 1.72x | +| 200-property bean, read | 1.04x | 0.99x | +| 200-property bean, write | 1.14x | 2.07x | + +Two rows are slower than classic Blackbird. In both, this version keeps the +stock deserializer: + +- A POJO with a property-based `@JsonCreator` stays on the stock + deserializer. Classic Blackbird accelerates that creator. +- The generated reader of the 200-property bean would be larger than 7000 + bytes of bytecode, so the bean keeps the stock deserializer. HotSpot does + not compile methods over 8000 bytes (`DontCompileHugeMethods`), and an + interpreted reader was 3.7 times slower than stock. + +Cold start costs more than stock. The first read and write of 500 distinct +types (250 records and 250 setter beans) in a fresh JVM take 0.83 ms per +type with stock databind, 1.85 ms with this version and 1.82 ms with +classic Blackbird. Thus each bean type costs approximately 1 ms more on +first use, for introspection, code generation and class definition. + +Environment: JMH 1.37, JDK 25.0.4, Linux on x86_64 (AMD Ryzen 7 5800X3D, +8 cores and 16 threads), 3 forks of 5 iterations of 1 s after 5 warmup +iterations, and 20 forks for cold start. Other processes used 1 to 2 CPUs +during the runs, and a run during which they used more than 3 CPUs was +repeated. For the read rows, whose code did not change, an earlier run on +the same host with less than 0.4 CPU of other load gave ratios within +0.07x of these. To run the benchmarks, see +[benchmarks/README.md](benchmarks/README.md). diff --git a/blackbird/benchmarks/README.md b/blackbird/benchmarks/README.md index 757804bc1..a4a523bc9 100644 --- a/blackbird/benchmarks/README.md +++ b/blackbird/benchmarks/README.md @@ -1,137 +1,96 @@ -# Blackbird Benchmarks - -Blackbird needs to show compelling performance to be a credible alternative to Afterburner. -I've run some simple benchmarks built with JMH. - -``` -JMH version: 1.25 -VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -Linux 5.8.4-200.fc32.x86_64 -Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz -``` - -### Deserialize Bean Array - -Deserialize an array of 1000 like beans with 5 properties - -``` -Benchmark Mode Cnt Score Error Units -Vanilla.beanArrayDeser thrpt 50 1.184 ± 0.005 ops/ms -Afterburner.beanArrayDeser thrpt 50 1.299 ± 0.009 ops/ms -Blackbird.beanArrayDeser thrpt 50 1.301 ± 0.005 ops/ms - -VanillaGraal.beanArrayDeser thrpt 50 1.117 ± 0.009 ops/ms -AfterburnerGraal.beanArrayDeser thrpt 50 1.314 ± 0.015 ops/ms -BlackbirdGraal.beanArrayDeser thrpt 50 1.333 ± 0.014 ops/ms -``` - -### Serialize Bean Array - -Serialize an array of 1000 like beans with 5 properties - -``` -Vanilla.beanArraySer thrpt 50 2.041 ± 0.014 ops/ms -Afterburner.beanArraySer thrpt 50 2.193 ± 0.010 ops/ms -Blackbird.beanArraySer thrpt 50 2.115 ± 0.009 ops/ms - -VanillaGraal.beanArraySer thrpt 50 2.091 ± 0.013 ops/ms -AfterburnerGraal.beanArraySer thrpt 50 2.293 ± 0.018 ops/ms -BlackbirdGraal.beanArraySer thrpt 50 2.256 ± 0.015 ops/ms -``` - -### Constructor Array Deserialize - -Deserialize the same 5 property bean with a `@JsonConstructor` rather than setters - -``` -Vanilla.constructorArrayDeser thrpt 50 1.114 ± 0.003 ops/ms -Afterburner.constructorArrayDeser thrpt 50 1.124 ± 0.004 ops/ms -Blackbird.constructorArrayDeser thrpt 50 1.122 ± 0.004 ops/ms - -VanillaGraal.constructorArrayDeser thrpt 50 1.063 ± 0.007 ops/ms -AfterburnerGraal.constructorArrayDeser thrpt 50 1.087 ± 0.004 ops/ms -BlackbirdGraal.constructorArrayDeser thrpt 50 1.092 ± 0.003 ops/ms -``` - -### 8-int Bean Deserialize - -Deserialize a list of 1000 beans, each with 8 integer properties - -``` -Vanilla.classicBeanItemDeser thrpt 50 1.070 ± 0.008 ops/ms -Afterburner.classicBeanItemDeser thrpt 50 1.398 ± 0.024 ops/ms -Blackbird.classicBeanItemDeser thrpt 50 1.250 ± 0.017 ops/ms - -VanillaGraal.classicBeanItemDeser thrpt 50 1.082 ± 0.006 ops/ms -AfterburnerGraal.classicBeanItemDeser thrpt 50 1.515 ± 0.014 ops/ms -BlackbirdGraal.classicBeanItemDeser thrpt 50 1.480 ± 0.020 ops/ms -``` - -### 8-int Bean Serialize - -Serialize a list of 1000 beans, each with 8 integer properties - -``` -Vanilla.classicBeanItemSer thrpt 50 1.949 ± 0.020 ops/ms -Afterburner.classicBeanItemSer thrpt 50 2.325 ± 0.024 ops/ms -Blackbird.classicBeanItemSer thrpt 50 2.140 ± 0.017 ops/ms - -VanillaGraal.classicBeanItemSer thrpt 50 2.017 ± 0.019 ops/ms -AfterburnerGraal.classicBeanItemSer thrpt 50 2.485 ± 0.043 ops/ms -BlackbirdGraal.classicBeanItemSer thrpt 50 2.202 ± 0.053 ops/ms -``` - -### Photo Metadata Deserialize - -Deserialize a slightly more complicated bean with photo metadata - -``` -Vanilla.classicMediaItemDeser thrpt 50 324.917 ± 3.942 ops/ms -Afterburner.classicMediaItemDeser thrpt 50 403.335 ± 1.337 ops/ms -Blackbird.classicMediaItemDeser thrpt 50 387.160 ± 1.507 ops/ms - -VanillaGraal.classicMediaItemDeser thrpt 50 332.992 ± 4.667 ops/ms -AfterburnerGraal.classicMediaItemDeser thrpt 50 429.227 ± 2.092 ops/ms -BlackbirdGraal.classicMediaItemDeser thrpt 50 430.031 ± 1.192 ops/ms -``` - -### Photo Metadata Serialize - -Serialize a slightly more complicated bean with photo metadata - -``` -Vanilla.classicMediaItemSer thrpt 50 564.082 ± 6.896 ops/ms -Afterburner.classicMediaItemSer thrpt 50 610.446 ± 1.996 ops/ms -Blackbird.classicMediaItemSer thrpt 50 575.804 ± 4.887 ops/ms - -VanillaGraal.classicMediaItemSer thrpt 50 611.400 ± 3.187 ops/ms -AfterburnerGraal.classicMediaItemSer thrpt 50 676.551 ± 5.980 ops/ms -BlackbirdGraal.classicMediaItemSer thrpt 50 659.393 ± 2.591 ops/ms -``` - -### Startup time - -_(lower is better)_ - -``` -StartupTimeHotspot.vanilla ss 10 256.619 ± 14.163 ms/op -StartupTimeHotpost.afterburner ss 10 296.559 ± 27.770 ms/op -StartupTimeHotspot.blackbird ss 10 292.001 ± 26.581 ms/op - -StartupTimeGraal .vanilla ss 10 298.862 ± 37.968 ms/op -StartupTimeGraal .afterburner ss 10 369.617 ± 59.404 ms/op -StartupTimeGraal .blackbird ss 10 351.144 ± 53.208 ms/op -``` - -Both Afterburner and Blackbird incur a slight startup penalty. -Graal imposes another startup penalty. - -These JMH benchmarks should demonstrate that on modern JVMs (11+), -Blackbird is roughly comparable to Afterburner, and comfortably faster than vanilla Jackson. -If you can afford the startup time, Graal seems to generate faster code in almost all circumstances. -Right now you have to build the executable jar yourself with Maven. -Please report any problems or performance issues so we can improve Blackbird! - -TODO: pretty graphs - -TODO: merge this into [jackson-benchmarks](https://github.com/FasterXML/jackson-benchmarks) as appropriate +# Blackbird benchmarks + +JMH benchmarks that compare stock databind, the classic Blackbird engine +(LambdaMetafactory, release 3.2.2) and the generated-codec engine of this +build. + +## Benchmarks + +Each benchmark class has a `mode` parameter: `stock` (no module), +`blackbird` or `afterburner`. The default run measures `stock` and +`blackbird`. + +- `PojoBenchmark`: setter POJOs. Arrays of 1000 small beans, the same beans + through a property-based `@JsonCreator`, the media item bean, and arrays + of 1000 beans with 8 `int` properties. +- `RecordBenchmark`: a record with a nested record and a list of 3 records. +- `BuilderBenchmark`: a bean with 8 properties and a fluent builder + (`@JsonPOJOBuilder`). +- `WideBeanBenchmark`: setter beans with 40 and 200 properties. +- `ColdStartBenchmark`: the first read and write of 500 distinct types + (250 records, 250 setter beans) in a fresh JVM, with 20 forks. The score + is the time for all 500 types. + +## How to run + +Blackbird needs JDK 25 or later. The benchmark module is not part of the +reactor, so install the modules first: + +``` +./mvnw -pl blackbird,afterburner -am install -DskipTests +cd blackbird/benchmarks +../../mvnw package +../../mvnw package -Pclassic-blackbird +``` + +The first `package` writes `target/benchmarks.jar` (this build). The +`classic-blackbird` profile writes `target/classic/benchmarks-classic.jar`, +which has Blackbird 3.2.2 and the same databind. Run the two jars with the +same JMH options: + +``` +java -jar target/benchmarks.jar -f 3 -wi 5 -w 1s -i 5 -r 1s \ + 'Pojo|Record|Builder|WideBean' -rf json -rff new.json +java -jar target/classic/benchmarks-classic.jar -f 3 -wi 5 -w 1s -i 5 -r 1s \ + 'Pojo|Record|Builder|WideBean' -rf json -rff classic.json +java -jar target/benchmarks.jar ColdStart -rf json -rff cold-new.json +java -jar target/classic/benchmarks-classic.jar ColdStart \ + -rf json -rff cold-classic.json +``` + +Each run measures stock databind too. Divide each Blackbird score by the +stock score of the same run. A ratio across two runs includes any change +in the load of the host between the runs. + +Do not give `-wi` or `-i` to `ColdStartBenchmark`. A warmup iteration +makes the measured iteration warm. + +To see which beans get a generated codec, add +`-jvmArgsAppend -Dblackbird.debug.codegen=true`. + +## Results + +JMH 1.37, JDK 25.0.4, Linux on x86_64 (AMD Ryzen 7 5800X3D, 8 cores and 16 +threads), with the options above and `-Xms512m -Xmx512m` in each fork. +Other processes used 1 to 2 CPUs during the runs, and a run during which +they used more than 3 CPUs was repeated. Scores are ops/ms with the 99.9% +confidence interval. Classic is Blackbird 3.2.2, and new is this build. +Each Blackbird column has the stock score of its own run. + +| Benchmark | Stock (classic run) | Classic | Stock (new run) | New | +|---|---:|---:|---:|---:| +| BuilderBenchmark.builderDeser | 2074 ± 19 | 2093 ± 25 | 2166 ± 18 | 2719 ± 40 | +| BuilderBenchmark.builderSer | 2955 ± 79 | 3576 ± 139 | 3077 ± 51 | 4429 ± 146 | +| PojoBenchmark.beanArrayDeser | 2.65 ± 0.07 | 2.80 ± 0.06 | 2.58 ± 0.08 | 3.09 ± 0.06 | +| PojoBenchmark.beanArraySer | 4.73 ± 0.07 | 4.76 ± 0.12 | 4.53 ± 0.14 | 5.54 ± 0.14 | +| PojoBenchmark.classicBeanDeser | 2.45 ± 0.11 | 2.61 ± 0.08 | 2.48 ± 0.07 | 3.31 ± 0.03 | +| PojoBenchmark.classicBeanSer | 4.09 ± 0.13 | 4.65 ± 0.08 | 4.18 ± 0.08 | 6.65 ± 0.17 | +| PojoBenchmark.constructorArrayDeser | 1.87 ± 0.04 | 2.35 ± 0.05 | 1.87 ± 0.03 | 1.90 ± 0.04 | +| PojoBenchmark.mediaItemDeser | 738 ± 6 | 718 ± 11 | 736 ± 18 | 894 ± 16 | +| PojoBenchmark.mediaItemSer | 1201 ± 29 | 1217 ± 24 | 1176 ± 26 | 1402 ± 51 | +| RecordBenchmark.recordDeser | 598 ± 9 | 837 ± 34 | 600 ± 11 | 1391 ± 15 | +| RecordBenchmark.recordSer | 1859 ± 44 | 2016 ± 42 | 1839 ± 35 | 2252 ± 90 | +| WideBeanBenchmark.wideDeser (40) | 527 ± 33 | 558 ± 10 | 545 ± 12 | 738 ± 16 | +| WideBeanBenchmark.wideDeser (200) | 87.0 ± 2.1 | 90.2 ± 2.4 | 88.4 ± 1.9 | 87.1 ± 1.5 | +| WideBeanBenchmark.wideSer (40) | 813 ± 14 | 1013 ± 26 | 825 ± 21 | 1418 ± 30 | +| WideBeanBenchmark.wideSer (200) | 134 ± 4 | 152 ± 3 | 138 ± 8 | 286 ± 2 | + +`ColdStartBenchmark.firstUse`, ms for 500 types (lower is better): classic +run stock 416 ± 11 and classic 911 ± 8, new run stock 415 ± 6 and new +925 ± 10. + +The new engine does not generate a reader for `BeanWithPropertyConstructor` +(property-based creator), so `constructorArrayDeser` measures the stock +deserializer in the new column. It also does not generate a reader for +`Wide200`: the generated method would be larger than 7000 bytes, which +HotSpot does not compile. diff --git a/blackbird/benchmarks/pom.xml b/blackbird/benchmarks/pom.xml index 059c74656..dad2f38a3 100644 --- a/blackbird/benchmarks/pom.xml +++ b/blackbird/benchmarks/pom.xml @@ -23,11 +23,9 @@ tools.jackson.module jackson-modules-base - 3.1.6 - - + 3.3.0-SNAPSHOT + + ../../pom.xml jackson-module-blackbird-benchmark @@ -35,8 +33,13 @@ Jackson Blackbird JMH benchmarks - 1.25 + 1.37 + 25 + 25 + 25 true + true + ${project.version} benchmarks @@ -50,7 +53,7 @@ tools.jackson.module jackson-module-blackbird - ${project.version} + ${blackbird.version} tools.jackson.module @@ -100,6 +103,10 @@ org.openjdk.jmh.Main + + + true + @@ -122,4 +129,19 @@ + + + + + classic-blackbird + + 3.2.2 + benchmarks-classic + + + ${project.basedir}/target/classic + + + diff --git a/blackbird/benchmarks/results/profile_graal.txt b/blackbird/benchmarks/results/profile_graal.txt deleted file mode 100644 index c6fc93021..000000000 --- a/blackbird/benchmarks/results/profile_graal.txt +++ /dev/null @@ -1,978 +0,0 @@ - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 4 iterations, 10 s each -# Measurement: 6 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemDeser - -# Run progress: 33.33% complete, ETA 00:07:38 -# Fork: 1 of 1 -# Preparing profilers: LinuxPerfAsmProfiler -# Profilers consume stdout and stderr from target VM, use -v EXTRA to copy to console -# Warmup Iteration 1: 97.185 ops/ms -# Warmup Iteration 2: 469.084 ops/ms -# Warmup Iteration 3: 646.051 ops/ms -# Warmup Iteration 4: 645.022 ops/ms -Iteration 1: 646.729 ops/ms -Iteration 2: 644.663 ops/ms -Iteration 3: 653.936 ops/ms -Iteration 4: 644.031 ops/ms -Iteration 5: 650.329 ops/ms -Iteration 6: 654.378 ops/ms -# Processing profiler results: LinuxPerfAsmProfiler - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemDeser": - 649.011 ±(99.9%) 12.772 ops/ms [Average] - (min, avg, max) = (644.031, 649.011, 654.378), stdev = 4.555 - CI (99.9%): [636.239, 661.783] (assumes normal distribution) - -Secondary result "com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemDeser:·asm": -PrintAssembly processed: 595658 total address lines. -Perf output processed (skipped 46.061 seconds): - Column 1: cycles (180765 events) - -Hottest code regions (>10.00% "cycles" events): - -....[Hottest Region 1].............................................................................. -jvmci, level 4, com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString, version 2261 (1016 bytes) - - JVMCI data [0x00007f24916e7810,0x00007f24916e7820] = 16 - [Constant Pool (empty)] - [MachCode] - [Entry Point] - # {method} {0x00007f2440e2ba68} '_finishAndReturnString' '()Ljava/lang/String;' in 'com/fasterxml/jackson/core/json/UTF8StreamJsonParser' - # [sp+0x40] (sp of caller) - 0x00007f24916e64c0: 448b 5608 | 49c1 e203 - 0x00007f24916e64c8: ; {runtime_call ic_miss_stub} - 0x00007f24916e64c8: 493b c20f | 852f c86c | f590 6666 | 0f1f 8400 | 0000 0000 | 6666 6690 - [Verified Entry Point] - 0.03% 0x00007f24916e64e0: 8984 2400 | c0fe ff48 | 83ec 3848 | 896c 2430 - 0.02% 0x00007f24916e64f0: ;*getfield _inputPtr {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@1 (line 2447) - 0.02% 0x00007f24916e64f0: 448b 5e1c - 0.02% 0x00007f24916e64f4: ;*getfield _inputEnd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@7 (line 2448) - 0.02% 0x00007f24916e64f4: 448b 5640 | 0f1f 8400 | 0000 0000 | 453b d30f | 8ec4 0800 - 0x00007f24916e6508: ;*if_icmplt {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@10 (line 2448) - 0x00007f24916e6508: 0044 8b46 - 0x00007f24916e650c: ;*aload_0 {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@0 (line 2447) - 0x00007f24916e650c: 7846 0fbe | 14c5 1c00 | 0000 428b | 3cc5 2c00 - 0x00007f24916e651c: ;*getfield _currentSegment {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@42 (line 712) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@28 (line 2453) - 0x00007f24916e651c: 0000 6690 | 4584 d20f | 85a4 0800 - 0x00007f24916e6528: ;*ifeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@34 (line 709) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@28 (line 2453) - 0x00007f24916e6528: 0042 c704 | c50c 0000 | 00ff ffff - 0x00007f24916e6534: ;*putfield _inputStart {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@2 (line 700) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@28 (line 2453) - 0x00007f24916e6534: ff42 c704 | c518 0000 | 0000 0000 - 0x00007f24916e6540: ;*putfield _currentSize {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@7 (line 701) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@28 (line 2453) - 0x00007f24916e6540: 0042 c704 | c510 0000 | 0000 0000 - 0x00007f24916e654c: ;*putfield _inputLen {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@12 (line 702) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@28 (line 2453) - 0x00007f24916e654c: 0041 807f | 3000 0f85 | d303 0000 | 4c8b ce42 | c704 c524 | 0000 0000 - 0x00007f24916e6564: ;*putfield _inputBuffer {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@17 (line 704) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@28 (line 2453) - 0x00007f24916e6564: 0000 0041 | 807f 3000 | 0f85 f703 | 0000 42c7 | 04c5 3000 | 0000 0000 - 0x00007f24916e657c: ;*putfield _resultString {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@22 (line 705) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@28 (line 2453) - 0x00007f24916e657c: 0000 6690 | 4180 7f30 | 000f 851a | 0400 0042 | c704 c534 | 0000 0000 - 0x00007f24916e6594: ;*putfield _resultArray {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@27 (line 706) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@28 (line 2453) - 0x00007f24916e6594: 0000 0085 | ff0f 84f7 - 0x00007f24916e659c: ;*ifnonnull {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@47 (line 713) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@28 (line 2453) - 0x00007f24916e659c: 0400 0048 - 0x00007f24916e65a0: ;*astore_3 {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@31 (line 2453) - 0x00007f24916e65a0: c1e7 038b | 770c 418b - 0x00007f24916e65a8: ;*getfield _inputEnd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@38 (line 2456) - 0x00007f24916e65a8: 5140 418b - 0x00007f24916e65ac: ;*iadd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@44 (line 2456) - 0x00007f24916e65ac: c303 c63b | d00f 8e6d - 0x00007f24916e65b4: ; {oop([I{0x00000005cae53e00})} - 0x00007f24916e65b4: 0300 0048 | ba00 3ee5 | ca05 0000 | 0041 3bc3 | 0f8e 3b08 - 0x00007f24916e65c8: ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@59 (line 2458) - 0x00007f24916e65c8: 0000 458b | 91b4 0000 - 0x00007f24916e65d0: ;*istore {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@48 (line 2456) - 0x00007f24916e65d0: 0042 8b0c | d50c 0000 | 000f 1f80 | 0000 0000 | 413b cb0f | 86a1 0700 | 0047 0fbe - 0x00007f24916e65ec: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@65 (line 2459) - 0x00007f24916e65ec: 44d3 1041 - 0x00007f24916e65f0: ;*iaload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@76 (line 2460) - 0x00007f24916e65f0: 0fb6 d883 | 7c9a 1000 | 0f85 8604 - 0x00007f24916e65fc: ;*ifeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@77 (line 2460) - 0x00007f24916e65fc: 0000 6690 | 85f6 0f84 | a507 0000 | 450f b6c0 | 6644 8947 - 0x00007f24916e6610: ;*castore {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@114 (line 2468) - 0x00007f24916e6610: 1041 ffc3 | 458b c341 | bb01 0000 | 0066 6690 | e940 0000 - 0x00007f24916e6624: ;*iload_1 {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@56 (line 2458) - 0x00007f24916e6624: 0066 6666 | 0f1f 8400 - 0x00007f24916e662c: ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@59 (line 2458) - 0x00007f24916e662c: 0000 0000 | 413b c80f | 86b7 0700 | 0043 0fbe - 0x00007f24916e663c: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@65 (line 2459) - 0x00007f24916e663c: 5cd0 100f - 0x00007f24916e6640: ;*iaload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@76 (line 2460) - 0x00007f24916e6640: b6eb 837c | aa10 000f | 8522 0000 - 0x00007f24916e664c: ;*ifeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@77 (line 2460) - 0x00007f24916e664c: 0041 3bf3 | 0f86 6707 | 0000 0fb6 | db66 4289 - 0x00007f24916e665c: ;*castore {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@114 (line 2468) - 0x00007f24916e665c: 5c5f 1041 - 0x00007f24916e6660: ;*iinc {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@103 (line 2467) - 0x00007f24916e6660: ffc0 41ff - 0x00007f24916e6664: ;*iinc {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@108 (line 2468) - 0x00007f24916e6664: c341 3bc0 | 7fc6 e9b8 - 0x00007f24916e666c: ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@59 (line 2458) - 0x00007f24916e666c: 0600 000f - 0x00007f24916e6670: ;*iand {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@69 (line 2459) - 0x00007f24916e6670: b6f3 83fe | 220f 85f1 - 0x00007f24916e6678: ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@84 (line 2461) - 0x00007f24916e6678: 0600 0041 - 0x00007f24916e667c: ;*iload {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@80 (line 2461) - 0x00007f24916e667c: 8b59 788b | 14dd 1400 | 0000 448b | 14dd 2c00 - 0x00007f24916e668c: ;*getfield _currentSegment {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@36 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e668c: 0000 83fa | 010f 8dd5 - 0x00007f24916e6694: ;*ifle {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@9 (line 728) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e6694: 0600 0066 | 0f1f 8400 | 0000 0000 | 4585 db0f | 84c3 0600 - 0x00007f24916e66a8: ;*iload {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@80 (line 2461) - 0x00007f24916e66a8: 0042 8b2c | d50c 0000 | 0045 85db | 0f8c b206 - 0x00007f24916e66b8: ;*iflt {reexecute=0 rethrow=0 return_oop=0} - ; - java.lang.String::checkBoundsOffCount@5 (line 3740) - ; - java.lang.String::rangeCheck@4 (line 298) - ; - java.lang.String::<init>@7 (line 294) - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@41 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e66b8: 0000 8bd5 - 0.01% 0x00007f24916e66bc: ;*isub {reexecute=0 rethrow=0 return_oop=0} - ; - java.lang.String::checkBoundsOffCount@11 (line 3740) - ; - java.lang.String::rangeCheck@4 (line 298) - ; - java.lang.String::<init>@7 (line 294) - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@41 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0.01% 0x00007f24916e66bc: 412b d390 | 85d2 0f8c - 0x00007f24916e66c4: ;*if_icmple {reexecute=0 rethrow=0 return_oop=0} - ; - java.lang.String::checkBoundsOffCount@12 (line 3740) - ; - java.lang.String::rangeCheck@4 (line 298) - ; - java.lang.String::<init>@7 (line 294) - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@41 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e66c4: a406 0000 - 0x00007f24916e66c8: ;*iadd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@90 (line 2462) - 0x00007f24916e66c8: 41ff c045 - 0x00007f24916e66cc: ;*putfield _inputPtr {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@91 (line 2462) - 0x00007f24916e66cc: 8941 1c44 | 891c dd18 - 0x00007f24916e66d4: ;*putfield _currentSize {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@2 (line 726) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e66d4: 0000 004d | 8b87 2001 | 0000 498b | b730 0100 | 0041 8bfb | 488d 7f17 | 4883 e7f8 | 488b d749 - 0x00007f24916e66f4: ;*newarray {reexecute=0 rethrow=0 return_oop=0} - ; - java.lang.StringUTF16::compress@1 (line 161) - ; - java.lang.String::<init>@36 (line 3651) - ; - java.lang.String::<init>@10 (line 294) - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@41 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e66f4: 03d0 448b | cb49 c1e1 - 0x00007f24916e66fc: ;*getfield _textBuffer {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@95 (line 2463) - 0x00007f24916e66fc: 0366 6690 | 483b d60f | 8768 0400 | 0049 8997 | 2001 0000 | 420f 1884 | 0700 0100 | 0042 0f18 - 0x00007f24916e671c: 8407 4001 | 0000 420f | 1884 0780 | 0100 0045 | 8958 0c49 | c700 0100 | 0000 41c7 - 0x00007f24916e6738: ; {metadata({type array byte})} - 0x00007f24916e6738: 4008 0c01 | 00f8 488d | 4ff0 4881 | f900 0800 | 000f 8348 | 0400 0048 | c7c6 1000 | 0000 e911 - 0x00007f24916e6758: 0000 000f | 1f44 0000 | 49c7 0430 | 0000 0000 | 488d 7608 | 483b fe77 - 0x00007f24916e6770: ;*newarray {reexecute=0 rethrow=0 return_oop=0} - ; - java.lang.StringUTF16::compress@1 (line 161) - ; - java.lang.String::<init>@36 (line 3651) - ; - java.lang.String::<init>@10 (line 294) - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@41 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e6770: ef44 3bdd | 0f8f 3b06 - 0x00007f24916e6778: ;* unwind (locked if synchronized) - ; - java.lang.StringUTF16::compress@-3 - ; - java.lang.StringUTF16::compress@9 (line 162) - ; - java.lang.String::<init>@36 (line 3651) - ; - java.lang.String::<init>@10 (line 294) - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@41 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e6778: 0000 49c1 - 0x00007f24916e677c: ;*getfield _currentSegment {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@36 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e677c: e203 498d | 7210 498d | 7810 418b | d352 8bc2 | b900 ff00 | ff83 e2f0 | 83e0 0f85 | d20f 8447 - 0x00007f24916e679c: 0000 00c5 | f96e c1c5 | f970 c000 | c5e1 efdb | 488d 3456 | 488d 3c17 | 48f7 dac5 | fa6f 0c56 - 0.13% 0x00007f24916e67bc: c5e1 ebd9 | c5fa 6f54 | 5610 c5e1 | ebda c4e2 | 7917 d80f | 8573 0000 | 00c5 f167 | cac5 fa7f - 0x00007f24916e67dc: 0c17 6690 | 4883 c210 | 75d1 8bd0 | 83e2 f883 | e007 85d2 | 7428 c5f9 | 6ec1 c5f9 | 70c0 00c5 - 0x00007f24916e67fc: e9ef d2c5 | fa6f 0ec4 | e279 17c8 | 753e c5f1 | 67ca c5f9 | d60f 4883 | c610 4883 | c708 8bd0 - 0.01% 0x00007f24916e681c: 0f1f 4000 | 85d2 7421 | 488d 3456 | 488d 3c17 | 48f7 da0f | b704 56f7 | c000 ff00 | 0075 0d88 - 0x00007f24916e683c: 0417 6690 | 48ff c275 | ea58 eb06 | 33c0 4883 - 0x00007f24916e684c: ;* unwind (locked if synchronized) - ; - java.lang.StringUTF16::compress@-3 - ; - java.lang.StringUTF16::compress@9 (line 162) - ; - java.lang.String::<init>@36 (line 3651) - ; - java.lang.String::<init>@10 (line 294) - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@41 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e684c: c408 443b | d80f 85f7 - 0x00007f24916e6854: ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} - ; - java.lang.StringUTF16::compress@13 (line 162) - ; - java.lang.String::<init>@36 (line 3651) - ; - java.lang.String::<init>@10 (line 294) - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@41 (line 733) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e6854: 0400 0049 | 8bb7 2001 | 0000 488d | 5618 49ba - 0x00007f24916e6864: ; {metadata('java/lang/String')} - 0x00007f24916e6864: e81c 00c0 | 0700 0000 | 493b 9730 | 0100 000f | 87dc 0200 | 0049 8997 | 2001 0000 | 0f18 8618 - 0x00007f24916e6884: 0100 0049 | 8b92 b800 | 0000 4889 | 16c7 4608 - 0x00007f24916e6894: ; {metadata('java/lang/String')} - 0x00007f24916e6894: 9d03 00f8 | c746 0c00 | 0000 0048 | c746 1000 | 0000 004c | 8bde 49c1 | e803 4589 | 430c 4180 - 0x00007f24916e68b4: 7f30 000f | 8522 0100 | 0049 8bc3 | 48c1 e803 | 8904 dd30 - 0x00007f24916e68c8: ;*putfield _resultString {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::setCurrentAndReturn@47 (line 734) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@99 (line 2463) - 0x00007f24916e68c8: 0000 0049 | 8bc1 4933 | c348 f7c0 | 0000 e0ff | 0f84 4101 | 0000 6690 | 4d85 db0f | 844f 0100 - 0x00007f24916e68e8: 0048 be00 | 80f6 8224 | 7f00 0049 | c1e9 0966 | 0f1f 8400 | 0000 0000 | 4180 3c31 | 080f 8546 - 0x00007f24916e6908: 0100 0049 - 0x00007f24916e690c: ;*areturn {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString@102 (line 2463) - 0x00007f24916e690c: 8bc3 488b | 6c24 3048 | 83c4 3849 | 8b8f 1001 - 0x00007f24916e691c: ; {poll_return} - 0x00007f24916e691c: 0000 8501 | c5f8 77c3 | 8bc2 e98c | fcff ff4e | 8d14 c524 | 0000 0045 | 8b12 4585 | d20f 840e - 0x00007f24916e693c: 0200 0049 | 8b6f 3849 | c1e2 0348 | 85ed 0f84 | 6c02 0000 | 498b 5748 | 4c89 5415 | f84c 8d55 - 0x00007f24916e695c: f84d 8957 | 384c 8bce | e9f2 fbff - 0x00007f24916e6968: ;*putfield _inputBuffer {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.util.TextBuffer::emptyAndGetCurrentSegment@17 (line 704) -.................................................................................................... - 22.09% - -....[Hottest Region 2].............................................................................. -jvmci, level 4, com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName, version 2266 (1098 bytes) - - dependencies [0x00007f24917d8030,0x00007f24917d8038] = 8 - nul chk table [0x00007f24917d8038,0x00007f24917d8090] = 88 - JVMCI data [0x00007f24917d8090,0x00007f24917d80a0] = 16 - [Constant Pool (empty)] - [MachCode] - [Entry Point] - # {method} {0x00007f2440e27a18} 'nextFieldName' '(Lcom/fasterxml/jackson/core/SerializableString;)Z' in 'com/fasterxml/jackson/core/json/UTF8StreamJsonParser' - # this: rsi:rsi = 'com/fasterxml/jackson/core/json/UTF8StreamJsonParser' - # parm0: rdx:rdx = 'com/fasterxml/jackson/core/SerializableString' - # [sp+0x40] (sp of caller) - 0.00% 0x00007f24917d5440: 448b 5608 | 49c1 e203 - 0x00007f24917d5448: ; {runtime_call ic_miss_stub} - 0x00007f24917d5448: 493b c20f | 85af d85d | f590 6666 | 0f1f 8400 | 0000 0000 | 6666 6690 - [Verified Entry Point] - 0.00% 0x00007f24917d5460: 8984 2400 | c0fe ff48 | 83ec 3848 | 896c 2430 | 4889 7424 | 1044 8b56 - 0x00007f24917d5478: ;*getfield _currToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@6 (line 909) - 0x00007f24917d5478: 140f 1f80 | 0000 0000 - 0.00% 0x00007f24917d5480: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025ec0})} - 0.00% 0x00007f24917d5480: 4181 fad8 | 4b60 b90f | 84dc 1100 | 0080 bea0 | 0000 0000 | 0f85 a413 - 0x00007f24917d5498: ;*ifeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@26 (line 913) - 0x00007f24917d5498: 0000 8b46 - 0x00007f24917d549c: ;*getfield _inputPtr {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@1 (line 2992) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@34 (line 916) - 0x00007f24917d549c: 1c44 8b5e - 0x00007f24917d54a0: ;*getfield _inputEnd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@5 (line 2992) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@34 (line 916) - 0x00007f24917d54a0: 4044 3bd8 | 0f8e e913 - 0x00007f24917d54a8: ;*if_icmplt {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@8 (line 2992) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@34 (line 916) - 0x00007f24917d54a8: 0000 448b | 86b4 0000 - 0x00007f24917d54b0: ;*aload_0 {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@0 (line 908) - 0x00007f24917d54b0: 0046 8b0c | c50c 0000 | 000f 1f80 | 0000 0000 | 443b c80f | 86fd 1300 - 0x00007f24917d54c8: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@38 (line 2997) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@34 (line 916) - 0x00007f24917d54c8: 0042 0fb6 - 0x00007f24917d54cc: ;*iand {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@42 (line 2997) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@34 (line 916) - 0x00007f24917d54cc: 4cc0 1083 | f921 0f82 - 0x00007f24917d54d4: ;*if_icmple {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@47 (line 2998) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@34 (line 916) - 0x00007f24917d54d4: 0b12 0000 | 83f9 230f | 1f44 0000 | 0f8c 2900 | 0000 83f9 | 230f 8418 | 1300 0083 | f92f 0f84 - 0x00007f24917d54f4: 8f11 0000 | 83f9 5d0f | 1f44 0000 | 0f84 4313 | 0000 83f9 | 7d0f 84c2 - 0x00007f24917d550c: ;*if_icmpeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@53 (line 2999) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@34 (line 916) - 0x00007f24917d550c: 1300 008b - 0x00007f24917d5510: ;*aload_0 {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@0 (line 908) - 0x00007f24917d5510: 5e70 448b | 14dd 0c00 | 0000 8b3c | dd10 0000 - 0x00007f24917d5520: ;*getfield _index {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.JsonReadContext::expectComma@2 (line 192) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@100 (line 937) - 0x00007f24917d5520: 0041 83fa | 020f 8529 - 0x00007f24917d5528: ;*ifne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@190 (line 951) - 0x00007f24917d5528: 1300 0048 | 85d2 0f84 | ef03 0000 | 448b 5208 | 49c1 e203 - 0.06% 0x00007f24917d553c: ; {metadata('com/fasterxml/jackson/core/io/SerializedString')} - 0.06% 0x00007f24917d553c: 48bd 7862 | 28c0 0700 | 0000 493b | ea0f 8540 - 0x00007f24917d554c: ;*invokeinterface asQuotedUTF8 {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@216 (line 961) - 0x00007f24917d554c: 1100 00c7 | 4654 0000 - 0x00007f24917d5554: ;*putfield _numTypesValid {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@2 (line 908) - 0x00007f24917d5554: 0000 448b - 0x00007f24917d5558: ;*iadd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@34 (line 2997) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@34 (line 916) - 0x00007f24917d5558: e841 ffc5 - 0.01% 0x00007f24917d555c: ;*putfield _inputPtr {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@35 (line 2997) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@34 (line 916) - 0.01% 0x00007f24917d555c: 4489 6e1c | 4180 7f30 | 000f 855c | 0600 004c | 8bf6 41c7 | 8684 0000 | 0000 0000 - 0x00007f24917d5578: ;*putfield _binaryValue {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@55 (line 922) - 0x00007f24917d5578: 00ff c789 | 3cdd 1000 - 0x00007f24917d5580: ;*putfield _index {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.JsonReadContext::expectComma@8 (line 192) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@100 (line 937) - 0x00007f24917d5580: 0000 83ff | 010f 8c32 - 0x00007f24917d5588: ;*ifeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@103 (line 937) - 0x00007f24917d5588: 0600 0045 | 3bcd 0f86 - 0x00007f24917d5590: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWS@26 (line 2936) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@148 (line 941) - 0x00007f24917d5590: 5212 0000 | 430f b674 - 0x00007f24917d5598: ;*iand {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWS@30 (line 2936) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@148 (line 941) - 0x00007f24917d5598: c510 660f | 1f44 0000 | 83fe 210f | 82b2 1100 - 0x00007f24917d55a8: ;*if_icmple {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWS@35 (line 2937) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@148 (line 941) - 0x00007f24917d55a8: 0083 f92c | 0f85 0b12 - 0x00007f24917d55b0: ;*if_icmpeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@109 (line 938) - 0x00007f24917d55b0: 0000 453b | dd0f 8ec2 - 0x00007f24917d55b8: ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWS@8 (line 2935) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@148 (line 941) - 0x00007f24917d55b8: 1200 000f | 1f44 0000 | 83fe 2f0f | 84bf 1200 - 0x00007f24917d55c8: ;*if_icmpeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWS@41 (line 2938) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@148 (line 941) - 0x00007f24917d55c8: 0083 fe23 | 0f84 e011 - 0x00007f24917d55d0: ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWS@47 (line 2938) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@148 (line 941) - 0x00007f24917d55d0: 0000 6666 | 0f1f 8400 | 0000 0000 | 6666 6690 | 41f7 460c | 0008 0000 | 0f85 4011 - 0x00007f24917d55ec: ;*ifeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@160 (line 944) - 0x00007f24917d55ec: 0000 448d - 0x00007f24917d55f0: ;*iadd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWS@22 (line 2936) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@148 (line 941) - 0x00007f24917d55f0: 5002 4589 - 0x00007f24917d55f4: ;*putfield _inputPtr {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWS@23 (line 2936) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@148 (line 941) - 0x00007f24917d55f4: 561c 83fe | 220f 8508 - 0x00007f24917d55fc: ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@212 (line 959) - 0x00007f24917d55fc: 1100 0041 - 0x00007f24917d5600: ;*getfield _currInputRow {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateNameLocation@2 (line 3785) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@206 (line 958) - 0x00007f24917d5600: 8b76 4441 | 89b6 9800 - 0x00007f24917d5608: ;*putfield _nameStartRow {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateNameLocation@5 (line 3785) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@206 (line 958) - 0x00007f24917d5608: 0000 4589 | 9694 0000 - 0x00007f24917d5610: ;*putfield _nameStartOffset {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateNameLocation@15 (line 3787) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@206 (line 958) - 0x00007f24917d5610: 0045 2b56 - 0x00007f24917d5614: ;*isub {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateNameLocation@24 (line 3788) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@206 (line 958) - 0x00007f24917d5614: 4845 8996 - 0x00007f24917d5618: ;*putfield _nameStartCol {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateNameLocation@25 (line 3788) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@206 (line 958) - 0x00007f24917d5618: 9c00 0000 - 0.65% 0x00007f24917d561c: ;*getfield _quotedUTF8Ref {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.io.SerializedString::asQuotedUTF8@1 (line 119) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@216 (line 961) - 0.65% 0x00007f24917d561c: 8b72 1049 | bb00 80f6 | 8224 7f00 - 0x00007f24917d5628: ;*putfield _currentName {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.JsonReadContext::setCurrentName@2 (line 197) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@289 (line 973) - 0x00007f24917d5628: 004c 895c | 2420 85f6 | 0f84 b70e - 0x00007f24917d5634: ;*ifnonnull {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.io.SerializedString::asQuotedUTF8@6 (line 120) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@216 (line 961) - 0x00007f24917d5634: 0000 48c1 - 0x00007f24917d5638: ;*astore_3 {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@221 (line 961) - 0x00007f24917d5638: e603 448b | 560c 418b - 0x00007f24917d5640: ;*getfield _inputPtr {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@227 (line 965) - 0x00007f24917d5640: 6e1c 458b - 0x00007f24917d5644: ;*iadd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@232 (line 965) - 0x00007f24917d5644: c244 03c5 - 0x00007f24917d5648: ;*iadd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@234 (line 965) - 0x00007f24917d5648: 418d 4004 | 413b 4640 | 0f8d 6410 - 0x00007f24917d5654: ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@239 (line 965) - 0x00007f24917d5654: 0000 418b | 86b4 0000 - 0x00007f24917d565c: ;*astore_3 {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@221 (line 961) - 0x00007f24917d565c: 0044 8b0c | c50c 0000 | 0045 3bc8 | 0f86 6d10 - 0x00007f24917d566c: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@257 (line 968) - 0x00007f24917d566c: 0000 4180 | 7cc0 1022 | 0f85 a311 - 0x00007f24917d5678: ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@260 (line 968) - 0x00007f24917d5678: 0000 660f | 1f44 0000 | 4585 d20f | 842d 0500 - 0x00007f24917d5688: ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@276 (line 972) - 0x00007f24917d5688: 0044 3bcd | 0f86 d110 - 0x00007f24917d5690: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@318 (line 978) - 0x00007f24917d5690: 0000 0fbe - 0x00007f24917d5694: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@311 (line 978) - 0x00007f24917d5694: 4e10 6666 | 0f1f 8400 | 0000 0000 | 3a4c c510 | 0f85 7c10 - 0x00007f24917d56a8: ;*if_icmpeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@319 (line 978) - 0x00007f24917d56a8: 0000 ffc5 | bb01 0000 | 008b cde9 - 0x00007f24917d56b4: ;*iload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@272 (line 972) - 0x00007f24917d56b4: 4100 0000 | 0f1f 8400 - 0x00007f24917d56bc: ;*invokevirtual setCurrentName {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@289 (line 973) - 0x00007f24917d56bc: 0000 0000 | 443b d30f | 86c3 1000 - 0x00007f24917d56c8: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@311 (line 978) - 0x00007f24917d56c8: 0044 3bc9 | 0f86 4011 - 0x00007f24917d56d0: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@318 (line 978) - 0x00007f24917d56d0: 0000 0fbe - 0x00007f24917d56d4: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@311 (line 978) - 0x00007f24917d56d4: 6c1e 1066 | 0f1f 8400 | 0000 0000 | 403a 6cc1 | 100f 853d - 0x00007f24917d56e8: ;*if_icmpeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@319 (line 978) - 0x00007f24917d56e8: 1100 0049 | 8baf 1001 - 0x00007f24917d56f0: ; ImmutableOopMap {rax=NarrowOop rdx=Oop rsi=Oop r14=Oop [16]=Oop } - ;*iload {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@272 (line 972) - ; {poll} - 0x00007f24917d56f0: 0000 8545 - 0x00007f24917d56f4: ;*iinc {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@325 (line 981) - 0x00007f24917d56f4: 00ff c3ff - 0x00007f24917d56f8: ;*iinc {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@328 (line 982) - 0x00007f24917d56f8: c141 3bc8 - 0.01% 0x00007f24917d56fc: ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@276 (line 972) - 0.01% 0x00007f24917d56fc: 75c2 458b - 0x00007f24917d5700: ;*aload_0 {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@279 (line 973) - 0x00007f24917d5700: 4670 428b | 34c5 2000 | 0000 85f6 | 0f85 ea10 - 0x00007f24917d5710: ;*ifnull {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.JsonReadContext::setCurrentName@9 (line 198) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@289 (line 973) - 0x00007f24917d5710: 0000 8bf1 - 0x00007f24917d5714: ;*iadd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@296 (line 974) - 0x00007f24917d5714: ffc6 443b | ce0f 867f - 0x00007f24917d571c: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipColonFast@8 (line 1103) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@297 (line 974) - 0x00007f24917d571c: 1100 0090 | 807c c610 | 3a0f 85c6 - 0x00007f24917d5728: ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipColonFast@13 (line 1104) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@297 (line 974) - 0x00007f24917d5728: 1000 008d - 0x00007f24917d572c: ;*iinc {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipColonFast@5 (line 1103) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@297 (line 974) - 0x00007f24917d572c: 7102 443b | ce0f 86fc | 1000 0044 | 0fbe 4cc6 - 0x00007f24917d573c: ;*baload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipColonFast@24 (line 1105) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@297 (line 974) - 0x00007f24917d573c: 1066 6690 | 4183 f921 | 0f8c 9211 - 0x00007f24917d5748: ;*if_icmple {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipColonFast@29 (line 1106) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@297 (line 974) - 0x00007f24917d5748: 0000 4183 | f92f 0f84 - 0x00007f24917d5750: ;*if_icmpeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipColonFast@35 (line 1107) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@297 (line 974) - 0x00007f24917d5750: f70e 0000 | 4183 f923 | 0f84 f20f - 0x00007f24917d575c: ;*if_icmpeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipColonFast@41 (line 1107) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@297 (line 974) - 0x00007f24917d575c: 0000 6690 | 4180 7f30 | 000f 85d4 - 0x00007f24917d5768: ;*putfield _currentName {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.JsonReadContext::setCurrentName@2 (line 197) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@289 (line 973) - 0x00007f24917d5768: 0400 008b - 0x00007f24917d576c: ;*getfield _value {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.io.SerializedString::getValue@1 (line 91) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@284 (line 973) - 0x00007f24917d576c: 720c 4289 | 34c5 2800 | 0000 48c1 - 0x00007f24917d5778: ;*putfield _currentName {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.JsonReadContext::setCurrentName@2 (line 197) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@289 (line 973) - 0x00007f24917d5778: e603 49c1 - 0x00007f24917d577c: ;*getfield _parsingContext {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@280 (line 973) - 0x00007f24917d577c: e003 4d8b | d04c 33d6 | 49f7 c200 | 00e0 ff0f | 841a 0000 | 0048 85f6 | 0f84 1100 | 0000 49c1 - 0x00007f24917d579c: e809 6690 | 4380 3c18 | 080f 8553 - 0x00007f24917d57a8: ;*putfield _currentName {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.JsonReadContext::setCurrentName@2 (line 197) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@289 (line 973) - 0x00007f24917d57a8: 0500 0044 - 0x00007f24917d57ac: ;*iinc {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipColonFast@21 (line 1105) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@297 (line 974) - 0x00007f24917d57ac: 8d41 0345 - 0x00007f24917d57b0: ;*putfield _inputPtr {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipColonFast@46 (line 1108) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@297 (line 974) - 0x00007f24917d57b0: 8946 1c41 | 807f 3000 | 0f85 4704 - 0x00007f24917d57bc: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025ec0})} - 0x00007f24917d57bc: 0000 41c7 | 4614 d84b - 0x00007f24917d57c4: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025ec0})} - 0x00007f24917d57c4: 60b9 48be | c05e 02cb | 0500 0000 | 4d8b d64c | 33d6 6666 | 0f1f 8400 | 0000 0000 | 49f7 c200 - 0x00007f24917d57e4: 00e0 ff0f | 841e 0000 | 0048 85f6 | 0f84 1500 | 0000 498b | f648 c1ee | 0966 6690 | 4280 3c1e - 0x00007f24917d5804: 080f 85de - 0x00007f24917d5808: ;*putfield _currToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@4 (line 1151) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d5808: 0400 0041 - 0x00007f24917d580c: ;*getfield _currInputRow {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateLocation@2 (line 3776) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@8 (line 1152) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d580c: 8b76 4441 - 0x00007f24917d5810: ;*putfield _tokenInputRow {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateLocation@5 (line 3776) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@8 (line 1152) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d5810: 8976 4c49 - 0x00007f24917d5814: ;*i2l {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateLocation@19 (line 3778) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@8 (line 1152) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d5814: 63f0 4903 - 0x00007f24917d5818: ;*ladd {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateLocation@20 (line 3778) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@8 (line 1152) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d5818: 7620 4989 - 0x00007f24917d581c: ;*putfield _tokenInputTotal {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateLocation@21 (line 3778) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@8 (line 1152) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d581c: 7628 418b | f041 2b76 - 0x00007f24917d5824: ;*isub {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateLocation@30 (line 3779) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@8 (line 1152) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d5824: 4841 8976 - 0x00007f24917d5828: ;*putfield _tokenInputCol {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_updateLocation@31 (line 3779) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@8 (line 1152) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d5828: 5041 83f9 | 220f 8482 | 0000 0041 | 83f9 310f | 84f2 0000 | 0041 83f9 | 320f 84e8 | 0000 0041 - 0x00007f24917d5848: 83f9 5b0f | 842e 0400 | 0041 83f9 | 330f 84d4 | 0000 0041 | 83f9 3490 | 0f84 c900 | 0000 4183 - 0x00007f24917d5868: f936 0f84 | bf00 0000 | 4183 f937 | 0f84 b500 | 0000 4183 | f97b 6690 | 0f84 f104 | 0000 4183 - 0x00007f24917d5888: f935 0f84 | 9f00 0000 | 4183 f930 | 0f84 9500 | 0000 4183 | f938 6690 | 0f84 8900 | 0000 4183 - 0x00007f24917d58a8: f939 0f84 | 7f00 0000 | e9e2 0d00 - 0x00007f24917d58b4: ;*lookupswitch {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@12 (line 1154) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d58b4: 0041 c686 | a000 0000 - 0x00007f24917d58bc: ;*putfield _tokenIncomplete {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_isNextTokenNameYes@170 (line 1156) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName@303 (line 975) - 0x00007f24917d58bc: 0166 6690 | 4180 7f30 | 000f 855a - 0x00007f24917d58c8: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025e90})} - 0x00007f24917d58c8: 0400 0041 | c746 74d2 -.................................................................................................... - 21.52% - -....[Hottest Region 3].............................................................................. -jvmci, level 4, com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize, version 2396 (780 bytes) - - 0.00% 0x00007f2491954ea0: 48f7 c600 | 00e0 ff0f | 841d 0000 | 004d 85db | 0f84 1400 - 0x00007f2491954eb4: ;*putfield _currentValue {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.JsonReadContext::setCurrentValue@2 (line 96) - ; - com.fasterxml.jackson.core.base.ParserBase::setCurrentValue@5 (line 256) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@112 (line 138) - 0x00007f2491954eb4: 0000 488b | f048 c1ee | 0966 6690 | 803c 3e08 | 0f85 bb11 - 0x00007f2491954ec8: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb026068})} - 0x00007f2491954ec8: 0000 49ba | 6860 02cb | 0500 0000 | 4c89 5424 | 280f 1f80 | 0000 0000 | 4585 c90f | 8493 0b00 - 0x00007f2491954ee8: ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@170 (line 148) - 0x00007f2491954ee8: 0044 894c | 245c 8b73 - 0x00007f2491954ef0: ;*return {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.JsonReadContext::setCurrentValue@5 (line 97) - ; - com.fasterxml.jackson.core.base.ParserBase::setCurrentValue@5 (line 256) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@112 (line 138) - 0x00007f2491954ef0: 608b 2cf5 | 0c00 0000 | 468b 04c5 - 0x00007f2491954efc: ;*return {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.core.json.JsonReadContext::setCurrentValue@5 (line 97) - ; - com.fasterxml.jackson.core.base.ParserBase::setCurrentValue@5 (line 256) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@112 (line 138) - 0x00007f2491954efc: 1000 0000 - 0.00% 0x00007f2491954f00: ; implicit exception: deoptimizes - ; ImmutableOopMap {rax=Oop rcx=Oop rdx=Oop rbx=Oop rsi=NarrowOop r8=NarrowOop r11=Oop [40]=Oop [48]=Oop [56]=Oop [64]=Oop [72]=Oop } - 0.00% 0x00007f2491954f00: 468b 2cc5 | 0800 0000 | 85ed 0f84 | 7c21 0000 | 4489 6c24 | 588b 34f5 | 1000 0000 | 8bc6 48c1 - 0x00007f2491954f20: ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@189 (line 150) - 0x00007f2491954f20: e003 49c1 - 0x00007f2491954f24: ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@179 (line 149) - 0x00007f2491954f24: e003 488b - 0x00007f2491954f28: ;*invokevirtual nextFieldName {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@190 (line 150) - 0x00007f2491954f28: f248 8bd0 | 4c89 4424 | 2048 b870 | 3131 c007 | 0000 000f | 1f40 0090 | 0f1f 8000 - 0x00007f2491954f44: ; {virtual_call} - 0x00007f2491954f44: 0000 00e8 - 0x00007f2491954f48: ; ImmutableOopMap {[32]=Oop [40]=Oop [48]=Oop [56]=Oop [64]=Oop [72]=Oop } - ;*invokevirtual nextFieldName {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@190 (line 150) - 0x00007f2491954f48: f404 e8ff | 9085 c00f | 84b7 2100 - 0x00007f2491954f54: ;*ifne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@193 (line 150) - 0x00007f2491954f54: 0048 8b54 | 2440 448b - 0x00007f2491954f5c: ;*getfield _parsingContext {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@24 (line 883) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954f5c: 5a70 8b72 - 0x00007f2491954f60: ;*getfield _currToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@1 (line 715) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954f60: 1445 8bc3 | 49c1 e003 - 0x00007f2491954f68: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025ec0})} - 0x00007f2491954f68: 81fe d84b | 60b9 0f85 - 0x00007f2491954f70: ;*if_acmpne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@7 (line 715) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954f70: 130d 0000 - 0.03% 0x00007f2491954f74: ;*putfield _nameCopied {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@2 (line 875) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0.03% 0x00007f2491954f74: c642 6900 | 0f1f 8400 | 0000 0000 | 4180 7f30 | 000f 85c4 - 0x00007f2491954f88: ;*putfield _nextToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@12 (line 877) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954f88: 1100 0044 - 0x00007f2491954f8c: ;*getfield _nextToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@6 (line 876) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954f8c: 8b52 74c7 | 4274 0000 - 0x00007f2491954f94: ;*putfield _nextToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@12 (line 877) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954f94: 0000 448b - 0x00007f2491954f98: ;*getfield _tokenInputRow {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@28 (line 883) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954f98: 4a4c 8b4a - 0x00007f2491954f9c: ;*getfield _tokenInputCol {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@32 (line 883) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954f9c: 5041 8bda | 48c1 e303 - 0x00007f2491954fa4: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025f50})} - 0x00007f2491954fa4: 4181 faea | 4b60 b90f | 8443 1300 - 0x00007f2491954fb0: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025f20})} - 0x00007f2491954fb0: 0041 81fa | e44b 60b9 | 0f84 e714 - 0x00007f2491954fbc: ;*putfield _parsingContext {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@67 (line 885) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954fbc: 0000 4889 | 5c24 1844 | 8954 2414 | 488b 7c24 - 0x00007f2491954fcc: ;*aload_0 {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@70 (line 887) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491954fcc: 6041 807f | 3000 0f85 | ad11 0000 | 448b 5424 | 1444 8952 | 1448 8bf2 | 4833 7424 | 1848 f7c6 - 0x00007f2491954fec: 0000 e0ff | 0f84 c911 | 0000 488b | 5c24 180f | 1f44 0000 | 4885 db0f | 84c0 1100 | 0048 8bf2 - 0.02% 0x00007f249195500c: 48c1 ee09 | 803c 3e08 | 0f85 7513 | 0000 448b - 0x00007f249195501c: ;*putfield _currToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@73 (line 887) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f249195501c: 6c24 584a | 8b1c ed30 | 0300 0048 | 8b43 3848 | 8b74 2420 | 4c8b da48 - 0x00007f2491955034: ;*invokevirtual deserializeAndSet {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@226 (line 159) - 0x00007f2491955034: 8b4c 2438 | 4c8b 4424 - 0x00007f249195503c: ; ImmutableOopMap {[32]=Oop [40]=Oop [48]=Oop [56]=Oop [64]=Oop [72]=Oop } - ;*invokevirtual deserializeAndSet {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@226 (line 159) - 0x00007f249195503c: 30ff d090 | 41ba 0100 | 0000 e97c - 0x00007f2491955048: ;*iload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@166 (line 148) - 0x00007f2491955048: 0100 000f - 0x00007f249195504c: ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@170 (line 148) - 0x00007f249195504c: 1f44 0000 - 0x00007f2491955050: ;*getfield _orderedProperties {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@174 (line 149) - 0x00007f2491955050: 8b5e 6466 | 0f1f 8400 | 0000 0000 - 0.00% 0x00007f249195505c: ;*iload {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@166 (line 148) - 0.00% 0x00007f249195505c: 6666 6690 - 0x00007f2491955060: ; implicit exception: deoptimizes - ; ImmutableOopMap {rdx=Oop rbx=NarrowOop rsi=Oop [40]=Oop [48]=Oop [56]=Oop [64]=Oop [72]=Oop } - 0x00007f2491955060: 443b 14dd | 0c00 0000 | 0f83 b81e - 0x00007f249195506c: ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@179 (line 149) - 0x00007f249195506c: 0000 8b46 - 0x00007f2491955070: ;*iload {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@166 (line 148) - 0x00007f2491955070: 6044 3b14 | c50c 0000 | 000f 83a7 - 0x00007f249195507c: ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@189 (line 150) - 0x00007f249195507c: 1e00 0048 - 0x00007f2491955080: ;*getfield _orderedProperties {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@174 (line 149) - 0x00007f2491955080: c1e3 0342 - 0x00007f2491955084: ;*iload {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@166 (line 148) - 0x00007f2491955084: 8b5c 9310 - 0.04% 0x00007f2491955088: ; implicit exception: deoptimizes - ; ImmutableOopMap {rax=NarrowOop rdx=Oop rbx=NarrowOop [40]=Oop [48]=Oop [56]=Oop [64]=Oop [72]=Oop } - 0.04% 0x00007f2491955088: 8b2c dd08 | 0000 0048 - 0x00007f2491955090: ;*getfield _orderedPropertyNames {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@184 (line 150) - 0x00007f2491955090: c1e0 0342 | 8b44 9010 - 0x00007f2491955098: ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@189 (line 150) - 0x00007f2491955098: 48c1 e003 - 0x00007f249195509c: ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@179 (line 149) - 0x00007f249195509c: 48c1 e303 | 488b f248 - 0x00007f24919550a4: ;*invokevirtual nextFieldName {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@190 (line 150) - 0x00007f24919550a4: 8bd0 4489 | 5424 5848 | 895c 2418 | 896c 2454 | 48b8 7031 | 31c0 0700 | 0000 9090 | 0f1f 8000 - 0x00007f24919550c4: ; {virtual_call} - 0x00007f24919550c4: 0000 00e8 - 0x00007f24919550c8: ; ImmutableOopMap {[24]=Oop [40]=Oop [48]=Oop [56]=Oop [64]=Oop [72]=Oop } - ;*invokevirtual nextFieldName {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@190 (line 150) - 0x00007f24919550c8: 7403 e8ff | 9085 c00f | 8482 1d00 - 0x00007f24919550d4: ;*ifne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@193 (line 150) - 0x00007f24919550d4: 0048 8b54 | 2440 448b - 0x00007f24919550dc: ;*getfield _parsingContext {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@24 (line 883) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f24919550dc: 5a70 8b72 - 0x00007f24919550e0: ;*getfield _currToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@1 (line 715) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f24919550e0: 1445 8bc3 | 49c1 e003 - 0.02% 0x00007f24919550e8: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025ec0})} - 0.02% 0x00007f24919550e8: 81fe d84b | 60b9 0f85 - 0x00007f24919550f0: ;*if_acmpne {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@7 (line 715) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f24919550f0: f100 0000 - 0.05% 0x00007f24919550f4: ;*putfield _nameCopied {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@2 (line 875) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0.05% 0x00007f24919550f4: c642 6900 | 0f1f 8400 | 0000 0000 | 4180 7f30 | 000f 85da - 0x00007f2491955108: ;*putfield _nextToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@12 (line 877) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491955108: 0200 0044 - 0x00007f249195510c: ;*getfield _nextToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@6 (line 876) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f249195510c: 8b52 74c7 | 4274 0000 - 0x00007f2491955114: ;*putfield _nextToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@12 (line 877) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491955114: 0000 448b - 0x00007f2491955118: ;*getfield _tokenInputRow {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@28 (line 883) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f2491955118: 4a4c 8b4a - 0x00007f249195511c: ;*getfield _tokenInputCol {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@32 (line 883) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f249195511c: 5041 8bda | 48c1 e303 - 0.01% 0x00007f2491955124: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025f50})} - 0.01% 0x00007f2491955124: 4181 faea | 4b60 b90f | 84e2 0300 - 0x00007f2491955130: ; {oop(a 'com/fasterxml/jackson/core/JsonToken'{0x00000005cb025f20})} - 0x00007f2491955130: 0041 81fa | e44b 60b9 | 0f84 9104 - 0x00007f249195513c: ;*putfield _parsingContext {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@67 (line 885) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f249195513c: 0000 4889 | 5c24 0844 | 8954 2414 | 488b 7c24 - 0x00007f249195514c: ;*aload_0 {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@70 (line 887) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f249195514c: 6041 807f | 3000 0f85 | c702 0000 | 448b 5424 | 1444 8952 | 1448 8bf2 | 4833 7424 | 0848 f7c6 - 0x00007f249195516c: 0000 e0ff | 0f84 de02 | 0000 488b | 5c24 080f | 1f44 0000 | 4885 db0f | 84d4 0200 | 0048 8bf2 - 0.13% 0x00007f249195518c: 48c1 ee09 | 803c 3e08 | 0f85 e503 | 0000 8b6c - 0x00007f249195519c: ;*putfield _currToken {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextAfterName@73 (line 887) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@11 (line 716) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f249195519c: 2454 488b | 1ced 3003 | 0000 488b | 4338 488b | 7424 184c | 8bda 488b - 0x00007f24919551b4: ;*invokevirtual deserializeAndSet {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@226 (line 159) - 0x00007f24919551b4: 4c24 384c | 8b44 2430 - 0.10% 0x00007f24919551bc: ; ImmutableOopMap {[24]=Oop [40]=Oop [48]=Oop [56]=Oop [64]=Oop [72]=Oop } - ;*invokevirtual deserializeAndSet {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@226 (line 159) - 0.10% 0x00007f24919551bc: ffd0 9044 | 8b54 2458 - 0.04% 0x00007f24919551c4: ;*iinc {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@247 (line 148) - 0.04% 0x00007f24919551c4: 41ff c244 | 8b4c 245c | 488b 5424 | 4048 8b74 | 2448 453b | ca0f 8f71 | feff ff90 | e997 0800 - 0x00007f24919551e4: ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@170 (line 148) - 0x00007f24919551e4: 0080 baa0 | 0000 0000 | 0f85 6a1f - 0x00007f24919551f0: ;*ifeq {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@24 (line 721) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) - 0x00007f24919551f0: 0000 8b72 - 0x00007f24919551f4: ;*getfield _inputPtr {reexecute=0 rethrow=0 return_oop=0} - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWSOrEnd@1 (line 2992) - ; - com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken@32 (line 724) - ; - com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize@217 (line 157) -.................................................................................................... - 12.60% - -....[Hottest Regions]............................................................................... - 22.09% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString, version 2261 (1016 bytes) - 21.52% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName, version 2266 (1098 bytes) - 12.60% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize, version 2396 (780 bytes) - 9.09% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName, version 2266 (771 bytes) - 2.49% jvmci, level 4 com.fasterxml.jackson.databind.deser.std.EnumDeserializer::deserialize, version 2467 (533 bytes) - 2.22% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SettableObjectProperty::deserializeAndSet, version 2423 (241 bytes) - 2.17% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SettableStringProperty::deserializeAndSet, version 2403 (315 bytes) - 1.57% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize, version 2396 (448 bytes) - 1.36% jvmci, level 4 com.fasterxml.jackson.core.JsonFactory::createParser, version 2643 (693 bytes) - 1.27% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken, version 2363 (294 bytes) - 1.14% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SettableIntProperty::deserializeAndSet, version 2411 (324 bytes) - 1.10% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString, version 2261 (21 bytes) - 1.10% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SettableIntProperty::deserializeAndSet, version 2411 (188 bytes) - 1.10% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize, version 2396 (199 bytes) - 0.92% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextTokenNotInObject, version 2432 (404 bytes) - 0.73% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SettableLongProperty::deserializeAndSet, version 2509 (439 bytes) - 0.68% jvmci, level 4 com.fasterxml.jackson.databind.deser.std.CollectionDeserializer::_deserializeFromArray, version 2654 (432 bytes) - 0.56% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize, version 2396 (230 bytes) - 0.56% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextTokenNotInObject, version 2432 (141 bytes) - 0.55% jvmci, level 4 com.fasterxml.jackson.core.json.JsonReadContext::createChildArrayContext, version 2496 (374 bytes) - 15.17% <...other 765 warm regions...> -.................................................................................................... -100.00% - -....[Hottest Methods (after inlining)].............................................................. - 31.19% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextFieldName, version 2266 - 23.39% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_finishAndReturnString, version 2261 - 16.75% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SuperSonicBeanDeserializer::deserialize, version 2396 - 2.95% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SettableObjectProperty::deserializeAndSet, version 2423 - 2.94% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SettableStringProperty::deserializeAndSet, version 2403 - 2.88% jvmci, level 4 com.fasterxml.jackson.databind.deser.std.CollectionDeserializer::_deserializeFromArray, version 2654 - 2.63% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SettableIntProperty::deserializeAndSet, version 2411 - 2.59% jvmci, level 4 com.fasterxml.jackson.databind.deser.std.EnumDeserializer::deserialize, version 2467 - 2.33% jvmci, level 4 com.fasterxml.jackson.core.JsonFactory::createParser, version 2643 - 1.69% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextToken, version 2363 - 1.56% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_nextTokenNotInObject, version 2432 - 1.55% jvmci, level 4 com.fasterxml.jackson.databind.ObjectReader::_bindAndClose, version 2640 - 1.42% jvmci, level 4 com.fasterxml.jackson.databind.deser.std.StringCollectionDeserializer::deserialize, version 2690 - 1.11% jvmci, level 4 com.fasterxml.jackson.module.blackbird.deser.SettableLongProperty::deserializeAndSet, version 2509 - 0.66% jvmci, level 4 com.fasterxml.jackson.core.json.JsonReadContext::createChildArrayContext, version 2496 - 0.48% jvmci, level 4 com.fasterxml.jackson.module.blackbird.jmh_generated.Blackbird_classicMediaItemDeser_jmhTest::classicMediaItemDeser_thrpt_jmhStub, version 19656 - 0.48% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::nextTextValue, version 2470 - 0.42% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_closeArrayScope, version 13575 - 0.39% jvmci, level 4 com.fasterxml.jackson.core.json.UTF8StreamJsonParser::_skipWS, version 2377 - 0.39% jvmci, level 4 com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer::<init>, version 2645 - 2.22% <...other 380 warm methods...> -.................................................................................................... -100.00% - -....[Distribution by Source]........................................................................ - 98.71% jvmci, level 4 - 0.75% libjvm.so - 0.22% runtime stub - 0.13% kernel - 0.12% libc-2.31.so - 0.02% [vdso] - 0.02% ld-2.31.so - 0.02% libpthread-2.31.so - 0.00% interpreter - 0.00% c1, level 1 -.................................................................................................... -100.00% - diff --git a/blackbird/benchmarks/results/results_graal.txt b/blackbird/benchmarks/results/results_graal.txt deleted file mode 100644 index ff992ab0f..000000000 --- a/blackbird/benchmarks/results/results_graal.txt +++ /dev/null @@ -1,3308 +0,0 @@ -WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by org.openjdk.jmh.util.Utils (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to field java.io.PrintStream.charOut -WARNING: Please consider reporting this to the maintainers of org.openjdk.jmh.util.Utils -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.beanArrayDeser - -# Run progress: 0.00% complete, ETA 11:40:00 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.898 ops/ms -# Warmup Iteration 2: 1.333 ops/ms -# Warmup Iteration 3: 1.337 ops/ms -# Warmup Iteration 4: 1.331 ops/ms -# Warmup Iteration 5: 1.337 ops/ms -# Warmup Iteration 6: 1.334 ops/ms -# Warmup Iteration 7: 1.337 ops/ms -# Warmup Iteration 8: 1.335 ops/ms -# Warmup Iteration 9: 1.332 ops/ms -# Warmup Iteration 10: 1.337 ops/ms -Iteration 1: 1.337 ops/ms -Iteration 2: 1.337 ops/ms -Iteration 3: 1.337 ops/ms -Iteration 4: 1.338 ops/ms -Iteration 5: 1.338 ops/ms -Iteration 6: 1.337 ops/ms -Iteration 7: 1.337 ops/ms -Iteration 8: 1.337 ops/ms -Iteration 9: 1.338 ops/ms -Iteration 10: 1.337 ops/ms - -# Run progress: 0.95% complete, ETA 11:36:54 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.845 ops/ms -# Warmup Iteration 2: 1.283 ops/ms -# Warmup Iteration 3: 1.290 ops/ms -# Warmup Iteration 4: 1.289 ops/ms -# Warmup Iteration 5: 1.288 ops/ms -# Warmup Iteration 6: 1.290 ops/ms -# Warmup Iteration 7: 1.290 ops/ms -# Warmup Iteration 8: 1.289 ops/ms -# Warmup Iteration 9: 1.287 ops/ms -# Warmup Iteration 10: 1.290 ops/ms -Iteration 1: 1.290 ops/ms -Iteration 2: 1.288 ops/ms -Iteration 3: 1.290 ops/ms -Iteration 4: 1.289 ops/ms -Iteration 5: 1.290 ops/ms -Iteration 6: 1.290 ops/ms -Iteration 7: 1.290 ops/ms -Iteration 8: 1.289 ops/ms -Iteration 9: 1.290 ops/ms -Iteration 10: 1.290 ops/ms - -# Run progress: 1.90% complete, ETA 11:29:49 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.818 ops/ms -# Warmup Iteration 2: 1.257 ops/ms -# Warmup Iteration 3: 1.265 ops/ms -# Warmup Iteration 4: 1.265 ops/ms -# Warmup Iteration 5: 1.265 ops/ms -# Warmup Iteration 6: 1.262 ops/ms -# Warmup Iteration 7: 1.265 ops/ms -# Warmup Iteration 8: 1.265 ops/ms -# Warmup Iteration 9: 1.264 ops/ms -# Warmup Iteration 10: 1.265 ops/ms -Iteration 1: 1.266 ops/ms -Iteration 2: 1.266 ops/ms -Iteration 3: 1.266 ops/ms -Iteration 4: 1.266 ops/ms -Iteration 5: 1.266 ops/ms -Iteration 6: 1.266 ops/ms -Iteration 7: 1.265 ops/ms -Iteration 8: 1.266 ops/ms -Iteration 9: 1.265 ops/ms -Iteration 10: 1.266 ops/ms - -# Run progress: 2.86% complete, ETA 11:23:26 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.866 ops/ms -# Warmup Iteration 2: 1.328 ops/ms -# Warmup Iteration 3: 1.334 ops/ms -# Warmup Iteration 4: 1.334 ops/ms -# Warmup Iteration 5: 1.334 ops/ms -# Warmup Iteration 6: 1.334 ops/ms -# Warmup Iteration 7: 1.335 ops/ms -# Warmup Iteration 8: 1.330 ops/ms -# Warmup Iteration 9: 1.334 ops/ms -# Warmup Iteration 10: 1.334 ops/ms -Iteration 1: 1.335 ops/ms -Iteration 2: 1.335 ops/ms -Iteration 3: 1.335 ops/ms -Iteration 4: 1.335 ops/ms -Iteration 5: 1.335 ops/ms -Iteration 6: 1.335 ops/ms -Iteration 7: 1.335 ops/ms -Iteration 8: 1.334 ops/ms -Iteration 9: 1.335 ops/ms -Iteration 10: 1.335 ops/ms - -# Run progress: 3.81% complete, ETA 11:16:28 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.846 ops/ms -# Warmup Iteration 2: 1.335 ops/ms -# Warmup Iteration 3: 1.341 ops/ms -# Warmup Iteration 4: 1.342 ops/ms -# Warmup Iteration 5: 1.337 ops/ms -# Warmup Iteration 6: 1.341 ops/ms -# Warmup Iteration 7: 1.342 ops/ms -# Warmup Iteration 8: 1.338 ops/ms -# Warmup Iteration 9: 1.342 ops/ms -# Warmup Iteration 10: 1.342 ops/ms -Iteration 1: 1.343 ops/ms -Iteration 2: 1.341 ops/ms -Iteration 3: 1.342 ops/ms -Iteration 4: 1.343 ops/ms -Iteration 5: 1.342 ops/ms -Iteration 6: 1.343 ops/ms -Iteration 7: 1.343 ops/ms -Iteration 8: 1.343 ops/ms -Iteration 9: 1.342 ops/ms -Iteration 10: 1.342 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.beanArrayDeser": - 1.314 ±(99.9%) 0.015 ops/ms [Average] - (min, avg, max) = (1.265, 1.314, 1.343), stdev = 0.031 - CI (99.9%): [1.299, 1.329] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.beanArraySer - -# Run progress: 4.76% complete, ETA 11:09:41 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.693 ops/ms -# Warmup Iteration 2: 2.343 ops/ms -# Warmup Iteration 3: 2.347 ops/ms -# Warmup Iteration 4: 2.348 ops/ms -# Warmup Iteration 5: 2.344 ops/ms -# Warmup Iteration 6: 2.346 ops/ms -# Warmup Iteration 7: 2.348 ops/ms -# Warmup Iteration 8: 2.349 ops/ms -# Warmup Iteration 9: 2.349 ops/ms -# Warmup Iteration 10: 2.348 ops/ms -Iteration 1: 2.340 ops/ms -Iteration 2: 2.347 ops/ms -Iteration 3: 2.347 ops/ms -Iteration 4: 2.348 ops/ms -Iteration 5: 2.348 ops/ms -Iteration 6: 2.347 ops/ms -Iteration 7: 2.346 ops/ms -Iteration 8: 2.347 ops/ms -Iteration 9: 2.348 ops/ms -Iteration 10: 2.347 ops/ms - -# Run progress: 5.71% complete, ETA 11:03:15 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.547 ops/ms -# Warmup Iteration 2: 2.290 ops/ms -# Warmup Iteration 3: 2.302 ops/ms -# Warmup Iteration 4: 2.300 ops/ms -# Warmup Iteration 5: 2.299 ops/ms -# Warmup Iteration 6: 2.299 ops/ms -# Warmup Iteration 7: 2.300 ops/ms -# Warmup Iteration 8: 2.300 ops/ms -# Warmup Iteration 9: 2.300 ops/ms -# Warmup Iteration 10: 2.300 ops/ms -Iteration 1: 2.299 ops/ms -Iteration 2: 2.299 ops/ms -Iteration 3: 2.300 ops/ms -Iteration 4: 2.299 ops/ms -Iteration 5: 2.300 ops/ms -Iteration 6: 2.300 ops/ms -Iteration 7: 2.303 ops/ms -Iteration 8: 2.303 ops/ms -Iteration 9: 2.302 ops/ms -Iteration 10: 2.303 ops/ms - -# Run progress: 6.67% complete, ETA 10:56:24 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.825 ops/ms -# Warmup Iteration 2: 2.309 ops/ms -# Warmup Iteration 3: 2.311 ops/ms -# Warmup Iteration 4: 2.312 ops/ms -# Warmup Iteration 5: 2.308 ops/ms -# Warmup Iteration 6: 2.310 ops/ms -# Warmup Iteration 7: 2.311 ops/ms -# Warmup Iteration 8: 2.311 ops/ms -# Warmup Iteration 9: 2.311 ops/ms -# Warmup Iteration 10: 2.311 ops/ms -Iteration 1: 2.311 ops/ms -Iteration 2: 2.310 ops/ms -Iteration 3: 2.311 ops/ms -Iteration 4: 2.310 ops/ms -Iteration 5: 2.312 ops/ms -Iteration 6: 2.310 ops/ms -Iteration 7: 2.311 ops/ms -Iteration 8: 2.310 ops/ms -Iteration 9: 2.311 ops/ms -Iteration 10: 2.311 ops/ms - -# Run progress: 7.62% complete, ETA 10:49:55 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.484 ops/ms -# Warmup Iteration 2: 2.248 ops/ms -# Warmup Iteration 3: 2.258 ops/ms -# Warmup Iteration 4: 2.257 ops/ms -# Warmup Iteration 5: 2.253 ops/ms -# Warmup Iteration 6: 2.252 ops/ms -# Warmup Iteration 7: 2.253 ops/ms -# Warmup Iteration 8: 2.251 ops/ms -# Warmup Iteration 9: 2.251 ops/ms -# Warmup Iteration 10: 2.251 ops/ms -Iteration 1: 2.252 ops/ms -Iteration 2: 2.252 ops/ms -Iteration 3: 2.253 ops/ms -Iteration 4: 2.252 ops/ms -Iteration 5: 2.252 ops/ms -Iteration 6: 2.252 ops/ms -Iteration 7: 2.262 ops/ms -Iteration 8: 2.262 ops/ms -Iteration 9: 2.261 ops/ms -Iteration 10: 2.262 ops/ms - -# Run progress: 8.57% complete, ETA 10:43:08 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.482 ops/ms -# Warmup Iteration 2: 2.243 ops/ms -# Warmup Iteration 3: 2.251 ops/ms -# Warmup Iteration 4: 2.253 ops/ms -# Warmup Iteration 5: 2.253 ops/ms -# Warmup Iteration 6: 2.249 ops/ms -# Warmup Iteration 7: 2.250 ops/ms -# Warmup Iteration 8: 2.250 ops/ms -# Warmup Iteration 9: 2.250 ops/ms -# Warmup Iteration 10: 2.248 ops/ms -Iteration 1: 2.249 ops/ms -Iteration 2: 2.249 ops/ms -Iteration 3: 2.249 ops/ms -Iteration 4: 2.250 ops/ms -Iteration 5: 2.249 ops/ms -Iteration 6: 2.248 ops/ms -Iteration 7: 2.252 ops/ms -Iteration 8: 2.252 ops/ms -Iteration 9: 2.251 ops/ms -Iteration 10: 2.253 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.beanArraySer": - 2.293 ±(99.9%) 0.018 ops/ms [Average] - (min, avg, max) = (2.248, 2.293, 2.348), stdev = 0.036 - CI (99.9%): [2.275, 2.311] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.classicBeanItemDeser - -# Run progress: 9.52% complete, ETA 10:36:21 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.024 ops/ms -# Warmup Iteration 2: 1.507 ops/ms -# Warmup Iteration 3: 1.528 ops/ms -# Warmup Iteration 4: 1.527 ops/ms -# Warmup Iteration 5: 1.526 ops/ms -# Warmup Iteration 6: 1.526 ops/ms -# Warmup Iteration 7: 1.525 ops/ms -# Warmup Iteration 8: 1.520 ops/ms -# Warmup Iteration 9: 1.528 ops/ms -# Warmup Iteration 10: 1.528 ops/ms -Iteration 1: 1.528 ops/ms -Iteration 2: 1.528 ops/ms -Iteration 3: 1.528 ops/ms -Iteration 4: 1.528 ops/ms -Iteration 5: 1.528 ops/ms -Iteration 6: 1.528 ops/ms -Iteration 7: 1.527 ops/ms -Iteration 8: 1.528 ops/ms -Iteration 9: 1.527 ops/ms -Iteration 10: 1.528 ops/ms - -# Run progress: 10.48% complete, ETA 10:29:35 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.030 ops/ms -# Warmup Iteration 2: 1.488 ops/ms -# Warmup Iteration 3: 1.501 ops/ms -# Warmup Iteration 4: 1.500 ops/ms -# Warmup Iteration 5: 1.497 ops/ms -# Warmup Iteration 6: 1.502 ops/ms -# Warmup Iteration 7: 1.502 ops/ms -# Warmup Iteration 8: 1.495 ops/ms -# Warmup Iteration 9: 1.501 ops/ms -# Warmup Iteration 10: 1.503 ops/ms -Iteration 1: 1.503 ops/ms -Iteration 2: 1.503 ops/ms -Iteration 3: 1.503 ops/ms -Iteration 4: 1.504 ops/ms -Iteration 5: 1.503 ops/ms -Iteration 6: 1.503 ops/ms -Iteration 7: 1.502 ops/ms -Iteration 8: 1.502 ops/ms -Iteration 9: 1.502 ops/ms -Iteration 10: 1.502 ops/ms - -# Run progress: 11.43% complete, ETA 10:22:51 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.046 ops/ms -# Warmup Iteration 2: 1.506 ops/ms -# Warmup Iteration 3: 1.516 ops/ms -# Warmup Iteration 4: 1.517 ops/ms -# Warmup Iteration 5: 1.516 ops/ms -# Warmup Iteration 6: 1.517 ops/ms -# Warmup Iteration 7: 1.517 ops/ms -# Warmup Iteration 8: 1.512 ops/ms -# Warmup Iteration 9: 1.517 ops/ms -# Warmup Iteration 10: 1.517 ops/ms -Iteration 1: 1.517 ops/ms -Iteration 2: 1.515 ops/ms -Iteration 3: 1.517 ops/ms -Iteration 4: 1.517 ops/ms -Iteration 5: 1.517 ops/ms -Iteration 6: 1.517 ops/ms -Iteration 7: 1.517 ops/ms -Iteration 8: 1.517 ops/ms -Iteration 9: 1.516 ops/ms -Iteration 10: 1.518 ops/ms - -# Run progress: 12.38% complete, ETA 10:16:07 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.066 ops/ms -# Warmup Iteration 2: 1.505 ops/ms -# Warmup Iteration 3: 1.520 ops/ms -# Warmup Iteration 4: 1.521 ops/ms -# Warmup Iteration 5: 1.519 ops/ms -# Warmup Iteration 6: 1.520 ops/ms -# Warmup Iteration 7: 1.518 ops/ms -# Warmup Iteration 8: 1.520 ops/ms -# Warmup Iteration 9: 1.521 ops/ms -# Warmup Iteration 10: 1.521 ops/ms -Iteration 1: 1.522 ops/ms -Iteration 2: 1.521 ops/ms -Iteration 3: 1.521 ops/ms -Iteration 4: 1.521 ops/ms -Iteration 5: 1.521 ops/ms -Iteration 6: 1.521 ops/ms -Iteration 7: 1.521 ops/ms -Iteration 8: 1.521 ops/ms -Iteration 9: 1.521 ops/ms -Iteration 10: 1.521 ops/ms - -# Run progress: 13.33% complete, ETA 10:09:24 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.014 ops/ms -# Warmup Iteration 2: 1.515 ops/ms -# Warmup Iteration 3: 1.528 ops/ms -# Warmup Iteration 4: 1.528 ops/ms -# Warmup Iteration 5: 1.527 ops/ms -# Warmup Iteration 6: 1.529 ops/ms -# Warmup Iteration 7: 1.523 ops/ms -# Warmup Iteration 8: 1.524 ops/ms -# Warmup Iteration 9: 1.527 ops/ms -# Warmup Iteration 10: 1.528 ops/ms -Iteration 1: 1.326 ops/ms -Iteration 2: 1.529 ops/ms -Iteration 3: 1.529 ops/ms -Iteration 4: 1.529 ops/ms -Iteration 5: 1.528 ops/ms -Iteration 6: 1.528 ops/ms -Iteration 7: 1.527 ops/ms -Iteration 8: 1.526 ops/ms -Iteration 9: 1.527 ops/ms -Iteration 10: 1.527 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.classicBeanItemDeser": - 1.515 ±(99.9%) 0.014 ops/ms [Average] - (min, avg, max) = (1.326, 1.515, 1.529), stdev = 0.029 - CI (99.9%): [1.501, 1.530] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.classicBeanItemSer - -# Run progress: 14.29% complete, ETA 10:02:39 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.948 ops/ms -# Warmup Iteration 2: 2.563 ops/ms -# Warmup Iteration 3: 2.584 ops/ms -# Warmup Iteration 4: 2.583 ops/ms -# Warmup Iteration 5: 2.568 ops/ms -# Warmup Iteration 6: 2.586 ops/ms -# Warmup Iteration 7: 2.586 ops/ms -# Warmup Iteration 8: 2.583 ops/ms -# Warmup Iteration 9: 2.582 ops/ms -# Warmup Iteration 10: 2.580 ops/ms -Iteration 1: 2.582 ops/ms -Iteration 2: 2.579 ops/ms -Iteration 3: 2.580 ops/ms -Iteration 4: 2.582 ops/ms -Iteration 5: 2.582 ops/ms -Iteration 6: 2.587 ops/ms -Iteration 7: 2.584 ops/ms -Iteration 8: 2.585 ops/ms -Iteration 9: 2.584 ops/ms -Iteration 10: 2.582 ops/ms - -# Run progress: 15.24% complete, ETA 09:55:58 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.742 ops/ms -# Warmup Iteration 2: 2.390 ops/ms -# Warmup Iteration 3: 2.397 ops/ms -# Warmup Iteration 4: 2.401 ops/ms -# Warmup Iteration 5: 2.396 ops/ms -# Warmup Iteration 6: 2.399 ops/ms -# Warmup Iteration 7: 2.400 ops/ms -# Warmup Iteration 8: 2.399 ops/ms -# Warmup Iteration 9: 2.396 ops/ms -# Warmup Iteration 10: 2.397 ops/ms -Iteration 1: 2.397 ops/ms -Iteration 2: 2.397 ops/ms -Iteration 3: 2.397 ops/ms -Iteration 4: 2.397 ops/ms -Iteration 5: 2.395 ops/ms -Iteration 6: 2.396 ops/ms -Iteration 7: 2.388 ops/ms -Iteration 8: 2.387 ops/ms -Iteration 9: 2.392 ops/ms -Iteration 10: 2.396 ops/ms - -# Run progress: 16.19% complete, ETA 09:49:15 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.928 ops/ms -# Warmup Iteration 2: 2.576 ops/ms -# Warmup Iteration 3: 2.599 ops/ms -# Warmup Iteration 4: 2.597 ops/ms -# Warmup Iteration 5: 2.596 ops/ms -# Warmup Iteration 6: 2.596 ops/ms -# Warmup Iteration 7: 2.594 ops/ms -# Warmup Iteration 8: 2.599 ops/ms -# Warmup Iteration 9: 2.594 ops/ms -# Warmup Iteration 10: 2.596 ops/ms -Iteration 1: 2.596 ops/ms -Iteration 2: 2.593 ops/ms -Iteration 3: 2.590 ops/ms -Iteration 4: 2.593 ops/ms -Iteration 5: 2.592 ops/ms -Iteration 6: 2.597 ops/ms -Iteration 7: 2.593 ops/ms -Iteration 8: 2.598 ops/ms -Iteration 9: 2.598 ops/ms -Iteration 10: 2.594 ops/ms - -# Run progress: 17.14% complete, ETA 09:42:32 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.750 ops/ms -# Warmup Iteration 2: 2.413 ops/ms -# Warmup Iteration 3: 2.422 ops/ms -# Warmup Iteration 4: 2.425 ops/ms -# Warmup Iteration 5: 2.416 ops/ms -# Warmup Iteration 6: 2.425 ops/ms -# Warmup Iteration 7: 2.424 ops/ms -# Warmup Iteration 8: 2.426 ops/ms -# Warmup Iteration 9: 2.426 ops/ms -# Warmup Iteration 10: 2.422 ops/ms -Iteration 1: 2.426 ops/ms -Iteration 2: 2.426 ops/ms -Iteration 3: 2.424 ops/ms -Iteration 4: 2.428 ops/ms -Iteration 5: 2.427 ops/ms -Iteration 6: 2.427 ops/ms -Iteration 7: 2.425 ops/ms -Iteration 8: 2.426 ops/ms -Iteration 9: 2.426 ops/ms -Iteration 10: 2.427 ops/ms - -# Run progress: 18.10% complete, ETA 09:35:49 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.717 ops/ms -# Warmup Iteration 2: 2.430 ops/ms -# Warmup Iteration 3: 2.431 ops/ms -# Warmup Iteration 4: 2.433 ops/ms -# Warmup Iteration 5: 2.427 ops/ms -# Warmup Iteration 6: 2.432 ops/ms -# Warmup Iteration 7: 2.433 ops/ms -# Warmup Iteration 8: 2.433 ops/ms -# Warmup Iteration 9: 2.434 ops/ms -# Warmup Iteration 10: 2.433 ops/ms -Iteration 1: 2.433 ops/ms -Iteration 2: 2.432 ops/ms -Iteration 3: 2.432 ops/ms -Iteration 4: 2.430 ops/ms -Iteration 5: 2.432 ops/ms -Iteration 6: 2.433 ops/ms -Iteration 7: 2.424 ops/ms -Iteration 8: 2.428 ops/ms -Iteration 9: 2.428 ops/ms -Iteration 10: 2.427 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.classicBeanItemSer": - 2.485 ±(99.9%) 0.043 ops/ms [Average] - (min, avg, max) = (2.387, 2.485, 2.598), stdev = 0.086 - CI (99.9%): [2.443, 2.528] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.classicMediaItemDeser - -# Run progress: 19.05% complete, ETA 09:29:05 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -243.988 ops/ms -# Warmup Iteration 2: 425.598 ops/ms -# Warmup Iteration 3: 431.461 ops/ms -# Warmup Iteration 4: 431.309 ops/ms -# Warmup Iteration 5: 431.172 ops/ms -# Warmup Iteration 6: 431.330 ops/ms -# Warmup Iteration 7: 431.398 ops/ms -# Warmup Iteration 8: 431.233 ops/ms -# Warmup Iteration 9: 431.477 ops/ms -# Warmup Iteration 10: 431.766 ops/ms -Iteration 1: 431.264 ops/ms -Iteration 2: 431.423 ops/ms -Iteration 3: 431.279 ops/ms -Iteration 4: 431.473 ops/ms -Iteration 5: 431.238 ops/ms -Iteration 6: 431.180 ops/ms -Iteration 7: 431.410 ops/ms -Iteration 8: 431.291 ops/ms -Iteration 9: 430.958 ops/ms -Iteration 10: 430.953 ops/ms - -# Run progress: 20.00% complete, ETA 09:22:22 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -285.108 ops/ms -# Warmup Iteration 2: 414.745 ops/ms -# Warmup Iteration 3: 422.055 ops/ms -# Warmup Iteration 4: 421.981 ops/ms -# Warmup Iteration 5: 421.820 ops/ms -# Warmup Iteration 6: 422.187 ops/ms -# Warmup Iteration 7: 422.160 ops/ms -# Warmup Iteration 8: 422.253 ops/ms -# Warmup Iteration 9: 422.043 ops/ms -# Warmup Iteration 10: 422.028 ops/ms -Iteration 1: 421.926 ops/ms -Iteration 2: 421.957 ops/ms -Iteration 3: 421.952 ops/ms -Iteration 4: 422.074 ops/ms -Iteration 5: 421.821 ops/ms -Iteration 6: 422.013 ops/ms -Iteration 7: 421.901 ops/ms -Iteration 8: 421.687 ops/ms -Iteration 9: 421.548 ops/ms -Iteration 10: 421.588 ops/ms - -# Run progress: 20.95% complete, ETA 09:15:39 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -232.332 ops/ms -# Warmup Iteration 2: 424.228 ops/ms -# Warmup Iteration 3: 426.715 ops/ms -# Warmup Iteration 4: 427.992 ops/ms -# Warmup Iteration 5: 428.399 ops/ms -# Warmup Iteration 6: 428.119 ops/ms -# Warmup Iteration 7: 428.262 ops/ms -# Warmup Iteration 8: 428.053 ops/ms -# Warmup Iteration 9: 428.412 ops/ms -# Warmup Iteration 10: 427.957 ops/ms -Iteration 1: 428.409 ops/ms -Iteration 2: 428.470 ops/ms -Iteration 3: 428.278 ops/ms -Iteration 4: 428.410 ops/ms -Iteration 5: 428.358 ops/ms -Iteration 6: 428.342 ops/ms -Iteration 7: 428.422 ops/ms -Iteration 8: 428.226 ops/ms -Iteration 9: 428.326 ops/ms -Iteration 10: 428.516 ops/ms - -# Run progress: 21.90% complete, ETA 09:08:57 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -276.721 ops/ms -# Warmup Iteration 2: 422.702 ops/ms -# Warmup Iteration 3: 430.548 ops/ms -# Warmup Iteration 4: 429.974 ops/ms -# Warmup Iteration 5: 430.582 ops/ms -# Warmup Iteration 6: 430.402 ops/ms -# Warmup Iteration 7: 430.700 ops/ms -# Warmup Iteration 8: 430.641 ops/ms -# Warmup Iteration 9: 430.973 ops/ms -# Warmup Iteration 10: 430.679 ops/ms -Iteration 1: 428.337 ops/ms -Iteration 2: 430.469 ops/ms -Iteration 3: 430.271 ops/ms -Iteration 4: 430.735 ops/ms -Iteration 5: 430.868 ops/ms -Iteration 6: 430.421 ops/ms -Iteration 7: 430.670 ops/ms -Iteration 8: 430.222 ops/ms -Iteration 9: 430.747 ops/ms -Iteration 10: 430.572 ops/ms - -# Run progress: 22.86% complete, ETA 09:02:14 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -288.681 ops/ms -# Warmup Iteration 2: 432.696 ops/ms -# Warmup Iteration 3: 434.543 ops/ms -# Warmup Iteration 4: 435.271 ops/ms -# Warmup Iteration 5: 434.991 ops/ms -# Warmup Iteration 6: 434.372 ops/ms -# Warmup Iteration 7: 435.642 ops/ms -# Warmup Iteration 8: 435.423 ops/ms -# Warmup Iteration 9: 435.111 ops/ms -# Warmup Iteration 10: 434.738 ops/ms -Iteration 1: 434.750 ops/ms -Iteration 2: 434.756 ops/ms -Iteration 3: 435.156 ops/ms -Iteration 4: 434.538 ops/ms -Iteration 5: 434.810 ops/ms -Iteration 6: 434.950 ops/ms -Iteration 7: 433.254 ops/ms -Iteration 8: 433.854 ops/ms -Iteration 9: 433.501 ops/ms -Iteration 10: 433.763 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.classicMediaItemDeser": - 429.227 ±(99.9%) 2.092 ops/ms [Average] - (min, avg, max) = (421.548, 429.227, 435.156), stdev = 4.226 - CI (99.9%): [427.134, 431.319] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.classicMediaItemSer - -# Run progress: 23.81% complete, ETA 08:55:31 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -441.643 ops/ms -# Warmup Iteration 2: 656.208 ops/ms -# Warmup Iteration 3: 660.027 ops/ms -# Warmup Iteration 4: 660.429 ops/ms -# Warmup Iteration 5: 660.094 ops/ms -# Warmup Iteration 6: 659.792 ops/ms -# Warmup Iteration 7: 660.502 ops/ms -# Warmup Iteration 8: 660.249 ops/ms -# Warmup Iteration 9: 660.627 ops/ms -# Warmup Iteration 10: 660.605 ops/ms -Iteration 1: 660.225 ops/ms -Iteration 2: 660.413 ops/ms -Iteration 3: 660.384 ops/ms -Iteration 4: 660.604 ops/ms -Iteration 5: 660.579 ops/ms -Iteration 6: 660.588 ops/ms -Iteration 7: 651.040 ops/ms -Iteration 8: 651.045 ops/ms -Iteration 9: 650.728 ops/ms -Iteration 10: 650.967 ops/ms - -# Run progress: 24.76% complete, ETA 08:48:49 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -454.195 ops/ms -# Warmup Iteration 2: 685.598 ops/ms -# Warmup Iteration 3: 688.144 ops/ms -# Warmup Iteration 4: 689.180 ops/ms -# Warmup Iteration 5: 689.536 ops/ms -# Warmup Iteration 6: 689.036 ops/ms -# Warmup Iteration 7: 689.528 ops/ms -# Warmup Iteration 8: 689.565 ops/ms -# Warmup Iteration 9: 688.792 ops/ms -# Warmup Iteration 10: 689.504 ops/ms -Iteration 1: 689.420 ops/ms -Iteration 2: 687.971 ops/ms -Iteration 3: 689.540 ops/ms -Iteration 4: 689.600 ops/ms -Iteration 5: 689.467 ops/ms -Iteration 6: 689.273 ops/ms -Iteration 7: 689.308 ops/ms -Iteration 8: 689.042 ops/ms -Iteration 9: 689.338 ops/ms -Iteration 10: 689.461 ops/ms - -# Run progress: 25.71% complete, ETA 08:42:06 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -471.130 ops/ms -# Warmup Iteration 2: 677.693 ops/ms -# Warmup Iteration 3: 683.694 ops/ms -# Warmup Iteration 4: 683.661 ops/ms -# Warmup Iteration 5: 683.107 ops/ms -# Warmup Iteration 6: 683.316 ops/ms -# Warmup Iteration 7: 683.160 ops/ms -# Warmup Iteration 8: 683.413 ops/ms -# Warmup Iteration 9: 683.657 ops/ms -# Warmup Iteration 10: 683.670 ops/ms -Iteration 1: 683.621 ops/ms -Iteration 2: 683.175 ops/ms -Iteration 3: 683.492 ops/ms -Iteration 4: 683.431 ops/ms -Iteration 5: 683.424 ops/ms -Iteration 6: 683.431 ops/ms -Iteration 7: 682.752 ops/ms -Iteration 8: 682.754 ops/ms -Iteration 9: 682.773 ops/ms -Iteration 10: 682.380 ops/ms - -# Run progress: 26.67% complete, ETA 08:35:24 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -455.205 ops/ms -# Warmup Iteration 2: 678.644 ops/ms -# Warmup Iteration 3: 683.124 ops/ms -# Warmup Iteration 4: 683.336 ops/ms -# Warmup Iteration 5: 683.406 ops/ms -# Warmup Iteration 6: 683.107 ops/ms -# Warmup Iteration 7: 683.635 ops/ms -# Warmup Iteration 8: 683.815 ops/ms -# Warmup Iteration 9: 683.470 ops/ms -# Warmup Iteration 10: 683.511 ops/ms -Iteration 1: 683.634 ops/ms -Iteration 2: 683.777 ops/ms -Iteration 3: 683.520 ops/ms -Iteration 4: 683.208 ops/ms -Iteration 5: 683.335 ops/ms -Iteration 6: 683.651 ops/ms -Iteration 7: 683.795 ops/ms -Iteration 8: 683.932 ops/ms -Iteration 9: 683.790 ops/ms -Iteration 10: 684.023 ops/ms - -# Run progress: 27.62% complete, ETA 08:28:42 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -432.710 ops/ms -# Warmup Iteration 2: 658.589 ops/ms -# Warmup Iteration 3: 669.610 ops/ms -# Warmup Iteration 4: 669.984 ops/ms -# Warmup Iteration 5: 669.228 ops/ms -# Warmup Iteration 6: 670.203 ops/ms -# Warmup Iteration 7: 670.141 ops/ms -# Warmup Iteration 8: 669.994 ops/ms -# Warmup Iteration 9: 669.855 ops/ms -# Warmup Iteration 10: 670.004 ops/ms -Iteration 1: 669.964 ops/ms -Iteration 2: 669.956 ops/ms -Iteration 3: 670.119 ops/ms -Iteration 4: 669.724 ops/ms -Iteration 5: 670.031 ops/ms -Iteration 6: 670.052 ops/ms -Iteration 7: 670.419 ops/ms -Iteration 8: 669.794 ops/ms -Iteration 9: 670.345 ops/ms -Iteration 10: 670.246 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.classicMediaItemSer": - 676.551 ±(99.9%) 5.980 ops/ms [Average] - (min, avg, max) = (650.728, 676.551, 689.600), stdev = 12.079 - CI (99.9%): [670.571, 682.531] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.constructorArrayDeser - -# Run progress: 28.57% complete, ETA 08:21:59 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.701 ops/ms -# Warmup Iteration 2: 1.079 ops/ms -# Warmup Iteration 3: 1.097 ops/ms -# Warmup Iteration 4: 1.097 ops/ms -# Warmup Iteration 5: 1.098 ops/ms -# Warmup Iteration 6: 1.097 ops/ms -# Warmup Iteration 7: 1.098 ops/ms -# Warmup Iteration 8: 1.099 ops/ms -# Warmup Iteration 9: 1.098 ops/ms -# Warmup Iteration 10: 1.096 ops/ms -Iteration 1: 1.098 ops/ms -Iteration 2: 1.098 ops/ms -Iteration 3: 1.098 ops/ms -Iteration 4: 1.098 ops/ms -Iteration 5: 1.098 ops/ms -Iteration 6: 1.098 ops/ms -Iteration 7: 1.099 ops/ms -Iteration 8: 1.099 ops/ms -Iteration 9: 1.098 ops/ms -Iteration 10: 1.099 ops/ms - -# Run progress: 29.52% complete, ETA 08:15:17 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.653 ops/ms -# Warmup Iteration 2: 1.059 ops/ms -# Warmup Iteration 3: 1.081 ops/ms -# Warmup Iteration 4: 1.079 ops/ms -# Warmup Iteration 5: 1.080 ops/ms -# Warmup Iteration 6: 1.080 ops/ms -# Warmup Iteration 7: 1.079 ops/ms -# Warmup Iteration 8: 1.081 ops/ms -# Warmup Iteration 9: 1.080 ops/ms -# Warmup Iteration 10: 1.076 ops/ms -Iteration 1: 1.080 ops/ms -Iteration 2: 1.080 ops/ms -Iteration 3: 1.080 ops/ms -Iteration 4: 1.080 ops/ms -Iteration 5: 1.080 ops/ms -Iteration 6: 1.080 ops/ms -Iteration 7: 1.079 ops/ms -Iteration 8: 1.080 ops/ms -Iteration 9: 1.080 ops/ms -Iteration 10: 1.080 ops/ms - -# Run progress: 30.48% complete, ETA 08:08:35 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.711 ops/ms -# Warmup Iteration 2: 1.072 ops/ms -# Warmup Iteration 3: 1.092 ops/ms -# Warmup Iteration 4: 1.093 ops/ms -# Warmup Iteration 5: 1.092 ops/ms -# Warmup Iteration 6: 1.092 ops/ms -# Warmup Iteration 7: 1.092 ops/ms -# Warmup Iteration 8: 1.092 ops/ms -# Warmup Iteration 9: 1.092 ops/ms -# Warmup Iteration 10: 1.091 ops/ms -Iteration 1: 1.088 ops/ms -Iteration 2: 1.093 ops/ms -Iteration 3: 1.092 ops/ms -Iteration 4: 1.093 ops/ms -Iteration 5: 1.093 ops/ms -Iteration 6: 1.092 ops/ms -Iteration 7: 1.093 ops/ms -Iteration 8: 1.092 ops/ms -Iteration 9: 1.093 ops/ms -Iteration 10: 1.093 ops/ms - -# Run progress: 31.43% complete, ETA 08:01:53 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.688 ops/ms -# Warmup Iteration 2: 1.067 ops/ms -# Warmup Iteration 3: 1.088 ops/ms -# Warmup Iteration 4: 1.087 ops/ms -# Warmup Iteration 5: 1.088 ops/ms -# Warmup Iteration 6: 1.088 ops/ms -# Warmup Iteration 7: 1.088 ops/ms -# Warmup Iteration 8: 1.088 ops/ms -# Warmup Iteration 9: 1.088 ops/ms -# Warmup Iteration 10: 1.086 ops/ms -Iteration 1: 1.088 ops/ms -Iteration 2: 1.088 ops/ms -Iteration 3: 1.088 ops/ms -Iteration 4: 1.088 ops/ms -Iteration 5: 1.088 ops/ms -Iteration 6: 1.088 ops/ms -Iteration 7: 1.088 ops/ms -Iteration 8: 1.088 ops/ms -Iteration 9: 1.088 ops/ms -Iteration 10: 1.087 ops/ms - -# Run progress: 32.38% complete, ETA 07:55:11 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -0.657 ops/ms -# Warmup Iteration 2: 1.055 ops/ms -# Warmup Iteration 3: 1.078 ops/ms -# Warmup Iteration 4: 1.078 ops/ms -# Warmup Iteration 5: 1.078 ops/ms -# Warmup Iteration 6: 1.078 ops/ms -# Warmup Iteration 7: 1.078 ops/ms -# Warmup Iteration 8: 1.079 ops/ms -# Warmup Iteration 9: 1.078 ops/ms -# Warmup Iteration 10: 1.073 ops/ms -Iteration 1: 1.078 ops/ms -Iteration 2: 1.078 ops/ms -Iteration 3: 1.078 ops/ms -Iteration 4: 1.079 ops/ms -Iteration 5: 1.079 ops/ms -Iteration 6: 1.079 ops/ms -Iteration 7: 1.078 ops/ms -Iteration 8: 1.078 ops/ms -Iteration 9: 1.078 ops/ms -Iteration 10: 1.078 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.constructorArrayDeser": - 1.087 ±(99.9%) 0.004 ops/ms [Average] - (min, avg, max) = (1.078, 1.087, 1.099), stdev = 0.008 - CI (99.9%): [1.084, 1.091] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.beanArrayDeser - -# Run progress: 33.33% complete, ETA 07:48:30 -# Fork: 1 of 5 -# Warmup Iteration 1: 0.865 ops/ms -# Warmup Iteration 2: 1.327 ops/ms -# Warmup Iteration 3: 1.336 ops/ms -# Warmup Iteration 4: 1.336 ops/ms -# Warmup Iteration 5: 1.336 ops/ms -# Warmup Iteration 6: 1.337 ops/ms -# Warmup Iteration 7: 1.337 ops/ms -# Warmup Iteration 8: 1.335 ops/ms -# Warmup Iteration 9: 1.336 ops/ms -# Warmup Iteration 10: 1.337 ops/ms -Iteration 1: 1.338 ops/ms -Iteration 2: 1.338 ops/ms -Iteration 3: 1.337 ops/ms -Iteration 4: 1.338 ops/ms -Iteration 5: 1.338 ops/ms -Iteration 6: 1.337 ops/ms -Iteration 7: 1.337 ops/ms -Iteration 8: 1.337 ops/ms -Iteration 9: 1.338 ops/ms -Iteration 10: 1.338 ops/ms - -# Run progress: 34.29% complete, ETA 07:41:48 -# Fork: 2 of 5 -# Warmup Iteration 1: 0.870 ops/ms -# Warmup Iteration 2: 1.332 ops/ms -# Warmup Iteration 3: 1.340 ops/ms -# Warmup Iteration 4: 1.338 ops/ms -# Warmup Iteration 5: 1.339 ops/ms -# Warmup Iteration 6: 1.337 ops/ms -# Warmup Iteration 7: 1.338 ops/ms -# Warmup Iteration 8: 1.339 ops/ms -# Warmup Iteration 9: 1.338 ops/ms -# Warmup Iteration 10: 1.339 ops/ms -Iteration 1: 1.338 ops/ms -Iteration 2: 1.338 ops/ms -Iteration 3: 1.339 ops/ms -Iteration 4: 1.340 ops/ms -Iteration 5: 1.339 ops/ms -Iteration 6: 1.339 ops/ms -Iteration 7: 1.339 ops/ms -Iteration 8: 1.339 ops/ms -Iteration 9: 1.339 ops/ms -Iteration 10: 1.339 ops/ms - -# Run progress: 35.24% complete, ETA 07:35:06 -# Fork: 3 of 5 -# Warmup Iteration 1: 0.900 ops/ms -# Warmup Iteration 2: 1.361 ops/ms -# Warmup Iteration 3: 1.365 ops/ms -# Warmup Iteration 4: 1.364 ops/ms -# Warmup Iteration 5: 1.365 ops/ms -# Warmup Iteration 6: 1.365 ops/ms -# Warmup Iteration 7: 1.365 ops/ms -# Warmup Iteration 8: 1.360 ops/ms -# Warmup Iteration 9: 1.366 ops/ms -# Warmup Iteration 10: 1.365 ops/ms -Iteration 1: 1.366 ops/ms -Iteration 2: 1.364 ops/ms -Iteration 3: 1.365 ops/ms -Iteration 4: 1.366 ops/ms -Iteration 5: 1.366 ops/ms -Iteration 6: 1.366 ops/ms -Iteration 7: 1.366 ops/ms -Iteration 8: 1.367 ops/ms -Iteration 9: 1.366 ops/ms -Iteration 10: 1.367 ops/ms - -# Run progress: 36.19% complete, ETA 07:28:24 -# Fork: 4 of 5 -# Warmup Iteration 1: 0.850 ops/ms -# Warmup Iteration 2: 1.279 ops/ms -# Warmup Iteration 3: 1.281 ops/ms -# Warmup Iteration 4: 1.281 ops/ms -# Warmup Iteration 5: 1.281 ops/ms -# Warmup Iteration 6: 1.279 ops/ms -# Warmup Iteration 7: 1.282 ops/ms -# Warmup Iteration 8: 1.281 ops/ms -# Warmup Iteration 9: 1.274 ops/ms -# Warmup Iteration 10: 1.281 ops/ms -Iteration 1: 1.280 ops/ms -Iteration 2: 1.279 ops/ms -Iteration 3: 1.282 ops/ms -Iteration 4: 1.281 ops/ms -Iteration 5: 1.281 ops/ms -Iteration 6: 1.281 ops/ms -Iteration 7: 1.282 ops/ms -Iteration 8: 1.282 ops/ms -Iteration 9: 1.281 ops/ms -Iteration 10: 1.281 ops/ms - -# Run progress: 37.14% complete, ETA 07:21:43 -# Fork: 5 of 5 -# Warmup Iteration 1: 0.883 ops/ms -# Warmup Iteration 2: 1.337 ops/ms -# Warmup Iteration 3: 1.343 ops/ms -# Warmup Iteration 4: 1.343 ops/ms -# Warmup Iteration 5: 1.343 ops/ms -# Warmup Iteration 6: 1.343 ops/ms -# Warmup Iteration 7: 1.343 ops/ms -# Warmup Iteration 8: 1.339 ops/ms -# Warmup Iteration 9: 1.343 ops/ms -# Warmup Iteration 10: 1.344 ops/ms -Iteration 1: 1.344 ops/ms -Iteration 2: 1.344 ops/ms -Iteration 3: 1.344 ops/ms -Iteration 4: 1.344 ops/ms -Iteration 5: 1.344 ops/ms -Iteration 6: 1.344 ops/ms -Iteration 7: 1.344 ops/ms -Iteration 8: 1.344 ops/ms -Iteration 9: 1.343 ops/ms -Iteration 10: 1.344 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.beanArrayDeser": - 1.333 ±(99.9%) 0.014 ops/ms [Average] - (min, avg, max) = (1.279, 1.333, 1.367), stdev = 0.028 - CI (99.9%): [1.319, 1.348] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.beanArraySer - -# Run progress: 38.10% complete, ETA 07:15:01 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.741 ops/ms -# Warmup Iteration 2: 2.300 ops/ms -# Warmup Iteration 3: 2.305 ops/ms -# Warmup Iteration 4: 2.306 ops/ms -# Warmup Iteration 5: 2.304 ops/ms -# Warmup Iteration 6: 2.305 ops/ms -# Warmup Iteration 7: 2.306 ops/ms -# Warmup Iteration 8: 2.306 ops/ms -# Warmup Iteration 9: 2.307 ops/ms -# Warmup Iteration 10: 2.304 ops/ms -Iteration 1: 2.307 ops/ms -Iteration 2: 2.306 ops/ms -Iteration 3: 2.307 ops/ms -Iteration 4: 2.307 ops/ms -Iteration 5: 2.308 ops/ms -Iteration 6: 2.307 ops/ms -Iteration 7: 2.309 ops/ms -Iteration 8: 2.310 ops/ms -Iteration 9: 2.309 ops/ms -Iteration 10: 2.309 ops/ms - -# Run progress: 39.05% complete, ETA 07:08:21 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.533 ops/ms -# Warmup Iteration 2: 2.249 ops/ms -# Warmup Iteration 3: 2.254 ops/ms -# Warmup Iteration 4: 2.256 ops/ms -# Warmup Iteration 5: 2.254 ops/ms -# Warmup Iteration 6: 2.255 ops/ms -# Warmup Iteration 7: 2.253 ops/ms -# Warmup Iteration 8: 2.254 ops/ms -# Warmup Iteration 9: 2.255 ops/ms -# Warmup Iteration 10: 2.146 ops/ms -Iteration 1: 2.232 ops/ms -Iteration 2: 2.255 ops/ms -Iteration 3: 2.255 ops/ms -Iteration 4: 2.255 ops/ms -Iteration 5: 2.256 ops/ms -Iteration 6: 2.255 ops/ms -Iteration 7: 2.257 ops/ms -Iteration 8: 2.257 ops/ms -Iteration 9: 2.257 ops/ms -Iteration 10: 2.257 ops/ms - -# Run progress: 40.00% complete, ETA 07:01:39 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.473 ops/ms -# Warmup Iteration 2: 2.248 ops/ms -# Warmup Iteration 3: 2.252 ops/ms -# Warmup Iteration 4: 2.253 ops/ms -# Warmup Iteration 5: 2.251 ops/ms -# Warmup Iteration 6: 2.253 ops/ms -# Warmup Iteration 7: 2.256 ops/ms -# Warmup Iteration 8: 2.254 ops/ms -# Warmup Iteration 9: 2.255 ops/ms -# Warmup Iteration 10: 2.255 ops/ms -Iteration 1: 2.255 ops/ms -Iteration 2: 2.254 ops/ms -Iteration 3: 2.254 ops/ms -Iteration 4: 2.255 ops/ms -Iteration 5: 2.255 ops/ms -Iteration 6: 2.255 ops/ms -Iteration 7: 2.253 ops/ms -Iteration 8: 2.253 ops/ms -Iteration 9: 2.253 ops/ms -Iteration 10: 2.253 ops/ms - -# Run progress: 40.95% complete, ETA 06:54:57 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.494 ops/ms -# Warmup Iteration 2: 2.229 ops/ms -# Warmup Iteration 3: 2.244 ops/ms -# Warmup Iteration 4: 2.243 ops/ms -# Warmup Iteration 5: 2.242 ops/ms -# Warmup Iteration 6: 2.244 ops/ms -# Warmup Iteration 7: 2.244 ops/ms -# Warmup Iteration 8: 2.244 ops/ms -# Warmup Iteration 9: 2.244 ops/ms -# Warmup Iteration 10: 2.244 ops/ms -Iteration 1: 2.245 ops/ms -Iteration 2: 2.245 ops/ms -Iteration 3: 2.244 ops/ms -Iteration 4: 2.245 ops/ms -Iteration 5: 2.244 ops/ms -Iteration 6: 2.244 ops/ms -Iteration 7: 2.253 ops/ms -Iteration 8: 2.252 ops/ms -Iteration 9: 2.252 ops/ms -Iteration 10: 2.252 ops/ms - -# Run progress: 41.90% complete, ETA 06:48:15 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.486 ops/ms -# Warmup Iteration 2: 2.210 ops/ms -# Warmup Iteration 3: 2.216 ops/ms -# Warmup Iteration 4: 2.219 ops/ms -# Warmup Iteration 5: 2.216 ops/ms -# Warmup Iteration 6: 2.213 ops/ms -# Warmup Iteration 7: 2.214 ops/ms -# Warmup Iteration 8: 2.215 ops/ms -# Warmup Iteration 9: 2.213 ops/ms -# Warmup Iteration 10: 2.214 ops/ms -Iteration 1: 2.215 ops/ms -Iteration 2: 2.215 ops/ms -Iteration 3: 2.213 ops/ms -Iteration 4: 2.212 ops/ms -Iteration 5: 2.213 ops/ms -Iteration 6: 2.215 ops/ms -Iteration 7: 2.225 ops/ms -Iteration 8: 2.226 ops/ms -Iteration 9: 2.225 ops/ms -Iteration 10: 2.224 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.beanArraySer": - 2.256 ±(99.9%) 0.015 ops/ms [Average] - (min, avg, max) = (2.212, 2.256, 2.310), stdev = 0.030 - CI (99.9%): [2.242, 2.271] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.classicBeanItemDeser - -# Run progress: 42.86% complete, ETA 06:41:33 -# Fork: 1 of 5 -# Warmup Iteration 1: 0.996 ops/ms -# Warmup Iteration 2: 1.517 ops/ms -# Warmup Iteration 3: 1.531 ops/ms -# Warmup Iteration 4: 1.531 ops/ms -# Warmup Iteration 5: 1.530 ops/ms -# Warmup Iteration 6: 1.532 ops/ms -# Warmup Iteration 7: 1.527 ops/ms -# Warmup Iteration 8: 1.531 ops/ms -# Warmup Iteration 9: 1.533 ops/ms -# Warmup Iteration 10: 1.533 ops/ms -Iteration 1: 1.533 ops/ms -Iteration 2: 1.533 ops/ms -Iteration 3: 1.534 ops/ms -Iteration 4: 1.535 ops/ms -Iteration 5: 1.534 ops/ms -Iteration 6: 1.533 ops/ms -Iteration 7: 1.533 ops/ms -Iteration 8: 1.533 ops/ms -Iteration 9: 1.532 ops/ms -Iteration 10: 1.532 ops/ms - -# Run progress: 43.81% complete, ETA 06:34:51 -# Fork: 2 of 5 -# Warmup Iteration 1: 0.965 ops/ms -# Warmup Iteration 2: 1.406 ops/ms -# Warmup Iteration 3: 1.417 ops/ms -# Warmup Iteration 4: 1.417 ops/ms -# Warmup Iteration 5: 1.415 ops/ms -# Warmup Iteration 6: 1.416 ops/ms -# Warmup Iteration 7: 1.417 ops/ms -# Warmup Iteration 8: 1.410 ops/ms -# Warmup Iteration 9: 1.417 ops/ms -# Warmup Iteration 10: 1.417 ops/ms -Iteration 1: 1.417 ops/ms -Iteration 2: 1.417 ops/ms -Iteration 3: 1.417 ops/ms -Iteration 4: 1.418 ops/ms -Iteration 5: 1.417 ops/ms -Iteration 6: 1.417 ops/ms -Iteration 7: 1.417 ops/ms -Iteration 8: 1.417 ops/ms -Iteration 9: 1.417 ops/ms -Iteration 10: 1.417 ops/ms - -# Run progress: 44.76% complete, ETA 06:28:09 -# Fork: 3 of 5 -# Warmup Iteration 1: 0.997 ops/ms -# Warmup Iteration 2: 1.467 ops/ms -# Warmup Iteration 3: 1.478 ops/ms -# Warmup Iteration 4: 1.479 ops/ms -# Warmup Iteration 5: 1.477 ops/ms -# Warmup Iteration 6: 1.480 ops/ms -# Warmup Iteration 7: 1.481 ops/ms -# Warmup Iteration 8: 1.471 ops/ms -# Warmup Iteration 9: 1.481 ops/ms -# Warmup Iteration 10: 1.481 ops/ms -Iteration 1: 1.481 ops/ms -Iteration 2: 1.480 ops/ms -Iteration 3: 1.481 ops/ms -Iteration 4: 1.481 ops/ms -Iteration 5: 1.481 ops/ms -Iteration 6: 1.480 ops/ms -Iteration 7: 1.481 ops/ms -Iteration 8: 1.481 ops/ms -Iteration 9: 1.481 ops/ms -Iteration 10: 1.481 ops/ms - -# Run progress: 45.71% complete, ETA 06:21:28 -# Fork: 4 of 5 -# Warmup Iteration 1: 0.984 ops/ms -# Warmup Iteration 2: 1.447 ops/ms -# Warmup Iteration 3: 1.462 ops/ms -# Warmup Iteration 4: 1.462 ops/ms -# Warmup Iteration 5: 1.458 ops/ms -# Warmup Iteration 6: 1.462 ops/ms -# Warmup Iteration 7: 1.463 ops/ms -# Warmup Iteration 8: 1.458 ops/ms -# Warmup Iteration 9: 1.461 ops/ms -# Warmup Iteration 10: 1.461 ops/ms -Iteration 1: 1.461 ops/ms -Iteration 2: 1.461 ops/ms -Iteration 3: 1.461 ops/ms -Iteration 4: 1.461 ops/ms -Iteration 5: 1.462 ops/ms -Iteration 6: 1.462 ops/ms -Iteration 7: 1.462 ops/ms -Iteration 8: 1.462 ops/ms -Iteration 9: 1.462 ops/ms -Iteration 10: 1.462 ops/ms - -# Run progress: 46.67% complete, ETA 06:14:46 -# Fork: 5 of 5 -# Warmup Iteration 1: 0.969 ops/ms -# Warmup Iteration 2: 1.493 ops/ms -# Warmup Iteration 3: 1.503 ops/ms -# Warmup Iteration 4: 1.503 ops/ms -# Warmup Iteration 5: 1.502 ops/ms -# Warmup Iteration 6: 1.505 ops/ms -# Warmup Iteration 7: 1.505 ops/ms -# Warmup Iteration 8: 1.490 ops/ms -# Warmup Iteration 9: 1.503 ops/ms -# Warmup Iteration 10: 1.506 ops/ms -Iteration 1: 1.506 ops/ms -Iteration 2: 1.505 ops/ms -Iteration 3: 1.506 ops/ms -Iteration 4: 1.505 ops/ms -Iteration 5: 1.506 ops/ms -Iteration 6: 1.505 ops/ms -Iteration 7: 1.506 ops/ms -Iteration 8: 1.506 ops/ms -Iteration 9: 1.505 ops/ms -Iteration 10: 1.505 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.classicBeanItemDeser": - 1.480 ±(99.9%) 0.020 ops/ms [Average] - (min, avg, max) = (1.417, 1.480, 1.535), stdev = 0.040 - CI (99.9%): [1.460, 1.499] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.classicBeanItemSer - -# Run progress: 47.62% complete, ETA 06:08:04 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.505 ops/ms -# Warmup Iteration 2: 2.139 ops/ms -# Warmup Iteration 3: 2.149 ops/ms -# Warmup Iteration 4: 2.151 ops/ms -# Warmup Iteration 5: 2.152 ops/ms -# Warmup Iteration 6: 2.146 ops/ms -# Warmup Iteration 7: 2.150 ops/ms -# Warmup Iteration 8: 2.148 ops/ms -# Warmup Iteration 9: 2.150 ops/ms -# Warmup Iteration 10: 2.123 ops/ms -Iteration 1: 2.146 ops/ms -Iteration 2: 2.150 ops/ms -Iteration 3: 2.150 ops/ms -Iteration 4: 2.150 ops/ms -Iteration 5: 2.150 ops/ms -Iteration 6: 2.149 ops/ms -Iteration 7: 2.145 ops/ms -Iteration 8: 2.145 ops/ms -Iteration 9: 2.146 ops/ms -Iteration 10: 2.146 ops/ms - -# Run progress: 48.57% complete, ETA 06:01:22 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.571 ops/ms -# Warmup Iteration 2: 2.202 ops/ms -# Warmup Iteration 3: 2.237 ops/ms -# Warmup Iteration 4: 2.225 ops/ms -# Warmup Iteration 5: 2.227 ops/ms -# Warmup Iteration 6: 2.238 ops/ms -# Warmup Iteration 7: 2.240 ops/ms -# Warmup Iteration 8: 2.240 ops/ms -# Warmup Iteration 9: 2.239 ops/ms -# Warmup Iteration 10: 2.234 ops/ms -Iteration 1: 2.238 ops/ms -Iteration 2: 2.238 ops/ms -Iteration 3: 2.237 ops/ms -Iteration 4: 2.239 ops/ms -Iteration 5: 2.238 ops/ms -Iteration 6: 2.238 ops/ms -Iteration 7: 2.234 ops/ms -Iteration 8: 2.235 ops/ms -Iteration 9: 2.234 ops/ms -Iteration 10: 2.234 ops/ms - -# Run progress: 49.52% complete, ETA 05:54:40 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.502 ops/ms -# Warmup Iteration 2: 2.100 ops/ms -# Warmup Iteration 3: 2.116 ops/ms -# Warmup Iteration 4: 2.118 ops/ms -# Warmup Iteration 5: 2.119 ops/ms -# Warmup Iteration 6: 2.108 ops/ms -# Warmup Iteration 7: 2.119 ops/ms -# Warmup Iteration 8: 2.119 ops/ms -# Warmup Iteration 9: 2.119 ops/ms -# Warmup Iteration 10: 2.118 ops/ms -Iteration 1: 2.119 ops/ms -Iteration 2: 2.120 ops/ms -Iteration 3: 2.119 ops/ms -Iteration 4: 2.119 ops/ms -Iteration 5: 2.120 ops/ms -Iteration 6: 2.119 ops/ms -Iteration 7: 2.118 ops/ms -Iteration 8: 2.119 ops/ms -Iteration 9: 2.120 ops/ms -Iteration 10: 2.120 ops/ms - -# Run progress: 50.48% complete, ETA 05:47:59 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.689 ops/ms -# Warmup Iteration 2: 2.380 ops/ms -# Warmup Iteration 3: 2.399 ops/ms -# Warmup Iteration 4: 2.400 ops/ms -# Warmup Iteration 5: 2.383 ops/ms -# Warmup Iteration 6: 2.396 ops/ms -# Warmup Iteration 7: 2.396 ops/ms -# Warmup Iteration 8: 2.396 ops/ms -# Warmup Iteration 9: 2.396 ops/ms -# Warmup Iteration 10: 2.396 ops/ms -Iteration 1: 2.396 ops/ms -Iteration 2: 2.396 ops/ms -Iteration 3: 2.397 ops/ms -Iteration 4: 2.396 ops/ms -Iteration 5: 2.396 ops/ms -Iteration 6: 2.395 ops/ms -Iteration 7: 2.396 ops/ms -Iteration 8: 2.396 ops/ms -Iteration 9: 2.395 ops/ms -Iteration 10: 2.395 ops/ms - -# Run progress: 51.43% complete, ETA 05:41:17 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.445 ops/ms -# Warmup Iteration 2: 2.083 ops/ms -# Warmup Iteration 3: 2.108 ops/ms -# Warmup Iteration 4: 2.116 ops/ms -# Warmup Iteration 5: 2.111 ops/ms -# Warmup Iteration 6: 2.106 ops/ms -# Warmup Iteration 7: 2.116 ops/ms -# Warmup Iteration 8: 2.113 ops/ms -# Warmup Iteration 9: 2.113 ops/ms -# Warmup Iteration 10: 2.113 ops/ms -Iteration 1: 2.114 ops/ms -Iteration 2: 2.114 ops/ms -Iteration 3: 2.114 ops/ms -Iteration 4: 2.115 ops/ms -Iteration 5: 2.114 ops/ms -Iteration 6: 2.115 ops/ms -Iteration 7: 2.105 ops/ms -Iteration 8: 2.105 ops/ms -Iteration 9: 2.105 ops/ms -Iteration 10: 2.105 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.classicBeanItemSer": - 2.202 ±(99.9%) 0.053 ops/ms [Average] - (min, avg, max) = (2.105, 2.202, 2.397), stdev = 0.108 - CI (99.9%): [2.149, 2.255] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemDeser - -# Run progress: 52.38% complete, ETA 05:34:35 -# Fork: 1 of 5 -# Warmup Iteration 1: 269.842 ops/ms -# Warmup Iteration 2: 426.965 ops/ms -# Warmup Iteration 3: 427.886 ops/ms -# Warmup Iteration 4: 427.760 ops/ms -# Warmup Iteration 5: 427.672 ops/ms -# Warmup Iteration 6: 427.550 ops/ms -# Warmup Iteration 7: 427.680 ops/ms -# Warmup Iteration 8: 427.674 ops/ms -# Warmup Iteration 9: 427.671 ops/ms -# Warmup Iteration 10: 427.791 ops/ms -Iteration 1: 427.871 ops/ms -Iteration 2: 427.645 ops/ms -Iteration 3: 427.736 ops/ms -Iteration 4: 427.744 ops/ms -Iteration 5: 427.634 ops/ms -Iteration 6: 427.466 ops/ms -Iteration 7: 427.705 ops/ms -Iteration 8: 427.661 ops/ms -Iteration 9: 427.602 ops/ms -Iteration 10: 427.812 ops/ms - -# Run progress: 53.33% complete, ETA 05:27:54 -# Fork: 2 of 5 -# Warmup Iteration 1: 264.994 ops/ms -# Warmup Iteration 2: 432.247 ops/ms -# Warmup Iteration 3: 434.637 ops/ms -# Warmup Iteration 4: 434.852 ops/ms -# Warmup Iteration 5: 434.296 ops/ms -# Warmup Iteration 6: 434.533 ops/ms -# Warmup Iteration 7: 434.305 ops/ms -# Warmup Iteration 8: 434.574 ops/ms -# Warmup Iteration 9: 434.759 ops/ms -# Warmup Iteration 10: 434.833 ops/ms -Iteration 1: 434.794 ops/ms -Iteration 2: 433.783 ops/ms -Iteration 3: 434.736 ops/ms -Iteration 4: 434.715 ops/ms -Iteration 5: 434.723 ops/ms -Iteration 6: 434.631 ops/ms -Iteration 7: 434.835 ops/ms -Iteration 8: 434.625 ops/ms -Iteration 9: 434.351 ops/ms -Iteration 10: 434.534 ops/ms - -# Run progress: 54.29% complete, ETA 05:21:12 -# Fork: 3 of 5 -# Warmup Iteration 1: 271.124 ops/ms -# Warmup Iteration 2: 428.101 ops/ms -# Warmup Iteration 3: 429.294 ops/ms -# Warmup Iteration 4: 429.562 ops/ms -# Warmup Iteration 5: 429.510 ops/ms -# Warmup Iteration 6: 429.475 ops/ms -# Warmup Iteration 7: 429.138 ops/ms -# Warmup Iteration 8: 429.367 ops/ms -# Warmup Iteration 9: 429.293 ops/ms -# Warmup Iteration 10: 429.242 ops/ms -Iteration 1: 429.146 ops/ms -Iteration 2: 428.835 ops/ms -Iteration 3: 429.279 ops/ms -Iteration 4: 429.285 ops/ms -Iteration 5: 429.218 ops/ms -Iteration 6: 429.479 ops/ms -Iteration 7: 429.519 ops/ms -Iteration 8: 429.586 ops/ms -Iteration 9: 429.405 ops/ms -Iteration 10: 429.479 ops/ms - -# Run progress: 55.24% complete, ETA 05:14:30 -# Fork: 4 of 5 -# Warmup Iteration 1: 278.919 ops/ms -# Warmup Iteration 2: 425.624 ops/ms -# Warmup Iteration 3: 429.124 ops/ms -# Warmup Iteration 4: 429.193 ops/ms -# Warmup Iteration 5: 429.275 ops/ms -# Warmup Iteration 6: 428.729 ops/ms -# Warmup Iteration 7: 428.849 ops/ms -# Warmup Iteration 8: 428.840 ops/ms -# Warmup Iteration 9: 428.624 ops/ms -# Warmup Iteration 10: 429.092 ops/ms -Iteration 1: 429.160 ops/ms -Iteration 2: 428.441 ops/ms -Iteration 3: 428.926 ops/ms -Iteration 4: 428.795 ops/ms -Iteration 5: 429.211 ops/ms -Iteration 6: 428.798 ops/ms -Iteration 7: 428.962 ops/ms -Iteration 8: 428.797 ops/ms -Iteration 9: 428.819 ops/ms -Iteration 10: 428.828 ops/ms - -# Run progress: 56.19% complete, ETA 05:07:49 -# Fork: 5 of 5 -# Warmup Iteration 1: 270.693 ops/ms -# Warmup Iteration 2: 428.425 ops/ms -# Warmup Iteration 3: 430.090 ops/ms -# Warmup Iteration 4: 430.014 ops/ms -# Warmup Iteration 5: 429.679 ops/ms -# Warmup Iteration 6: 430.022 ops/ms -# Warmup Iteration 7: 429.834 ops/ms -# Warmup Iteration 8: 430.877 ops/ms -# Warmup Iteration 9: 429.457 ops/ms -# Warmup Iteration 10: 428.485 ops/ms -Iteration 1: 429.235 ops/ms -Iteration 2: 430.117 ops/ms -Iteration 3: 429.521 ops/ms -Iteration 4: 430.274 ops/ms -Iteration 5: 430.086 ops/ms -Iteration 6: 430.021 ops/ms -Iteration 7: 429.557 ops/ms -Iteration 8: 429.530 ops/ms -Iteration 9: 429.257 ops/ms -Iteration 10: 429.360 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemDeser": - 430.031 ±(99.9%) 1.192 ops/ms [Average] - (min, avg, max) = (427.466, 430.031, 434.835), stdev = 2.407 - CI (99.9%): [428.839, 431.222] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemSer - -# Run progress: 57.14% complete, ETA 05:01:07 -# Fork: 1 of 5 -# Warmup Iteration 1: 419.815 ops/ms -# Warmup Iteration 2: 653.959 ops/ms -# Warmup Iteration 3: 660.267 ops/ms -# Warmup Iteration 4: 660.270 ops/ms -# Warmup Iteration 5: 659.690 ops/ms -# Warmup Iteration 6: 660.104 ops/ms -# Warmup Iteration 7: 659.442 ops/ms -# Warmup Iteration 8: 660.428 ops/ms -# Warmup Iteration 9: 659.630 ops/ms -# Warmup Iteration 10: 660.550 ops/ms -Iteration 1: 660.554 ops/ms -Iteration 2: 660.062 ops/ms -Iteration 3: 660.594 ops/ms -Iteration 4: 659.338 ops/ms -Iteration 5: 659.314 ops/ms -Iteration 6: 658.849 ops/ms -Iteration 7: 662.076 ops/ms -Iteration 8: 662.076 ops/ms -Iteration 9: 662.230 ops/ms -Iteration 10: 662.442 ops/ms - -# Run progress: 58.10% complete, ETA 04:54:25 -# Fork: 2 of 5 -# Warmup Iteration 1: 431.011 ops/ms -# Warmup Iteration 2: 653.791 ops/ms -# Warmup Iteration 3: 657.293 ops/ms -# Warmup Iteration 4: 656.996 ops/ms -# Warmup Iteration 5: 656.962 ops/ms -# Warmup Iteration 6: 656.695 ops/ms -# Warmup Iteration 7: 657.525 ops/ms -# Warmup Iteration 8: 657.216 ops/ms -# Warmup Iteration 9: 656.581 ops/ms -# Warmup Iteration 10: 657.250 ops/ms -Iteration 1: 657.329 ops/ms -Iteration 2: 657.331 ops/ms -Iteration 3: 657.506 ops/ms -Iteration 4: 657.544 ops/ms -Iteration 5: 657.322 ops/ms -Iteration 6: 657.273 ops/ms -Iteration 7: 657.881 ops/ms -Iteration 8: 657.667 ops/ms -Iteration 9: 657.905 ops/ms -Iteration 10: 658.438 ops/ms - -# Run progress: 59.05% complete, ETA 04:47:43 -# Fork: 3 of 5 -# Warmup Iteration 1: 431.742 ops/ms -# Warmup Iteration 2: 646.056 ops/ms -# Warmup Iteration 3: 651.938 ops/ms -# Warmup Iteration 4: 651.688 ops/ms -# Warmup Iteration 5: 651.590 ops/ms -# Warmup Iteration 6: 651.669 ops/ms -# Warmup Iteration 7: 651.384 ops/ms -# Warmup Iteration 8: 651.560 ops/ms -# Warmup Iteration 9: 651.615 ops/ms -# Warmup Iteration 10: 650.625 ops/ms -Iteration 1: 651.499 ops/ms -Iteration 2: 651.333 ops/ms -Iteration 3: 651.278 ops/ms -Iteration 4: 650.907 ops/ms -Iteration 5: 651.245 ops/ms -Iteration 6: 651.398 ops/ms -Iteration 7: 650.099 ops/ms -Iteration 8: 650.504 ops/ms -Iteration 9: 649.807 ops/ms -Iteration 10: 650.461 ops/ms - -# Run progress: 60.00% complete, ETA 04:41:02 -# Fork: 4 of 5 -# Warmup Iteration 1: 418.774 ops/ms -# Warmup Iteration 2: 663.891 ops/ms -# Warmup Iteration 3: 667.084 ops/ms -# Warmup Iteration 4: 666.423 ops/ms -# Warmup Iteration 5: 666.864 ops/ms -# Warmup Iteration 6: 666.846 ops/ms -# Warmup Iteration 7: 666.455 ops/ms -# Warmup Iteration 8: 666.951 ops/ms -# Warmup Iteration 9: 666.543 ops/ms -# Warmup Iteration 10: 666.752 ops/ms -Iteration 1: 666.794 ops/ms -Iteration 2: 667.143 ops/ms -Iteration 3: 666.799 ops/ms -Iteration 4: 666.316 ops/ms -Iteration 5: 666.235 ops/ms -Iteration 6: 666.516 ops/ms -Iteration 7: 665.412 ops/ms -Iteration 8: 665.276 ops/ms -Iteration 9: 665.358 ops/ms -Iteration 10: 665.454 ops/ms - -# Run progress: 60.95% complete, ETA 04:34:20 -# Fork: 5 of 5 -# Warmup Iteration 1: 426.510 ops/ms -# Warmup Iteration 2: 660.379 ops/ms -# Warmup Iteration 3: 662.777 ops/ms -# Warmup Iteration 4: 663.068 ops/ms -# Warmup Iteration 5: 663.108 ops/ms -# Warmup Iteration 6: 662.787 ops/ms -# Warmup Iteration 7: 662.482 ops/ms -# Warmup Iteration 8: 663.283 ops/ms -# Warmup Iteration 9: 663.045 ops/ms -# Warmup Iteration 10: 663.005 ops/ms -Iteration 1: 662.232 ops/ms -Iteration 2: 662.971 ops/ms -Iteration 3: 663.310 ops/ms -Iteration 4: 663.342 ops/ms -Iteration 5: 663.310 ops/ms -Iteration 6: 663.327 ops/ms -Iteration 7: 659.804 ops/ms -Iteration 8: 659.166 ops/ms -Iteration 9: 658.826 ops/ms -Iteration 10: 659.791 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemSer": - 659.393 ±(99.9%) 2.591 ops/ms [Average] - (min, avg, max) = (649.807, 659.393, 667.143), stdev = 5.234 - CI (99.9%): [656.802, 661.984] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.constructorArrayDeser - -# Run progress: 61.90% complete, ETA 04:27:39 -# Fork: 1 of 5 -# Warmup Iteration 1: 0.704 ops/ms -# Warmup Iteration 2: 1.088 ops/ms -# Warmup Iteration 3: 1.100 ops/ms -# Warmup Iteration 4: 1.099 ops/ms -# Warmup Iteration 5: 1.099 ops/ms -# Warmup Iteration 6: 1.099 ops/ms -# Warmup Iteration 7: 1.100 ops/ms -# Warmup Iteration 8: 1.098 ops/ms -# Warmup Iteration 9: 1.098 ops/ms -# Warmup Iteration 10: 1.091 ops/ms -Iteration 1: 1.100 ops/ms -Iteration 2: 1.098 ops/ms -Iteration 3: 1.099 ops/ms -Iteration 4: 1.100 ops/ms -Iteration 5: 1.100 ops/ms -Iteration 6: 1.091 ops/ms -Iteration 7: 1.091 ops/ms -Iteration 8: 1.092 ops/ms -Iteration 9: 1.091 ops/ms -Iteration 10: 1.092 ops/ms - -# Run progress: 62.86% complete, ETA 04:20:57 -# Fork: 2 of 5 -# Warmup Iteration 1: 0.694 ops/ms -# Warmup Iteration 2: 1.072 ops/ms -# Warmup Iteration 3: 1.092 ops/ms -# Warmup Iteration 4: 1.091 ops/ms -# Warmup Iteration 5: 1.092 ops/ms -# Warmup Iteration 6: 1.092 ops/ms -# Warmup Iteration 7: 1.093 ops/ms -# Warmup Iteration 8: 1.092 ops/ms -# Warmup Iteration 9: 1.092 ops/ms -# Warmup Iteration 10: 1.077 ops/ms -Iteration 1: 1.083 ops/ms -Iteration 2: 1.084 ops/ms -Iteration 3: 1.084 ops/ms -Iteration 4: 1.084 ops/ms -Iteration 5: 1.083 ops/ms -Iteration 6: 1.084 ops/ms -Iteration 7: 1.085 ops/ms -Iteration 8: 1.084 ops/ms -Iteration 9: 1.084 ops/ms -Iteration 10: 1.084 ops/ms - -# Run progress: 63.81% complete, ETA 04:14:15 -# Fork: 3 of 5 -# Warmup Iteration 1: 0.691 ops/ms -# Warmup Iteration 2: 1.081 ops/ms -# Warmup Iteration 3: 1.093 ops/ms -# Warmup Iteration 4: 1.093 ops/ms -# Warmup Iteration 5: 1.093 ops/ms -# Warmup Iteration 6: 1.092 ops/ms -# Warmup Iteration 7: 1.089 ops/ms -# Warmup Iteration 8: 1.093 ops/ms -# Warmup Iteration 9: 1.093 ops/ms -# Warmup Iteration 10: 1.090 ops/ms -Iteration 1: 1.093 ops/ms -Iteration 2: 1.093 ops/ms -Iteration 3: 1.093 ops/ms -Iteration 4: 1.093 ops/ms -Iteration 5: 1.093 ops/ms -Iteration 6: 1.093 ops/ms -Iteration 7: 1.093 ops/ms -Iteration 8: 1.093 ops/ms -Iteration 9: 1.093 ops/ms -Iteration 10: 1.093 ops/ms - -# Run progress: 64.76% complete, ETA 04:07:34 -# Fork: 4 of 5 -# Warmup Iteration 1: 0.668 ops/ms -# Warmup Iteration 2: 1.071 ops/ms -# Warmup Iteration 3: 1.089 ops/ms -# Warmup Iteration 4: 1.089 ops/ms -# Warmup Iteration 5: 1.091 ops/ms -# Warmup Iteration 6: 1.090 ops/ms -# Warmup Iteration 7: 1.088 ops/ms -# Warmup Iteration 8: 1.090 ops/ms -# Warmup Iteration 9: 1.066 ops/ms -# Warmup Iteration 10: 1.018 ops/ms -Iteration 1: 1.091 ops/ms -Iteration 2: 1.088 ops/ms -Iteration 3: 1.086 ops/ms -Iteration 4: 1.087 ops/ms -Iteration 5: 1.089 ops/ms -Iteration 6: 1.089 ops/ms -Iteration 7: 1.089 ops/ms -Iteration 8: 1.089 ops/ms -Iteration 9: 1.088 ops/ms -Iteration 10: 1.091 ops/ms - -# Run progress: 65.71% complete, ETA 04:00:52 -# Fork: 5 of 5 -# Warmup Iteration 1: 0.700 ops/ms -# Warmup Iteration 2: 1.083 ops/ms -# Warmup Iteration 3: 1.106 ops/ms -# Warmup Iteration 4: 1.107 ops/ms -# Warmup Iteration 5: 1.106 ops/ms -# Warmup Iteration 6: 1.101 ops/ms -# Warmup Iteration 7: 1.093 ops/ms -# Warmup Iteration 8: 1.097 ops/ms -# Warmup Iteration 9: 1.101 ops/ms -# Warmup Iteration 10: 1.092 ops/ms -Iteration 1: 1.098 ops/ms -Iteration 2: 1.096 ops/ms -Iteration 3: 1.099 ops/ms -Iteration 4: 1.098 ops/ms -Iteration 5: 1.099 ops/ms -Iteration 6: 1.097 ops/ms -Iteration 7: 1.099 ops/ms -Iteration 8: 1.098 ops/ms -Iteration 9: 1.098 ops/ms -Iteration 10: 1.098 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.constructorArrayDeser": - 1.092 ±(99.9%) 0.003 ops/ms [Average] - (min, avg, max) = (1.083, 1.092, 1.100), stdev = 0.006 - CI (99.9%): [1.089, 1.095] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.beanArrayDeser - -# Run progress: 66.67% complete, ETA 03:54:11 -# Fork: 1 of 5 -# Warmup Iteration 1: 0.725 ops/ms -# Warmup Iteration 2: 1.116 ops/ms -# Warmup Iteration 3: 1.137 ops/ms -# Warmup Iteration 4: 1.136 ops/ms -# Warmup Iteration 5: 1.135 ops/ms -# Warmup Iteration 6: 1.136 ops/ms -# Warmup Iteration 7: 1.136 ops/ms -# Warmup Iteration 8: 1.136 ops/ms -# Warmup Iteration 9: 1.136 ops/ms -# Warmup Iteration 10: 1.130 ops/ms -Iteration 1: 1.137 ops/ms -Iteration 2: 1.137 ops/ms -Iteration 3: 1.137 ops/ms -Iteration 4: 1.137 ops/ms -Iteration 5: 1.136 ops/ms -Iteration 6: 1.137 ops/ms -Iteration 7: 1.137 ops/ms -Iteration 8: 1.138 ops/ms -Iteration 9: 1.137 ops/ms -Iteration 10: 1.138 ops/ms - -# Run progress: 67.62% complete, ETA 03:47:29 -# Fork: 2 of 5 -# Warmup Iteration 1: 0.713 ops/ms -# Warmup Iteration 2: 1.076 ops/ms -# Warmup Iteration 3: 1.096 ops/ms -# Warmup Iteration 4: 1.096 ops/ms -# Warmup Iteration 5: 1.096 ops/ms -# Warmup Iteration 6: 1.096 ops/ms -# Warmup Iteration 7: 1.097 ops/ms -# Warmup Iteration 8: 1.096 ops/ms -# Warmup Iteration 9: 1.096 ops/ms -# Warmup Iteration 10: 1.093 ops/ms -Iteration 1: 1.097 ops/ms -Iteration 2: 1.097 ops/ms -Iteration 3: 1.097 ops/ms -Iteration 4: 1.097 ops/ms -Iteration 5: 1.097 ops/ms -Iteration 6: 1.097 ops/ms -Iteration 7: 1.098 ops/ms -Iteration 8: 1.097 ops/ms -Iteration 9: 1.097 ops/ms -Iteration 10: 1.097 ops/ms - -# Run progress: 68.57% complete, ETA 03:40:48 -# Fork: 3 of 5 -# Warmup Iteration 1: 0.649 ops/ms -# Warmup Iteration 2: 1.079 ops/ms -# Warmup Iteration 3: 1.087 ops/ms -# Warmup Iteration 4: 1.087 ops/ms -# Warmup Iteration 5: 1.087 ops/ms -# Warmup Iteration 6: 1.087 ops/ms -# Warmup Iteration 7: 1.090 ops/ms -# Warmup Iteration 8: 1.092 ops/ms -# Warmup Iteration 9: 1.092 ops/ms -# Warmup Iteration 10: 1.081 ops/ms -Iteration 1: 1.093 ops/ms -Iteration 2: 1.093 ops/ms -Iteration 3: 1.093 ops/ms -Iteration 4: 1.093 ops/ms -Iteration 5: 1.093 ops/ms -Iteration 6: 1.093 ops/ms -Iteration 7: 1.094 ops/ms -Iteration 8: 1.095 ops/ms -Iteration 9: 1.094 ops/ms -Iteration 10: 1.094 ops/ms - -# Run progress: 69.52% complete, ETA 03:34:06 -# Fork: 4 of 5 -# Warmup Iteration 1: 0.714 ops/ms -# Warmup Iteration 2: 1.115 ops/ms -# Warmup Iteration 3: 1.127 ops/ms -# Warmup Iteration 4: 1.127 ops/ms -# Warmup Iteration 5: 1.127 ops/ms -# Warmup Iteration 6: 1.127 ops/ms -# Warmup Iteration 7: 1.127 ops/ms -# Warmup Iteration 8: 1.127 ops/ms -# Warmup Iteration 9: 1.127 ops/ms -# Warmup Iteration 10: 1.125 ops/ms -Iteration 1: 1.128 ops/ms -Iteration 2: 1.126 ops/ms -Iteration 3: 1.128 ops/ms -Iteration 4: 1.128 ops/ms -Iteration 5: 1.127 ops/ms -Iteration 6: 1.127 ops/ms -Iteration 7: 1.129 ops/ms -Iteration 8: 1.129 ops/ms -Iteration 9: 1.128 ops/ms -Iteration 10: 1.129 ops/ms - -# Run progress: 70.48% complete, ETA 03:27:25 -# Fork: 5 of 5 -# Warmup Iteration 1: 0.736 ops/ms -# Warmup Iteration 2: 1.119 ops/ms -# Warmup Iteration 3: 1.131 ops/ms -# Warmup Iteration 4: 1.131 ops/ms -# Warmup Iteration 5: 1.131 ops/ms -# Warmup Iteration 6: 1.131 ops/ms -# Warmup Iteration 7: 1.131 ops/ms -# Warmup Iteration 8: 1.132 ops/ms -# Warmup Iteration 9: 1.128 ops/ms -# Warmup Iteration 10: 1.118 ops/ms -Iteration 1: 1.131 ops/ms -Iteration 2: 1.131 ops/ms -Iteration 3: 1.131 ops/ms -Iteration 4: 1.132 ops/ms -Iteration 5: 1.131 ops/ms -Iteration 6: 1.131 ops/ms -Iteration 7: 1.132 ops/ms -Iteration 8: 1.133 ops/ms -Iteration 9: 1.132 ops/ms -Iteration 10: 1.132 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.beanArrayDeser": - 1.117 ±(99.9%) 0.009 ops/ms [Average] - (min, avg, max) = (1.093, 1.117, 1.138), stdev = 0.018 - CI (99.9%): [1.108, 1.127] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.beanArraySer - -# Run progress: 71.43% complete, ETA 03:20:43 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.314 ops/ms -# Warmup Iteration 2: 2.039 ops/ms -# Warmup Iteration 3: 2.046 ops/ms -# Warmup Iteration 4: 2.047 ops/ms -# Warmup Iteration 5: 2.043 ops/ms -# Warmup Iteration 6: 2.045 ops/ms -# Warmup Iteration 7: 2.047 ops/ms -# Warmup Iteration 8: 2.046 ops/ms -# Warmup Iteration 9: 2.048 ops/ms -# Warmup Iteration 10: 2.047 ops/ms -Iteration 1: 2.048 ops/ms -Iteration 2: 2.047 ops/ms -Iteration 3: 2.047 ops/ms -Iteration 4: 2.047 ops/ms -Iteration 5: 2.047 ops/ms -Iteration 6: 2.047 ops/ms -Iteration 7: 2.047 ops/ms -Iteration 8: 2.047 ops/ms -Iteration 9: 2.047 ops/ms -Iteration 10: 2.048 ops/ms - -# Run progress: 72.38% complete, ETA 03:14:02 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.356 ops/ms -# Warmup Iteration 2: 2.110 ops/ms -# Warmup Iteration 3: 2.119 ops/ms -# Warmup Iteration 4: 2.119 ops/ms -# Warmup Iteration 5: 2.121 ops/ms -# Warmup Iteration 6: 2.111 ops/ms -# Warmup Iteration 7: 2.115 ops/ms -# Warmup Iteration 8: 2.117 ops/ms -# Warmup Iteration 9: 2.116 ops/ms -# Warmup Iteration 10: 2.116 ops/ms -Iteration 1: 2.115 ops/ms -Iteration 2: 2.116 ops/ms -Iteration 3: 2.117 ops/ms -Iteration 4: 2.116 ops/ms -Iteration 5: 2.116 ops/ms -Iteration 6: 2.117 ops/ms -Iteration 7: 2.113 ops/ms -Iteration 8: 2.113 ops/ms -Iteration 9: 2.112 ops/ms -Iteration 10: 2.113 ops/ms - -# Run progress: 73.33% complete, ETA 03:07:20 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.346 ops/ms -# Warmup Iteration 2: 2.061 ops/ms -# Warmup Iteration 3: 2.077 ops/ms -# Warmup Iteration 4: 2.075 ops/ms -# Warmup Iteration 5: 2.076 ops/ms -# Warmup Iteration 6: 2.076 ops/ms -# Warmup Iteration 7: 2.079 ops/ms -# Warmup Iteration 8: 2.078 ops/ms -# Warmup Iteration 9: 2.073 ops/ms -# Warmup Iteration 10: 2.079 ops/ms -Iteration 1: 2.079 ops/ms -Iteration 2: 2.078 ops/ms -Iteration 3: 2.079 ops/ms -Iteration 4: 2.080 ops/ms -Iteration 5: 2.079 ops/ms -Iteration 6: 2.078 ops/ms -Iteration 7: 2.070 ops/ms -Iteration 8: 2.070 ops/ms -Iteration 9: 2.070 ops/ms -Iteration 10: 2.069 ops/ms - -# Run progress: 74.29% complete, ETA 03:00:39 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.354 ops/ms -# Warmup Iteration 2: 2.093 ops/ms -# Warmup Iteration 3: 2.096 ops/ms -# Warmup Iteration 4: 2.100 ops/ms -# Warmup Iteration 5: 2.100 ops/ms -# Warmup Iteration 6: 2.098 ops/ms -# Warmup Iteration 7: 2.098 ops/ms -# Warmup Iteration 8: 2.100 ops/ms -# Warmup Iteration 9: 2.099 ops/ms -# Warmup Iteration 10: 2.098 ops/ms -Iteration 1: 2.100 ops/ms -Iteration 2: 2.095 ops/ms -Iteration 3: 2.100 ops/ms -Iteration 4: 2.099 ops/ms -Iteration 5: 2.099 ops/ms -Iteration 6: 2.100 ops/ms -Iteration 7: 2.099 ops/ms -Iteration 8: 2.099 ops/ms -Iteration 9: 2.099 ops/ms -Iteration 10: 2.099 ops/ms - -# Run progress: 75.24% complete, ETA 02:53:57 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.386 ops/ms -# Warmup Iteration 2: 2.101 ops/ms -# Warmup Iteration 3: 2.113 ops/ms -# Warmup Iteration 4: 2.117 ops/ms -# Warmup Iteration 5: 2.119 ops/ms -# Warmup Iteration 6: 2.107 ops/ms -# Warmup Iteration 7: 2.118 ops/ms -# Warmup Iteration 8: 2.116 ops/ms -# Warmup Iteration 9: 2.117 ops/ms -# Warmup Iteration 10: 2.117 ops/ms -Iteration 1: 2.117 ops/ms -Iteration 2: 2.118 ops/ms -Iteration 3: 2.117 ops/ms -Iteration 4: 2.118 ops/ms -Iteration 5: 2.118 ops/ms -Iteration 6: 2.118 ops/ms -Iteration 7: 2.121 ops/ms -Iteration 8: 2.121 ops/ms -Iteration 9: 2.121 ops/ms -Iteration 10: 2.121 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.beanArraySer": - 2.091 ±(99.9%) 0.013 ops/ms [Average] - (min, avg, max) = (2.047, 2.091, 2.121), stdev = 0.027 - CI (99.9%): [2.078, 2.104] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.classicBeanItemDeser - -# Run progress: 76.19% complete, ETA 02:47:16 -# Fork: 1 of 5 -# Warmup Iteration 1: 0.686 ops/ms -# Warmup Iteration 2: 1.065 ops/ms -# Warmup Iteration 3: 1.080 ops/ms -# Warmup Iteration 4: 1.080 ops/ms -# Warmup Iteration 5: 1.080 ops/ms -# Warmup Iteration 6: 1.080 ops/ms -# Warmup Iteration 7: 1.079 ops/ms -# Warmup Iteration 8: 1.081 ops/ms -# Warmup Iteration 9: 1.081 ops/ms -# Warmup Iteration 10: 1.077 ops/ms -Iteration 1: 1.082 ops/ms -Iteration 2: 1.081 ops/ms -Iteration 3: 1.082 ops/ms -Iteration 4: 1.082 ops/ms -Iteration 5: 1.082 ops/ms -Iteration 6: 1.081 ops/ms -Iteration 7: 1.083 ops/ms -Iteration 8: 1.083 ops/ms -Iteration 9: 1.083 ops/ms -Iteration 10: 1.083 ops/ms - -# Run progress: 77.14% complete, ETA 02:40:34 -# Fork: 2 of 5 -# Warmup Iteration 1: 0.703 ops/ms -# Warmup Iteration 2: 1.083 ops/ms -# Warmup Iteration 3: 1.096 ops/ms -# Warmup Iteration 4: 1.095 ops/ms -# Warmup Iteration 5: 1.096 ops/ms -# Warmup Iteration 6: 1.094 ops/ms -# Warmup Iteration 7: 1.090 ops/ms -# Warmup Iteration 8: 1.095 ops/ms -# Warmup Iteration 9: 1.096 ops/ms -# Warmup Iteration 10: 1.090 ops/ms -Iteration 1: 1.096 ops/ms -Iteration 2: 1.096 ops/ms -Iteration 3: 1.097 ops/ms -Iteration 4: 1.096 ops/ms -Iteration 5: 1.096 ops/ms -Iteration 6: 1.096 ops/ms -Iteration 7: 1.095 ops/ms -Iteration 8: 1.096 ops/ms -Iteration 9: 1.095 ops/ms -Iteration 10: 1.095 ops/ms - -# Run progress: 78.10% complete, ETA 02:33:53 -# Fork: 3 of 5 -# Warmup Iteration 1: 0.695 ops/ms -# Warmup Iteration 2: 1.076 ops/ms -# Warmup Iteration 3: 1.089 ops/ms -# Warmup Iteration 4: 1.089 ops/ms -# Warmup Iteration 5: 1.089 ops/ms -# Warmup Iteration 6: 1.088 ops/ms -# Warmup Iteration 7: 1.086 ops/ms -# Warmup Iteration 8: 1.088 ops/ms -# Warmup Iteration 9: 1.089 ops/ms -# Warmup Iteration 10: 1.084 ops/ms -Iteration 1: 1.089 ops/ms -Iteration 2: 1.089 ops/ms -Iteration 3: 1.089 ops/ms -Iteration 4: 1.090 ops/ms -Iteration 5: 1.089 ops/ms -Iteration 6: 1.089 ops/ms -Iteration 7: 1.090 ops/ms -Iteration 8: 1.090 ops/ms -Iteration 9: 1.089 ops/ms -Iteration 10: 1.090 ops/ms - -# Run progress: 79.05% complete, ETA 02:27:11 -# Fork: 4 of 5 -# Warmup Iteration 1: 0.676 ops/ms -# Warmup Iteration 2: 1.035 ops/ms -# Warmup Iteration 3: 1.050 ops/ms -# Warmup Iteration 4: 1.050 ops/ms -# Warmup Iteration 5: 1.050 ops/ms -# Warmup Iteration 6: 1.051 ops/ms -# Warmup Iteration 7: 1.052 ops/ms -# Warmup Iteration 8: 1.056 ops/ms -# Warmup Iteration 9: 1.058 ops/ms -# Warmup Iteration 10: 1.057 ops/ms -Iteration 1: 1.055 ops/ms -Iteration 2: 1.055 ops/ms -Iteration 3: 1.057 ops/ms -Iteration 4: 1.058 ops/ms -Iteration 5: 1.057 ops/ms -Iteration 6: 1.057 ops/ms -Iteration 7: 1.063 ops/ms -Iteration 8: 1.063 ops/ms -Iteration 9: 1.064 ops/ms -Iteration 10: 1.061 ops/ms - -# Run progress: 80.00% complete, ETA 02:20:30 -# Fork: 5 of 5 -# Warmup Iteration 1: 0.706 ops/ms -# Warmup Iteration 2: 1.073 ops/ms -# Warmup Iteration 3: 1.081 ops/ms -# Warmup Iteration 4: 1.082 ops/ms -# Warmup Iteration 5: 1.082 ops/ms -# Warmup Iteration 6: 1.082 ops/ms -# Warmup Iteration 7: 1.076 ops/ms -# Warmup Iteration 8: 1.082 ops/ms -# Warmup Iteration 9: 1.081 ops/ms -# Warmup Iteration 10: 1.076 ops/ms -Iteration 1: 1.083 ops/ms -Iteration 2: 1.082 ops/ms -Iteration 3: 1.083 ops/ms -Iteration 4: 1.083 ops/ms -Iteration 5: 1.083 ops/ms -Iteration 6: 1.083 ops/ms -Iteration 7: 1.082 ops/ms -Iteration 8: 1.082 ops/ms -Iteration 9: 1.082 ops/ms -Iteration 10: 1.082 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.classicBeanItemDeser": - 1.082 ±(99.9%) 0.006 ops/ms [Average] - (min, avg, max) = (1.055, 1.082, 1.097), stdev = 0.013 - CI (99.9%): [1.076, 1.088] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.classicBeanItemSer - -# Run progress: 80.95% complete, ETA 02:13:48 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.359 ops/ms -# Warmup Iteration 2: 2.022 ops/ms -# Warmup Iteration 3: 2.045 ops/ms -# Warmup Iteration 4: 2.047 ops/ms -# Warmup Iteration 5: 2.048 ops/ms -# Warmup Iteration 6: 2.034 ops/ms -# Warmup Iteration 7: 2.048 ops/ms -# Warmup Iteration 8: 2.050 ops/ms -# Warmup Iteration 9: 2.049 ops/ms -# Warmup Iteration 10: 2.049 ops/ms -Iteration 1: 2.049 ops/ms -Iteration 2: 2.048 ops/ms -Iteration 3: 2.049 ops/ms -Iteration 4: 2.047 ops/ms -Iteration 5: 2.047 ops/ms -Iteration 6: 2.048 ops/ms -Iteration 7: 2.038 ops/ms -Iteration 8: 2.039 ops/ms -Iteration 9: 2.038 ops/ms -Iteration 10: 2.039 ops/ms - -# Run progress: 81.90% complete, ETA 02:07:07 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.362 ops/ms -# Warmup Iteration 2: 1.974 ops/ms -# Warmup Iteration 3: 1.982 ops/ms -# Warmup Iteration 4: 1.979 ops/ms -# Warmup Iteration 5: 1.983 ops/ms -# Warmup Iteration 6: 1.976 ops/ms -# Warmup Iteration 7: 1.982 ops/ms -# Warmup Iteration 8: 1.978 ops/ms -# Warmup Iteration 9: 1.985 ops/ms -# Warmup Iteration 10: 1.984 ops/ms -Iteration 1: 1.984 ops/ms -Iteration 2: 1.984 ops/ms -Iteration 3: 1.984 ops/ms -Iteration 4: 1.985 ops/ms -Iteration 5: 1.986 ops/ms -Iteration 6: 1.983 ops/ms -Iteration 7: 2.003 ops/ms -Iteration 8: 2.004 ops/ms -Iteration 9: 2.003 ops/ms -Iteration 10: 2.003 ops/ms - -# Run progress: 82.86% complete, ETA 02:00:25 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.376 ops/ms -# Warmup Iteration 2: 2.014 ops/ms -# Warmup Iteration 3: 2.035 ops/ms -# Warmup Iteration 4: 2.033 ops/ms -# Warmup Iteration 5: 2.036 ops/ms -# Warmup Iteration 6: 2.034 ops/ms -# Warmup Iteration 7: 2.039 ops/ms -# Warmup Iteration 8: 2.039 ops/ms -# Warmup Iteration 9: 2.038 ops/ms -# Warmup Iteration 10: 2.038 ops/ms -Iteration 1: 2.039 ops/ms -Iteration 2: 2.035 ops/ms -Iteration 3: 2.039 ops/ms -Iteration 4: 2.039 ops/ms -Iteration 5: 2.039 ops/ms -Iteration 6: 2.039 ops/ms -Iteration 7: 2.035 ops/ms -Iteration 8: 2.035 ops/ms -Iteration 9: 2.035 ops/ms -Iteration 10: 2.035 ops/ms - -# Run progress: 83.81% complete, ETA 01:53:44 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.401 ops/ms -# Warmup Iteration 2: 1.948 ops/ms -# Warmup Iteration 3: 1.961 ops/ms -# Warmup Iteration 4: 1.958 ops/ms -# Warmup Iteration 5: 1.961 ops/ms -# Warmup Iteration 6: 1.949 ops/ms -# Warmup Iteration 7: 1.959 ops/ms -# Warmup Iteration 8: 1.960 ops/ms -# Warmup Iteration 9: 1.960 ops/ms -# Warmup Iteration 10: 1.961 ops/ms -Iteration 1: 1.960 ops/ms -Iteration 2: 1.960 ops/ms -Iteration 3: 1.959 ops/ms -Iteration 4: 1.961 ops/ms -Iteration 5: 1.960 ops/ms -Iteration 6: 1.960 ops/ms -Iteration 7: 1.955 ops/ms -Iteration 8: 1.954 ops/ms -Iteration 9: 1.954 ops/ms -Iteration 10: 1.954 ops/ms - -# Run progress: 84.76% complete, ETA 01:47:02 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.404 ops/ms -# Warmup Iteration 2: 2.037 ops/ms -# Warmup Iteration 3: 2.045 ops/ms -# Warmup Iteration 4: 2.049 ops/ms -# Warmup Iteration 5: 2.050 ops/ms -# Warmup Iteration 6: 2.053 ops/ms -# Warmup Iteration 7: 2.056 ops/ms -# Warmup Iteration 8: 2.058 ops/ms -# Warmup Iteration 9: 2.058 ops/ms -# Warmup Iteration 10: 2.058 ops/ms -Iteration 1: 2.058 ops/ms -Iteration 2: 2.058 ops/ms -Iteration 3: 2.058 ops/ms -Iteration 4: 2.059 ops/ms -Iteration 5: 2.058 ops/ms -Iteration 6: 2.058 ops/ms -Iteration 7: 2.053 ops/ms -Iteration 8: 2.054 ops/ms -Iteration 9: 2.053 ops/ms -Iteration 10: 2.054 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.classicBeanItemSer": - 2.017 ±(99.9%) 0.019 ops/ms [Average] - (min, avg, max) = (1.954, 2.017, 2.059), stdev = 0.038 - CI (99.9%): [1.999, 2.036] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.classicMediaItemDeser - -# Run progress: 85.71% complete, ETA 01:40:21 -# Fork: 1 of 5 -# Warmup Iteration 1: 194.252 ops/ms -# Warmup Iteration 2: 339.623 ops/ms -# Warmup Iteration 3: 341.565 ops/ms -# Warmup Iteration 4: 341.914 ops/ms -# Warmup Iteration 5: 341.110 ops/ms -# Warmup Iteration 6: 341.756 ops/ms -# Warmup Iteration 7: 341.894 ops/ms -# Warmup Iteration 8: 341.881 ops/ms -# Warmup Iteration 9: 341.850 ops/ms -# Warmup Iteration 10: 341.508 ops/ms -Iteration 1: 341.821 ops/ms -Iteration 2: 341.460 ops/ms -Iteration 3: 341.993 ops/ms -Iteration 4: 341.772 ops/ms -Iteration 5: 341.898 ops/ms -Iteration 6: 341.823 ops/ms -Iteration 7: 340.236 ops/ms -Iteration 8: 340.162 ops/ms -Iteration 9: 340.074 ops/ms -Iteration 10: 339.919 ops/ms - -# Run progress: 86.67% complete, ETA 01:33:39 -# Fork: 2 of 5 -# Warmup Iteration 1: 196.710 ops/ms -# Warmup Iteration 2: 324.260 ops/ms -# Warmup Iteration 3: 326.333 ops/ms -# Warmup Iteration 4: 326.736 ops/ms -# Warmup Iteration 5: 326.429 ops/ms -# Warmup Iteration 6: 326.540 ops/ms -# Warmup Iteration 7: 326.369 ops/ms -# Warmup Iteration 8: 326.260 ops/ms -# Warmup Iteration 9: 327.050 ops/ms -# Warmup Iteration 10: 326.552 ops/ms -Iteration 1: 326.366 ops/ms -Iteration 2: 326.611 ops/ms -Iteration 3: 326.563 ops/ms -Iteration 4: 326.580 ops/ms -Iteration 5: 326.353 ops/ms -Iteration 6: 326.367 ops/ms -Iteration 7: 329.875 ops/ms -Iteration 8: 329.460 ops/ms -Iteration 9: 329.515 ops/ms -Iteration 10: 330.032 ops/ms - -# Run progress: 87.62% complete, ETA 01:26:58 -# Fork: 3 of 5 -# Warmup Iteration 1: 208.365 ops/ms -# Warmup Iteration 2: 343.539 ops/ms -# Warmup Iteration 3: 346.540 ops/ms -# Warmup Iteration 4: 347.175 ops/ms -# Warmup Iteration 5: 347.156 ops/ms -# Warmup Iteration 6: 346.966 ops/ms -# Warmup Iteration 7: 346.747 ops/ms -# Warmup Iteration 8: 346.482 ops/ms -# Warmup Iteration 9: 346.561 ops/ms -# Warmup Iteration 10: 347.158 ops/ms -Iteration 1: 347.132 ops/ms -Iteration 2: 346.373 ops/ms -Iteration 3: 346.811 ops/ms -Iteration 4: 347.031 ops/ms -Iteration 5: 347.009 ops/ms -Iteration 6: 347.230 ops/ms -Iteration 7: 346.721 ops/ms -Iteration 8: 346.827 ops/ms -Iteration 9: 346.732 ops/ms -Iteration 10: 346.769 ops/ms - -# Run progress: 88.57% complete, ETA 01:20:17 -# Fork: 4 of 5 -# Warmup Iteration 1: 178.961 ops/ms -# Warmup Iteration 2: 320.222 ops/ms -# Warmup Iteration 3: 323.938 ops/ms -# Warmup Iteration 4: 323.920 ops/ms -# Warmup Iteration 5: 323.606 ops/ms -# Warmup Iteration 6: 324.084 ops/ms -# Warmup Iteration 7: 323.640 ops/ms -# Warmup Iteration 8: 323.563 ops/ms -# Warmup Iteration 9: 323.827 ops/ms -# Warmup Iteration 10: 323.461 ops/ms -Iteration 1: 323.801 ops/ms -Iteration 2: 323.579 ops/ms -Iteration 3: 323.628 ops/ms -Iteration 4: 323.750 ops/ms -Iteration 5: 323.807 ops/ms -Iteration 6: 323.110 ops/ms -Iteration 7: 322.514 ops/ms -Iteration 8: 322.010 ops/ms -Iteration 9: 322.266 ops/ms -Iteration 10: 322.123 ops/ms - -# Run progress: 89.52% complete, ETA 01:13:35 -# Fork: 5 of 5 -# Warmup Iteration 1: 174.766 ops/ms -# Warmup Iteration 2: 317.896 ops/ms -# Warmup Iteration 3: 325.847 ops/ms -# Warmup Iteration 4: 325.905 ops/ms -# Warmup Iteration 5: 326.325 ops/ms -# Warmup Iteration 6: 325.535 ops/ms -# Warmup Iteration 7: 326.018 ops/ms -# Warmup Iteration 8: 325.583 ops/ms -# Warmup Iteration 9: 325.625 ops/ms -# Warmup Iteration 10: 326.015 ops/ms -Iteration 1: 325.725 ops/ms -Iteration 2: 325.704 ops/ms -Iteration 3: 325.441 ops/ms -Iteration 4: 325.553 ops/ms -Iteration 5: 325.609 ops/ms -Iteration 6: 325.516 ops/ms -Iteration 7: 327.403 ops/ms -Iteration 8: 327.071 ops/ms -Iteration 9: 326.468 ops/ms -Iteration 10: 327.007 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.classicMediaItemDeser": - 332.992 ±(99.9%) 4.667 ops/ms [Average] - (min, avg, max) = (322.010, 332.992, 347.230), stdev = 9.428 - CI (99.9%): [328.325, 337.659] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.classicMediaItemSer - -# Run progress: 90.48% complete, ETA 01:06:54 -# Fork: 1 of 5 -# Warmup Iteration 1: 377.117 ops/ms -# Warmup Iteration 2: 602.697 ops/ms -# Warmup Iteration 3: 608.613 ops/ms -# Warmup Iteration 4: 608.291 ops/ms -# Warmup Iteration 5: 608.434 ops/ms -# Warmup Iteration 6: 608.059 ops/ms -# Warmup Iteration 7: 606.685 ops/ms -# Warmup Iteration 8: 547.789 ops/ms -# Warmup Iteration 9: 608.443 ops/ms -# Warmup Iteration 10: 608.273 ops/ms -Iteration 1: 608.259 ops/ms -Iteration 2: 608.075 ops/ms -Iteration 3: 608.447 ops/ms -Iteration 4: 608.454 ops/ms -Iteration 5: 607.956 ops/ms -Iteration 6: 608.272 ops/ms -Iteration 7: 616.531 ops/ms -Iteration 8: 616.309 ops/ms -Iteration 9: 614.612 ops/ms -Iteration 10: 615.975 ops/ms - -# Run progress: 91.43% complete, ETA 01:00:12 -# Fork: 2 of 5 -# Warmup Iteration 1: 385.510 ops/ms -# Warmup Iteration 2: 617.074 ops/ms -# Warmup Iteration 3: 619.024 ops/ms -# Warmup Iteration 4: 619.390 ops/ms -# Warmup Iteration 5: 619.330 ops/ms -# Warmup Iteration 6: 619.985 ops/ms -# Warmup Iteration 7: 619.377 ops/ms -# Warmup Iteration 8: 619.527 ops/ms -# Warmup Iteration 9: 619.200 ops/ms -# Warmup Iteration 10: 619.515 ops/ms -Iteration 1: 619.146 ops/ms -Iteration 2: 619.551 ops/ms -Iteration 3: 619.547 ops/ms -Iteration 4: 618.810 ops/ms -Iteration 5: 619.908 ops/ms -Iteration 6: 619.170 ops/ms -Iteration 7: 624.808 ops/ms -Iteration 8: 625.062 ops/ms -Iteration 9: 625.455 ops/ms -Iteration 10: 624.809 ops/ms - -# Run progress: 92.38% complete, ETA 00:53:31 -# Fork: 3 of 5 -# Warmup Iteration 1: 381.887 ops/ms -# Warmup Iteration 2: 605.476 ops/ms -# Warmup Iteration 3: 608.069 ops/ms -# Warmup Iteration 4: 607.789 ops/ms -# Warmup Iteration 5: 608.153 ops/ms -# Warmup Iteration 6: 607.799 ops/ms -# Warmup Iteration 7: 608.147 ops/ms -# Warmup Iteration 8: 607.683 ops/ms -# Warmup Iteration 9: 608.201 ops/ms -# Warmup Iteration 10: 607.741 ops/ms -Iteration 1: 608.236 ops/ms -Iteration 2: 608.008 ops/ms -Iteration 3: 608.467 ops/ms -Iteration 4: 607.727 ops/ms -Iteration 5: 608.357 ops/ms -Iteration 6: 608.483 ops/ms -Iteration 7: 603.768 ops/ms -Iteration 8: 603.438 ops/ms -Iteration 9: 603.408 ops/ms -Iteration 10: 603.482 ops/ms - -# Run progress: 93.33% complete, ETA 00:46:49 -# Fork: 4 of 5 -# Warmup Iteration 1: 401.221 ops/ms -# Warmup Iteration 2: 611.520 ops/ms -# Warmup Iteration 3: 614.008 ops/ms -# Warmup Iteration 4: 614.989 ops/ms -# Warmup Iteration 5: 613.836 ops/ms -# Warmup Iteration 6: 613.867 ops/ms -# Warmup Iteration 7: 614.326 ops/ms -# Warmup Iteration 8: 613.515 ops/ms -# Warmup Iteration 9: 613.809 ops/ms -# Warmup Iteration 10: 614.965 ops/ms -Iteration 1: 613.396 ops/ms -Iteration 2: 614.386 ops/ms -Iteration 3: 614.504 ops/ms -Iteration 4: 613.974 ops/ms -Iteration 5: 614.332 ops/ms -Iteration 6: 614.035 ops/ms -Iteration 7: 609.787 ops/ms -Iteration 8: 609.511 ops/ms -Iteration 9: 609.617 ops/ms -Iteration 10: 609.087 ops/ms - -# Run progress: 94.29% complete, ETA 00:40:08 -# Fork: 5 of 5 -# Warmup Iteration 1: 385.131 ops/ms -# Warmup Iteration 2: 600.456 ops/ms -# Warmup Iteration 3: 606.621 ops/ms -# Warmup Iteration 4: 606.344 ops/ms -# Warmup Iteration 5: 606.218 ops/ms -# Warmup Iteration 6: 606.414 ops/ms -# Warmup Iteration 7: 606.653 ops/ms -# Warmup Iteration 8: 606.817 ops/ms -# Warmup Iteration 9: 606.638 ops/ms -# Warmup Iteration 10: 606.544 ops/ms -Iteration 1: 607.273 ops/ms -Iteration 2: 607.348 ops/ms -Iteration 3: 606.763 ops/ms -Iteration 4: 607.447 ops/ms -Iteration 5: 607.361 ops/ms -Iteration 6: 607.134 ops/ms -Iteration 7: 602.459 ops/ms -Iteration 8: 603.052 ops/ms -Iteration 9: 602.958 ops/ms -Iteration 10: 603.021 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.classicMediaItemSer": - 611.400 ±(99.9%) 3.187 ops/ms [Average] - (min, avg, max) = (602.459, 611.400, 625.455), stdev = 6.438 - CI (99.9%): [608.212, 614.587] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.constructorArrayDeser - -# Run progress: 95.24% complete, ETA 00:33:27 -# Fork: 1 of 5 -# Warmup Iteration 1: 0.661 ops/ms -# Warmup Iteration 2: 1.054 ops/ms -# Warmup Iteration 3: 1.077 ops/ms -# Warmup Iteration 4: 1.079 ops/ms -# Warmup Iteration 5: 1.078 ops/ms -# Warmup Iteration 6: 1.077 ops/ms -# Warmup Iteration 7: 1.079 ops/ms -# Warmup Iteration 8: 1.077 ops/ms -# Warmup Iteration 9: 1.079 ops/ms -# Warmup Iteration 10: 1.077 ops/ms -Iteration 1: 1.080 ops/ms -Iteration 2: 1.080 ops/ms -Iteration 3: 1.080 ops/ms -Iteration 4: 1.080 ops/ms -Iteration 5: 1.080 ops/ms -Iteration 6: 1.080 ops/ms -Iteration 7: 1.080 ops/ms -Iteration 8: 1.080 ops/ms -Iteration 9: 1.080 ops/ms -Iteration 10: 1.080 ops/ms - -# Run progress: 96.19% complete, ETA 00:26:45 -# Fork: 2 of 5 -# Warmup Iteration 1: 0.650 ops/ms -# Warmup Iteration 2: 1.048 ops/ms -# Warmup Iteration 3: 1.068 ops/ms -# Warmup Iteration 4: 1.067 ops/ms -# Warmup Iteration 5: 1.067 ops/ms -# Warmup Iteration 6: 1.067 ops/ms -# Warmup Iteration 7: 1.067 ops/ms -# Warmup Iteration 8: 1.067 ops/ms -# Warmup Iteration 9: 1.066 ops/ms -# Warmup Iteration 10: 1.065 ops/ms -Iteration 1: 1.067 ops/ms -Iteration 2: 1.067 ops/ms -Iteration 3: 1.067 ops/ms -Iteration 4: 1.067 ops/ms -Iteration 5: 1.067 ops/ms -Iteration 6: 1.067 ops/ms -Iteration 7: 1.066 ops/ms -Iteration 8: 1.067 ops/ms -Iteration 9: 1.066 ops/ms -Iteration 10: 1.067 ops/ms - -# Run progress: 97.14% complete, ETA 00:20:04 -# Fork: 3 of 5 -# Warmup Iteration 1: 0.613 ops/ms -# Warmup Iteration 2: 1.027 ops/ms -# Warmup Iteration 3: 1.056 ops/ms -# Warmup Iteration 4: 1.056 ops/ms -# Warmup Iteration 5: 1.056 ops/ms -# Warmup Iteration 6: 1.056 ops/ms -# Warmup Iteration 7: 1.057 ops/ms -# Warmup Iteration 8: 1.057 ops/ms -# Warmup Iteration 9: 1.056 ops/ms -# Warmup Iteration 10: 1.057 ops/ms -Iteration 1: 1.055 ops/ms -Iteration 2: 1.056 ops/ms -Iteration 3: 1.056 ops/ms -Iteration 4: 1.056 ops/ms -Iteration 5: 1.055 ops/ms -Iteration 6: 1.057 ops/ms -Iteration 7: 1.057 ops/ms -Iteration 8: 1.057 ops/ms -Iteration 9: 1.057 ops/ms -Iteration 10: 1.057 ops/ms - -# Run progress: 98.10% complete, ETA 00:13:22 -# Fork: 4 of 5 -# Warmup Iteration 1: 0.639 ops/ms -# Warmup Iteration 2: 1.022 ops/ms -# Warmup Iteration 3: 1.043 ops/ms -# Warmup Iteration 4: 1.042 ops/ms -# Warmup Iteration 5: 1.043 ops/ms -# Warmup Iteration 6: 1.042 ops/ms -# Warmup Iteration 7: 1.042 ops/ms -# Warmup Iteration 8: 1.042 ops/ms -# Warmup Iteration 9: 1.042 ops/ms -# Warmup Iteration 10: 1.042 ops/ms -Iteration 1: 1.041 ops/ms -Iteration 2: 1.043 ops/ms -Iteration 3: 1.043 ops/ms -Iteration 4: 1.043 ops/ms -Iteration 5: 1.043 ops/ms -Iteration 6: 1.042 ops/ms -Iteration 7: 1.041 ops/ms -Iteration 8: 1.041 ops/ms -Iteration 9: 1.041 ops/ms -Iteration 10: 1.042 ops/ms - -# Run progress: 99.05% complete, ETA 00:06:41 -# Fork: 5 of 5 -# Warmup Iteration 1: 0.672 ops/ms -# Warmup Iteration 2: 1.063 ops/ms -# Warmup Iteration 3: 1.072 ops/ms -# Warmup Iteration 4: 1.072 ops/ms -# Warmup Iteration 5: 1.071 ops/ms -# Warmup Iteration 6: 1.071 ops/ms -# Warmup Iteration 7: 1.072 ops/ms -# Warmup Iteration 8: 1.067 ops/ms -# Warmup Iteration 9: 1.071 ops/ms -# Warmup Iteration 10: 1.067 ops/ms -Iteration 1: 1.072 ops/ms -Iteration 2: 1.069 ops/ms -Iteration 3: 1.071 ops/ms -Iteration 4: 1.071 ops/ms -Iteration 5: 1.072 ops/ms -Iteration 6: 1.071 ops/ms -Iteration 7: 1.073 ops/ms -Iteration 8: 1.073 ops/ms -Iteration 9: 1.072 ops/ms -Iteration 10: 1.072 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.constructorArrayDeser": - 1.063 ±(99.9%) 0.007 ops/ms [Average] - (min, avg, max) = (1.041, 1.063, 1.080), stdev = 0.013 - CI (99.9%): [1.057, 1.070] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: -# Measurement: 1 iterations, single-shot each -# Timeout: 10 min per iteration -# Threads: 1 thread -# Benchmark mode: Single shot invocation time -# Benchmark: com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.afterburner - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 1 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -379.078 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 2 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -371.217 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 3 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -396.962 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 4 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -301.322 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 5 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -426.411 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 6 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -376.001 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 7 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -333.731 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 8 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -402.663 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 9 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -320.619 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 10 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -388.165 ms/op - - -Result "com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.afterburner": - N = 10 - mean = 369.617 ±(99.9%) 59.404 ms/op - - Histogram, ms/op: - [300.000, 312.500) = 1 - [312.500, 325.000) = 1 - [325.000, 337.500) = 1 - [337.500, 350.000) = 0 - [350.000, 362.500) = 0 - [362.500, 375.000) = 1 - [375.000, 387.500) = 2 - [387.500, 400.000) = 2 - [400.000, 412.500) = 1 - [412.500, 425.000) = 0 - [425.000, 437.500) = 1 - [437.500, 450.000) = 0 - [450.000, 462.500) = 0 - [462.500, 475.000) = 0 - [475.000, 487.500) = 0 - - Percentiles, ms/op: - p(0.0000) = 301.322 ms/op - p(50.0000) = 377.540 ms/op - p(90.0000) = 424.036 ms/op - p(95.0000) = 426.411 ms/op - p(99.0000) = 426.411 ms/op - p(99.9000) = 426.411 ms/op - p(99.9900) = 426.411 ms/op - p(99.9990) = 426.411 ms/op - p(99.9999) = 426.411 ms/op - p(100.0000) = 426.411 ms/op - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: -# Measurement: 1 iterations, single-shot each -# Timeout: 10 min per iteration -# Threads: 1 thread -# Benchmark mode: Single shot invocation time -# Benchmark: com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.blackbird - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 1 of 10 -Iteration 1: 332.060 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 2 of 10 -Iteration 1: 316.452 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 3 of 10 -Iteration 1: 422.261 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 4 of 10 -Iteration 1: 381.216 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 5 of 10 -Iteration 1: 302.745 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 6 of 10 -Iteration 1: 338.349 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 7 of 10 -Iteration 1: 339.388 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 8 of 10 -Iteration 1: 365.457 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 9 of 10 -Iteration 1: 375.537 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 10 of 10 -Iteration 1: 337.978 ms/op - - -Result "com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.blackbird": - N = 10 - mean = 351.144 ±(99.9%) 53.208 ms/op - - Histogram, ms/op: - [300.000, 312.500) = 1 - [312.500, 325.000) = 1 - [325.000, 337.500) = 1 - [337.500, 350.000) = 3 - [350.000, 362.500) = 0 - [362.500, 375.000) = 1 - [375.000, 387.500) = 2 - [387.500, 400.000) = 0 - [400.000, 412.500) = 0 - [412.500, 425.000) = 1 - [425.000, 437.500) = 0 - [437.500, 450.000) = 0 - [450.000, 462.500) = 0 - [462.500, 475.000) = 0 - [475.000, 487.500) = 0 - - Percentiles, ms/op: - p(0.0000) = 302.745 ms/op - p(50.0000) = 338.868 ms/op - p(90.0000) = 418.157 ms/op - p(95.0000) = 422.261 ms/op - p(99.0000) = 422.261 ms/op - p(99.9000) = 422.261 ms/op - p(99.9900) = 422.261 ms/op - p(99.9990) = 422.261 ms/op - p(99.9999) = 422.261 ms/op - p(100.0000) = 422.261 ms/op - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler --module-path=/home/steven/mp --upgrade-module-path=/home/steven/mp/compiler.jar:/home/steven/mp/compiler-management.jar -# Warmup: -# Measurement: 1 iterations, single-shot each -# Timeout: 10 min per iteration -# Threads: 1 thread -# Benchmark mode: Single shot invocation time -# Benchmark: com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.vanilla - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 1 of 10 -Iteration 1: 291.318 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 2 of 10 -Iteration 1: 326.087 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 3 of 10 -Iteration 1: 297.577 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 4 of 10 -Iteration 1: 320.014 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 5 of 10 -Iteration 1: 305.533 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 6 of 10 -Iteration 1: 340.941 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 7 of 10 -Iteration 1: 294.189 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 8 of 10 -Iteration 1: 258.291 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 9 of 10 -Iteration 1: 274.227 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 10 of 10 -Iteration 1: 280.447 ms/op - - -Result "com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.vanilla": - N = 10 - mean = 298.862 ±(99.9%) 37.968 ms/op - - Histogram, ms/op: - [250.000, 260.000) = 1 - [260.000, 270.000) = 0 - [270.000, 280.000) = 1 - [280.000, 290.000) = 1 - [290.000, 300.000) = 3 - [300.000, 310.000) = 1 - [310.000, 320.000) = 0 - [320.000, 330.000) = 2 - [330.000, 340.000) = 0 - - Percentiles, ms/op: - p(0.0000) = 258.291 ms/op - p(50.0000) = 295.883 ms/op - p(90.0000) = 339.456 ms/op - p(95.0000) = 340.941 ms/op - p(99.0000) = 340.941 ms/op - p(99.9000) = 340.941 ms/op - p(99.9900) = 340.941 ms/op - p(99.9990) = 340.941 ms/op - p(99.9999) = 340.941 ms/op - p(100.0000) = 340.941 ms/op - - -# Run complete. Total time: 11:43:06 - -REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on -why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial -experiments, perform baseline and negative tests that provide experimental control, make sure -the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. -Do not assume the numbers tell you what you want them to tell. - -Benchmark Mode Cnt Score Error Units -Afterburner.beanArrayDeser thrpt 50 1.314 ± 0.015 ops/ms -Afterburner.beanArraySer thrpt 50 2.293 ± 0.018 ops/ms -Afterburner.classicBeanItemDeser thrpt 50 1.515 ± 0.014 ops/ms -Afterburner.classicBeanItemSer thrpt 50 2.485 ± 0.043 ops/ms -Afterburner.classicMediaItemDeser thrpt 50 429.227 ± 2.092 ops/ms -Afterburner.classicMediaItemSer thrpt 50 676.551 ± 5.980 ops/ms -Afterburner.constructorArrayDeser thrpt 50 1.087 ± 0.004 ops/ms -Blackbird.beanArrayDeser thrpt 50 1.333 ± 0.014 ops/ms -Blackbird.beanArraySer thrpt 50 2.256 ± 0.015 ops/ms -Blackbird.classicBeanItemDeser thrpt 50 1.480 ± 0.020 ops/ms -Blackbird.classicBeanItemSer thrpt 50 2.202 ± 0.053 ops/ms -Blackbird.classicMediaItemDeser thrpt 50 430.031 ± 1.192 ops/ms -Blackbird.classicMediaItemSer thrpt 50 659.393 ± 2.591 ops/ms -Blackbird.constructorArrayDeser thrpt 50 1.092 ± 0.003 ops/ms -Vanilla.beanArrayDeser thrpt 50 1.117 ± 0.009 ops/ms -Vanilla.beanArraySer thrpt 50 2.091 ± 0.013 ops/ms -Vanilla.classicBeanItemDeser thrpt 50 1.082 ± 0.006 ops/ms -Vanilla.classicBeanItemSer thrpt 50 2.017 ± 0.019 ops/ms -Vanilla.classicMediaItemDeser thrpt 50 332.992 ± 4.667 ops/ms -Vanilla.classicMediaItemSer thrpt 50 611.400 ± 3.187 ops/ms -Vanilla.constructorArrayDeser thrpt 50 1.063 ± 0.007 ops/ms -StartupTimeBenchmark.afterburner ss 10 369.617 ± 59.404 ms/op -StartupTimeBenchmark.blackbird ss 10 351.144 ± 53.208 ms/op -StartupTimeBenchmark.vanilla ss 10 298.862 ± 37.968 ms/op diff --git a/blackbird/benchmarks/results/results_hs.txt b/blackbird/benchmarks/results/results_hs.txt deleted file mode 100644 index a6f5ba743..000000000 --- a/blackbird/benchmarks/results/results_hs.txt +++ /dev/null @@ -1,3310 +0,0 @@ -WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by org.openjdk.jmh.util.Utils (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to field java.io.PrintStream.charOut -WARNING: Please consider reporting this to the maintainers of org.openjdk.jmh.util.Utils -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.beanArrayDeser - -# Run progress: 0.00% complete, ETA 11:40:00 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.216 ops/ms -# Warmup Iteration 2: 1.279 ops/ms -# Warmup Iteration 3: 1.278 ops/ms -# Warmup Iteration 4: 1.279 ops/ms -# Warmup Iteration 5: 1.276 ops/ms -# Warmup Iteration 6: 1.281 ops/ms -# Warmup Iteration 7: 1.281 ops/ms -# Warmup Iteration 8: 1.279 ops/ms -# Warmup Iteration 9: 1.280 ops/ms -# Warmup Iteration 10: 1.280 ops/ms -Iteration 1: 1.280 ops/ms -Iteration 2: 1.280 ops/ms -Iteration 3: 1.280 ops/ms -Iteration 4: 1.280 ops/ms -Iteration 5: 1.280 ops/ms -Iteration 6: 1.280 ops/ms -Iteration 7: 1.280 ops/ms -Iteration 8: 1.280 ops/ms -Iteration 9: 1.280 ops/ms -Iteration 10: 1.280 ops/ms - -# Run progress: 0.95% complete, ETA 11:35:12 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.271 ops/ms -# Warmup Iteration 2: 1.323 ops/ms -# Warmup Iteration 3: 1.323 ops/ms -# Warmup Iteration 4: 1.323 ops/ms -# Warmup Iteration 5: 1.322 ops/ms -# Warmup Iteration 6: 1.324 ops/ms -# Warmup Iteration 7: 1.324 ops/ms -# Warmup Iteration 8: 1.323 ops/ms -# Warmup Iteration 9: 1.324 ops/ms -# Warmup Iteration 10: 1.323 ops/ms -Iteration 1: 1.324 ops/ms -Iteration 2: 1.324 ops/ms -Iteration 3: 1.324 ops/ms -Iteration 4: 1.324 ops/ms -Iteration 5: 1.324 ops/ms -Iteration 6: 1.323 ops/ms -Iteration 7: 1.324 ops/ms -Iteration 8: 1.324 ops/ms -Iteration 9: 1.324 ops/ms -Iteration 10: 1.324 ops/ms - -# Run progress: 1.90% complete, ETA 11:30:05 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.243 ops/ms -# Warmup Iteration 2: 1.309 ops/ms -# Warmup Iteration 3: 1.309 ops/ms -# Warmup Iteration 4: 1.309 ops/ms -# Warmup Iteration 5: 1.307 ops/ms -# Warmup Iteration 6: 1.310 ops/ms -# Warmup Iteration 7: 1.310 ops/ms -# Warmup Iteration 8: 1.309 ops/ms -# Warmup Iteration 9: 1.311 ops/ms -# Warmup Iteration 10: 1.310 ops/ms -Iteration 1: 1.309 ops/ms -Iteration 2: 1.310 ops/ms -Iteration 3: 1.310 ops/ms -Iteration 4: 1.310 ops/ms -Iteration 5: 1.310 ops/ms -Iteration 6: 1.309 ops/ms -Iteration 7: 1.310 ops/ms -Iteration 8: 1.310 ops/ms -Iteration 9: 1.310 ops/ms -Iteration 10: 1.310 ops/ms - -# Run progress: 2.86% complete, ETA 11:22:53 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.226 ops/ms -# Warmup Iteration 2: 1.278 ops/ms -# Warmup Iteration 3: 1.280 ops/ms -# Warmup Iteration 4: 1.279 ops/ms -# Warmup Iteration 5: 1.279 ops/ms -# Warmup Iteration 6: 1.280 ops/ms -# Warmup Iteration 7: 1.280 ops/ms -# Warmup Iteration 8: 1.280 ops/ms -# Warmup Iteration 9: 1.277 ops/ms -# Warmup Iteration 10: 1.279 ops/ms -Iteration 1: 1.280 ops/ms -Iteration 2: 1.279 ops/ms -Iteration 3: 1.279 ops/ms -Iteration 4: 1.279 ops/ms -Iteration 5: 1.279 ops/ms -Iteration 6: 1.280 ops/ms -Iteration 7: 1.280 ops/ms -Iteration 8: 1.280 ops/ms -Iteration 9: 1.279 ops/ms -Iteration 10: 1.280 ops/ms - -# Run progress: 3.81% complete, ETA 11:16:13 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.251 ops/ms -# Warmup Iteration 2: 1.301 ops/ms -# Warmup Iteration 3: 1.302 ops/ms -# Warmup Iteration 4: 1.301 ops/ms -# Warmup Iteration 5: 1.300 ops/ms -# Warmup Iteration 6: 1.301 ops/ms -# Warmup Iteration 7: 1.301 ops/ms -# Warmup Iteration 8: 1.300 ops/ms -# Warmup Iteration 9: 1.302 ops/ms -# Warmup Iteration 10: 1.302 ops/ms -Iteration 1: 1.302 ops/ms -Iteration 2: 1.302 ops/ms -Iteration 3: 1.302 ops/ms -Iteration 4: 1.302 ops/ms -Iteration 5: 1.302 ops/ms -Iteration 6: 1.302 ops/ms -Iteration 7: 1.300 ops/ms -Iteration 8: 1.302 ops/ms -Iteration 9: 1.301 ops/ms -Iteration 10: 1.302 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.beanArrayDeser": - 1.299 ±(99.9%) 0.009 ops/ms [Average] - (min, avg, max) = (1.279, 1.299, 1.324), stdev = 0.017 - CI (99.9%): [1.290, 1.308] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.beanArraySer - -# Run progress: 4.76% complete, ETA 11:09:18 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.089 ops/ms -# Warmup Iteration 2: 2.158 ops/ms -# Warmup Iteration 3: 2.161 ops/ms -# Warmup Iteration 4: 2.160 ops/ms -# Warmup Iteration 5: 2.158 ops/ms -# Warmup Iteration 6: 2.160 ops/ms -# Warmup Iteration 7: 2.159 ops/ms -# Warmup Iteration 8: 2.160 ops/ms -# Warmup Iteration 9: 2.162 ops/ms -# Warmup Iteration 10: 2.160 ops/ms -Iteration 1: 2.160 ops/ms -Iteration 2: 2.160 ops/ms -Iteration 3: 2.160 ops/ms -Iteration 4: 2.159 ops/ms -Iteration 5: 2.161 ops/ms -Iteration 6: 2.161 ops/ms -Iteration 7: 2.160 ops/ms -Iteration 8: 2.162 ops/ms -Iteration 9: 2.161 ops/ms -Iteration 10: 2.162 ops/ms - -# Run progress: 5.71% complete, ETA 11:02:43 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.147 ops/ms -# Warmup Iteration 2: 2.211 ops/ms -# Warmup Iteration 3: 2.213 ops/ms -# Warmup Iteration 4: 2.213 ops/ms -# Warmup Iteration 5: 2.213 ops/ms -# Warmup Iteration 6: 2.212 ops/ms -# Warmup Iteration 7: 2.213 ops/ms -# Warmup Iteration 8: 2.214 ops/ms -# Warmup Iteration 9: 2.212 ops/ms -# Warmup Iteration 10: 2.213 ops/ms -Iteration 1: 2.214 ops/ms -Iteration 2: 2.213 ops/ms -Iteration 3: 2.213 ops/ms -Iteration 4: 2.213 ops/ms -Iteration 5: 2.213 ops/ms -Iteration 6: 2.214 ops/ms -Iteration 7: 2.214 ops/ms -Iteration 8: 2.213 ops/ms -Iteration 9: 2.214 ops/ms -Iteration 10: 2.213 ops/ms - -# Run progress: 6.67% complete, ETA 10:55:56 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.147 ops/ms -# Warmup Iteration 2: 2.207 ops/ms -# Warmup Iteration 3: 2.207 ops/ms -# Warmup Iteration 4: 2.208 ops/ms -# Warmup Iteration 5: 2.207 ops/ms -# Warmup Iteration 6: 2.208 ops/ms -# Warmup Iteration 7: 2.207 ops/ms -# Warmup Iteration 8: 2.206 ops/ms -# Warmup Iteration 9: 2.208 ops/ms -# Warmup Iteration 10: 2.209 ops/ms -Iteration 1: 2.208 ops/ms -Iteration 2: 2.208 ops/ms -Iteration 3: 2.208 ops/ms -Iteration 4: 2.208 ops/ms -Iteration 5: 2.208 ops/ms -Iteration 6: 2.207 ops/ms -Iteration 7: 2.208 ops/ms -Iteration 8: 2.208 ops/ms -Iteration 9: 2.208 ops/ms -Iteration 10: 2.207 ops/ms - -# Run progress: 7.62% complete, ETA 10:49:11 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.111 ops/ms -# Warmup Iteration 2: 2.178 ops/ms -# Warmup Iteration 3: 2.177 ops/ms -# Warmup Iteration 4: 2.177 ops/ms -# Warmup Iteration 5: 2.177 ops/ms -# Warmup Iteration 6: 2.180 ops/ms -# Warmup Iteration 7: 2.177 ops/ms -# Warmup Iteration 8: 2.177 ops/ms -# Warmup Iteration 9: 2.178 ops/ms -# Warmup Iteration 10: 2.179 ops/ms -Iteration 1: 2.178 ops/ms -Iteration 2: 2.178 ops/ms -Iteration 3: 2.178 ops/ms -Iteration 4: 2.178 ops/ms -Iteration 5: 2.178 ops/ms -Iteration 6: 2.178 ops/ms -Iteration 7: 2.179 ops/ms -Iteration 8: 2.178 ops/ms -Iteration 9: 2.179 ops/ms -Iteration 10: 2.178 ops/ms - -# Run progress: 8.57% complete, ETA 10:42:24 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.145 ops/ms -# Warmup Iteration 2: 2.201 ops/ms -# Warmup Iteration 3: 2.204 ops/ms -# Warmup Iteration 4: 2.205 ops/ms -# Warmup Iteration 5: 2.204 ops/ms -# Warmup Iteration 6: 2.204 ops/ms -# Warmup Iteration 7: 2.205 ops/ms -# Warmup Iteration 8: 2.205 ops/ms -# Warmup Iteration 9: 2.204 ops/ms -# Warmup Iteration 10: 2.205 ops/ms -Iteration 1: 2.205 ops/ms -Iteration 2: 2.205 ops/ms -Iteration 3: 2.205 ops/ms -Iteration 4: 2.205 ops/ms -Iteration 5: 2.205 ops/ms -Iteration 6: 2.205 ops/ms -Iteration 7: 2.202 ops/ms -Iteration 8: 2.202 ops/ms -Iteration 9: 2.202 ops/ms -Iteration 10: 2.203 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.beanArraySer": - 2.193 ±(99.9%) 0.010 ops/ms [Average] - (min, avg, max) = (2.159, 2.193, 2.214), stdev = 0.020 - CI (99.9%): [2.183, 2.203] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.classicBeanItemDeser - -# Run progress: 9.52% complete, ETA 10:35:36 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.447 ops/ms -# Warmup Iteration 2: 1.485 ops/ms -# Warmup Iteration 3: 1.485 ops/ms -# Warmup Iteration 4: 1.486 ops/ms -# Warmup Iteration 5: 1.483 ops/ms -# Warmup Iteration 6: 1.489 ops/ms -# Warmup Iteration 7: 1.487 ops/ms -# Warmup Iteration 8: 1.486 ops/ms -# Warmup Iteration 9: 1.487 ops/ms -# Warmup Iteration 10: 1.485 ops/ms -Iteration 1: 1.486 ops/ms -Iteration 2: 1.486 ops/ms -Iteration 3: 1.486 ops/ms -Iteration 4: 1.487 ops/ms -Iteration 5: 1.486 ops/ms -Iteration 6: 1.487 ops/ms -Iteration 7: 1.488 ops/ms -Iteration 8: 1.488 ops/ms -Iteration 9: 1.487 ops/ms -Iteration 10: 1.488 ops/ms - -# Run progress: 10.48% complete, ETA 10:28:51 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.338 ops/ms -# Warmup Iteration 2: 1.382 ops/ms -# Warmup Iteration 3: 1.382 ops/ms -# Warmup Iteration 4: 1.383 ops/ms -# Warmup Iteration 5: 1.381 ops/ms -# Warmup Iteration 6: 1.382 ops/ms -# Warmup Iteration 7: 1.383 ops/ms -# Warmup Iteration 8: 1.383 ops/ms -# Warmup Iteration 9: 1.384 ops/ms -# Warmup Iteration 10: 1.384 ops/ms -Iteration 1: 1.383 ops/ms -Iteration 2: 1.383 ops/ms -Iteration 3: 1.383 ops/ms -Iteration 4: 1.384 ops/ms -Iteration 5: 1.384 ops/ms -Iteration 6: 1.383 ops/ms -Iteration 7: 1.384 ops/ms -Iteration 8: 1.384 ops/ms -Iteration 9: 1.384 ops/ms -Iteration 10: 1.384 ops/ms - -# Run progress: 11.43% complete, ETA 10:22:08 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.347 ops/ms -# Warmup Iteration 2: 1.399 ops/ms -# Warmup Iteration 3: 1.399 ops/ms -# Warmup Iteration 4: 1.401 ops/ms -# Warmup Iteration 5: 1.397 ops/ms -# Warmup Iteration 6: 1.399 ops/ms -# Warmup Iteration 7: 1.399 ops/ms -# Warmup Iteration 8: 1.396 ops/ms -# Warmup Iteration 9: 1.401 ops/ms -# Warmup Iteration 10: 1.401 ops/ms -Iteration 1: 1.401 ops/ms -Iteration 2: 1.401 ops/ms -Iteration 3: 1.402 ops/ms -Iteration 4: 1.402 ops/ms -Iteration 5: 1.402 ops/ms -Iteration 6: 1.401 ops/ms -Iteration 7: 1.399 ops/ms -Iteration 8: 1.399 ops/ms -Iteration 9: 1.399 ops/ms -Iteration 10: 1.400 ops/ms - -# Run progress: 12.38% complete, ETA 10:15:24 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.296 ops/ms -# Warmup Iteration 2: 1.344 ops/ms -# Warmup Iteration 3: 1.344 ops/ms -# Warmup Iteration 4: 1.345 ops/ms -# Warmup Iteration 5: 1.342 ops/ms -# Warmup Iteration 6: 1.346 ops/ms -# Warmup Iteration 7: 1.345 ops/ms -# Warmup Iteration 8: 1.344 ops/ms -# Warmup Iteration 9: 1.346 ops/ms -# Warmup Iteration 10: 1.346 ops/ms -Iteration 1: 1.346 ops/ms -Iteration 2: 1.346 ops/ms -Iteration 3: 1.346 ops/ms -Iteration 4: 1.346 ops/ms -Iteration 5: 1.347 ops/ms -Iteration 6: 1.346 ops/ms -Iteration 7: 1.321 ops/ms -Iteration 8: 1.345 ops/ms -Iteration 9: 1.345 ops/ms -Iteration 10: 1.346 ops/ms - -# Run progress: 13.33% complete, ETA 10:08:41 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.334 ops/ms -# Warmup Iteration 2: 1.371 ops/ms -# Warmup Iteration 3: 1.370 ops/ms -# Warmup Iteration 4: 1.372 ops/ms -# Warmup Iteration 5: 1.371 ops/ms -# Warmup Iteration 6: 1.373 ops/ms -# Warmup Iteration 7: 1.373 ops/ms -# Warmup Iteration 8: 1.372 ops/ms -# Warmup Iteration 9: 1.373 ops/ms -# Warmup Iteration 10: 1.372 ops/ms -Iteration 1: 1.373 ops/ms -Iteration 2: 1.373 ops/ms -Iteration 3: 1.373 ops/ms -Iteration 4: 1.373 ops/ms -Iteration 5: 1.373 ops/ms -Iteration 6: 1.373 ops/ms -Iteration 7: 1.373 ops/ms -Iteration 8: 1.373 ops/ms -Iteration 9: 1.373 ops/ms -Iteration 10: 1.373 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.classicBeanItemDeser": - 1.398 ±(99.9%) 0.024 ops/ms [Average] - (min, avg, max) = (1.321, 1.398, 1.488), stdev = 0.049 - CI (99.9%): [1.373, 1.422] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.classicBeanItemSer - -# Run progress: 14.29% complete, ETA 10:01:57 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.313 ops/ms -# Warmup Iteration 2: 2.351 ops/ms -# Warmup Iteration 3: 2.343 ops/ms -# Warmup Iteration 4: 2.352 ops/ms -# Warmup Iteration 5: 2.348 ops/ms -# Warmup Iteration 6: 2.350 ops/ms -# Warmup Iteration 7: 2.351 ops/ms -# Warmup Iteration 8: 2.353 ops/ms -# Warmup Iteration 9: 2.353 ops/ms -# Warmup Iteration 10: 2.352 ops/ms -Iteration 1: 2.351 ops/ms -Iteration 2: 2.351 ops/ms -Iteration 3: 2.352 ops/ms -Iteration 4: 2.353 ops/ms -Iteration 5: 2.334 ops/ms -Iteration 6: 2.352 ops/ms -Iteration 7: 2.335 ops/ms -Iteration 8: 2.354 ops/ms -Iteration 9: 2.348 ops/ms -Iteration 10: 2.351 ops/ms - -# Run progress: 15.24% complete, ETA 09:55:13 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.345 ops/ms -# Warmup Iteration 2: 2.390 ops/ms -# Warmup Iteration 3: 2.387 ops/ms -# Warmup Iteration 4: 2.387 ops/ms -# Warmup Iteration 5: 2.387 ops/ms -# Warmup Iteration 6: 2.386 ops/ms -# Warmup Iteration 7: 2.387 ops/ms -# Warmup Iteration 8: 2.387 ops/ms -# Warmup Iteration 9: 2.388 ops/ms -# Warmup Iteration 10: 2.387 ops/ms -Iteration 1: 2.385 ops/ms -Iteration 2: 2.390 ops/ms -Iteration 3: 2.386 ops/ms -Iteration 4: 2.388 ops/ms -Iteration 5: 2.385 ops/ms -Iteration 6: 2.387 ops/ms -Iteration 7: 2.386 ops/ms -Iteration 8: 2.387 ops/ms -Iteration 9: 2.380 ops/ms -Iteration 10: 2.383 ops/ms - -# Run progress: 16.19% complete, ETA 09:48:31 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.224 ops/ms -# Warmup Iteration 2: 2.232 ops/ms -# Warmup Iteration 3: 2.229 ops/ms -# Warmup Iteration 4: 2.250 ops/ms -# Warmup Iteration 5: 2.259 ops/ms -# Warmup Iteration 6: 2.252 ops/ms -# Warmup Iteration 7: 2.253 ops/ms -# Warmup Iteration 8: 2.264 ops/ms -# Warmup Iteration 9: 2.250 ops/ms -# Warmup Iteration 10: 2.255 ops/ms -Iteration 1: 2.252 ops/ms -Iteration 2: 2.246 ops/ms -Iteration 3: 2.244 ops/ms -Iteration 4: 2.272 ops/ms -Iteration 5: 2.277 ops/ms -Iteration 6: 2.265 ops/ms -Iteration 7: 2.272 ops/ms -Iteration 8: 2.258 ops/ms -Iteration 9: 2.249 ops/ms -Iteration 10: 2.268 ops/ms - -# Run progress: 17.14% complete, ETA 09:41:49 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.256 ops/ms -# Warmup Iteration 2: 2.273 ops/ms -# Warmup Iteration 3: 2.267 ops/ms -# Warmup Iteration 4: 2.269 ops/ms -# Warmup Iteration 5: 2.269 ops/ms -# Warmup Iteration 6: 2.274 ops/ms -# Warmup Iteration 7: 2.273 ops/ms -# Warmup Iteration 8: 2.272 ops/ms -# Warmup Iteration 9: 2.273 ops/ms -# Warmup Iteration 10: 2.273 ops/ms -Iteration 1: 2.272 ops/ms -Iteration 2: 2.273 ops/ms -Iteration 3: 2.273 ops/ms -Iteration 4: 2.283 ops/ms -Iteration 5: 2.282 ops/ms -Iteration 6: 2.283 ops/ms -Iteration 7: 2.283 ops/ms -Iteration 8: 2.279 ops/ms -Iteration 9: 2.282 ops/ms -Iteration 10: 2.283 ops/ms - -# Run progress: 18.10% complete, ETA 09:35:06 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -2.313 ops/ms -# Warmup Iteration 2: 2.355 ops/ms -# Warmup Iteration 3: 2.353 ops/ms -# Warmup Iteration 4: 2.354 ops/ms -# Warmup Iteration 5: 2.352 ops/ms -# Warmup Iteration 6: 2.355 ops/ms -# Warmup Iteration 7: 2.349 ops/ms -# Warmup Iteration 8: 2.354 ops/ms -# Warmup Iteration 9: 2.355 ops/ms -# Warmup Iteration 10: 2.353 ops/ms -Iteration 1: 2.351 ops/ms -Iteration 2: 2.354 ops/ms -Iteration 3: 2.352 ops/ms -Iteration 4: 2.354 ops/ms -Iteration 5: 2.352 ops/ms -Iteration 6: 2.352 ops/ms -Iteration 7: 2.349 ops/ms -Iteration 8: 2.349 ops/ms -Iteration 9: 2.355 ops/ms -Iteration 10: 2.361 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.classicBeanItemSer": - 2.325 ±(99.9%) 0.024 ops/ms [Average] - (min, avg, max) = (2.244, 2.325, 2.390), stdev = 0.048 - CI (99.9%): [2.301, 2.349] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.classicMediaItemDeser - -# Run progress: 19.05% complete, ETA 09:28:25 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -391.916 ops/ms -# Warmup Iteration 2: 403.219 ops/ms -# Warmup Iteration 3: 403.618 ops/ms -# Warmup Iteration 4: 403.723 ops/ms -# Warmup Iteration 5: 403.727 ops/ms -# Warmup Iteration 6: 403.652 ops/ms -# Warmup Iteration 7: 403.653 ops/ms -# Warmup Iteration 8: 403.807 ops/ms -# Warmup Iteration 9: 403.426 ops/ms -# Warmup Iteration 10: 403.559 ops/ms -Iteration 1: 403.407 ops/ms -Iteration 2: 403.520 ops/ms -Iteration 3: 403.564 ops/ms -Iteration 4: 403.689 ops/ms -Iteration 5: 403.551 ops/ms -Iteration 6: 403.599 ops/ms -Iteration 7: 403.084 ops/ms -Iteration 8: 403.222 ops/ms -Iteration 9: 403.335 ops/ms -Iteration 10: 403.357 ops/ms - -# Run progress: 20.00% complete, ETA 09:21:42 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -385.141 ops/ms -# Warmup Iteration 2: 398.113 ops/ms -# Warmup Iteration 3: 399.119 ops/ms -# Warmup Iteration 4: 399.236 ops/ms -# Warmup Iteration 5: 399.002 ops/ms -# Warmup Iteration 6: 399.276 ops/ms -# Warmup Iteration 7: 399.049 ops/ms -# Warmup Iteration 8: 399.021 ops/ms -# Warmup Iteration 9: 399.012 ops/ms -# Warmup Iteration 10: 399.163 ops/ms -Iteration 1: 399.102 ops/ms -Iteration 2: 399.144 ops/ms -Iteration 3: 398.877 ops/ms -Iteration 4: 399.131 ops/ms -Iteration 5: 399.164 ops/ms -Iteration 6: 399.191 ops/ms -Iteration 7: 399.786 ops/ms -Iteration 8: 399.392 ops/ms -Iteration 9: 399.452 ops/ms -Iteration 10: 399.575 ops/ms - -# Run progress: 20.95% complete, ETA 09:15:00 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -396.069 ops/ms -# Warmup Iteration 2: 406.923 ops/ms -# Warmup Iteration 3: 407.835 ops/ms -# Warmup Iteration 4: 408.035 ops/ms -# Warmup Iteration 5: 408.109 ops/ms -# Warmup Iteration 6: 407.750 ops/ms -# Warmup Iteration 7: 407.912 ops/ms -# Warmup Iteration 8: 407.952 ops/ms -# Warmup Iteration 9: 407.945 ops/ms -# Warmup Iteration 10: 407.820 ops/ms -Iteration 1: 407.961 ops/ms -Iteration 2: 407.933 ops/ms -Iteration 3: 408.117 ops/ms -Iteration 4: 407.922 ops/ms -Iteration 5: 407.751 ops/ms -Iteration 6: 407.876 ops/ms -Iteration 7: 405.013 ops/ms -Iteration 8: 407.765 ops/ms -Iteration 9: 407.917 ops/ms -Iteration 10: 407.651 ops/ms - -# Run progress: 21.90% complete, ETA 09:08:17 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -388.940 ops/ms -# Warmup Iteration 2: 402.232 ops/ms -# Warmup Iteration 3: 403.629 ops/ms -# Warmup Iteration 4: 403.344 ops/ms -# Warmup Iteration 5: 403.886 ops/ms -# Warmup Iteration 6: 403.534 ops/ms -# Warmup Iteration 7: 403.627 ops/ms -# Warmup Iteration 8: 403.403 ops/ms -# Warmup Iteration 9: 403.459 ops/ms -# Warmup Iteration 10: 403.500 ops/ms -Iteration 1: 403.632 ops/ms -Iteration 2: 403.538 ops/ms -Iteration 3: 403.709 ops/ms -Iteration 4: 403.727 ops/ms -Iteration 5: 403.718 ops/ms -Iteration 6: 403.520 ops/ms -Iteration 7: 403.322 ops/ms -Iteration 8: 403.356 ops/ms -Iteration 9: 403.488 ops/ms -Iteration 10: 403.180 ops/ms - -# Run progress: 22.86% complete, ETA 09:01:36 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -389.372 ops/ms -# Warmup Iteration 2: 403.056 ops/ms -# Warmup Iteration 3: 402.717 ops/ms -# Warmup Iteration 4: 403.029 ops/ms -# Warmup Iteration 5: 402.653 ops/ms -# Warmup Iteration 6: 402.835 ops/ms -# Warmup Iteration 7: 402.755 ops/ms -# Warmup Iteration 8: 402.737 ops/ms -# Warmup Iteration 9: 402.862 ops/ms -# Warmup Iteration 10: 402.963 ops/ms -Iteration 1: 402.999 ops/ms -Iteration 2: 402.866 ops/ms -Iteration 3: 402.829 ops/ms -Iteration 4: 403.041 ops/ms -Iteration 5: 402.884 ops/ms -Iteration 6: 403.007 ops/ms -Iteration 7: 402.754 ops/ms -Iteration 8: 402.746 ops/ms -Iteration 9: 402.629 ops/ms -Iteration 10: 402.754 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.classicMediaItemDeser": - 403.335 ±(99.9%) 1.337 ops/ms [Average] - (min, avg, max) = (398.877, 403.335, 408.117), stdev = 2.700 - CI (99.9%): [401.998, 404.671] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.classicMediaItemSer - -# Run progress: 23.81% complete, ETA 08:54:54 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -589.486 ops/ms -# Warmup Iteration 2: 608.238 ops/ms -# Warmup Iteration 3: 609.837 ops/ms -# Warmup Iteration 4: 609.769 ops/ms -# Warmup Iteration 5: 609.624 ops/ms -# Warmup Iteration 6: 609.581 ops/ms -# Warmup Iteration 7: 609.524 ops/ms -# Warmup Iteration 8: 609.515 ops/ms -# Warmup Iteration 9: 609.170 ops/ms -# Warmup Iteration 10: 609.529 ops/ms -Iteration 1: 609.605 ops/ms -Iteration 2: 609.343 ops/ms -Iteration 3: 609.546 ops/ms -Iteration 4: 609.587 ops/ms -Iteration 5: 609.209 ops/ms -Iteration 6: 609.499 ops/ms -Iteration 7: 609.274 ops/ms -Iteration 8: 609.436 ops/ms -Iteration 9: 609.211 ops/ms -Iteration 10: 609.458 ops/ms - -# Run progress: 24.76% complete, ETA 08:48:11 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -596.972 ops/ms -# Warmup Iteration 2: 615.479 ops/ms -# Warmup Iteration 3: 615.213 ops/ms -# Warmup Iteration 4: 615.278 ops/ms -# Warmup Iteration 5: 615.426 ops/ms -# Warmup Iteration 6: 615.380 ops/ms -# Warmup Iteration 7: 615.478 ops/ms -# Warmup Iteration 8: 614.939 ops/ms -# Warmup Iteration 9: 615.347 ops/ms -# Warmup Iteration 10: 615.585 ops/ms -Iteration 1: 615.279 ops/ms -Iteration 2: 615.486 ops/ms -Iteration 3: 615.452 ops/ms -Iteration 4: 615.248 ops/ms -Iteration 5: 615.270 ops/ms -Iteration 6: 615.228 ops/ms -Iteration 7: 616.897 ops/ms -Iteration 8: 616.630 ops/ms -Iteration 9: 616.727 ops/ms -Iteration 10: 616.847 ops/ms - -# Run progress: 25.71% complete, ETA 08:41:32 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -598.024 ops/ms -# Warmup Iteration 2: 614.556 ops/ms -# Warmup Iteration 3: 614.368 ops/ms -# Warmup Iteration 4: 614.309 ops/ms -# Warmup Iteration 5: 614.719 ops/ms -# Warmup Iteration 6: 614.442 ops/ms -# Warmup Iteration 7: 614.036 ops/ms -# Warmup Iteration 8: 614.632 ops/ms -# Warmup Iteration 9: 614.424 ops/ms -# Warmup Iteration 10: 614.347 ops/ms -Iteration 1: 614.660 ops/ms -Iteration 2: 614.505 ops/ms -Iteration 3: 614.757 ops/ms -Iteration 4: 614.498 ops/ms -Iteration 5: 614.670 ops/ms -Iteration 6: 612.457 ops/ms -Iteration 7: 613.982 ops/ms -Iteration 8: 613.638 ops/ms -Iteration 9: 613.956 ops/ms -Iteration 10: 613.418 ops/ms - -# Run progress: 26.67% complete, ETA 08:34:50 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -590.944 ops/ms -# Warmup Iteration 2: 605.799 ops/ms -# Warmup Iteration 3: 604.719 ops/ms -# Warmup Iteration 4: 605.611 ops/ms -# Warmup Iteration 5: 605.521 ops/ms -# Warmup Iteration 6: 604.296 ops/ms -# Warmup Iteration 7: 604.492 ops/ms -# Warmup Iteration 8: 605.078 ops/ms -# Warmup Iteration 9: 604.788 ops/ms -# Warmup Iteration 10: 605.290 ops/ms -Iteration 1: 605.538 ops/ms -Iteration 2: 605.584 ops/ms -Iteration 3: 605.351 ops/ms -Iteration 4: 605.203 ops/ms -Iteration 5: 605.122 ops/ms -Iteration 6: 605.186 ops/ms -Iteration 7: 604.640 ops/ms -Iteration 8: 605.778 ops/ms -Iteration 9: 605.425 ops/ms -Iteration 10: 605.636 ops/ms - -# Run progress: 27.62% complete, ETA 08:28:08 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -589.857 ops/ms -# Warmup Iteration 2: 606.156 ops/ms -# Warmup Iteration 3: 607.449 ops/ms -# Warmup Iteration 4: 607.734 ops/ms -# Warmup Iteration 5: 607.297 ops/ms -# Warmup Iteration 6: 607.587 ops/ms -# Warmup Iteration 7: 607.857 ops/ms -# Warmup Iteration 8: 607.682 ops/ms -# Warmup Iteration 9: 607.814 ops/ms -# Warmup Iteration 10: 607.810 ops/ms -Iteration 1: 607.303 ops/ms -Iteration 2: 607.555 ops/ms -Iteration 3: 607.649 ops/ms -Iteration 4: 607.563 ops/ms -Iteration 5: 607.461 ops/ms -Iteration 6: 607.451 ops/ms -Iteration 7: 607.548 ops/ms -Iteration 8: 607.250 ops/ms -Iteration 9: 607.533 ops/ms -Iteration 10: 607.774 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.classicMediaItemSer": - 610.446 ±(99.9%) 1.996 ops/ms [Average] - (min, avg, max) = (604.640, 610.446, 616.897), stdev = 4.032 - CI (99.9%): [608.450, 612.442] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Afterburner.constructorArrayDeser - -# Run progress: 28.57% complete, ETA 08:21:27 -# Fork: 1 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.057 ops/ms -# Warmup Iteration 2: 1.121 ops/ms -# Warmup Iteration 3: 1.122 ops/ms -# Warmup Iteration 4: 1.121 ops/ms -# Warmup Iteration 5: 1.121 ops/ms -# Warmup Iteration 6: 1.119 ops/ms -# Warmup Iteration 7: 1.121 ops/ms -# Warmup Iteration 8: 1.121 ops/ms -# Warmup Iteration 9: 1.121 ops/ms -# Warmup Iteration 10: 1.118 ops/ms -Iteration 1: 1.121 ops/ms -Iteration 2: 1.121 ops/ms -Iteration 3: 1.122 ops/ms -Iteration 4: 1.121 ops/ms -Iteration 5: 1.121 ops/ms -Iteration 6: 1.120 ops/ms -Iteration 7: 1.122 ops/ms -Iteration 8: 1.122 ops/ms -Iteration 9: 1.122 ops/ms -Iteration 10: 1.122 ops/ms - -# Run progress: 29.52% complete, ETA 08:14:45 -# Fork: 2 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.065 ops/ms -# Warmup Iteration 2: 1.127 ops/ms -# Warmup Iteration 3: 1.128 ops/ms -# Warmup Iteration 4: 1.128 ops/ms -# Warmup Iteration 5: 1.128 ops/ms -# Warmup Iteration 6: 1.126 ops/ms -# Warmup Iteration 7: 1.128 ops/ms -# Warmup Iteration 8: 1.128 ops/ms -# Warmup Iteration 9: 1.128 ops/ms -# Warmup Iteration 10: 1.127 ops/ms -Iteration 1: 1.128 ops/ms -Iteration 2: 1.127 ops/ms -Iteration 3: 1.128 ops/ms -Iteration 4: 1.128 ops/ms -Iteration 5: 1.127 ops/ms -Iteration 6: 1.124 ops/ms -Iteration 7: 1.129 ops/ms -Iteration 8: 1.129 ops/ms -Iteration 9: 1.129 ops/ms -Iteration 10: 1.129 ops/ms - -# Run progress: 30.48% complete, ETA 08:08:05 -# Fork: 3 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.065 ops/ms -# Warmup Iteration 2: 1.127 ops/ms -# Warmup Iteration 3: 1.127 ops/ms -# Warmup Iteration 4: 1.127 ops/ms -# Warmup Iteration 5: 1.127 ops/ms -# Warmup Iteration 6: 1.125 ops/ms -# Warmup Iteration 7: 1.127 ops/ms -# Warmup Iteration 8: 1.127 ops/ms -# Warmup Iteration 9: 1.127 ops/ms -# Warmup Iteration 10: 1.127 ops/ms -Iteration 1: 1.127 ops/ms -Iteration 2: 1.127 ops/ms -Iteration 3: 1.127 ops/ms -Iteration 4: 1.127 ops/ms -Iteration 5: 1.127 ops/ms -Iteration 6: 1.127 ops/ms -Iteration 7: 1.127 ops/ms -Iteration 8: 1.127 ops/ms -Iteration 9: 1.127 ops/ms -Iteration 10: 1.127 ops/ms - -# Run progress: 31.43% complete, ETA 08:01:23 -# Fork: 4 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.053 ops/ms -# Warmup Iteration 2: 1.108 ops/ms -# Warmup Iteration 3: 1.108 ops/ms -# Warmup Iteration 4: 1.109 ops/ms -# Warmup Iteration 5: 1.109 ops/ms -# Warmup Iteration 6: 1.100 ops/ms -# Warmup Iteration 7: 1.110 ops/ms -# Warmup Iteration 8: 1.108 ops/ms -# Warmup Iteration 9: 1.109 ops/ms -# Warmup Iteration 10: 1.106 ops/ms -Iteration 1: 1.110 ops/ms -Iteration 2: 1.110 ops/ms -Iteration 3: 1.110 ops/ms -Iteration 4: 1.110 ops/ms -Iteration 5: 1.110 ops/ms -Iteration 6: 1.110 ops/ms -Iteration 7: 1.110 ops/ms -Iteration 8: 1.110 ops/ms -Iteration 9: 1.110 ops/ms -Iteration 10: 1.110 ops/ms - -# Run progress: 32.38% complete, ETA 07:54:43 -# Fork: 5 of 5 -# Warmup Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -1.074 ops/ms -# Warmup Iteration 2: 1.132 ops/ms -# Warmup Iteration 3: 1.133 ops/ms -# Warmup Iteration 4: 1.132 ops/ms -# Warmup Iteration 5: 1.132 ops/ms -# Warmup Iteration 6: 1.131 ops/ms -# Warmup Iteration 7: 1.134 ops/ms -# Warmup Iteration 8: 1.133 ops/ms -# Warmup Iteration 9: 1.133 ops/ms -# Warmup Iteration 10: 1.133 ops/ms -Iteration 1: 1.133 ops/ms -Iteration 2: 1.133 ops/ms -Iteration 3: 1.133 ops/ms -Iteration 4: 1.134 ops/ms -Iteration 5: 1.133 ops/ms -Iteration 6: 1.133 ops/ms -Iteration 7: 1.133 ops/ms -Iteration 8: 1.133 ops/ms -Iteration 9: 1.134 ops/ms -Iteration 10: 1.133 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Afterburner.constructorArrayDeser": - 1.124 ±(99.9%) 0.004 ops/ms [Average] - (min, avg, max) = (1.110, 1.124, 1.134), stdev = 0.008 - CI (99.9%): [1.120, 1.128] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.beanArrayDeser - -# Run progress: 33.33% complete, ETA 07:48:04 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.239 ops/ms -# Warmup Iteration 2: 1.307 ops/ms -# Warmup Iteration 3: 1.308 ops/ms -# Warmup Iteration 4: 1.309 ops/ms -# Warmup Iteration 5: 1.308 ops/ms -# Warmup Iteration 6: 1.309 ops/ms -# Warmup Iteration 7: 1.309 ops/ms -# Warmup Iteration 8: 1.307 ops/ms -# Warmup Iteration 9: 1.309 ops/ms -# Warmup Iteration 10: 1.309 ops/ms -Iteration 1: 1.309 ops/ms -Iteration 2: 1.309 ops/ms -Iteration 3: 1.309 ops/ms -Iteration 4: 1.309 ops/ms -Iteration 5: 1.309 ops/ms -Iteration 6: 1.309 ops/ms -Iteration 7: 1.309 ops/ms -Iteration 8: 1.309 ops/ms -Iteration 9: 1.309 ops/ms -Iteration 10: 1.309 ops/ms - -# Run progress: 34.29% complete, ETA 07:41:23 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.243 ops/ms -# Warmup Iteration 2: 1.295 ops/ms -# Warmup Iteration 3: 1.293 ops/ms -# Warmup Iteration 4: 1.295 ops/ms -# Warmup Iteration 5: 1.284 ops/ms -# Warmup Iteration 6: 1.296 ops/ms -# Warmup Iteration 7: 1.296 ops/ms -# Warmup Iteration 8: 1.295 ops/ms -# Warmup Iteration 9: 1.296 ops/ms -# Warmup Iteration 10: 1.296 ops/ms -Iteration 1: 1.295 ops/ms -Iteration 2: 1.296 ops/ms -Iteration 3: 1.295 ops/ms -Iteration 4: 1.296 ops/ms -Iteration 5: 1.296 ops/ms -Iteration 6: 1.296 ops/ms -Iteration 7: 1.296 ops/ms -Iteration 8: 1.295 ops/ms -Iteration 9: 1.295 ops/ms -Iteration 10: 1.295 ops/ms - -# Run progress: 35.24% complete, ETA 07:34:43 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.255 ops/ms -# Warmup Iteration 2: 1.310 ops/ms -# Warmup Iteration 3: 1.311 ops/ms -# Warmup Iteration 4: 1.311 ops/ms -# Warmup Iteration 5: 1.308 ops/ms -# Warmup Iteration 6: 1.312 ops/ms -# Warmup Iteration 7: 1.312 ops/ms -# Warmup Iteration 8: 1.310 ops/ms -# Warmup Iteration 9: 1.312 ops/ms -# Warmup Iteration 10: 1.312 ops/ms -Iteration 1: 1.312 ops/ms -Iteration 2: 1.312 ops/ms -Iteration 3: 1.312 ops/ms -Iteration 4: 1.312 ops/ms -Iteration 5: 1.312 ops/ms -Iteration 6: 1.312 ops/ms -Iteration 7: 1.312 ops/ms -Iteration 8: 1.312 ops/ms -Iteration 9: 1.312 ops/ms -Iteration 10: 1.312 ops/ms - -# Run progress: 36.19% complete, ETA 07:28:05 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.247 ops/ms -# Warmup Iteration 2: 1.303 ops/ms -# Warmup Iteration 3: 1.303 ops/ms -# Warmup Iteration 4: 1.303 ops/ms -# Warmup Iteration 5: 1.296 ops/ms -# Warmup Iteration 6: 1.304 ops/ms -# Warmup Iteration 7: 1.305 ops/ms -# Warmup Iteration 8: 1.302 ops/ms -# Warmup Iteration 9: 1.305 ops/ms -# Warmup Iteration 10: 1.305 ops/ms -Iteration 1: 1.305 ops/ms -Iteration 2: 1.305 ops/ms -Iteration 3: 1.305 ops/ms -Iteration 4: 1.305 ops/ms -Iteration 5: 1.304 ops/ms -Iteration 6: 1.305 ops/ms -Iteration 7: 1.305 ops/ms -Iteration 8: 1.305 ops/ms -Iteration 9: 1.305 ops/ms -Iteration 10: 1.305 ops/ms - -# Run progress: 37.14% complete, ETA 07:21:23 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.231 ops/ms -# Warmup Iteration 2: 1.281 ops/ms -# Warmup Iteration 3: 1.280 ops/ms -# Warmup Iteration 4: 1.281 ops/ms -# Warmup Iteration 5: 1.280 ops/ms -# Warmup Iteration 6: 1.283 ops/ms -# Warmup Iteration 7: 1.283 ops/ms -# Warmup Iteration 8: 1.282 ops/ms -# Warmup Iteration 9: 1.282 ops/ms -# Warmup Iteration 10: 1.284 ops/ms -Iteration 1: 1.282 ops/ms -Iteration 2: 1.282 ops/ms -Iteration 3: 1.283 ops/ms -Iteration 4: 1.283 ops/ms -Iteration 5: 1.282 ops/ms -Iteration 6: 1.282 ops/ms -Iteration 7: 1.282 ops/ms -Iteration 8: 1.282 ops/ms -Iteration 9: 1.283 ops/ms -Iteration 10: 1.282 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.beanArrayDeser": - 1.301 ±(99.9%) 0.005 ops/ms [Average] - (min, avg, max) = (1.282, 1.301, 1.312), stdev = 0.011 - CI (99.9%): [1.295, 1.306] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.beanArraySer - -# Run progress: 38.10% complete, ETA 07:14:41 -# Fork: 1 of 5 -# Warmup Iteration 1: 2.038 ops/ms -# Warmup Iteration 2: 2.112 ops/ms -# Warmup Iteration 3: 2.111 ops/ms -# Warmup Iteration 4: 2.113 ops/ms -# Warmup Iteration 5: 2.112 ops/ms -# Warmup Iteration 6: 2.114 ops/ms -# Warmup Iteration 7: 2.113 ops/ms -# Warmup Iteration 8: 2.115 ops/ms -# Warmup Iteration 9: 2.113 ops/ms -# Warmup Iteration 10: 2.112 ops/ms -Iteration 1: 2.113 ops/ms -Iteration 2: 2.113 ops/ms -Iteration 3: 2.114 ops/ms -Iteration 4: 2.113 ops/ms -Iteration 5: 2.113 ops/ms -Iteration 6: 2.031 ops/ms -Iteration 7: 2.110 ops/ms -Iteration 8: 2.110 ops/ms -Iteration 9: 2.111 ops/ms -Iteration 10: 2.111 ops/ms - -# Run progress: 39.05% complete, ETA 07:07:59 -# Fork: 2 of 5 -# Warmup Iteration 1: 2.067 ops/ms -# Warmup Iteration 2: 2.123 ops/ms -# Warmup Iteration 3: 2.123 ops/ms -# Warmup Iteration 4: 2.124 ops/ms -# Warmup Iteration 5: 2.125 ops/ms -# Warmup Iteration 6: 2.125 ops/ms -# Warmup Iteration 7: 2.124 ops/ms -# Warmup Iteration 8: 2.125 ops/ms -# Warmup Iteration 9: 2.124 ops/ms -# Warmup Iteration 10: 2.124 ops/ms -Iteration 1: 2.124 ops/ms -Iteration 2: 2.125 ops/ms -Iteration 3: 2.124 ops/ms -Iteration 4: 2.124 ops/ms -Iteration 5: 2.125 ops/ms -Iteration 6: 2.124 ops/ms -Iteration 7: 2.125 ops/ms -Iteration 8: 2.125 ops/ms -Iteration 9: 2.125 ops/ms -Iteration 10: 2.125 ops/ms - -# Run progress: 40.00% complete, ETA 07:01:18 -# Fork: 3 of 5 -# Warmup Iteration 1: 2.056 ops/ms -# Warmup Iteration 2: 2.137 ops/ms -# Warmup Iteration 3: 2.136 ops/ms -# Warmup Iteration 4: 2.137 ops/ms -# Warmup Iteration 5: 2.138 ops/ms -# Warmup Iteration 6: 2.139 ops/ms -# Warmup Iteration 7: 2.139 ops/ms -# Warmup Iteration 8: 2.137 ops/ms -# Warmup Iteration 9: 2.136 ops/ms -# Warmup Iteration 10: 2.139 ops/ms -Iteration 1: 2.139 ops/ms -Iteration 2: 2.138 ops/ms -Iteration 3: 2.139 ops/ms -Iteration 4: 2.138 ops/ms -Iteration 5: 2.138 ops/ms -Iteration 6: 2.138 ops/ms -Iteration 7: 2.128 ops/ms -Iteration 8: 2.127 ops/ms -Iteration 9: 2.128 ops/ms -Iteration 10: 2.128 ops/ms - -# Run progress: 40.95% complete, ETA 06:54:36 -# Fork: 4 of 5 -# Warmup Iteration 1: 2.036 ops/ms -# Warmup Iteration 2: 2.093 ops/ms -# Warmup Iteration 3: 2.092 ops/ms -# Warmup Iteration 4: 2.094 ops/ms -# Warmup Iteration 5: 2.095 ops/ms -# Warmup Iteration 6: 2.095 ops/ms -# Warmup Iteration 7: 2.097 ops/ms -# Warmup Iteration 8: 2.096 ops/ms -# Warmup Iteration 9: 2.096 ops/ms -# Warmup Iteration 10: 2.096 ops/ms -Iteration 1: 2.096 ops/ms -Iteration 2: 2.096 ops/ms -Iteration 3: 2.096 ops/ms -Iteration 4: 2.096 ops/ms -Iteration 5: 2.096 ops/ms -Iteration 6: 2.096 ops/ms -Iteration 7: 2.093 ops/ms -Iteration 8: 2.093 ops/ms -Iteration 9: 2.093 ops/ms -Iteration 10: 2.092 ops/ms - -# Run progress: 41.90% complete, ETA 06:47:54 -# Fork: 5 of 5 -# Warmup Iteration 1: 2.061 ops/ms -# Warmup Iteration 2: 2.117 ops/ms -# Warmup Iteration 3: 2.117 ops/ms -# Warmup Iteration 4: 2.116 ops/ms -# Warmup Iteration 5: 2.117 ops/ms -# Warmup Iteration 6: 2.118 ops/ms -# Warmup Iteration 7: 2.117 ops/ms -# Warmup Iteration 8: 2.118 ops/ms -# Warmup Iteration 9: 2.117 ops/ms -# Warmup Iteration 10: 2.117 ops/ms -Iteration 1: 2.117 ops/ms -Iteration 2: 2.117 ops/ms -Iteration 3: 2.117 ops/ms -Iteration 4: 2.116 ops/ms -Iteration 5: 2.117 ops/ms -Iteration 6: 2.117 ops/ms -Iteration 7: 2.116 ops/ms -Iteration 8: 2.117 ops/ms -Iteration 9: 2.117 ops/ms -Iteration 10: 2.116 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.beanArraySer": - 2.115 ±(99.9%) 0.009 ops/ms [Average] - (min, avg, max) = (2.031, 2.115, 2.139), stdev = 0.018 - CI (99.9%): [2.106, 2.124] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.classicBeanItemDeser - -# Run progress: 42.86% complete, ETA 06:41:12 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.178 ops/ms -# Warmup Iteration 2: 1.217 ops/ms -# Warmup Iteration 3: 1.217 ops/ms -# Warmup Iteration 4: 1.218 ops/ms -# Warmup Iteration 5: 1.219 ops/ms -# Warmup Iteration 6: 1.213 ops/ms -# Warmup Iteration 7: 1.218 ops/ms -# Warmup Iteration 8: 1.218 ops/ms -# Warmup Iteration 9: 1.218 ops/ms -# Warmup Iteration 10: 1.219 ops/ms -Iteration 1: 1.219 ops/ms -Iteration 2: 1.218 ops/ms -Iteration 3: 1.219 ops/ms -Iteration 4: 1.219 ops/ms -Iteration 5: 1.219 ops/ms -Iteration 6: 1.219 ops/ms -Iteration 7: 1.218 ops/ms -Iteration 8: 1.218 ops/ms -Iteration 9: 1.219 ops/ms -Iteration 10: 1.218 ops/ms - -# Run progress: 43.81% complete, ETA 06:34:31 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.271 ops/ms -# Warmup Iteration 2: 1.314 ops/ms -# Warmup Iteration 3: 1.316 ops/ms -# Warmup Iteration 4: 1.315 ops/ms -# Warmup Iteration 5: 1.310 ops/ms -# Warmup Iteration 6: 1.317 ops/ms -# Warmup Iteration 7: 1.316 ops/ms -# Warmup Iteration 8: 1.316 ops/ms -# Warmup Iteration 9: 1.316 ops/ms -# Warmup Iteration 10: 1.316 ops/ms -Iteration 1: 1.316 ops/ms -Iteration 2: 1.316 ops/ms -Iteration 3: 1.316 ops/ms -Iteration 4: 1.316 ops/ms -Iteration 5: 1.316 ops/ms -Iteration 6: 1.316 ops/ms -Iteration 7: 1.317 ops/ms -Iteration 8: 1.317 ops/ms -Iteration 9: 1.317 ops/ms -Iteration 10: 1.317 ops/ms - -# Run progress: 44.76% complete, ETA 06:27:49 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.205 ops/ms -# Warmup Iteration 2: 1.251 ops/ms -# Warmup Iteration 3: 1.250 ops/ms -# Warmup Iteration 4: 1.250 ops/ms -# Warmup Iteration 5: 1.249 ops/ms -# Warmup Iteration 6: 1.248 ops/ms -# Warmup Iteration 7: 1.252 ops/ms -# Warmup Iteration 8: 1.251 ops/ms -# Warmup Iteration 9: 1.251 ops/ms -# Warmup Iteration 10: 1.251 ops/ms -Iteration 1: 1.251 ops/ms -Iteration 2: 1.251 ops/ms -Iteration 3: 1.251 ops/ms -Iteration 4: 1.252 ops/ms -Iteration 5: 1.251 ops/ms -Iteration 6: 1.252 ops/ms -Iteration 7: 1.252 ops/ms -Iteration 8: 1.252 ops/ms -Iteration 9: 1.252 ops/ms -Iteration 10: 1.252 ops/ms - -# Run progress: 45.71% complete, ETA 06:21:08 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.194 ops/ms -# Warmup Iteration 2: 1.232 ops/ms -# Warmup Iteration 3: 1.234 ops/ms -# Warmup Iteration 4: 1.234 ops/ms -# Warmup Iteration 5: 1.235 ops/ms -# Warmup Iteration 6: 1.228 ops/ms -# Warmup Iteration 7: 1.234 ops/ms -# Warmup Iteration 8: 1.234 ops/ms -# Warmup Iteration 9: 1.235 ops/ms -# Warmup Iteration 10: 1.235 ops/ms -Iteration 1: 1.234 ops/ms -Iteration 2: 1.234 ops/ms -Iteration 3: 1.235 ops/ms -Iteration 4: 1.235 ops/ms -Iteration 5: 1.235 ops/ms -Iteration 6: 1.235 ops/ms -Iteration 7: 1.235 ops/ms -Iteration 8: 1.235 ops/ms -Iteration 9: 1.235 ops/ms -Iteration 10: 1.235 ops/ms - -# Run progress: 46.67% complete, ETA 06:14:26 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.191 ops/ms -# Warmup Iteration 2: 1.228 ops/ms -# Warmup Iteration 3: 1.230 ops/ms -# Warmup Iteration 4: 1.229 ops/ms -# Warmup Iteration 5: 1.230 ops/ms -# Warmup Iteration 6: 1.229 ops/ms -# Warmup Iteration 7: 1.229 ops/ms -# Warmup Iteration 8: 1.230 ops/ms -# Warmup Iteration 9: 1.230 ops/ms -# Warmup Iteration 10: 1.230 ops/ms -Iteration 1: 1.230 ops/ms -Iteration 2: 1.230 ops/ms -Iteration 3: 1.230 ops/ms -Iteration 4: 1.230 ops/ms -Iteration 5: 1.230 ops/ms -Iteration 6: 1.229 ops/ms -Iteration 7: 1.230 ops/ms -Iteration 8: 1.230 ops/ms -Iteration 9: 1.230 ops/ms -Iteration 10: 1.230 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.classicBeanItemDeser": - 1.250 ±(99.9%) 0.017 ops/ms [Average] - (min, avg, max) = (1.218, 1.250, 1.317), stdev = 0.035 - CI (99.9%): [1.233, 1.268] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.classicBeanItemSer - -# Run progress: 47.62% complete, ETA 06:07:45 -# Fork: 1 of 5 -# Warmup Iteration 1: 2.120 ops/ms -# Warmup Iteration 2: 2.151 ops/ms -# Warmup Iteration 3: 2.160 ops/ms -# Warmup Iteration 4: 2.158 ops/ms -# Warmup Iteration 5: 2.150 ops/ms -# Warmup Iteration 6: 2.145 ops/ms -# Warmup Iteration 7: 2.146 ops/ms -# Warmup Iteration 8: 2.112 ops/ms -# Warmup Iteration 9: 2.146 ops/ms -# Warmup Iteration 10: 2.136 ops/ms -Iteration 1: 2.139 ops/ms -Iteration 2: 2.146 ops/ms -Iteration 3: 2.146 ops/ms -Iteration 4: 2.146 ops/ms -Iteration 5: 2.140 ops/ms -Iteration 6: 2.131 ops/ms -Iteration 7: 2.151 ops/ms -Iteration 8: 2.154 ops/ms -Iteration 9: 2.151 ops/ms -Iteration 10: 2.150 ops/ms - -# Run progress: 48.57% complete, ETA 06:01:03 -# Fork: 2 of 5 -# Warmup Iteration 1: 2.074 ops/ms -# Warmup Iteration 2: 2.105 ops/ms -# Warmup Iteration 3: 2.089 ops/ms -# Warmup Iteration 4: 2.101 ops/ms -# Warmup Iteration 5: 2.101 ops/ms -# Warmup Iteration 6: 2.098 ops/ms -# Warmup Iteration 7: 2.100 ops/ms -# Warmup Iteration 8: 2.101 ops/ms -# Warmup Iteration 9: 2.103 ops/ms -# Warmup Iteration 10: 2.101 ops/ms -Iteration 1: 2.101 ops/ms -Iteration 2: 2.101 ops/ms -Iteration 3: 2.101 ops/ms -Iteration 4: 2.101 ops/ms -Iteration 5: 2.100 ops/ms -Iteration 6: 2.102 ops/ms -Iteration 7: 2.088 ops/ms -Iteration 8: 2.088 ops/ms -Iteration 9: 2.088 ops/ms -Iteration 10: 2.089 ops/ms - -# Run progress: 49.52% complete, ETA 05:54:22 -# Fork: 3 of 5 -# Warmup Iteration 1: 2.073 ops/ms -# Warmup Iteration 2: 2.114 ops/ms -# Warmup Iteration 3: 2.115 ops/ms -# Warmup Iteration 4: 2.113 ops/ms -# Warmup Iteration 5: 2.107 ops/ms -# Warmup Iteration 6: 2.117 ops/ms -# Warmup Iteration 7: 2.117 ops/ms -# Warmup Iteration 8: 2.117 ops/ms -# Warmup Iteration 9: 2.117 ops/ms -# Warmup Iteration 10: 2.116 ops/ms -Iteration 1: 2.117 ops/ms -Iteration 2: 2.115 ops/ms -Iteration 3: 2.118 ops/ms -Iteration 4: 2.117 ops/ms -Iteration 5: 2.117 ops/ms -Iteration 6: 2.117 ops/ms -Iteration 7: 2.103 ops/ms -Iteration 8: 2.103 ops/ms -Iteration 9: 2.104 ops/ms -Iteration 10: 2.102 ops/ms - -# Run progress: 50.48% complete, ETA 05:47:41 -# Fork: 4 of 5 -# Warmup Iteration 1: 2.188 ops/ms -# Warmup Iteration 2: 2.224 ops/ms -# Warmup Iteration 3: 2.223 ops/ms -# Warmup Iteration 4: 2.199 ops/ms -# Warmup Iteration 5: 2.174 ops/ms -# Warmup Iteration 6: 2.198 ops/ms -# Warmup Iteration 7: 2.184 ops/ms -# Warmup Iteration 8: 2.204 ops/ms -# Warmup Iteration 9: 2.173 ops/ms -# Warmup Iteration 10: 2.194 ops/ms -Iteration 1: 2.196 ops/ms -Iteration 2: 2.193 ops/ms -Iteration 3: 2.190 ops/ms -Iteration 4: 2.203 ops/ms -Iteration 5: 2.191 ops/ms -Iteration 6: 2.188 ops/ms -Iteration 7: 2.181 ops/ms -Iteration 8: 2.177 ops/ms -Iteration 9: 2.176 ops/ms -Iteration 10: 2.181 ops/ms - -# Run progress: 51.43% complete, ETA 05:41:00 -# Fork: 5 of 5 -# Warmup Iteration 1: 2.121 ops/ms -# Warmup Iteration 2: 2.179 ops/ms -# Warmup Iteration 3: 2.179 ops/ms -# Warmup Iteration 4: 2.173 ops/ms -# Warmup Iteration 5: 2.166 ops/ms -# Warmup Iteration 6: 2.173 ops/ms -# Warmup Iteration 7: 2.173 ops/ms -# Warmup Iteration 8: 2.173 ops/ms -# Warmup Iteration 9: 2.173 ops/ms -# Warmup Iteration 10: 2.173 ops/ms -Iteration 1: 2.174 ops/ms -Iteration 2: 2.173 ops/ms -Iteration 3: 2.173 ops/ms -Iteration 4: 2.172 ops/ms -Iteration 5: 2.173 ops/ms -Iteration 6: 2.173 ops/ms -Iteration 7: 2.142 ops/ms -Iteration 8: 2.142 ops/ms -Iteration 9: 2.142 ops/ms -Iteration 10: 2.141 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.classicBeanItemSer": - 2.140 ±(99.9%) 0.017 ops/ms [Average] - (min, avg, max) = (2.088, 2.140, 2.203), stdev = 0.035 - CI (99.9%): [2.123, 2.157] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemDeser - -# Run progress: 52.38% complete, ETA 05:34:18 -# Fork: 1 of 5 -# Warmup Iteration 1: 373.099 ops/ms -# Warmup Iteration 2: 386.706 ops/ms -# Warmup Iteration 3: 386.418 ops/ms -# Warmup Iteration 4: 386.766 ops/ms -# Warmup Iteration 5: 386.708 ops/ms -# Warmup Iteration 6: 386.449 ops/ms -# Warmup Iteration 7: 386.559 ops/ms -# Warmup Iteration 8: 386.889 ops/ms -# Warmup Iteration 9: 386.682 ops/ms -# Warmup Iteration 10: 386.846 ops/ms -Iteration 1: 386.704 ops/ms -Iteration 2: 386.533 ops/ms -Iteration 3: 386.954 ops/ms -Iteration 4: 386.505 ops/ms -Iteration 5: 386.644 ops/ms -Iteration 6: 386.357 ops/ms -Iteration 7: 386.382 ops/ms -Iteration 8: 386.481 ops/ms -Iteration 9: 386.339 ops/ms -Iteration 10: 386.344 ops/ms - -# Run progress: 53.33% complete, ETA 05:27:37 -# Fork: 2 of 5 -# Warmup Iteration 1: 377.475 ops/ms -# Warmup Iteration 2: 390.200 ops/ms -# Warmup Iteration 3: 390.929 ops/ms -# Warmup Iteration 4: 390.733 ops/ms -# Warmup Iteration 5: 390.845 ops/ms -# Warmup Iteration 6: 390.707 ops/ms -# Warmup Iteration 7: 390.821 ops/ms -# Warmup Iteration 8: 390.675 ops/ms -# Warmup Iteration 9: 390.821 ops/ms -# Warmup Iteration 10: 390.695 ops/ms -Iteration 1: 390.970 ops/ms -Iteration 2: 390.646 ops/ms -Iteration 3: 390.650 ops/ms -Iteration 4: 390.814 ops/ms -Iteration 5: 390.476 ops/ms -Iteration 6: 390.735 ops/ms -Iteration 7: 389.087 ops/ms -Iteration 8: 389.450 ops/ms -Iteration 9: 389.219 ops/ms -Iteration 10: 389.325 ops/ms - -# Run progress: 54.29% complete, ETA 05:20:55 -# Fork: 3 of 5 -# Warmup Iteration 1: 372.393 ops/ms -# Warmup Iteration 2: 382.887 ops/ms -# Warmup Iteration 3: 383.158 ops/ms -# Warmup Iteration 4: 382.460 ops/ms -# Warmup Iteration 5: 382.930 ops/ms -# Warmup Iteration 6: 382.892 ops/ms -# Warmup Iteration 7: 382.128 ops/ms -# Warmup Iteration 8: 382.340 ops/ms -# Warmup Iteration 9: 382.534 ops/ms -# Warmup Iteration 10: 382.628 ops/ms -Iteration 1: 382.339 ops/ms -Iteration 2: 382.698 ops/ms -Iteration 3: 382.281 ops/ms -Iteration 4: 382.747 ops/ms -Iteration 5: 382.433 ops/ms -Iteration 6: 382.336 ops/ms -Iteration 7: 381.227 ops/ms -Iteration 8: 381.638 ops/ms -Iteration 9: 381.135 ops/ms -Iteration 10: 381.291 ops/ms - -# Run progress: 55.24% complete, ETA 05:14:15 -# Fork: 4 of 5 -# Warmup Iteration 1: 376.557 ops/ms -# Warmup Iteration 2: 389.824 ops/ms -# Warmup Iteration 3: 387.598 ops/ms -# Warmup Iteration 4: 387.706 ops/ms -# Warmup Iteration 5: 388.504 ops/ms -# Warmup Iteration 6: 387.822 ops/ms -# Warmup Iteration 7: 387.301 ops/ms -# Warmup Iteration 8: 387.832 ops/ms -# Warmup Iteration 9: 387.620 ops/ms -# Warmup Iteration 10: 386.801 ops/ms -Iteration 1: 387.770 ops/ms -Iteration 2: 387.595 ops/ms -Iteration 3: 387.981 ops/ms -Iteration 4: 387.718 ops/ms -Iteration 5: 387.554 ops/ms -Iteration 6: 386.737 ops/ms -Iteration 7: 385.877 ops/ms -Iteration 8: 386.851 ops/ms -Iteration 9: 386.506 ops/ms -Iteration 10: 386.397 ops/ms - -# Run progress: 56.19% complete, ETA 05:07:34 -# Fork: 5 of 5 -# Warmup Iteration 1: 374.670 ops/ms -# Warmup Iteration 2: 389.434 ops/ms -# Warmup Iteration 3: 390.256 ops/ms -# Warmup Iteration 4: 389.986 ops/ms -# Warmup Iteration 5: 390.065 ops/ms -# Warmup Iteration 6: 390.244 ops/ms -# Warmup Iteration 7: 390.221 ops/ms -# Warmup Iteration 8: 390.178 ops/ms -# Warmup Iteration 9: 389.840 ops/ms -# Warmup Iteration 10: 389.953 ops/ms -Iteration 1: 390.079 ops/ms -Iteration 2: 390.052 ops/ms -Iteration 3: 390.121 ops/ms -Iteration 4: 390.149 ops/ms -Iteration 5: 390.340 ops/ms -Iteration 6: 390.116 ops/ms -Iteration 7: 389.697 ops/ms -Iteration 8: 389.921 ops/ms -Iteration 9: 390.039 ops/ms -Iteration 10: 389.751 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemDeser": - 387.160 ±(99.9%) 1.507 ops/ms [Average] - (min, avg, max) = (381.135, 387.160, 390.970), stdev = 3.043 - CI (99.9%): [385.653, 388.666] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemSer - -# Run progress: 57.14% complete, ETA 05:00:54 -# Fork: 1 of 5 -# Warmup Iteration 1: 564.856 ops/ms -# Warmup Iteration 2: 563.840 ops/ms -# Warmup Iteration 3: 566.729 ops/ms -# Warmup Iteration 4: 566.415 ops/ms -# Warmup Iteration 5: 567.093 ops/ms -# Warmup Iteration 6: 566.916 ops/ms -# Warmup Iteration 7: 566.675 ops/ms -# Warmup Iteration 8: 566.657 ops/ms -# Warmup Iteration 9: 566.836 ops/ms -# Warmup Iteration 10: 566.812 ops/ms -Iteration 1: 567.264 ops/ms -Iteration 2: 567.316 ops/ms -Iteration 3: 566.845 ops/ms -Iteration 4: 567.368 ops/ms -Iteration 5: 566.994 ops/ms -Iteration 6: 567.245 ops/ms -Iteration 7: 567.747 ops/ms -Iteration 8: 567.346 ops/ms -Iteration 9: 567.455 ops/ms -Iteration 10: 567.475 ops/ms - -# Run progress: 58.10% complete, ETA 04:54:13 -# Fork: 2 of 5 -# Warmup Iteration 1: 570.368 ops/ms -# Warmup Iteration 2: 584.465 ops/ms -# Warmup Iteration 3: 586.427 ops/ms -# Warmup Iteration 4: 586.408 ops/ms -# Warmup Iteration 5: 585.084 ops/ms -# Warmup Iteration 6: 586.455 ops/ms -# Warmup Iteration 7: 586.442 ops/ms -# Warmup Iteration 8: 585.707 ops/ms -# Warmup Iteration 9: 586.499 ops/ms -# Warmup Iteration 10: 586.847 ops/ms -Iteration 1: 586.585 ops/ms -Iteration 2: 586.723 ops/ms -Iteration 3: 586.578 ops/ms -Iteration 4: 586.622 ops/ms -Iteration 5: 586.922 ops/ms -Iteration 6: 586.739 ops/ms -Iteration 7: 588.170 ops/ms -Iteration 8: 587.766 ops/ms -Iteration 9: 587.750 ops/ms -Iteration 10: 588.250 ops/ms - -# Run progress: 59.05% complete, ETA 04:47:32 -# Fork: 3 of 5 -# Warmup Iteration 1: 551.028 ops/ms -# Warmup Iteration 2: 561.184 ops/ms -# Warmup Iteration 3: 564.790 ops/ms -# Warmup Iteration 4: 563.672 ops/ms -# Warmup Iteration 5: 562.532 ops/ms -# Warmup Iteration 6: 561.632 ops/ms -# Warmup Iteration 7: 564.402 ops/ms -# Warmup Iteration 8: 565.039 ops/ms -# Warmup Iteration 9: 564.591 ops/ms -# Warmup Iteration 10: 565.852 ops/ms -Iteration 1: 565.253 ops/ms -Iteration 2: 565.408 ops/ms -Iteration 3: 561.840 ops/ms -Iteration 4: 561.538 ops/ms -Iteration 5: 565.281 ops/ms -Iteration 6: 565.467 ops/ms -Iteration 7: 561.995 ops/ms -Iteration 8: 562.708 ops/ms -Iteration 9: 562.319 ops/ms -Iteration 10: 562.103 ops/ms - -# Run progress: 60.00% complete, ETA 04:40:50 -# Fork: 4 of 5 -# Warmup Iteration 1: 574.157 ops/ms -# Warmup Iteration 2: 583.391 ops/ms -# Warmup Iteration 3: 587.119 ops/ms -# Warmup Iteration 4: 586.384 ops/ms -# Warmup Iteration 5: 587.410 ops/ms -# Warmup Iteration 6: 587.175 ops/ms -# Warmup Iteration 7: 587.178 ops/ms -# Warmup Iteration 8: 587.126 ops/ms -# Warmup Iteration 9: 587.133 ops/ms -# Warmup Iteration 10: 587.425 ops/ms -Iteration 1: 587.456 ops/ms -Iteration 2: 587.545 ops/ms -Iteration 3: 587.688 ops/ms -Iteration 4: 587.647 ops/ms -Iteration 5: 587.500 ops/ms -Iteration 6: 587.030 ops/ms -Iteration 7: 584.387 ops/ms -Iteration 8: 584.994 ops/ms -Iteration 9: 585.153 ops/ms -Iteration 10: 585.184 ops/ms - -# Run progress: 60.95% complete, ETA 04:34:09 -# Fork: 5 of 5 -# Warmup Iteration 1: 561.155 ops/ms -# Warmup Iteration 2: 575.469 ops/ms -# Warmup Iteration 3: 573.437 ops/ms -# Warmup Iteration 4: 574.134 ops/ms -# Warmup Iteration 5: 574.453 ops/ms -# Warmup Iteration 6: 574.563 ops/ms -# Warmup Iteration 7: 574.035 ops/ms -# Warmup Iteration 8: 575.710 ops/ms -# Warmup Iteration 9: 574.868 ops/ms -# Warmup Iteration 10: 575.173 ops/ms -Iteration 1: 575.730 ops/ms -Iteration 2: 574.667 ops/ms -Iteration 3: 576.177 ops/ms -Iteration 4: 576.256 ops/ms -Iteration 5: 574.222 ops/ms -Iteration 6: 575.649 ops/ms -Iteration 7: 573.770 ops/ms -Iteration 8: 572.502 ops/ms -Iteration 9: 573.370 ops/ms -Iteration 10: 574.174 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.classicMediaItemSer": - 575.804 ±(99.9%) 4.887 ops/ms [Average] - (min, avg, max) = (561.538, 575.804, 588.250), stdev = 9.871 - CI (99.9%): [570.917, 580.690] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Blackbird.constructorArrayDeser - -# Run progress: 61.90% complete, ETA 04:27:28 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.050 ops/ms -# Warmup Iteration 2: 1.119 ops/ms -# Warmup Iteration 3: 1.120 ops/ms -# Warmup Iteration 4: 1.119 ops/ms -# Warmup Iteration 5: 1.120 ops/ms -# Warmup Iteration 6: 1.116 ops/ms -# Warmup Iteration 7: 1.120 ops/ms -# Warmup Iteration 8: 1.120 ops/ms -# Warmup Iteration 9: 1.120 ops/ms -# Warmup Iteration 10: 1.118 ops/ms -Iteration 1: 1.120 ops/ms -Iteration 2: 1.120 ops/ms -Iteration 3: 1.120 ops/ms -Iteration 4: 1.120 ops/ms -Iteration 5: 1.120 ops/ms -Iteration 6: 1.120 ops/ms -Iteration 7: 1.120 ops/ms -Iteration 8: 1.119 ops/ms -Iteration 9: 1.119 ops/ms -Iteration 10: 1.119 ops/ms - -# Run progress: 62.86% complete, ETA 04:20:46 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.061 ops/ms -# Warmup Iteration 2: 1.126 ops/ms -# Warmup Iteration 3: 1.127 ops/ms -# Warmup Iteration 4: 1.127 ops/ms -# Warmup Iteration 5: 1.127 ops/ms -# Warmup Iteration 6: 1.123 ops/ms -# Warmup Iteration 7: 1.127 ops/ms -# Warmup Iteration 8: 1.127 ops/ms -# Warmup Iteration 9: 1.127 ops/ms -# Warmup Iteration 10: 1.126 ops/ms -Iteration 1: 1.127 ops/ms -Iteration 2: 1.127 ops/ms -Iteration 3: 1.128 ops/ms -Iteration 4: 1.127 ops/ms -Iteration 5: 1.127 ops/ms -Iteration 6: 1.127 ops/ms -Iteration 7: 1.128 ops/ms -Iteration 8: 1.128 ops/ms -Iteration 9: 1.127 ops/ms -Iteration 10: 1.128 ops/ms - -# Run progress: 63.81% complete, ETA 04:14:05 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.073 ops/ms -# Warmup Iteration 2: 1.130 ops/ms -# Warmup Iteration 3: 1.131 ops/ms -# Warmup Iteration 4: 1.131 ops/ms -# Warmup Iteration 5: 1.132 ops/ms -# Warmup Iteration 6: 1.128 ops/ms -# Warmup Iteration 7: 1.132 ops/ms -# Warmup Iteration 8: 1.132 ops/ms -# Warmup Iteration 9: 1.132 ops/ms -# Warmup Iteration 10: 1.132 ops/ms -Iteration 1: 1.133 ops/ms -Iteration 2: 1.133 ops/ms -Iteration 3: 1.133 ops/ms -Iteration 4: 1.133 ops/ms -Iteration 5: 1.132 ops/ms -Iteration 6: 1.097 ops/ms -Iteration 7: 1.132 ops/ms -Iteration 8: 1.132 ops/ms -Iteration 9: 1.132 ops/ms -Iteration 10: 1.132 ops/ms - -# Run progress: 64.76% complete, ETA 04:07:24 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.060 ops/ms -# Warmup Iteration 2: 1.125 ops/ms -# Warmup Iteration 3: 1.124 ops/ms -# Warmup Iteration 4: 1.125 ops/ms -# Warmup Iteration 5: 1.126 ops/ms -# Warmup Iteration 6: 1.122 ops/ms -# Warmup Iteration 7: 1.126 ops/ms -# Warmup Iteration 8: 1.126 ops/ms -# Warmup Iteration 9: 1.126 ops/ms -# Warmup Iteration 10: 1.126 ops/ms -Iteration 1: 1.127 ops/ms -Iteration 2: 1.126 ops/ms -Iteration 3: 1.126 ops/ms -Iteration 4: 1.126 ops/ms -Iteration 5: 1.126 ops/ms -Iteration 6: 1.126 ops/ms -Iteration 7: 1.122 ops/ms -Iteration 8: 1.122 ops/ms -Iteration 9: 1.122 ops/ms -Iteration 10: 1.122 ops/ms - -# Run progress: 65.71% complete, ETA 04:00:42 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.052 ops/ms -# Warmup Iteration 2: 1.112 ops/ms -# Warmup Iteration 3: 1.112 ops/ms -# Warmup Iteration 4: 1.112 ops/ms -# Warmup Iteration 5: 1.112 ops/ms -# Warmup Iteration 6: 1.111 ops/ms -# Warmup Iteration 7: 1.113 ops/ms -# Warmup Iteration 8: 1.112 ops/ms -# Warmup Iteration 9: 1.113 ops/ms -# Warmup Iteration 10: 1.109 ops/ms -Iteration 1: 1.113 ops/ms -Iteration 2: 1.113 ops/ms -Iteration 3: 1.112 ops/ms -Iteration 4: 1.113 ops/ms -Iteration 5: 1.112 ops/ms -Iteration 6: 1.112 ops/ms -Iteration 7: 1.105 ops/ms -Iteration 8: 1.105 ops/ms -Iteration 9: 1.105 ops/ms -Iteration 10: 1.105 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Blackbird.constructorArrayDeser": - 1.122 ±(99.9%) 0.004 ops/ms [Average] - (min, avg, max) = (1.097, 1.122, 1.133), stdev = 0.009 - CI (99.9%): [1.118, 1.126] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.beanArrayDeser - -# Run progress: 66.67% complete, ETA 03:54:01 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.125 ops/ms -# Warmup Iteration 2: 1.189 ops/ms -# Warmup Iteration 3: 1.189 ops/ms -# Warmup Iteration 4: 1.189 ops/ms -# Warmup Iteration 5: 1.188 ops/ms -# Warmup Iteration 6: 1.187 ops/ms -# Warmup Iteration 7: 1.188 ops/ms -# Warmup Iteration 8: 1.188 ops/ms -# Warmup Iteration 9: 1.182 ops/ms -# Warmup Iteration 10: 1.189 ops/ms -Iteration 1: 1.189 ops/ms -Iteration 2: 1.188 ops/ms -Iteration 3: 1.189 ops/ms -Iteration 4: 1.189 ops/ms -Iteration 5: 1.188 ops/ms -Iteration 6: 1.189 ops/ms -Iteration 7: 1.189 ops/ms -Iteration 8: 1.189 ops/ms -Iteration 9: 1.188 ops/ms -Iteration 10: 1.189 ops/ms - -# Run progress: 67.62% complete, ETA 03:47:20 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.123 ops/ms -# Warmup Iteration 2: 1.182 ops/ms -# Warmup Iteration 3: 1.183 ops/ms -# Warmup Iteration 4: 1.183 ops/ms -# Warmup Iteration 5: 1.182 ops/ms -# Warmup Iteration 6: 1.179 ops/ms -# Warmup Iteration 7: 1.182 ops/ms -# Warmup Iteration 8: 1.183 ops/ms -# Warmup Iteration 9: 1.180 ops/ms -# Warmup Iteration 10: 1.184 ops/ms -Iteration 1: 1.183 ops/ms -Iteration 2: 1.183 ops/ms -Iteration 3: 1.183 ops/ms -Iteration 4: 1.183 ops/ms -Iteration 5: 1.183 ops/ms -Iteration 6: 1.183 ops/ms -Iteration 7: 1.184 ops/ms -Iteration 8: 1.184 ops/ms -Iteration 9: 1.184 ops/ms -Iteration 10: 1.183 ops/ms - -# Run progress: 68.57% complete, ETA 03:40:38 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.115 ops/ms -# Warmup Iteration 2: 1.165 ops/ms -# Warmup Iteration 3: 1.166 ops/ms -# Warmup Iteration 4: 1.165 ops/ms -# Warmup Iteration 5: 1.166 ops/ms -# Warmup Iteration 6: 1.165 ops/ms -# Warmup Iteration 7: 1.166 ops/ms -# Warmup Iteration 8: 1.166 ops/ms -# Warmup Iteration 9: 1.165 ops/ms -# Warmup Iteration 10: 1.166 ops/ms -Iteration 1: 1.166 ops/ms -Iteration 2: 1.166 ops/ms -Iteration 3: 1.166 ops/ms -Iteration 4: 1.166 ops/ms -Iteration 5: 1.166 ops/ms -Iteration 6: 1.166 ops/ms -Iteration 7: 1.166 ops/ms -Iteration 8: 1.166 ops/ms -Iteration 9: 1.167 ops/ms -Iteration 10: 1.166 ops/ms - -# Run progress: 69.52% complete, ETA 03:33:57 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.143 ops/ms -# Warmup Iteration 2: 1.197 ops/ms -# Warmup Iteration 3: 1.197 ops/ms -# Warmup Iteration 4: 1.197 ops/ms -# Warmup Iteration 5: 1.197 ops/ms -# Warmup Iteration 6: 1.194 ops/ms -# Warmup Iteration 7: 1.197 ops/ms -# Warmup Iteration 8: 1.197 ops/ms -# Warmup Iteration 9: 1.193 ops/ms -# Warmup Iteration 10: 1.198 ops/ms -Iteration 1: 1.197 ops/ms -Iteration 2: 1.198 ops/ms -Iteration 3: 1.198 ops/ms -Iteration 4: 1.198 ops/ms -Iteration 5: 1.198 ops/ms -Iteration 6: 1.198 ops/ms -Iteration 7: 1.199 ops/ms -Iteration 8: 1.198 ops/ms -Iteration 9: 1.198 ops/ms -Iteration 10: 1.199 ops/ms - -# Run progress: 70.48% complete, ETA 03:27:16 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.118 ops/ms -# Warmup Iteration 2: 1.181 ops/ms -# Warmup Iteration 3: 1.181 ops/ms -# Warmup Iteration 4: 1.182 ops/ms -# Warmup Iteration 5: 1.182 ops/ms -# Warmup Iteration 6: 1.170 ops/ms -# Warmup Iteration 7: 1.182 ops/ms -# Warmup Iteration 8: 1.183 ops/ms -# Warmup Iteration 9: 1.180 ops/ms -# Warmup Iteration 10: 1.183 ops/ms -Iteration 1: 1.183 ops/ms -Iteration 2: 1.182 ops/ms -Iteration 3: 1.183 ops/ms -Iteration 4: 1.182 ops/ms -Iteration 5: 1.182 ops/ms -Iteration 6: 1.183 ops/ms -Iteration 7: 1.182 ops/ms -Iteration 8: 1.182 ops/ms -Iteration 9: 1.182 ops/ms -Iteration 10: 1.182 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.beanArrayDeser": - 1.184 ±(99.9%) 0.005 ops/ms [Average] - (min, avg, max) = (1.166, 1.184, 1.199), stdev = 0.010 - CI (99.9%): [1.179, 1.189] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.beanArraySer - -# Run progress: 71.43% complete, ETA 03:20:35 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.990 ops/ms -# Warmup Iteration 2: 2.045 ops/ms -# Warmup Iteration 3: 2.045 ops/ms -# Warmup Iteration 4: 2.046 ops/ms -# Warmup Iteration 5: 2.046 ops/ms -# Warmup Iteration 6: 2.047 ops/ms -# Warmup Iteration 7: 2.047 ops/ms -# Warmup Iteration 8: 2.047 ops/ms -# Warmup Iteration 9: 2.046 ops/ms -# Warmup Iteration 10: 2.047 ops/ms -Iteration 1: 2.047 ops/ms -Iteration 2: 2.047 ops/ms -Iteration 3: 2.047 ops/ms -Iteration 4: 2.047 ops/ms -Iteration 5: 2.047 ops/ms -Iteration 6: 2.047 ops/ms -Iteration 7: 2.037 ops/ms -Iteration 8: 2.037 ops/ms -Iteration 9: 2.038 ops/ms -Iteration 10: 2.037 ops/ms - -# Run progress: 72.38% complete, ETA 03:13:53 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.957 ops/ms -# Warmup Iteration 2: 2.012 ops/ms -# Warmup Iteration 3: 2.012 ops/ms -# Warmup Iteration 4: 2.013 ops/ms -# Warmup Iteration 5: 2.013 ops/ms -# Warmup Iteration 6: 2.014 ops/ms -# Warmup Iteration 7: 2.007 ops/ms -# Warmup Iteration 8: 2.006 ops/ms -# Warmup Iteration 9: 2.006 ops/ms -# Warmup Iteration 10: 2.007 ops/ms -Iteration 1: 2.007 ops/ms -Iteration 2: 2.005 ops/ms -Iteration 3: 2.007 ops/ms -Iteration 4: 2.007 ops/ms -Iteration 5: 2.007 ops/ms -Iteration 6: 2.003 ops/ms -Iteration 7: 2.016 ops/ms -Iteration 8: 2.016 ops/ms -Iteration 9: 2.016 ops/ms -Iteration 10: 2.016 ops/ms - -# Run progress: 73.33% complete, ETA 03:07:12 -# Fork: 3 of 5 -# Warmup Iteration 1: 2.017 ops/ms -# Warmup Iteration 2: 2.076 ops/ms -# Warmup Iteration 3: 2.077 ops/ms -# Warmup Iteration 4: 2.080 ops/ms -# Warmup Iteration 5: 2.081 ops/ms -# Warmup Iteration 6: 2.082 ops/ms -# Warmup Iteration 7: 2.082 ops/ms -# Warmup Iteration 8: 2.082 ops/ms -# Warmup Iteration 9: 2.082 ops/ms -# Warmup Iteration 10: 2.083 ops/ms -Iteration 1: 2.082 ops/ms -Iteration 2: 2.082 ops/ms -Iteration 3: 2.083 ops/ms -Iteration 4: 2.082 ops/ms -Iteration 5: 2.082 ops/ms -Iteration 6: 2.082 ops/ms -Iteration 7: 2.083 ops/ms -Iteration 8: 2.084 ops/ms -Iteration 9: 2.084 ops/ms -Iteration 10: 2.084 ops/ms - -# Run progress: 74.29% complete, ETA 03:00:31 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.948 ops/ms -# Warmup Iteration 2: 2.008 ops/ms -# Warmup Iteration 3: 2.008 ops/ms -# Warmup Iteration 4: 2.005 ops/ms -# Warmup Iteration 5: 2.008 ops/ms -# Warmup Iteration 6: 2.008 ops/ms -# Warmup Iteration 7: 2.011 ops/ms -# Warmup Iteration 8: 2.012 ops/ms -# Warmup Iteration 9: 2.011 ops/ms -# Warmup Iteration 10: 2.010 ops/ms -Iteration 1: 2.011 ops/ms -Iteration 2: 2.011 ops/ms -Iteration 3: 2.011 ops/ms -Iteration 4: 2.011 ops/ms -Iteration 5: 2.011 ops/ms -Iteration 6: 2.011 ops/ms -Iteration 7: 2.007 ops/ms -Iteration 8: 2.006 ops/ms -Iteration 9: 2.006 ops/ms -Iteration 10: 2.006 ops/ms - -# Run progress: 75.24% complete, ETA 02:53:50 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.989 ops/ms -# Warmup Iteration 2: 2.057 ops/ms -# Warmup Iteration 3: 2.057 ops/ms -# Warmup Iteration 4: 2.058 ops/ms -# Warmup Iteration 5: 2.059 ops/ms -# Warmup Iteration 6: 2.058 ops/ms -# Warmup Iteration 7: 2.058 ops/ms -# Warmup Iteration 8: 2.058 ops/ms -# Warmup Iteration 9: 2.058 ops/ms -# Warmup Iteration 10: 2.057 ops/ms -Iteration 1: 2.057 ops/ms -Iteration 2: 2.059 ops/ms -Iteration 3: 2.059 ops/ms -Iteration 4: 2.058 ops/ms -Iteration 5: 2.058 ops/ms -Iteration 6: 2.058 ops/ms -Iteration 7: 2.058 ops/ms -Iteration 8: 2.059 ops/ms -Iteration 9: 2.059 ops/ms -Iteration 10: 2.058 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.beanArraySer": - 2.041 ±(99.9%) 0.014 ops/ms [Average] - (min, avg, max) = (2.003, 2.041, 2.084), stdev = 0.029 - CI (99.9%): [2.026, 2.055] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.classicBeanItemDeser - -# Run progress: 76.19% complete, ETA 02:47:09 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.021 ops/ms -# Warmup Iteration 2: 1.060 ops/ms -# Warmup Iteration 3: 1.061 ops/ms -# Warmup Iteration 4: 1.061 ops/ms -# Warmup Iteration 5: 1.061 ops/ms -# Warmup Iteration 6: 1.060 ops/ms -# Warmup Iteration 7: 1.061 ops/ms -# Warmup Iteration 8: 1.062 ops/ms -# Warmup Iteration 9: 1.061 ops/ms -# Warmup Iteration 10: 1.061 ops/ms -Iteration 1: 1.062 ops/ms -Iteration 2: 1.062 ops/ms -Iteration 3: 1.062 ops/ms -Iteration 4: 1.062 ops/ms -Iteration 5: 1.062 ops/ms -Iteration 6: 1.062 ops/ms -Iteration 7: 1.062 ops/ms -Iteration 8: 1.062 ops/ms -Iteration 9: 1.062 ops/ms -Iteration 10: 1.062 ops/ms - -# Run progress: 77.14% complete, ETA 02:40:28 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.007 ops/ms -# Warmup Iteration 2: 1.048 ops/ms -# Warmup Iteration 3: 1.048 ops/ms -# Warmup Iteration 4: 1.047 ops/ms -# Warmup Iteration 5: 1.048 ops/ms -# Warmup Iteration 6: 1.047 ops/ms -# Warmup Iteration 7: 1.048 ops/ms -# Warmup Iteration 8: 1.048 ops/ms -# Warmup Iteration 9: 1.048 ops/ms -# Warmup Iteration 10: 1.047 ops/ms -Iteration 1: 1.048 ops/ms -Iteration 2: 1.048 ops/ms -Iteration 3: 1.048 ops/ms -Iteration 4: 1.048 ops/ms -Iteration 5: 1.048 ops/ms -Iteration 6: 1.048 ops/ms -Iteration 7: 1.048 ops/ms -Iteration 8: 1.048 ops/ms -Iteration 9: 1.048 ops/ms -Iteration 10: 1.048 ops/ms - -# Run progress: 78.10% complete, ETA 02:33:47 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.036 ops/ms -# Warmup Iteration 2: 1.088 ops/ms -# Warmup Iteration 3: 1.088 ops/ms -# Warmup Iteration 4: 1.088 ops/ms -# Warmup Iteration 5: 1.088 ops/ms -# Warmup Iteration 6: 1.082 ops/ms -# Warmup Iteration 7: 1.089 ops/ms -# Warmup Iteration 8: 1.089 ops/ms -# Warmup Iteration 9: 1.089 ops/ms -# Warmup Iteration 10: 1.089 ops/ms -Iteration 1: 1.089 ops/ms -Iteration 2: 1.089 ops/ms -Iteration 3: 1.089 ops/ms -Iteration 4: 1.089 ops/ms -Iteration 5: 1.089 ops/ms -Iteration 6: 1.090 ops/ms -Iteration 7: 1.089 ops/ms -Iteration 8: 1.089 ops/ms -Iteration 9: 1.088 ops/ms -Iteration 10: 1.089 ops/ms - -# Run progress: 79.05% complete, ETA 02:27:05 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.026 ops/ms -# Warmup Iteration 2: 1.065 ops/ms -# Warmup Iteration 3: 1.065 ops/ms -# Warmup Iteration 4: 1.065 ops/ms -# Warmup Iteration 5: 1.065 ops/ms -# Warmup Iteration 6: 1.062 ops/ms -# Warmup Iteration 7: 1.064 ops/ms -# Warmup Iteration 8: 1.064 ops/ms -# Warmup Iteration 9: 1.064 ops/ms -# Warmup Iteration 10: 1.064 ops/ms -Iteration 1: 1.066 ops/ms -Iteration 2: 1.066 ops/ms -Iteration 3: 1.066 ops/ms -Iteration 4: 1.065 ops/ms -Iteration 5: 1.065 ops/ms -Iteration 6: 1.065 ops/ms -Iteration 7: 1.066 ops/ms -Iteration 8: 1.065 ops/ms -Iteration 9: 1.065 ops/ms -Iteration 10: 1.066 ops/ms - -# Run progress: 80.00% complete, ETA 02:20:24 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.044 ops/ms -# Warmup Iteration 2: 1.084 ops/ms -# Warmup Iteration 3: 1.084 ops/ms -# Warmup Iteration 4: 1.083 ops/ms -# Warmup Iteration 5: 1.084 ops/ms -# Warmup Iteration 6: 1.082 ops/ms -# Warmup Iteration 7: 1.084 ops/ms -# Warmup Iteration 8: 1.083 ops/ms -# Warmup Iteration 9: 1.084 ops/ms -# Warmup Iteration 10: 1.084 ops/ms -Iteration 1: 1.085 ops/ms -Iteration 2: 1.085 ops/ms -Iteration 3: 1.085 ops/ms -Iteration 4: 1.085 ops/ms -Iteration 5: 1.085 ops/ms -Iteration 6: 1.084 ops/ms -Iteration 7: 1.089 ops/ms -Iteration 8: 1.089 ops/ms -Iteration 9: 1.089 ops/ms -Iteration 10: 1.089 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.classicBeanItemDeser": - 1.070 ±(99.9%) 0.008 ops/ms [Average] - (min, avg, max) = (1.048, 1.070, 1.090), stdev = 0.016 - CI (99.9%): [1.062, 1.078] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.classicBeanItemSer - -# Run progress: 80.95% complete, ETA 02:13:43 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.956 ops/ms -# Warmup Iteration 2: 2.010 ops/ms -# Warmup Iteration 3: 2.019 ops/ms -# Warmup Iteration 4: 2.021 ops/ms -# Warmup Iteration 5: 2.023 ops/ms -# Warmup Iteration 6: 2.018 ops/ms -# Warmup Iteration 7: 2.019 ops/ms -# Warmup Iteration 8: 2.018 ops/ms -# Warmup Iteration 9: 2.017 ops/ms -# Warmup Iteration 10: 2.018 ops/ms -Iteration 1: 2.018 ops/ms -Iteration 2: 2.019 ops/ms -Iteration 3: 2.018 ops/ms -Iteration 4: 2.017 ops/ms -Iteration 5: 2.018 ops/ms -Iteration 6: 2.016 ops/ms -Iteration 7: 2.007 ops/ms -Iteration 8: 2.007 ops/ms -Iteration 9: 2.006 ops/ms -Iteration 10: 2.007 ops/ms - -# Run progress: 81.90% complete, ETA 02:07:02 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.915 ops/ms -# Warmup Iteration 2: 1.976 ops/ms -# Warmup Iteration 3: 1.973 ops/ms -# Warmup Iteration 4: 1.965 ops/ms -# Warmup Iteration 5: 1.970 ops/ms -# Warmup Iteration 6: 1.971 ops/ms -# Warmup Iteration 7: 1.972 ops/ms -# Warmup Iteration 8: 1.970 ops/ms -# Warmup Iteration 9: 1.970 ops/ms -# Warmup Iteration 10: 1.972 ops/ms -Iteration 1: 1.973 ops/ms -Iteration 2: 1.972 ops/ms -Iteration 3: 1.972 ops/ms -Iteration 4: 1.972 ops/ms -Iteration 5: 1.972 ops/ms -Iteration 6: 1.973 ops/ms -Iteration 7: 1.982 ops/ms -Iteration 8: 1.982 ops/ms -Iteration 9: 1.980 ops/ms -Iteration 10: 1.982 ops/ms - -# Run progress: 82.86% complete, ETA 02:00:21 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.873 ops/ms -# Warmup Iteration 2: 1.906 ops/ms -# Warmup Iteration 3: 1.924 ops/ms -# Warmup Iteration 4: 1.924 ops/ms -# Warmup Iteration 5: 1.921 ops/ms -# Warmup Iteration 6: 1.925 ops/ms -# Warmup Iteration 7: 1.927 ops/ms -# Warmup Iteration 8: 1.929 ops/ms -# Warmup Iteration 9: 1.928 ops/ms -# Warmup Iteration 10: 1.929 ops/ms -Iteration 1: 1.926 ops/ms -Iteration 2: 1.926 ops/ms -Iteration 3: 1.929 ops/ms -Iteration 4: 1.929 ops/ms -Iteration 5: 1.929 ops/ms -Iteration 6: 1.929 ops/ms -Iteration 7: 1.920 ops/ms -Iteration 8: 1.920 ops/ms -Iteration 9: 1.921 ops/ms -Iteration 10: 1.920 ops/ms - -# Run progress: 83.81% complete, ETA 01:53:40 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.895 ops/ms -# Warmup Iteration 2: 1.920 ops/ms -# Warmup Iteration 3: 1.920 ops/ms -# Warmup Iteration 4: 1.932 ops/ms -# Warmup Iteration 5: 1.937 ops/ms -# Warmup Iteration 6: 1.934 ops/ms -# Warmup Iteration 7: 1.932 ops/ms -# Warmup Iteration 8: 1.930 ops/ms -# Warmup Iteration 9: 1.930 ops/ms -# Warmup Iteration 10: 1.930 ops/ms -Iteration 1: 1.931 ops/ms -Iteration 2: 1.930 ops/ms -Iteration 3: 1.930 ops/ms -Iteration 4: 1.931 ops/ms -Iteration 5: 1.932 ops/ms -Iteration 6: 1.930 ops/ms -Iteration 7: 1.926 ops/ms -Iteration 8: 1.927 ops/ms -Iteration 9: 1.928 ops/ms -Iteration 10: 1.929 ops/ms - -# Run progress: 84.76% complete, ETA 01:46:58 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.859 ops/ms -# Warmup Iteration 2: 1.900 ops/ms -# Warmup Iteration 3: 1.900 ops/ms -# Warmup Iteration 4: 1.894 ops/ms -# Warmup Iteration 5: 1.904 ops/ms -# Warmup Iteration 6: 1.895 ops/ms -# Warmup Iteration 7: 1.898 ops/ms -# Warmup Iteration 8: 1.897 ops/ms -# Warmup Iteration 9: 1.898 ops/ms -# Warmup Iteration 10: 1.899 ops/ms -Iteration 1: 1.898 ops/ms -Iteration 2: 1.897 ops/ms -Iteration 3: 1.898 ops/ms -Iteration 4: 1.899 ops/ms -Iteration 5: 1.898 ops/ms -Iteration 6: 1.898 ops/ms -Iteration 7: 1.906 ops/ms -Iteration 8: 1.907 ops/ms -Iteration 9: 1.907 ops/ms -Iteration 10: 1.907 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.classicBeanItemSer": - 1.949 ±(99.9%) 0.020 ops/ms [Average] - (min, avg, max) = (1.897, 1.949, 2.019), stdev = 0.041 - CI (99.9%): [1.929, 1.969] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.classicMediaItemDeser - -# Run progress: 85.71% complete, ETA 01:40:17 -# Fork: 1 of 5 -# Warmup Iteration 1: 307.076 ops/ms -# Warmup Iteration 2: 320.514 ops/ms -# Warmup Iteration 3: 319.693 ops/ms -# Warmup Iteration 4: 319.831 ops/ms -# Warmup Iteration 5: 319.237 ops/ms -# Warmup Iteration 6: 319.485 ops/ms -# Warmup Iteration 7: 319.655 ops/ms -# Warmup Iteration 8: 319.951 ops/ms -# Warmup Iteration 9: 319.970 ops/ms -# Warmup Iteration 10: 319.948 ops/ms -Iteration 1: 319.879 ops/ms -Iteration 2: 319.929 ops/ms -Iteration 3: 319.821 ops/ms -Iteration 4: 319.920 ops/ms -Iteration 5: 319.646 ops/ms -Iteration 6: 319.848 ops/ms -Iteration 7: 319.331 ops/ms -Iteration 8: 319.545 ops/ms -Iteration 9: 319.567 ops/ms -Iteration 10: 319.362 ops/ms - -# Run progress: 86.67% complete, ETA 01:33:36 -# Fork: 2 of 5 -# Warmup Iteration 1: 309.629 ops/ms -# Warmup Iteration 2: 322.616 ops/ms -# Warmup Iteration 3: 323.571 ops/ms -# Warmup Iteration 4: 323.505 ops/ms -# Warmup Iteration 5: 322.865 ops/ms -# Warmup Iteration 6: 323.349 ops/ms -# Warmup Iteration 7: 323.207 ops/ms -# Warmup Iteration 8: 323.478 ops/ms -# Warmup Iteration 9: 323.068 ops/ms -# Warmup Iteration 10: 323.566 ops/ms -Iteration 1: 323.341 ops/ms -Iteration 2: 323.430 ops/ms -Iteration 3: 323.300 ops/ms -Iteration 4: 323.341 ops/ms -Iteration 5: 323.077 ops/ms -Iteration 6: 323.379 ops/ms -Iteration 7: 321.567 ops/ms -Iteration 8: 321.919 ops/ms -Iteration 9: 321.842 ops/ms -Iteration 10: 321.934 ops/ms - -# Run progress: 87.62% complete, ETA 01:26:55 -# Fork: 3 of 5 -# Warmup Iteration 1: 303.332 ops/ms -# Warmup Iteration 2: 317.430 ops/ms -# Warmup Iteration 3: 317.701 ops/ms -# Warmup Iteration 4: 317.709 ops/ms -# Warmup Iteration 5: 318.044 ops/ms -# Warmup Iteration 6: 317.705 ops/ms -# Warmup Iteration 7: 317.675 ops/ms -# Warmup Iteration 8: 318.006 ops/ms -# Warmup Iteration 9: 317.763 ops/ms -# Warmup Iteration 10: 317.847 ops/ms -Iteration 1: 317.761 ops/ms -Iteration 2: 317.534 ops/ms -Iteration 3: 317.842 ops/ms -Iteration 4: 317.867 ops/ms -Iteration 5: 317.812 ops/ms -Iteration 6: 317.637 ops/ms -Iteration 7: 322.327 ops/ms -Iteration 8: 322.548 ops/ms -Iteration 9: 322.095 ops/ms -Iteration 10: 322.222 ops/ms - -# Run progress: 88.57% complete, ETA 01:20:14 -# Fork: 4 of 5 -# Warmup Iteration 1: 320.327 ops/ms -# Warmup Iteration 2: 337.747 ops/ms -# Warmup Iteration 3: 338.104 ops/ms -# Warmup Iteration 4: 338.374 ops/ms -# Warmup Iteration 5: 338.032 ops/ms -# Warmup Iteration 6: 338.216 ops/ms -# Warmup Iteration 7: 338.101 ops/ms -# Warmup Iteration 8: 338.265 ops/ms -# Warmup Iteration 9: 338.511 ops/ms -# Warmup Iteration 10: 338.356 ops/ms -Iteration 1: 338.195 ops/ms -Iteration 2: 338.283 ops/ms -Iteration 3: 338.456 ops/ms -Iteration 4: 338.282 ops/ms -Iteration 5: 338.314 ops/ms -Iteration 6: 338.311 ops/ms -Iteration 7: 339.568 ops/ms -Iteration 8: 339.515 ops/ms -Iteration 9: 339.438 ops/ms -Iteration 10: 339.442 ops/ms - -# Run progress: 89.52% complete, ETA 01:13:32 -# Fork: 5 of 5 -# Warmup Iteration 1: 312.903 ops/ms -# Warmup Iteration 2: 325.678 ops/ms -# Warmup Iteration 3: 326.211 ops/ms -# Warmup Iteration 4: 326.388 ops/ms -# Warmup Iteration 5: 326.058 ops/ms -# Warmup Iteration 6: 326.327 ops/ms -# Warmup Iteration 7: 326.052 ops/ms -# Warmup Iteration 8: 326.301 ops/ms -# Warmup Iteration 9: 326.313 ops/ms -# Warmup Iteration 10: 326.341 ops/ms -Iteration 1: 326.231 ops/ms -Iteration 2: 326.195 ops/ms -Iteration 3: 326.159 ops/ms -Iteration 4: 326.269 ops/ms -Iteration 5: 326.270 ops/ms -Iteration 6: 302.513 ops/ms -Iteration 7: 326.429 ops/ms -Iteration 8: 325.975 ops/ms -Iteration 9: 326.295 ops/ms -Iteration 10: 326.089 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.classicMediaItemDeser": - 324.917 ±(99.9%) 3.942 ops/ms [Average] - (min, avg, max) = (302.513, 324.917, 339.568), stdev = 7.963 - CI (99.9%): [320.975, 328.859] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.classicMediaItemSer - -# Run progress: 90.48% complete, ETA 01:06:51 -# Fork: 1 of 5 -# Warmup Iteration 1: 539.407 ops/ms -# Warmup Iteration 2: 542.896 ops/ms -# Warmup Iteration 3: 544.085 ops/ms -# Warmup Iteration 4: 543.990 ops/ms -# Warmup Iteration 5: 543.925 ops/ms -# Warmup Iteration 6: 543.329 ops/ms -# Warmup Iteration 7: 544.025 ops/ms -# Warmup Iteration 8: 543.787 ops/ms -# Warmup Iteration 9: 543.440 ops/ms -# Warmup Iteration 10: 543.892 ops/ms -Iteration 1: 544.268 ops/ms -Iteration 2: 544.181 ops/ms -Iteration 3: 543.758 ops/ms -Iteration 4: 544.266 ops/ms -Iteration 5: 544.294 ops/ms -Iteration 6: 543.711 ops/ms -Iteration 7: 541.672 ops/ms -Iteration 8: 542.018 ops/ms -Iteration 9: 542.206 ops/ms -Iteration 10: 542.150 ops/ms - -# Run progress: 91.43% complete, ETA 01:00:10 -# Fork: 2 of 5 -# Warmup Iteration 1: 551.494 ops/ms -# Warmup Iteration 2: 560.189 ops/ms -# Warmup Iteration 3: 562.946 ops/ms -# Warmup Iteration 4: 561.429 ops/ms -# Warmup Iteration 5: 562.786 ops/ms -# Warmup Iteration 6: 561.624 ops/ms -# Warmup Iteration 7: 562.618 ops/ms -# Warmup Iteration 8: 562.230 ops/ms -# Warmup Iteration 9: 562.240 ops/ms -# Warmup Iteration 10: 561.966 ops/ms -Iteration 1: 562.113 ops/ms -Iteration 2: 562.443 ops/ms -Iteration 3: 563.336 ops/ms -Iteration 4: 562.691 ops/ms -Iteration 5: 563.134 ops/ms -Iteration 6: 562.152 ops/ms -Iteration 7: 557.290 ops/ms -Iteration 8: 556.683 ops/ms -Iteration 9: 556.708 ops/ms -Iteration 10: 556.262 ops/ms - -# Run progress: 92.38% complete, ETA 00:53:29 -# Fork: 3 of 5 -# Warmup Iteration 1: 557.204 ops/ms -# Warmup Iteration 2: 576.852 ops/ms -# Warmup Iteration 3: 570.713 ops/ms -# Warmup Iteration 4: 570.709 ops/ms -# Warmup Iteration 5: 570.457 ops/ms -# Warmup Iteration 6: 570.508 ops/ms -# Warmup Iteration 7: 570.360 ops/ms -# Warmup Iteration 8: 570.504 ops/ms -# Warmup Iteration 9: 570.057 ops/ms -# Warmup Iteration 10: 569.930 ops/ms -Iteration 1: 570.497 ops/ms -Iteration 2: 570.700 ops/ms -Iteration 3: 570.959 ops/ms -Iteration 4: 571.111 ops/ms -Iteration 5: 570.818 ops/ms -Iteration 6: 570.567 ops/ms -Iteration 7: 576.793 ops/ms -Iteration 8: 576.430 ops/ms -Iteration 9: 576.307 ops/ms -Iteration 10: 576.468 ops/ms - -# Run progress: 93.33% complete, ETA 00:46:48 -# Fork: 4 of 5 -# Warmup Iteration 1: 567.322 ops/ms -# Warmup Iteration 2: 585.424 ops/ms -# Warmup Iteration 3: 582.046 ops/ms -# Warmup Iteration 4: 582.700 ops/ms -# Warmup Iteration 5: 584.687 ops/ms -# Warmup Iteration 6: 582.838 ops/ms -# Warmup Iteration 7: 585.508 ops/ms -# Warmup Iteration 8: 581.788 ops/ms -# Warmup Iteration 9: 583.876 ops/ms -# Warmup Iteration 10: 583.505 ops/ms -Iteration 1: 583.863 ops/ms -Iteration 2: 583.229 ops/ms -Iteration 3: 582.765 ops/ms -Iteration 4: 583.484 ops/ms -Iteration 5: 583.840 ops/ms -Iteration 6: 583.558 ops/ms -Iteration 7: 584.770 ops/ms -Iteration 8: 584.107 ops/ms -Iteration 9: 584.604 ops/ms -Iteration 10: 583.438 ops/ms - -# Run progress: 94.29% complete, ETA 00:40:06 -# Fork: 5 of 5 -# Warmup Iteration 1: 552.230 ops/ms -# Warmup Iteration 2: 557.255 ops/ms -# Warmup Iteration 3: 559.884 ops/ms -# Warmup Iteration 4: 560.204 ops/ms -# Warmup Iteration 5: 559.436 ops/ms -# Warmup Iteration 6: 558.909 ops/ms -# Warmup Iteration 7: 559.537 ops/ms -# Warmup Iteration 8: 559.250 ops/ms -# Warmup Iteration 9: 559.918 ops/ms -# Warmup Iteration 10: 559.781 ops/ms -Iteration 1: 559.995 ops/ms -Iteration 2: 559.292 ops/ms -Iteration 3: 560.061 ops/ms -Iteration 4: 559.463 ops/ms -Iteration 5: 559.969 ops/ms -Iteration 6: 559.677 ops/ms -Iteration 7: 560.088 ops/ms -Iteration 8: 560.808 ops/ms -Iteration 9: 559.997 ops/ms -Iteration 10: 561.092 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.classicMediaItemSer": - 564.082 ±(99.9%) 6.896 ops/ms [Average] - (min, avg, max) = (541.672, 564.082, 584.770), stdev = 13.931 - CI (99.9%): [557.185, 570.978] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: 10 iterations, 10 s each -# Measurement: 10 iterations, 30 s each -# Timeout: 10 min per iteration -# Threads: 1 thread, will synchronize iterations -# Benchmark mode: Throughput, ops/time -# Benchmark: com.fasterxml.jackson.module.blackbird.Vanilla.constructorArrayDeser - -# Run progress: 95.24% complete, ETA 00:33:25 -# Fork: 1 of 5 -# Warmup Iteration 1: 1.040 ops/ms -# Warmup Iteration 2: 1.101 ops/ms -# Warmup Iteration 3: 1.102 ops/ms -# Warmup Iteration 4: 1.102 ops/ms -# Warmup Iteration 5: 1.101 ops/ms -# Warmup Iteration 6: 1.098 ops/ms -# Warmup Iteration 7: 1.102 ops/ms -# Warmup Iteration 8: 1.102 ops/ms -# Warmup Iteration 9: 1.101 ops/ms -# Warmup Iteration 10: 1.097 ops/ms -Iteration 1: 1.102 ops/ms -Iteration 2: 1.102 ops/ms -Iteration 3: 1.102 ops/ms -Iteration 4: 1.102 ops/ms -Iteration 5: 1.102 ops/ms -Iteration 6: 1.102 ops/ms -Iteration 7: 1.102 ops/ms -Iteration 8: 1.101 ops/ms -Iteration 9: 1.102 ops/ms -Iteration 10: 1.102 ops/ms - -# Run progress: 96.19% complete, ETA 00:26:44 -# Fork: 2 of 5 -# Warmup Iteration 1: 1.054 ops/ms -# Warmup Iteration 2: 1.119 ops/ms -# Warmup Iteration 3: 1.120 ops/ms -# Warmup Iteration 4: 1.120 ops/ms -# Warmup Iteration 5: 1.120 ops/ms -# Warmup Iteration 6: 1.118 ops/ms -# Warmup Iteration 7: 1.121 ops/ms -# Warmup Iteration 8: 1.120 ops/ms -# Warmup Iteration 9: 1.121 ops/ms -# Warmup Iteration 10: 1.120 ops/ms -Iteration 1: 1.120 ops/ms -Iteration 2: 1.120 ops/ms -Iteration 3: 1.121 ops/ms -Iteration 4: 1.120 ops/ms -Iteration 5: 1.119 ops/ms -Iteration 6: 1.120 ops/ms -Iteration 7: 1.121 ops/ms -Iteration 8: 1.122 ops/ms -Iteration 9: 1.122 ops/ms -Iteration 10: 1.122 ops/ms - -# Run progress: 97.14% complete, ETA 00:20:03 -# Fork: 3 of 5 -# Warmup Iteration 1: 1.060 ops/ms -# Warmup Iteration 2: 1.116 ops/ms -# Warmup Iteration 3: 1.116 ops/ms -# Warmup Iteration 4: 1.117 ops/ms -# Warmup Iteration 5: 1.115 ops/ms -# Warmup Iteration 6: 1.112 ops/ms -# Warmup Iteration 7: 1.116 ops/ms -# Warmup Iteration 8: 1.115 ops/ms -# Warmup Iteration 9: 1.116 ops/ms -# Warmup Iteration 10: 1.112 ops/ms -Iteration 1: 1.116 ops/ms -Iteration 2: 1.116 ops/ms -Iteration 3: 1.116 ops/ms -Iteration 4: 1.116 ops/ms -Iteration 5: 1.116 ops/ms -Iteration 6: 1.115 ops/ms -Iteration 7: 1.115 ops/ms -Iteration 8: 1.115 ops/ms -Iteration 9: 1.115 ops/ms -Iteration 10: 1.115 ops/ms - -# Run progress: 98.10% complete, ETA 00:13:22 -# Fork: 4 of 5 -# Warmup Iteration 1: 1.062 ops/ms -# Warmup Iteration 2: 1.110 ops/ms -# Warmup Iteration 3: 1.111 ops/ms -# Warmup Iteration 4: 1.110 ops/ms -# Warmup Iteration 5: 1.111 ops/ms -# Warmup Iteration 6: 1.106 ops/ms -# Warmup Iteration 7: 1.115 ops/ms -# Warmup Iteration 8: 1.115 ops/ms -# Warmup Iteration 9: 1.115 ops/ms -# Warmup Iteration 10: 1.112 ops/ms -Iteration 1: 1.116 ops/ms -Iteration 2: 1.115 ops/ms -Iteration 3: 1.115 ops/ms -Iteration 4: 1.116 ops/ms -Iteration 5: 1.116 ops/ms -Iteration 6: 1.114 ops/ms -Iteration 7: 1.118 ops/ms -Iteration 8: 1.118 ops/ms -Iteration 9: 1.118 ops/ms -Iteration 10: 1.117 ops/ms - -# Run progress: 99.05% complete, ETA 00:06:41 -# Fork: 5 of 5 -# Warmup Iteration 1: 1.061 ops/ms -# Warmup Iteration 2: 1.114 ops/ms -# Warmup Iteration 3: 1.115 ops/ms -# Warmup Iteration 4: 1.114 ops/ms -# Warmup Iteration 5: 1.114 ops/ms -# Warmup Iteration 6: 1.107 ops/ms -# Warmup Iteration 7: 1.114 ops/ms -# Warmup Iteration 8: 1.114 ops/ms -# Warmup Iteration 9: 1.114 ops/ms -# Warmup Iteration 10: 1.111 ops/ms -Iteration 1: 1.114 ops/ms -Iteration 2: 1.114 ops/ms -Iteration 3: 1.114 ops/ms -Iteration 4: 1.115 ops/ms -Iteration 5: 1.114 ops/ms -Iteration 6: 1.114 ops/ms -Iteration 7: 1.115 ops/ms -Iteration 8: 1.115 ops/ms -Iteration 9: 1.114 ops/ms -Iteration 10: 1.114 ops/ms - - -Result "com.fasterxml.jackson.module.blackbird.Vanilla.constructorArrayDeser": - 1.114 ±(99.9%) 0.003 ops/ms [Average] - (min, avg, max) = (1.101, 1.114, 1.122), stdev = 0.006 - CI (99.9%): [1.111, 1.117] (assumes normal distribution) - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: -# Measurement: 1 iterations, single-shot each -# Timeout: 10 min per iteration -# Threads: 1 thread -# Benchmark mode: Single shot invocation time -# Benchmark: com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.afterburner - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 1 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -314.001 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 2 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -307.692 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 3 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -284.860 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 4 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -315.952 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 5 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -312.115 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 6 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -292.334 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 7 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -286.988 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 8 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -257.177 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 9 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -307.035 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 10 of 10 -Iteration 1: WARNING: An illegal reflective access operation has occurred -WARNING: Illegal reflective access by com.fasterxml.jackson.module.afterburner.util.MyClassLoader (file:/home/steven/code/jackson-modules-base/blackbird/benchmarks/target/benchmarks.jar) to method java.lang.ClassLoader.findLoadedClass(java.lang.String) -WARNING: Please consider reporting this to the maintainers of com.fasterxml.jackson.module.afterburner.util.MyClassLoader -WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations -WARNING: All illegal access operations will be denied in a future release -287.438 ms/op - - -Result "com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.afterburner": - N = 10 - mean = 296.559 ±(99.9%) 27.770 ms/op - - Histogram, ms/op: - [250.000, 255.000) = 0 - [255.000, 260.000) = 1 - [260.000, 265.000) = 0 - [265.000, 270.000) = 0 - [270.000, 275.000) = 0 - [275.000, 280.000) = 0 - [280.000, 285.000) = 1 - [285.000, 290.000) = 2 - [290.000, 295.000) = 1 - [295.000, 300.000) = 0 - [300.000, 305.000) = 0 - [305.000, 310.000) = 2 - [310.000, 315.000) = 2 - - Percentiles, ms/op: - p(0.0000) = 257.177 ms/op - p(50.0000) = 299.685 ms/op - p(90.0000) = 315.757 ms/op - p(95.0000) = 315.952 ms/op - p(99.0000) = 315.952 ms/op - p(99.9000) = 315.952 ms/op - p(99.9900) = 315.952 ms/op - p(99.9990) = 315.952 ms/op - p(99.9999) = 315.952 ms/op - p(100.0000) = 315.952 ms/op - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: -# Measurement: 1 iterations, single-shot each -# Timeout: 10 min per iteration -# Threads: 1 thread -# Benchmark mode: Single shot invocation time -# Benchmark: com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.blackbird - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 1 of 10 -Iteration 1: 265.623 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 2 of 10 -Iteration 1: 312.177 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 3 of 10 -Iteration 1: 292.352 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 4 of 10 -Iteration 1: 299.969 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 5 of 10 -Iteration 1: 267.998 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 6 of 10 -Iteration 1: 286.434 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 7 of 10 -Iteration 1: 291.120 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 8 of 10 -Iteration 1: 300.891 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 9 of 10 -Iteration 1: 320.951 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 10 of 10 -Iteration 1: 282.491 ms/op - - -Result "com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.blackbird": - N = 10 - mean = 292.001 ±(99.9%) 26.581 ms/op - - Histogram, ms/op: - [260.000, 265.000) = 0 - [265.000, 270.000) = 2 - [270.000, 275.000) = 0 - [275.000, 280.000) = 0 - [280.000, 285.000) = 1 - [285.000, 290.000) = 1 - [290.000, 295.000) = 2 - [295.000, 300.000) = 1 - [300.000, 305.000) = 1 - [305.000, 310.000) = 0 - [310.000, 315.000) = 1 - [315.000, 320.000) = 0 - [320.000, 325.000) = 1 - - Percentiles, ms/op: - p(0.0000) = 265.623 ms/op - p(50.0000) = 291.736 ms/op - p(90.0000) = 320.074 ms/op - p(95.0000) = 320.951 ms/op - p(99.0000) = 320.951 ms/op - p(99.9000) = 320.951 ms/op - p(99.9900) = 320.951 ms/op - p(99.9990) = 320.951 ms/op - p(99.9999) = 320.951 ms/op - p(100.0000) = 320.951 ms/op - - -# JMH version: 1.25 -# VM version: JDK 14.0.2, OpenJDK 64-Bit Server VM, 14.0.2+12 -# VM invoker: /usr/lib/jvm/java-14-openjdk-14.0.2.12-1.rolling.fc32.x86_64/bin/java -# VM options: -# Warmup: -# Measurement: 1 iterations, single-shot each -# Timeout: 10 min per iteration -# Threads: 1 thread -# Benchmark mode: Single shot invocation time -# Benchmark: com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.vanilla - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 1 of 10 -Iteration 1: 249.109 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 2 of 10 -Iteration 1: 268.947 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 3 of 10 -Iteration 1: 270.761 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 4 of 10 -Iteration 1: 241.265 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 5 of 10 -Iteration 1: 254.474 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 6 of 10 -Iteration 1: 261.242 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 7 of 10 -Iteration 1: 253.752 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 8 of 10 -Iteration 1: 260.014 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 9 of 10 -Iteration 1: 259.565 ms/op - -# Run progress: 100.00% complete, ETA 00:00:00 -# Fork: 10 of 10 -Iteration 1: 247.064 ms/op - - -Result "com.fasterxml.jackson.module.blackbird.StartupTimeBenchmark.vanilla": - N = 10 - mean = 256.619 ±(99.9%) 14.163 ms/op - - Histogram, ms/op: - [240.000, 242.500) = 1 - [242.500, 245.000) = 0 - [245.000, 247.500) = 1 - [247.500, 250.000) = 1 - [250.000, 252.500) = 0 - [252.500, 255.000) = 2 - [255.000, 257.500) = 0 - [257.500, 260.000) = 1 - [260.000, 262.500) = 2 - [262.500, 265.000) = 0 - [265.000, 267.500) = 0 - [267.500, 270.000) = 1 - [270.000, 272.500) = 1 - [272.500, 275.000) = 0 - [275.000, 277.500) = 0 - - Percentiles, ms/op: - p(0.0000) = 241.265 ms/op - p(50.0000) = 257.020 ms/op - p(90.0000) = 270.580 ms/op - p(95.0000) = 270.761 ms/op - p(99.0000) = 270.761 ms/op - p(99.9000) = 270.761 ms/op - p(99.9900) = 270.761 ms/op - p(99.9990) = 270.761 ms/op - p(99.9999) = 270.761 ms/op - p(100.0000) = 270.761 ms/op - - -# Run complete. Total time: 11:42:19 - -REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on -why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial -experiments, perform baseline and negative tests that provide experimental control, make sure -the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. -Do not assume the numbers tell you what you want them to tell. - -Benchmark Mode Cnt Score Error Units -Afterburner.beanArrayDeser thrpt 50 1.299 ± 0.009 ops/ms -Afterburner.beanArraySer thrpt 50 2.193 ± 0.010 ops/ms -Afterburner.classicBeanItemDeser thrpt 50 1.398 ± 0.024 ops/ms -Afterburner.classicBeanItemSer thrpt 50 2.325 ± 0.024 ops/ms -Afterburner.classicMediaItemDeser thrpt 50 403.335 ± 1.337 ops/ms -Afterburner.classicMediaItemSer thrpt 50 610.446 ± 1.996 ops/ms -Afterburner.constructorArrayDeser thrpt 50 1.124 ± 0.004 ops/ms -Blackbird.beanArrayDeser thrpt 50 1.301 ± 0.005 ops/ms -Blackbird.beanArraySer thrpt 50 2.115 ± 0.009 ops/ms -Blackbird.classicBeanItemDeser thrpt 50 1.250 ± 0.017 ops/ms -Blackbird.classicBeanItemSer thrpt 50 2.140 ± 0.017 ops/ms -Blackbird.classicMediaItemDeser thrpt 50 387.160 ± 1.507 ops/ms -Blackbird.classicMediaItemSer thrpt 50 575.804 ± 4.887 ops/ms -Blackbird.constructorArrayDeser thrpt 50 1.122 ± 0.004 ops/ms -Vanilla.beanArrayDeser thrpt 50 1.184 ± 0.005 ops/ms -Vanilla.beanArraySer thrpt 50 2.041 ± 0.014 ops/ms -Vanilla.classicBeanItemDeser thrpt 50 1.070 ± 0.008 ops/ms -Vanilla.classicBeanItemSer thrpt 50 1.949 ± 0.020 ops/ms -Vanilla.classicMediaItemDeser thrpt 50 324.917 ± 3.942 ops/ms -Vanilla.classicMediaItemSer thrpt 50 564.082 ± 6.896 ops/ms -Vanilla.constructorArrayDeser thrpt 50 1.114 ± 0.003 ops/ms -StartupTimeBenchmark.afterburner ss 10 296.559 ± 27.770 ms/op -StartupTimeBenchmark.blackbird ss 10 292.001 ± 26.581 ms/op -StartupTimeBenchmark.vanilla ss 10 256.619 ± 14.163 ms/op diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Afterburner.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Afterburner.java deleted file mode 100644 index 795503bb3..000000000 --- a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Afterburner.java +++ /dev/null @@ -1,24 +0,0 @@ -package tools.jackson.module.blackbird; - -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; -import tools.jackson.module.afterburner.AfterburnerModule; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -public class Afterburner extends BaseBenchmark { - public static void main(String[] args) throws RunnerException { - Options options = new OptionsBuilder() - .include(Afterburner.class.getSimpleName()) - .forks(1) - .build(); - new Runner(options).run(); - } - - @Override - protected ObjectMapper createObjectMapper() { - return JsonMapper.builder().addModule(new AfterburnerModule()).build(); - } -} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Blackbird.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Blackbird.java deleted file mode 100644 index f5af04ba5..000000000 --- a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Blackbird.java +++ /dev/null @@ -1,23 +0,0 @@ -package tools.jackson.module.blackbird; - -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -public class Blackbird extends BaseBenchmark { - public static void main(String[] args) throws RunnerException { - Options options = new OptionsBuilder() - .include(Blackbird.class.getSimpleName()) - .forks(1) - .build(); - new Runner(options).run(); - } - - @Override - protected ObjectMapper createObjectMapper() { - return JsonMapper.builder().addModule(new BlackbirdModule()).build(); - } -} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/BuilderBenchmark.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/BuilderBenchmark.java new file mode 100644 index 000000000..5f2d385d3 --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/BuilderBenchmark.java @@ -0,0 +1,55 @@ +package tools.jackson.module.blackbird; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import tools.jackson.databind.ObjectReader; +import tools.jackson.databind.ObjectWriter; + +/** + * Builder-based bean: one {@link Invoice} with eight scalar properties. + */ +@State(Scope.Thread) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +public class BuilderBenchmark +{ + @Param({"stock", "blackbird"}) + public String mode; + + private ObjectReader reader; + private ObjectWriter writer; + private byte[] json; + + @Setup + public void setup() { + var mapper = Mappers.create(mode); + reader = mapper.readerFor(Invoice.class); + writer = mapper.writerFor(Invoice.class); + json = writer.writeValueAsBytes(Invoice.SAMPLE); + } + + @Benchmark + public Invoice builderDeser() { + return reader.readValue(json); + } + + @Benchmark + public byte[] builderSer() { + return writer.writeValueAsBytes(Invoice.SAMPLE); + } +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/ColdPojo.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/ColdPojo.java new file mode 100644 index 000000000..c67e74f4f --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/ColdPojo.java @@ -0,0 +1,34 @@ +package tools.jackson.module.blackbird; + +/** + * Properties of the setter beans in {@link ColdTypes}, with the same names + * and types as the components of the records there. + */ +public abstract class ColdPojo +{ + private String a; + private int b; + private long c; + private boolean d; + private String e; + private Integer f; + private double g; + private String h; + + public String getA() { return a; } + public void setA(String v) { a = v; } + public int getB() { return b; } + public void setB(int v) { b = v; } + public long getC() { return c; } + public void setC(long v) { c = v; } + public boolean isD() { return d; } + public void setD(boolean v) { d = v; } + public String getE() { return e; } + public void setE(String v) { e = v; } + public Integer getF() { return f; } + public void setF(Integer v) { f = v; } + public double getG() { return g; } + public void setG(double v) { g = v; } + public String getH() { return h; } + public void setH(String v) { h = v; } +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/ColdStartBenchmark.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/ColdStartBenchmark.java new file mode 100644 index 000000000..db1e93a73 --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/ColdStartBenchmark.java @@ -0,0 +1,58 @@ +package tools.jackson.module.blackbird; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import tools.jackson.databind.ObjectMapper; + +/** + * First use of 500 distinct types ({@link ColdTypes}) in a fresh JVM: one + * read and one write of each type, which includes introspection and, with + * Blackbird, code generation. Divide the score by 500 for the cost per type. + */ +@State(Scope.Thread) +@BenchmarkMode(Mode.SingleShotTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 0) +@Measurement(iterations = 1) +@Fork(20) +public class ColdStartBenchmark +{ + private static final byte[] JSON = ("{\"a\":\"s\",\"b\":7,\"c\":9,\"d\":true," + + "\"e\":\"t\",\"f\":3,\"g\":1.5,\"h\":\"u\"}").getBytes(); + + @Param({"stock", "blackbird"}) + public String mode; + + private ObjectMapper mapper; + private Class[] types; + + @Setup + public void setup() { + mapper = Mappers.create(mode); + types = ColdTypes.class.getClasses(); + Arrays.sort(types, Comparator.comparing(Class::getName)); + } + + @Benchmark + public void firstUse(Blackhole bh) { + for (Class type : types) { + Object value = mapper.readValue(JSON, type); + bh.consume(mapper.writeValueAsBytes(value)); + } + } +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/ColdTypes.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/ColdTypes.java new file mode 100644 index 000000000..c11b021cc --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/ColdTypes.java @@ -0,0 +1,513 @@ +package tools.jackson.module.blackbird; + +/** + * 250 records and 250 setter beans, each a distinct type for databind + * introspection and for Blackbird code generation. The beans share their + * properties through {@link ColdPojo}; the records declare their own. + */ +public final class ColdTypes +{ + private ColdTypes() { } + + public record R000(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R001(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R002(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R003(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R004(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R005(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R006(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R007(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R008(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R009(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R010(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R011(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R012(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R013(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R014(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R015(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R016(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R017(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R018(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R019(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R020(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R021(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R022(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R023(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R024(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R025(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R026(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R027(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R028(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R029(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R030(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R031(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R032(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R033(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R034(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R035(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R036(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R037(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R038(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R039(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R040(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R041(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R042(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R043(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R044(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R045(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R046(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R047(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R048(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R049(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R050(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R051(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R052(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R053(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R054(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R055(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R056(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R057(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R058(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R059(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R060(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R061(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R062(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R063(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R064(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R065(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R066(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R067(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R068(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R069(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R070(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R071(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R072(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R073(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R074(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R075(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R076(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R077(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R078(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R079(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R080(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R081(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R082(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R083(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R084(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R085(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R086(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R087(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R088(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R089(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R090(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R091(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R092(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R093(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R094(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R095(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R096(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R097(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R098(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R099(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R100(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R101(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R102(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R103(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R104(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R105(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R106(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R107(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R108(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R109(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R110(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R111(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R112(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R113(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R114(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R115(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R116(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R117(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R118(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R119(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R120(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R121(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R122(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R123(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R124(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R125(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R126(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R127(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R128(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R129(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R130(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R131(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R132(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R133(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R134(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R135(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R136(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R137(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R138(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R139(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R140(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R141(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R142(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R143(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R144(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R145(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R146(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R147(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R148(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R149(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R150(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R151(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R152(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R153(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R154(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R155(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R156(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R157(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R158(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R159(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R160(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R161(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R162(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R163(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R164(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R165(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R166(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R167(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R168(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R169(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R170(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R171(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R172(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R173(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R174(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R175(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R176(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R177(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R178(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R179(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R180(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R181(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R182(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R183(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R184(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R185(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R186(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R187(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R188(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R189(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R190(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R191(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R192(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R193(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R194(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R195(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R196(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R197(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R198(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R199(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R200(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R201(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R202(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R203(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R204(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R205(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R206(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R207(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R208(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R209(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R210(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R211(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R212(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R213(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R214(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R215(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R216(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R217(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R218(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R219(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R220(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R221(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R222(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R223(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R224(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R225(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R226(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R227(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R228(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R229(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R230(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R231(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R232(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R233(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R234(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R235(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R236(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R237(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R238(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R239(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R240(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R241(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R242(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R243(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R244(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R245(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R246(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R247(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R248(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + public record R249(String a, int b, long c, boolean d, String e, Integer f, double g, String h) { } + + public static class P000 extends ColdPojo { } + public static class P001 extends ColdPojo { } + public static class P002 extends ColdPojo { } + public static class P003 extends ColdPojo { } + public static class P004 extends ColdPojo { } + public static class P005 extends ColdPojo { } + public static class P006 extends ColdPojo { } + public static class P007 extends ColdPojo { } + public static class P008 extends ColdPojo { } + public static class P009 extends ColdPojo { } + public static class P010 extends ColdPojo { } + public static class P011 extends ColdPojo { } + public static class P012 extends ColdPojo { } + public static class P013 extends ColdPojo { } + public static class P014 extends ColdPojo { } + public static class P015 extends ColdPojo { } + public static class P016 extends ColdPojo { } + public static class P017 extends ColdPojo { } + public static class P018 extends ColdPojo { } + public static class P019 extends ColdPojo { } + public static class P020 extends ColdPojo { } + public static class P021 extends ColdPojo { } + public static class P022 extends ColdPojo { } + public static class P023 extends ColdPojo { } + public static class P024 extends ColdPojo { } + public static class P025 extends ColdPojo { } + public static class P026 extends ColdPojo { } + public static class P027 extends ColdPojo { } + public static class P028 extends ColdPojo { } + public static class P029 extends ColdPojo { } + public static class P030 extends ColdPojo { } + public static class P031 extends ColdPojo { } + public static class P032 extends ColdPojo { } + public static class P033 extends ColdPojo { } + public static class P034 extends ColdPojo { } + public static class P035 extends ColdPojo { } + public static class P036 extends ColdPojo { } + public static class P037 extends ColdPojo { } + public static class P038 extends ColdPojo { } + public static class P039 extends ColdPojo { } + public static class P040 extends ColdPojo { } + public static class P041 extends ColdPojo { } + public static class P042 extends ColdPojo { } + public static class P043 extends ColdPojo { } + public static class P044 extends ColdPojo { } + public static class P045 extends ColdPojo { } + public static class P046 extends ColdPojo { } + public static class P047 extends ColdPojo { } + public static class P048 extends ColdPojo { } + public static class P049 extends ColdPojo { } + public static class P050 extends ColdPojo { } + public static class P051 extends ColdPojo { } + public static class P052 extends ColdPojo { } + public static class P053 extends ColdPojo { } + public static class P054 extends ColdPojo { } + public static class P055 extends ColdPojo { } + public static class P056 extends ColdPojo { } + public static class P057 extends ColdPojo { } + public static class P058 extends ColdPojo { } + public static class P059 extends ColdPojo { } + public static class P060 extends ColdPojo { } + public static class P061 extends ColdPojo { } + public static class P062 extends ColdPojo { } + public static class P063 extends ColdPojo { } + public static class P064 extends ColdPojo { } + public static class P065 extends ColdPojo { } + public static class P066 extends ColdPojo { } + public static class P067 extends ColdPojo { } + public static class P068 extends ColdPojo { } + public static class P069 extends ColdPojo { } + public static class P070 extends ColdPojo { } + public static class P071 extends ColdPojo { } + public static class P072 extends ColdPojo { } + public static class P073 extends ColdPojo { } + public static class P074 extends ColdPojo { } + public static class P075 extends ColdPojo { } + public static class P076 extends ColdPojo { } + public static class P077 extends ColdPojo { } + public static class P078 extends ColdPojo { } + public static class P079 extends ColdPojo { } + public static class P080 extends ColdPojo { } + public static class P081 extends ColdPojo { } + public static class P082 extends ColdPojo { } + public static class P083 extends ColdPojo { } + public static class P084 extends ColdPojo { } + public static class P085 extends ColdPojo { } + public static class P086 extends ColdPojo { } + public static class P087 extends ColdPojo { } + public static class P088 extends ColdPojo { } + public static class P089 extends ColdPojo { } + public static class P090 extends ColdPojo { } + public static class P091 extends ColdPojo { } + public static class P092 extends ColdPojo { } + public static class P093 extends ColdPojo { } + public static class P094 extends ColdPojo { } + public static class P095 extends ColdPojo { } + public static class P096 extends ColdPojo { } + public static class P097 extends ColdPojo { } + public static class P098 extends ColdPojo { } + public static class P099 extends ColdPojo { } + public static class P100 extends ColdPojo { } + public static class P101 extends ColdPojo { } + public static class P102 extends ColdPojo { } + public static class P103 extends ColdPojo { } + public static class P104 extends ColdPojo { } + public static class P105 extends ColdPojo { } + public static class P106 extends ColdPojo { } + public static class P107 extends ColdPojo { } + public static class P108 extends ColdPojo { } + public static class P109 extends ColdPojo { } + public static class P110 extends ColdPojo { } + public static class P111 extends ColdPojo { } + public static class P112 extends ColdPojo { } + public static class P113 extends ColdPojo { } + public static class P114 extends ColdPojo { } + public static class P115 extends ColdPojo { } + public static class P116 extends ColdPojo { } + public static class P117 extends ColdPojo { } + public static class P118 extends ColdPojo { } + public static class P119 extends ColdPojo { } + public static class P120 extends ColdPojo { } + public static class P121 extends ColdPojo { } + public static class P122 extends ColdPojo { } + public static class P123 extends ColdPojo { } + public static class P124 extends ColdPojo { } + public static class P125 extends ColdPojo { } + public static class P126 extends ColdPojo { } + public static class P127 extends ColdPojo { } + public static class P128 extends ColdPojo { } + public static class P129 extends ColdPojo { } + public static class P130 extends ColdPojo { } + public static class P131 extends ColdPojo { } + public static class P132 extends ColdPojo { } + public static class P133 extends ColdPojo { } + public static class P134 extends ColdPojo { } + public static class P135 extends ColdPojo { } + public static class P136 extends ColdPojo { } + public static class P137 extends ColdPojo { } + public static class P138 extends ColdPojo { } + public static class P139 extends ColdPojo { } + public static class P140 extends ColdPojo { } + public static class P141 extends ColdPojo { } + public static class P142 extends ColdPojo { } + public static class P143 extends ColdPojo { } + public static class P144 extends ColdPojo { } + public static class P145 extends ColdPojo { } + public static class P146 extends ColdPojo { } + public static class P147 extends ColdPojo { } + public static class P148 extends ColdPojo { } + public static class P149 extends ColdPojo { } + public static class P150 extends ColdPojo { } + public static class P151 extends ColdPojo { } + public static class P152 extends ColdPojo { } + public static class P153 extends ColdPojo { } + public static class P154 extends ColdPojo { } + public static class P155 extends ColdPojo { } + public static class P156 extends ColdPojo { } + public static class P157 extends ColdPojo { } + public static class P158 extends ColdPojo { } + public static class P159 extends ColdPojo { } + public static class P160 extends ColdPojo { } + public static class P161 extends ColdPojo { } + public static class P162 extends ColdPojo { } + public static class P163 extends ColdPojo { } + public static class P164 extends ColdPojo { } + public static class P165 extends ColdPojo { } + public static class P166 extends ColdPojo { } + public static class P167 extends ColdPojo { } + public static class P168 extends ColdPojo { } + public static class P169 extends ColdPojo { } + public static class P170 extends ColdPojo { } + public static class P171 extends ColdPojo { } + public static class P172 extends ColdPojo { } + public static class P173 extends ColdPojo { } + public static class P174 extends ColdPojo { } + public static class P175 extends ColdPojo { } + public static class P176 extends ColdPojo { } + public static class P177 extends ColdPojo { } + public static class P178 extends ColdPojo { } + public static class P179 extends ColdPojo { } + public static class P180 extends ColdPojo { } + public static class P181 extends ColdPojo { } + public static class P182 extends ColdPojo { } + public static class P183 extends ColdPojo { } + public static class P184 extends ColdPojo { } + public static class P185 extends ColdPojo { } + public static class P186 extends ColdPojo { } + public static class P187 extends ColdPojo { } + public static class P188 extends ColdPojo { } + public static class P189 extends ColdPojo { } + public static class P190 extends ColdPojo { } + public static class P191 extends ColdPojo { } + public static class P192 extends ColdPojo { } + public static class P193 extends ColdPojo { } + public static class P194 extends ColdPojo { } + public static class P195 extends ColdPojo { } + public static class P196 extends ColdPojo { } + public static class P197 extends ColdPojo { } + public static class P198 extends ColdPojo { } + public static class P199 extends ColdPojo { } + public static class P200 extends ColdPojo { } + public static class P201 extends ColdPojo { } + public static class P202 extends ColdPojo { } + public static class P203 extends ColdPojo { } + public static class P204 extends ColdPojo { } + public static class P205 extends ColdPojo { } + public static class P206 extends ColdPojo { } + public static class P207 extends ColdPojo { } + public static class P208 extends ColdPojo { } + public static class P209 extends ColdPojo { } + public static class P210 extends ColdPojo { } + public static class P211 extends ColdPojo { } + public static class P212 extends ColdPojo { } + public static class P213 extends ColdPojo { } + public static class P214 extends ColdPojo { } + public static class P215 extends ColdPojo { } + public static class P216 extends ColdPojo { } + public static class P217 extends ColdPojo { } + public static class P218 extends ColdPojo { } + public static class P219 extends ColdPojo { } + public static class P220 extends ColdPojo { } + public static class P221 extends ColdPojo { } + public static class P222 extends ColdPojo { } + public static class P223 extends ColdPojo { } + public static class P224 extends ColdPojo { } + public static class P225 extends ColdPojo { } + public static class P226 extends ColdPojo { } + public static class P227 extends ColdPojo { } + public static class P228 extends ColdPojo { } + public static class P229 extends ColdPojo { } + public static class P230 extends ColdPojo { } + public static class P231 extends ColdPojo { } + public static class P232 extends ColdPojo { } + public static class P233 extends ColdPojo { } + public static class P234 extends ColdPojo { } + public static class P235 extends ColdPojo { } + public static class P236 extends ColdPojo { } + public static class P237 extends ColdPojo { } + public static class P238 extends ColdPojo { } + public static class P239 extends ColdPojo { } + public static class P240 extends ColdPojo { } + public static class P241 extends ColdPojo { } + public static class P242 extends ColdPojo { } + public static class P243 extends ColdPojo { } + public static class P244 extends ColdPojo { } + public static class P245 extends ColdPojo { } + public static class P246 extends ColdPojo { } + public static class P247 extends ColdPojo { } + public static class P248 extends ColdPojo { } + public static class P249 extends ColdPojo { } +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Invoice.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Invoice.java new file mode 100644 index 000000000..dce8c2348 --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Invoice.java @@ -0,0 +1,78 @@ +package tools.jackson.module.blackbird; + +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonPOJOBuilder; + +/** + * Immutable bean with a fluent builder, in the shape that Immutables and + * Lombok generate. + */ +@JsonDeserialize(builder = Invoice.Builder.class) +public final class Invoice +{ + static final Invoice SAMPLE = new Builder() + .number("INV-2026-000417") + .customerId("cust-88412") + .currency("EUR") + .totalCents(1_250_075L) + .taxCents(237_514L) + .lineCount(14) + .paid(true) + .note("Net 30") + .build(); + + private final String number; + private final String customerId; + private final String currency; + private final long totalCents; + private final long taxCents; + private final int lineCount; + private final boolean paid; + private final String note; + + private Invoice(Builder b) { + number = b.number; + customerId = b.customerId; + currency = b.currency; + totalCents = b.totalCents; + taxCents = b.taxCents; + lineCount = b.lineCount; + paid = b.paid; + note = b.note; + } + + public String getNumber() { return number; } + public String getCustomerId() { return customerId; } + public String getCurrency() { return currency; } + public long getTotalCents() { return totalCents; } + public long getTaxCents() { return taxCents; } + public int getLineCount() { return lineCount; } + public boolean isPaid() { return paid; } + public String getNote() { return note; } + + @JsonPOJOBuilder(withPrefix = "") + public static final class Builder + { + private String number; + private String customerId; + private String currency; + private long totalCents; + private long taxCents; + private int lineCount; + private boolean paid; + private String note; + + public Builder number(String v) { number = v; return this; } + public Builder customerId(String v) { customerId = v; return this; } + public Builder currency(String v) { currency = v; return this; } + public Builder totalCents(long v) { totalCents = v; return this; } + public Builder taxCents(long v) { taxCents = v; return this; } + public Builder lineCount(int v) { lineCount = v; return this; } + public Builder paid(boolean v) { paid = v; return this; } + public Builder note(String v) { note = v; return this; } + + public Invoice build() { + return new Invoice(this); + } + } +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Mappers.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Mappers.java new file mode 100644 index 000000000..d86f24fbe --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Mappers.java @@ -0,0 +1,26 @@ +package tools.jackson.module.blackbird; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.module.afterburner.AfterburnerModule; + +/** + * Creates the mapper for the {@code mode} parameter of each benchmark: + * {@code stock} (no module), {@code blackbird} or {@code afterburner}. + * Which Blackbird engine {@code blackbird} measures depends on the jar: + * {@code benchmarks.jar} has this build, {@code benchmarks-classic.jar} + * has the last release of the LambdaMetafactory engine. + */ +final class Mappers +{ + private Mappers() { } + + static ObjectMapper create(String mode) { + return switch (mode) { + case "stock" -> JsonMapper.builder().build(); + case "blackbird" -> JsonMapper.builder().addModule(new BlackbirdModule()).build(); + case "afterburner" -> JsonMapper.builder().addModule(new AfterburnerModule()).build(); + default -> throw new IllegalArgumentException("Unknown mode: " + mode); + }; + } +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Order.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Order.java new file mode 100644 index 000000000..ce31ed373 --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Order.java @@ -0,0 +1,19 @@ +package tools.jackson.module.blackbird; + +import java.util.List; + +/** + * Record graph: nested records and a list of records. + */ +public record Order(String id, long amountCents, boolean expedited, Customer customer, List lines) +{ + public record Customer(String name, String email, int tier) { } + + public record Line(String sku, int quantity, long priceCents) { } + + static final Order SAMPLE = new Order("ord-20261014-0042", 1_234_599L, true, + new Customer("Ada Lovelace", "ada@example.com", 3), + List.of(new Line("sku-1001", 2, 19_999L), + new Line("sku-2002", 1, 1_049_900L), + new Line("sku-3003", 12, 5_475L))); +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/BaseBenchmark.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/PojoBenchmark.java similarity index 50% rename from blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/BaseBenchmark.java rename to blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/PojoBenchmark.java index 61008ebeb..6806d4549 100644 --- a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/BaseBenchmark.java +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/PojoBenchmark.java @@ -5,111 +5,106 @@ import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.ObjectReader; -import tools.jackson.databind.ObjectWriter; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectReader; +import tools.jackson.databind.ObjectWriter; + +/** + * Setter POJOs: arrays of 1000 small beans, the same beans through a + * property-based creator and a builder, and the media item bean. + */ @State(Scope.Thread) @BenchmarkMode(Mode.Throughput) -@Measurement(time = 30, iterations = 10) -@Warmup(time = 10, iterations = 10) @OutputTimeUnit(TimeUnit.MILLISECONDS) -@Fork(5) -public abstract class BaseBenchmark { - - Random random = new Random(1337); - ObjectMapper mapper; - SomeBean[] beans; - byte[] beansBytes; - byte[] mediaItemJson; - ObjectWriter mediaItemWriter; - ObjectReader mediaItemReader; - ClassicBean[] classicBeans; - byte[] classicBeansJson; - ObjectWriter classicBeanWriter; - ObjectReader classicBeanReader; - - public static void main(final String[] args) throws RunnerException { - final Options options = new OptionsBuilder() - .include(BaseBenchmark.class.getSimpleName()) - .build(); - new Runner(options).run(); - } - - protected abstract ObjectMapper createObjectMapper(); +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +public class PojoBenchmark +{ + @Param({"stock", "blackbird"}) + public String mode; + + private ObjectMapper mapper; + private SomeBean[] beans; + private byte[] beansJson; + private ObjectReader mediaItemReader; + private ObjectWriter mediaItemWriter; + private byte[] mediaItemJson; + private ObjectReader classicBeanReader; + private ObjectWriter classicBeanWriter; + private ClassicBean[] classicBeans; + private byte[] classicBeansJson; @Setup - public void setup() throws Exception { - mapper = createObjectMapper(); + public void setup() { + mapper = Mappers.create(mode); + Random random = new Random(1337); beans = IntStream.range(0, 1000) .mapToObj(i -> SomeBean.random(random)) .toArray(SomeBean[]::new); + beansJson = mapper.writeValueAsBytes(beans); - beansBytes = mapper.writeValueAsBytes(beans); mediaItemReader = mapper.readerFor(MediaItem.class); mediaItemWriter = mapper.writerFor(MediaItem.class); mediaItemJson = mediaItemWriter.writeValueAsBytes(MediaItem.SAMPLE); classicBeanReader = mapper.readerFor(ClassicBean[].class); classicBeanWriter = mapper.writerFor(ClassicBean[].class); - final ClassicBean classicBean = new ClassicBean(); - classicBean.setUp(); - classicBeans = Collections.nCopies(1000, classicBean).toArray(new ClassicBean[0]); + classicBeans = Collections.nCopies(1000, new ClassicBean().setUp()) + .toArray(new ClassicBean[0]); classicBeansJson = classicBeanWriter.writeValueAsBytes(classicBeans); } @Benchmark - public byte[] beanArraySer() throws Exception { + public byte[] beanArraySer() { return mapper.writeValueAsBytes(beans); } @Benchmark - public SomeBean[] beanArrayDeser() throws Exception { - return mapper.readValue(beansBytes, SomeBean[].class); + public SomeBean[] beanArrayDeser() { + return mapper.readValue(beansJson, SomeBean[].class); } @Benchmark - public BeanWithPropertyConstructor[] constructorArrayDeser() throws Exception { - return mapper.readValue(beansBytes, BeanWithPropertyConstructor[].class); + public BeanWithPropertyConstructor[] constructorArrayDeser() { + return mapper.readValue(beansJson, BeanWithPropertyConstructor[].class); } @Benchmark - public BeanWithBuilder[] builderArrayDeser() throws Exception { - return mapper.readValue(beansBytes, BeanWithBuilder[].class); + public BeanWithBuilder[] builderArrayDeser() { + return mapper.readValue(beansJson, BeanWithBuilder[].class); } @Benchmark - public void classicMediaItemSer(final Blackhole bh) throws Exception { + public void mediaItemSer(Blackhole bh) { mediaItemWriter.writeValue(new NopOutputStream(bh), MediaItem.SAMPLE); } @Benchmark - public MediaItem classicMediaItemDeser() throws Exception { + public MediaItem mediaItemDeser() { return mediaItemReader.readValue(mediaItemJson); } @Benchmark - public void classicBeanItemSer(final Blackhole bh) throws Exception { + public void classicBeanSer(Blackhole bh) { classicBeanWriter.writeValue(new NopOutputStream(bh), classicBeans); } @Benchmark - public ClassicBean[] classicBeanItemDeser() throws Exception { + public ClassicBean[] classicBeanDeser() { return classicBeanReader.readValue(classicBeansJson); } } diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/RecordBenchmark.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/RecordBenchmark.java new file mode 100644 index 000000000..b3efd5eb8 --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/RecordBenchmark.java @@ -0,0 +1,55 @@ +package tools.jackson.module.blackbird; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import tools.jackson.databind.ObjectReader; +import tools.jackson.databind.ObjectWriter; + +/** + * Records: one {@link Order} with a nested record and a list of three records. + */ +@State(Scope.Thread) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +public class RecordBenchmark +{ + @Param({"stock", "blackbird"}) + public String mode; + + private ObjectReader reader; + private ObjectWriter writer; + private byte[] json; + + @Setup + public void setup() { + var mapper = Mappers.create(mode); + reader = mapper.readerFor(Order.class); + writer = mapper.writerFor(Order.class); + json = writer.writeValueAsBytes(Order.SAMPLE); + } + + @Benchmark + public Order recordDeser() { + return reader.readValue(json); + } + + @Benchmark + public byte[] recordSer() { + return writer.writeValueAsBytes(Order.SAMPLE); + } +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/StartupTimeBenchmark.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/StartupTimeBenchmark.java deleted file mode 100644 index 56c3171df..000000000 --- a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/StartupTimeBenchmark.java +++ /dev/null @@ -1,58 +0,0 @@ -package tools.jackson.module.blackbird; - -import java.util.List; -import java.util.Random; -import java.util.concurrent.TimeUnit; - -import tools.jackson.core.JacksonException; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; -import tools.jackson.module.afterburner.AfterburnerModule; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -@BenchmarkMode(Mode.SingleShotTime) -@Measurement(iterations = 1) -@Warmup(iterations = 0) -@OutputTimeUnit(TimeUnit.MILLISECONDS) -@Fork(10) -public class StartupTimeBenchmark { - public static void main(String[] args) throws RunnerException { - Options options = new OptionsBuilder() - .include(StartupTimeBenchmark.class.getSimpleName()) - .build(); - new Runner(options).run(); - } - - @Benchmark - public byte[] vanilla() throws Exception { - return singleShot(new ObjectMapper()); - } - - @Benchmark - public byte[] blackbird() throws Exception { - return singleShot(JsonMapper.builder().addModule(new BlackbirdModule()).build()); - } - - @Benchmark - public byte[] afterburner() throws Exception { - return singleShot(JsonMapper.builder().addModule(new AfterburnerModule()).build()); - } - - private static byte[] singleShot(ObjectMapper mapper) throws JacksonException { - final Random random = new Random(); - return mapper.writeValueAsBytes(List.of( - SomeBean.random(random), - new ClassicBean().setUp(), - new BeanWithPropertyConstructor(42, "foo", 8675309, SomeBean.random(random), SomeEnum.EB))); - } -} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Vanilla.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Vanilla.java deleted file mode 100644 index a4cbe3c90..000000000 --- a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Vanilla.java +++ /dev/null @@ -1,22 +0,0 @@ -package tools.jackson.module.blackbird; - -import tools.jackson.databind.ObjectMapper; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -public class Vanilla extends BaseBenchmark { - public static void main(String[] args) throws RunnerException { - Options options = new OptionsBuilder() - .include(Vanilla.class.getSimpleName()) - .forks(1) - .build(); - new Runner(options).run(); - } - - @Override - protected ObjectMapper createObjectMapper() { - return new ObjectMapper(); - } -} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Wide200.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Wide200.java new file mode 100644 index 000000000..1bebf5e52 --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Wide200.java @@ -0,0 +1,610 @@ +package tools.jackson.module.blackbird; + +/** + * Setter bean with 200 properties. Property {@code pI} has type + * String, int, long or boolean for {@code I % 4} = 0, 1, 2 or 3. + */ +public class Wide200 +{ + private String p0; + private int p1; + private long p2; + private boolean p3; + private String p4; + private int p5; + private long p6; + private boolean p7; + private String p8; + private int p9; + private long p10; + private boolean p11; + private String p12; + private int p13; + private long p14; + private boolean p15; + private String p16; + private int p17; + private long p18; + private boolean p19; + private String p20; + private int p21; + private long p22; + private boolean p23; + private String p24; + private int p25; + private long p26; + private boolean p27; + private String p28; + private int p29; + private long p30; + private boolean p31; + private String p32; + private int p33; + private long p34; + private boolean p35; + private String p36; + private int p37; + private long p38; + private boolean p39; + private String p40; + private int p41; + private long p42; + private boolean p43; + private String p44; + private int p45; + private long p46; + private boolean p47; + private String p48; + private int p49; + private long p50; + private boolean p51; + private String p52; + private int p53; + private long p54; + private boolean p55; + private String p56; + private int p57; + private long p58; + private boolean p59; + private String p60; + private int p61; + private long p62; + private boolean p63; + private String p64; + private int p65; + private long p66; + private boolean p67; + private String p68; + private int p69; + private long p70; + private boolean p71; + private String p72; + private int p73; + private long p74; + private boolean p75; + private String p76; + private int p77; + private long p78; + private boolean p79; + private String p80; + private int p81; + private long p82; + private boolean p83; + private String p84; + private int p85; + private long p86; + private boolean p87; + private String p88; + private int p89; + private long p90; + private boolean p91; + private String p92; + private int p93; + private long p94; + private boolean p95; + private String p96; + private int p97; + private long p98; + private boolean p99; + private String p100; + private int p101; + private long p102; + private boolean p103; + private String p104; + private int p105; + private long p106; + private boolean p107; + private String p108; + private int p109; + private long p110; + private boolean p111; + private String p112; + private int p113; + private long p114; + private boolean p115; + private String p116; + private int p117; + private long p118; + private boolean p119; + private String p120; + private int p121; + private long p122; + private boolean p123; + private String p124; + private int p125; + private long p126; + private boolean p127; + private String p128; + private int p129; + private long p130; + private boolean p131; + private String p132; + private int p133; + private long p134; + private boolean p135; + private String p136; + private int p137; + private long p138; + private boolean p139; + private String p140; + private int p141; + private long p142; + private boolean p143; + private String p144; + private int p145; + private long p146; + private boolean p147; + private String p148; + private int p149; + private long p150; + private boolean p151; + private String p152; + private int p153; + private long p154; + private boolean p155; + private String p156; + private int p157; + private long p158; + private boolean p159; + private String p160; + private int p161; + private long p162; + private boolean p163; + private String p164; + private int p165; + private long p166; + private boolean p167; + private String p168; + private int p169; + private long p170; + private boolean p171; + private String p172; + private int p173; + private long p174; + private boolean p175; + private String p176; + private int p177; + private long p178; + private boolean p179; + private String p180; + private int p181; + private long p182; + private boolean p183; + private String p184; + private int p185; + private long p186; + private boolean p187; + private String p188; + private int p189; + private long p190; + private boolean p191; + private String p192; + private int p193; + private long p194; + private boolean p195; + private String p196; + private int p197; + private long p198; + private boolean p199; + + public String getP0() { return p0; } + public void setP0(String v) { p0 = v; } + public int getP1() { return p1; } + public void setP1(int v) { p1 = v; } + public long getP2() { return p2; } + public void setP2(long v) { p2 = v; } + public boolean getP3() { return p3; } + public void setP3(boolean v) { p3 = v; } + public String getP4() { return p4; } + public void setP4(String v) { p4 = v; } + public int getP5() { return p5; } + public void setP5(int v) { p5 = v; } + public long getP6() { return p6; } + public void setP6(long v) { p6 = v; } + public boolean getP7() { return p7; } + public void setP7(boolean v) { p7 = v; } + public String getP8() { return p8; } + public void setP8(String v) { p8 = v; } + public int getP9() { return p9; } + public void setP9(int v) { p9 = v; } + public long getP10() { return p10; } + public void setP10(long v) { p10 = v; } + public boolean getP11() { return p11; } + public void setP11(boolean v) { p11 = v; } + public String getP12() { return p12; } + public void setP12(String v) { p12 = v; } + public int getP13() { return p13; } + public void setP13(int v) { p13 = v; } + public long getP14() { return p14; } + public void setP14(long v) { p14 = v; } + public boolean getP15() { return p15; } + public void setP15(boolean v) { p15 = v; } + public String getP16() { return p16; } + public void setP16(String v) { p16 = v; } + public int getP17() { return p17; } + public void setP17(int v) { p17 = v; } + public long getP18() { return p18; } + public void setP18(long v) { p18 = v; } + public boolean getP19() { return p19; } + public void setP19(boolean v) { p19 = v; } + public String getP20() { return p20; } + public void setP20(String v) { p20 = v; } + public int getP21() { return p21; } + public void setP21(int v) { p21 = v; } + public long getP22() { return p22; } + public void setP22(long v) { p22 = v; } + public boolean getP23() { return p23; } + public void setP23(boolean v) { p23 = v; } + public String getP24() { return p24; } + public void setP24(String v) { p24 = v; } + public int getP25() { return p25; } + public void setP25(int v) { p25 = v; } + public long getP26() { return p26; } + public void setP26(long v) { p26 = v; } + public boolean getP27() { return p27; } + public void setP27(boolean v) { p27 = v; } + public String getP28() { return p28; } + public void setP28(String v) { p28 = v; } + public int getP29() { return p29; } + public void setP29(int v) { p29 = v; } + public long getP30() { return p30; } + public void setP30(long v) { p30 = v; } + public boolean getP31() { return p31; } + public void setP31(boolean v) { p31 = v; } + public String getP32() { return p32; } + public void setP32(String v) { p32 = v; } + public int getP33() { return p33; } + public void setP33(int v) { p33 = v; } + public long getP34() { return p34; } + public void setP34(long v) { p34 = v; } + public boolean getP35() { return p35; } + public void setP35(boolean v) { p35 = v; } + public String getP36() { return p36; } + public void setP36(String v) { p36 = v; } + public int getP37() { return p37; } + public void setP37(int v) { p37 = v; } + public long getP38() { return p38; } + public void setP38(long v) { p38 = v; } + public boolean getP39() { return p39; } + public void setP39(boolean v) { p39 = v; } + public String getP40() { return p40; } + public void setP40(String v) { p40 = v; } + public int getP41() { return p41; } + public void setP41(int v) { p41 = v; } + public long getP42() { return p42; } + public void setP42(long v) { p42 = v; } + public boolean getP43() { return p43; } + public void setP43(boolean v) { p43 = v; } + public String getP44() { return p44; } + public void setP44(String v) { p44 = v; } + public int getP45() { return p45; } + public void setP45(int v) { p45 = v; } + public long getP46() { return p46; } + public void setP46(long v) { p46 = v; } + public boolean getP47() { return p47; } + public void setP47(boolean v) { p47 = v; } + public String getP48() { return p48; } + public void setP48(String v) { p48 = v; } + public int getP49() { return p49; } + public void setP49(int v) { p49 = v; } + public long getP50() { return p50; } + public void setP50(long v) { p50 = v; } + public boolean getP51() { return p51; } + public void setP51(boolean v) { p51 = v; } + public String getP52() { return p52; } + public void setP52(String v) { p52 = v; } + public int getP53() { return p53; } + public void setP53(int v) { p53 = v; } + public long getP54() { return p54; } + public void setP54(long v) { p54 = v; } + public boolean getP55() { return p55; } + public void setP55(boolean v) { p55 = v; } + public String getP56() { return p56; } + public void setP56(String v) { p56 = v; } + public int getP57() { return p57; } + public void setP57(int v) { p57 = v; } + public long getP58() { return p58; } + public void setP58(long v) { p58 = v; } + public boolean getP59() { return p59; } + public void setP59(boolean v) { p59 = v; } + public String getP60() { return p60; } + public void setP60(String v) { p60 = v; } + public int getP61() { return p61; } + public void setP61(int v) { p61 = v; } + public long getP62() { return p62; } + public void setP62(long v) { p62 = v; } + public boolean getP63() { return p63; } + public void setP63(boolean v) { p63 = v; } + public String getP64() { return p64; } + public void setP64(String v) { p64 = v; } + public int getP65() { return p65; } + public void setP65(int v) { p65 = v; } + public long getP66() { return p66; } + public void setP66(long v) { p66 = v; } + public boolean getP67() { return p67; } + public void setP67(boolean v) { p67 = v; } + public String getP68() { return p68; } + public void setP68(String v) { p68 = v; } + public int getP69() { return p69; } + public void setP69(int v) { p69 = v; } + public long getP70() { return p70; } + public void setP70(long v) { p70 = v; } + public boolean getP71() { return p71; } + public void setP71(boolean v) { p71 = v; } + public String getP72() { return p72; } + public void setP72(String v) { p72 = v; } + public int getP73() { return p73; } + public void setP73(int v) { p73 = v; } + public long getP74() { return p74; } + public void setP74(long v) { p74 = v; } + public boolean getP75() { return p75; } + public void setP75(boolean v) { p75 = v; } + public String getP76() { return p76; } + public void setP76(String v) { p76 = v; } + public int getP77() { return p77; } + public void setP77(int v) { p77 = v; } + public long getP78() { return p78; } + public void setP78(long v) { p78 = v; } + public boolean getP79() { return p79; } + public void setP79(boolean v) { p79 = v; } + public String getP80() { return p80; } + public void setP80(String v) { p80 = v; } + public int getP81() { return p81; } + public void setP81(int v) { p81 = v; } + public long getP82() { return p82; } + public void setP82(long v) { p82 = v; } + public boolean getP83() { return p83; } + public void setP83(boolean v) { p83 = v; } + public String getP84() { return p84; } + public void setP84(String v) { p84 = v; } + public int getP85() { return p85; } + public void setP85(int v) { p85 = v; } + public long getP86() { return p86; } + public void setP86(long v) { p86 = v; } + public boolean getP87() { return p87; } + public void setP87(boolean v) { p87 = v; } + public String getP88() { return p88; } + public void setP88(String v) { p88 = v; } + public int getP89() { return p89; } + public void setP89(int v) { p89 = v; } + public long getP90() { return p90; } + public void setP90(long v) { p90 = v; } + public boolean getP91() { return p91; } + public void setP91(boolean v) { p91 = v; } + public String getP92() { return p92; } + public void setP92(String v) { p92 = v; } + public int getP93() { return p93; } + public void setP93(int v) { p93 = v; } + public long getP94() { return p94; } + public void setP94(long v) { p94 = v; } + public boolean getP95() { return p95; } + public void setP95(boolean v) { p95 = v; } + public String getP96() { return p96; } + public void setP96(String v) { p96 = v; } + public int getP97() { return p97; } + public void setP97(int v) { p97 = v; } + public long getP98() { return p98; } + public void setP98(long v) { p98 = v; } + public boolean getP99() { return p99; } + public void setP99(boolean v) { p99 = v; } + public String getP100() { return p100; } + public void setP100(String v) { p100 = v; } + public int getP101() { return p101; } + public void setP101(int v) { p101 = v; } + public long getP102() { return p102; } + public void setP102(long v) { p102 = v; } + public boolean getP103() { return p103; } + public void setP103(boolean v) { p103 = v; } + public String getP104() { return p104; } + public void setP104(String v) { p104 = v; } + public int getP105() { return p105; } + public void setP105(int v) { p105 = v; } + public long getP106() { return p106; } + public void setP106(long v) { p106 = v; } + public boolean getP107() { return p107; } + public void setP107(boolean v) { p107 = v; } + public String getP108() { return p108; } + public void setP108(String v) { p108 = v; } + public int getP109() { return p109; } + public void setP109(int v) { p109 = v; } + public long getP110() { return p110; } + public void setP110(long v) { p110 = v; } + public boolean getP111() { return p111; } + public void setP111(boolean v) { p111 = v; } + public String getP112() { return p112; } + public void setP112(String v) { p112 = v; } + public int getP113() { return p113; } + public void setP113(int v) { p113 = v; } + public long getP114() { return p114; } + public void setP114(long v) { p114 = v; } + public boolean getP115() { return p115; } + public void setP115(boolean v) { p115 = v; } + public String getP116() { return p116; } + public void setP116(String v) { p116 = v; } + public int getP117() { return p117; } + public void setP117(int v) { p117 = v; } + public long getP118() { return p118; } + public void setP118(long v) { p118 = v; } + public boolean getP119() { return p119; } + public void setP119(boolean v) { p119 = v; } + public String getP120() { return p120; } + public void setP120(String v) { p120 = v; } + public int getP121() { return p121; } + public void setP121(int v) { p121 = v; } + public long getP122() { return p122; } + public void setP122(long v) { p122 = v; } + public boolean getP123() { return p123; } + public void setP123(boolean v) { p123 = v; } + public String getP124() { return p124; } + public void setP124(String v) { p124 = v; } + public int getP125() { return p125; } + public void setP125(int v) { p125 = v; } + public long getP126() { return p126; } + public void setP126(long v) { p126 = v; } + public boolean getP127() { return p127; } + public void setP127(boolean v) { p127 = v; } + public String getP128() { return p128; } + public void setP128(String v) { p128 = v; } + public int getP129() { return p129; } + public void setP129(int v) { p129 = v; } + public long getP130() { return p130; } + public void setP130(long v) { p130 = v; } + public boolean getP131() { return p131; } + public void setP131(boolean v) { p131 = v; } + public String getP132() { return p132; } + public void setP132(String v) { p132 = v; } + public int getP133() { return p133; } + public void setP133(int v) { p133 = v; } + public long getP134() { return p134; } + public void setP134(long v) { p134 = v; } + public boolean getP135() { return p135; } + public void setP135(boolean v) { p135 = v; } + public String getP136() { return p136; } + public void setP136(String v) { p136 = v; } + public int getP137() { return p137; } + public void setP137(int v) { p137 = v; } + public long getP138() { return p138; } + public void setP138(long v) { p138 = v; } + public boolean getP139() { return p139; } + public void setP139(boolean v) { p139 = v; } + public String getP140() { return p140; } + public void setP140(String v) { p140 = v; } + public int getP141() { return p141; } + public void setP141(int v) { p141 = v; } + public long getP142() { return p142; } + public void setP142(long v) { p142 = v; } + public boolean getP143() { return p143; } + public void setP143(boolean v) { p143 = v; } + public String getP144() { return p144; } + public void setP144(String v) { p144 = v; } + public int getP145() { return p145; } + public void setP145(int v) { p145 = v; } + public long getP146() { return p146; } + public void setP146(long v) { p146 = v; } + public boolean getP147() { return p147; } + public void setP147(boolean v) { p147 = v; } + public String getP148() { return p148; } + public void setP148(String v) { p148 = v; } + public int getP149() { return p149; } + public void setP149(int v) { p149 = v; } + public long getP150() { return p150; } + public void setP150(long v) { p150 = v; } + public boolean getP151() { return p151; } + public void setP151(boolean v) { p151 = v; } + public String getP152() { return p152; } + public void setP152(String v) { p152 = v; } + public int getP153() { return p153; } + public void setP153(int v) { p153 = v; } + public long getP154() { return p154; } + public void setP154(long v) { p154 = v; } + public boolean getP155() { return p155; } + public void setP155(boolean v) { p155 = v; } + public String getP156() { return p156; } + public void setP156(String v) { p156 = v; } + public int getP157() { return p157; } + public void setP157(int v) { p157 = v; } + public long getP158() { return p158; } + public void setP158(long v) { p158 = v; } + public boolean getP159() { return p159; } + public void setP159(boolean v) { p159 = v; } + public String getP160() { return p160; } + public void setP160(String v) { p160 = v; } + public int getP161() { return p161; } + public void setP161(int v) { p161 = v; } + public long getP162() { return p162; } + public void setP162(long v) { p162 = v; } + public boolean getP163() { return p163; } + public void setP163(boolean v) { p163 = v; } + public String getP164() { return p164; } + public void setP164(String v) { p164 = v; } + public int getP165() { return p165; } + public void setP165(int v) { p165 = v; } + public long getP166() { return p166; } + public void setP166(long v) { p166 = v; } + public boolean getP167() { return p167; } + public void setP167(boolean v) { p167 = v; } + public String getP168() { return p168; } + public void setP168(String v) { p168 = v; } + public int getP169() { return p169; } + public void setP169(int v) { p169 = v; } + public long getP170() { return p170; } + public void setP170(long v) { p170 = v; } + public boolean getP171() { return p171; } + public void setP171(boolean v) { p171 = v; } + public String getP172() { return p172; } + public void setP172(String v) { p172 = v; } + public int getP173() { return p173; } + public void setP173(int v) { p173 = v; } + public long getP174() { return p174; } + public void setP174(long v) { p174 = v; } + public boolean getP175() { return p175; } + public void setP175(boolean v) { p175 = v; } + public String getP176() { return p176; } + public void setP176(String v) { p176 = v; } + public int getP177() { return p177; } + public void setP177(int v) { p177 = v; } + public long getP178() { return p178; } + public void setP178(long v) { p178 = v; } + public boolean getP179() { return p179; } + public void setP179(boolean v) { p179 = v; } + public String getP180() { return p180; } + public void setP180(String v) { p180 = v; } + public int getP181() { return p181; } + public void setP181(int v) { p181 = v; } + public long getP182() { return p182; } + public void setP182(long v) { p182 = v; } + public boolean getP183() { return p183; } + public void setP183(boolean v) { p183 = v; } + public String getP184() { return p184; } + public void setP184(String v) { p184 = v; } + public int getP185() { return p185; } + public void setP185(int v) { p185 = v; } + public long getP186() { return p186; } + public void setP186(long v) { p186 = v; } + public boolean getP187() { return p187; } + public void setP187(boolean v) { p187 = v; } + public String getP188() { return p188; } + public void setP188(String v) { p188 = v; } + public int getP189() { return p189; } + public void setP189(int v) { p189 = v; } + public long getP190() { return p190; } + public void setP190(long v) { p190 = v; } + public boolean getP191() { return p191; } + public void setP191(boolean v) { p191 = v; } + public String getP192() { return p192; } + public void setP192(String v) { p192 = v; } + public int getP193() { return p193; } + public void setP193(int v) { p193 = v; } + public long getP194() { return p194; } + public void setP194(long v) { p194 = v; } + public boolean getP195() { return p195; } + public void setP195(boolean v) { p195 = v; } + public String getP196() { return p196; } + public void setP196(String v) { p196 = v; } + public int getP197() { return p197; } + public void setP197(int v) { p197 = v; } + public long getP198() { return p198; } + public void setP198(long v) { p198 = v; } + public boolean getP199() { return p199; } + public void setP199(boolean v) { p199 = v; } +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Wide40.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Wide40.java new file mode 100644 index 000000000..a264f0cf7 --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/Wide40.java @@ -0,0 +1,130 @@ +package tools.jackson.module.blackbird; + +/** + * Setter bean with 40 properties. Property {@code pI} has type + * String, int, long or boolean for {@code I % 4} = 0, 1, 2 or 3. + */ +public class Wide40 +{ + private String p0; + private int p1; + private long p2; + private boolean p3; + private String p4; + private int p5; + private long p6; + private boolean p7; + private String p8; + private int p9; + private long p10; + private boolean p11; + private String p12; + private int p13; + private long p14; + private boolean p15; + private String p16; + private int p17; + private long p18; + private boolean p19; + private String p20; + private int p21; + private long p22; + private boolean p23; + private String p24; + private int p25; + private long p26; + private boolean p27; + private String p28; + private int p29; + private long p30; + private boolean p31; + private String p32; + private int p33; + private long p34; + private boolean p35; + private String p36; + private int p37; + private long p38; + private boolean p39; + + public String getP0() { return p0; } + public void setP0(String v) { p0 = v; } + public int getP1() { return p1; } + public void setP1(int v) { p1 = v; } + public long getP2() { return p2; } + public void setP2(long v) { p2 = v; } + public boolean getP3() { return p3; } + public void setP3(boolean v) { p3 = v; } + public String getP4() { return p4; } + public void setP4(String v) { p4 = v; } + public int getP5() { return p5; } + public void setP5(int v) { p5 = v; } + public long getP6() { return p6; } + public void setP6(long v) { p6 = v; } + public boolean getP7() { return p7; } + public void setP7(boolean v) { p7 = v; } + public String getP8() { return p8; } + public void setP8(String v) { p8 = v; } + public int getP9() { return p9; } + public void setP9(int v) { p9 = v; } + public long getP10() { return p10; } + public void setP10(long v) { p10 = v; } + public boolean getP11() { return p11; } + public void setP11(boolean v) { p11 = v; } + public String getP12() { return p12; } + public void setP12(String v) { p12 = v; } + public int getP13() { return p13; } + public void setP13(int v) { p13 = v; } + public long getP14() { return p14; } + public void setP14(long v) { p14 = v; } + public boolean getP15() { return p15; } + public void setP15(boolean v) { p15 = v; } + public String getP16() { return p16; } + public void setP16(String v) { p16 = v; } + public int getP17() { return p17; } + public void setP17(int v) { p17 = v; } + public long getP18() { return p18; } + public void setP18(long v) { p18 = v; } + public boolean getP19() { return p19; } + public void setP19(boolean v) { p19 = v; } + public String getP20() { return p20; } + public void setP20(String v) { p20 = v; } + public int getP21() { return p21; } + public void setP21(int v) { p21 = v; } + public long getP22() { return p22; } + public void setP22(long v) { p22 = v; } + public boolean getP23() { return p23; } + public void setP23(boolean v) { p23 = v; } + public String getP24() { return p24; } + public void setP24(String v) { p24 = v; } + public int getP25() { return p25; } + public void setP25(int v) { p25 = v; } + public long getP26() { return p26; } + public void setP26(long v) { p26 = v; } + public boolean getP27() { return p27; } + public void setP27(boolean v) { p27 = v; } + public String getP28() { return p28; } + public void setP28(String v) { p28 = v; } + public int getP29() { return p29; } + public void setP29(int v) { p29 = v; } + public long getP30() { return p30; } + public void setP30(long v) { p30 = v; } + public boolean getP31() { return p31; } + public void setP31(boolean v) { p31 = v; } + public String getP32() { return p32; } + public void setP32(String v) { p32 = v; } + public int getP33() { return p33; } + public void setP33(int v) { p33 = v; } + public long getP34() { return p34; } + public void setP34(long v) { p34 = v; } + public boolean getP35() { return p35; } + public void setP35(boolean v) { p35 = v; } + public String getP36() { return p36; } + public void setP36(String v) { p36 = v; } + public int getP37() { return p37; } + public void setP37(int v) { p37 = v; } + public long getP38() { return p38; } + public void setP38(long v) { p38 = v; } + public boolean getP39() { return p39; } + public void setP39(boolean v) { p39 = v; } +} diff --git a/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/WideBeanBenchmark.java b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/WideBeanBenchmark.java new file mode 100644 index 000000000..ed9687948 --- /dev/null +++ b/blackbird/benchmarks/src/main/java/tools/jackson/module/blackbird/WideBeanBenchmark.java @@ -0,0 +1,84 @@ +package tools.jackson.module.blackbird; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import tools.jackson.databind.ObjectReader; +import tools.jackson.databind.ObjectWriter; + +/** + * Wide setter beans ({@link Wide40}, {@link Wide200}) with every property set. + * The 200-property bean is large enough that generated methods can pass the + * HotSpot limits for JIT compilation of huge methods. + */ +@State(Scope.Thread) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +public class WideBeanBenchmark +{ + @Param({"stock", "blackbird"}) + public String mode; + + @Param({"40", "200"}) + public int width; + + private ObjectReader reader; + private ObjectWriter writer; + private byte[] json; + private Object value; + + @Setup + public void setup() { + Class type = switch (width) { + case 40 -> Wide40.class; + case 200 -> Wide200.class; + default -> throw new IllegalArgumentException("Unknown width: " + width); + }; + var mapper = Mappers.create(mode); + reader = mapper.readerFor(type); + writer = mapper.writerFor(type); + json = sampleJson(width).getBytes(); + value = reader.readValue(json); + } + + private static String sampleJson(int width) { + StringBuilder sb = new StringBuilder("{"); + for (int i = 0; i < width; i++) { + if (i > 0) { + sb.append(','); + } + sb.append("\"p").append(i).append("\":"); + switch (i % 4) { + case 0 -> sb.append("\"value-").append(i).append('"'); + case 1 -> sb.append(i * 31); + case 2 -> sb.append(1_000_000_007L * i); + default -> sb.append(i % 3 == 0); + } + } + return sb.append('}').toString(); + } + + @Benchmark + public Object wideDeser() { + return reader.readValue(json); + } + + @Benchmark + public byte[] wideSer() { + return writer.writeValueAsBytes(value); + } +} diff --git a/blackbird/pom.xml b/blackbird/pom.xml index e673dd2e3..6c0b48075 100644 --- a/blackbird/pom.xml +++ b/blackbird/pom.xml @@ -14,7 +14,8 @@ Jackson module: Blackbird jar Jackson (https://github.com/FasterXML/jackson) extension module -that uses LambdaMetafactory based code generation to replace reflection calls. +that generates a deserializer and a serializer class per bean with the JDK ClassFile API +(Java 25 or later at run time). https://github.com/FasterXML/jackson-modules-base @@ -27,6 +28,16 @@ that uses LambdaMetafactory based code generation to replace reflection calls. + + 25 + + tools.jackson.module.blackbird;version=${project.version} + + java.lang.classfile;resolution:=optional,java.lang.classfile.*;resolution:=optional,* tools/jackson/module/blackbird ${project.groupId}.blackbird @@ -51,6 +62,139 @@ that uses LambdaMetafactory based code generation to replace reflection calls. com.google.code.maven-replacer-plugin replacer + + + + org.codehaus.mojo + build-helper-maven-plugin + + + + add-java25-sources + generate-sources + + add-source + + + + ${project.basedir}/src/main/java25 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + + + tools/jackson/module/blackbird/codegen/BeanReaderGenerator.java + tools/jackson/module/blackbird/codegen/BeanWriterGenerator.java + + + + + compile-java25 + compile + + compile + + + 25 + true + + ${project.basedir}/src/main/java25 + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-java25-classes + compile + + copy-resources + + + ${project.build.outputDirectory} + + + ${project.build.outputDirectory}/META-INF/versions/25 + + tools/jackson/module/blackbird/codegen/BeanReaderGenerator*.class + tools/jackson/module/blackbird/codegen/BeanWriterGenerator*.class + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + tools/jackson/module/blackbird/codegen/BeanReaderGenerator*.class + tools/jackson/module/blackbird/codegen/BeanWriterGenerator*.class + + + + + org.apache.felix + maven-bundle-plugin + + + + <_noee>true + osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=17))" + true + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + 25 + + + + org.apache.maven.plugins + maven-surefire-plugin + + + @{argLine} --patch-module tools.jackson.module.blackbird=${project.build.outputDirectory} + + + true + + + org.cyclonedx diff --git a/blackbird/src/main/java/module-info.java b/blackbird/src/main/java/module-info.java index cbe9b3422..a7c4b8a44 100644 --- a/blackbird/src/main/java/module-info.java +++ b/blackbird/src/main/java/module-info.java @@ -1,13 +1,11 @@ // Blackbird Main artifact Module descriptor module tools.jackson.module.blackbird { - requires java.logging; - requires tools.jackson.core; requires transitive tools.jackson.databind; exports tools.jackson.module.blackbird; provides tools.jackson.databind.JacksonModule with - tools.jackson.module.blackbird.BlackbirdModule; + tools.jackson.module.blackbird.internal.DiscoveredBlackbirdModule; } diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/BlackbirdModule.java b/blackbird/src/main/java/tools/jackson/module/blackbird/BlackbirdModule.java index 01e109943..49ed1bd02 100644 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/BlackbirdModule.java +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/BlackbirdModule.java @@ -2,6 +2,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.function.Supplier; @@ -10,14 +11,37 @@ import tools.jackson.databind.JacksonModule; import tools.jackson.databind.util.NativeImageUtil; +import tools.jackson.module.blackbird.codegen.ClassFileEmitters; import tools.jackson.module.blackbird.deser.BBDeserializerModifier; +import tools.jackson.module.blackbird.internal.DiscoveredBlackbirdModule; import tools.jackson.module.blackbird.ser.BBSerializerModifier; +/** + * Registers Blackbird's generated per-bean codecs. No access configuration is + * needed: generated codecs reach members through handles unreflected after + * databind's own access checks, so whatever stock databind can read or write + * accelerates (on the module path, the same {@code opens ... to + * tools.jackson.databind} stock needs). The lookup-supplying constructors and + * the {@code findLookup}/{@code findLookupSupplier} overrides remain for + * source compatibility, but no acceleration requires a lookup any more, and + * module setup no longer calls them. + *

+ * Code generation needs Java 25 or later: the generators use the ClassFile + * API (final in Java 24) and compile at release 25. On Java 17 to 24, and + * where the generators fail to initialize (for example with a jackson-core or + * jackson-databind of another version), module discovery + * ({@code MapperBuilder.findAndAddModules()}) skips this module with one + * logged warning, so that discovery still registers every other module. + * An explicit registration fails with an {@link IllegalStateException}, + * whether or not discovery runs too. + */ public class BlackbirdModule extends JacksonModule implements java.io.Serializable // @since 3.1 { private static final long serialVersionUID = 3L; + private static final AtomicBoolean WARNED_INACTIVE = new AtomicBoolean(); + // 13-Feb-2026, tatu: [blackbird#334] This is a mess due to need for // backwards-compatibility... but has to be @@ -31,8 +55,8 @@ public BlackbirdModule() { } /** - * @since 3.1 Use default (no-args) constructor and override - * {@link #findLookupSupplier()}) instead. + * @deprecated Since 3.1. Use the default (no-args) constructor: since 3.3 + * Blackbird no longer uses the lookup, so no override is needed either. */ @Deprecated // @since 3.1 public BlackbirdModule(Supplier lookupS) { @@ -41,8 +65,8 @@ public BlackbirdModule(Supplier lookupS) { } /** - * @since 3.1 Use default (no-args) constructor and override - * {@link #findLookup}) instead. + * @deprecated Since 3.1. Use the default (no-args) constructor: since 3.3 + * Blackbird no longer uses the lookup, so no override is needed either. */ @Deprecated // @since 3.1 public BlackbirdModule(Function, MethodHandles.Lookup> lookupF) { @@ -51,11 +75,14 @@ public BlackbirdModule(Function, MethodHandles.Lookup> lookupF) { } /** - * Overridable method module uses to access {@code MethodHandles.Lookup} supplier; + * Overridable method module used to access {@code MethodHandles.Lookup} supplier; * needed to keep module itself {@link java.io.Serializable}. * * @since 3.1 + * @deprecated Since 3.3: module setup no longer calls it, and overriding it + * has no effect. */ + @Deprecated // @since 3.3 protected Function, Lookup> findLookup() { if (_lookupFunction != null) { return _lookupFunction; @@ -64,18 +91,28 @@ protected Function, Lookup> findLookup() { } /** - * Overridable method module uses to access {@code MethodHandles.Lookup} supplier - * used to create actual lookup {@code Function} + * Overridable method module used to access {@code MethodHandles.Lookup} supplier + * used to create actual lookup {@code Function}. * * @since 3.1 + * @deprecated Since 3.3: module setup no longer calls it, and overriding it + * has no effect. */ + @Deprecated // @since 3.3 protected Supplier findLookupSupplier() { return (_lookupSupplier != null) ? _lookupSupplier : MethodHandles::lookup; } - + + /** + * Helper {@link #findLookup()} uses to wrap the lookup supplier. + * + * @deprecated Since 3.3: only {@link #findLookup()} called it, and module + * setup no longer calls that. + */ + @Deprecated // @since 3.3 protected Function, Lookup> _wrapWithJdkClassCheck(Supplier lookup) { return c -> { @@ -95,10 +132,41 @@ public void setupModule(SetupContext context) { return; } - Function, Lookup> lookup = findLookup(); - CrossLoaderAccess openSesame = new CrossLoaderAccess(); - context.addDeserializerModifier(new BBDeserializerModifier(lookup, openSesame)); - context.addSerializerModifier(new BBSerializerModifier(lookup, openSesame)); + if (!ClassFileEmitters.available()) { + // Discovery finds the jar wherever it is on the path, also where + // nobody asked for Blackbird; an explicit registration did ask. + if (this instanceof DiscoveredBlackbirdModule) { + if (WARNED_INACTIVE.compareAndSet(false, true)) { + System.getLogger("tools.jackson.module.blackbird").log( + System.Logger.Level.WARNING, + "jackson-module-blackbird, found through module discovery, is inactive: " + + _unavailableReason() + ". Mappers work without it.", + ClassFileEmitters.initFailure()); + } + return; + } + throw new IllegalStateException("Cannot register BlackbirdModule: " + + _unavailableReason() + ". Remove the registration on this runtime" + + " (jackson-module-afterburner supports Java 17 and later).", + ClassFileEmitters.initFailure()); + } + context.addDeserializerModifier(new BBDeserializerModifier()); + context.addSerializerModifier(new BBSerializerModifier()); + } + + private static String _unavailableReason() { + LinkageError initFailure = ClassFileEmitters.initFailure(); + if (initFailure != null) { + return "its Java 25 classes failed to initialize (" + initFailure + + "); jackson-core or jackson-databind probably does not match its version, " + + PackageVersion.VERSION; + } + Runtime.Version version = Runtime.version(); + if (version.feature() < 25) { + return "it requires Java 25 or later, and this runtime is Java " + version; + } + return "its Java 25 classes (META-INF/versions/25 of the jar) are not visible on Java " + + version + "; the jar was probably repackaged without multi-release support"; } @Override diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/CrossLoaderAccess.java b/blackbird/src/main/java/tools/jackson/module/blackbird/CrossLoaderAccess.java deleted file mode 100644 index 2c064276d..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/CrossLoaderAccess.java +++ /dev/null @@ -1,184 +0,0 @@ -package tools.jackson.module.blackbird; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.function.UnaryOperator; - -class CrossLoaderAccess implements UnaryOperator { - private static final MethodHandle DEFINE_CLASS, HAS_FULL_ACCESS; - - /** - * @deprecated Since 3.2. Only referenced by {@link #accessClassIn}, which - * is dead code on JDK 9+ for all known inputs. Scheduled for removal - * together with the companion-class slow path. - */ - @Deprecated(since = "3.2", forRemoval = true) - private static final String CLASS_NAME = "$$JacksonBlackbirdAccess"; - - // Pre-compiled Java 8 bytecode: - // package ; - // public class BlackbirdAccess { - // public static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - // } - - /** - * @deprecated Since 3.2. See {@link #accessClassIn}. - */ - @Deprecated(since = "3.2", forRemoval = true) - private static final int[] HEADER = new int[] { - 0xca, 0xfe, 0xba, 0xbe, 0x00, 0x00, 0x00, 0x34, 0x00, 0x1c, 0x0a, 0x00, - 0x02, 0x00, 0x03, 0x07, 0x00, 0x04, 0x0c, 0x00, 0x05, 0x00, 0x06, 0x01, - 0x00, 0x10, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x01, 0x00, 0x06, 0x3c, 0x69, 0x6e, - 0x69, 0x74, 0x3e, 0x01, 0x00, 0x03, 0x28, 0x29, 0x56, 0x0a, 0x00, 0x08, - 0x00, 0x09, 0x07, 0x00, 0x0a, 0x0c, 0x00, 0x0b, 0x00, 0x0c, 0x01, 0x00, - 0x1e, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x69, - 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x2f, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x01, 0x00, 0x06, 0x6c, 0x6f, - 0x6f, 0x6b, 0x75, 0x70, 0x01, 0x00, 0x29, 0x28, 0x29, 0x4c, 0x6a, 0x61, - 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x76, 0x6f, - 0x6b, 0x65, 0x2f, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x48, 0x61, 0x6e, - 0x64, 0x6c, 0x65, 0x73, 0x24, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x3b, - 0x09, 0x00, 0x0e, 0x00, 0x0f, 0x07, 0x00, 0x10, 0x0c, 0x00, 0x11, 0x00, - 0x12, 0x01 - }; - /** - * @deprecated Since 3.2. See {@link #accessClassIn}. - */ - @Deprecated(since = "3.2", forRemoval = true) - private static final int[] FOOTER = new int[] { - 0x01, 0x00, 0x06, 0x4c, 0x4f, 0x4f, 0x4b, 0x55, 0x50, 0x01, 0x00, 0x27, - 0x4c, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x69, - 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x2f, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x24, 0x4c, 0x6f, 0x6f, 0x6b, - 0x75, 0x70, 0x3b, 0x01, 0x00, 0x04, 0x43, 0x6f, 0x64, 0x65, 0x01, 0x00, - 0x0f, 0x4c, 0x69, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x01, 0x00, 0x08, 0x3c, 0x63, 0x6c, 0x69, 0x6e, - 0x69, 0x74, 0x3e, 0x01, 0x00, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x46, 0x69, 0x6c, 0x65, 0x01, 0x00, 0x14, 0x42, 0x6c, 0x61, 0x63, 0x6b, - 0x62, 0x69, 0x72, 0x64, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x2e, 0x6a, - 0x61, 0x76, 0x61, 0x01, 0x00, 0x0c, 0x49, 0x6e, 0x6e, 0x65, 0x72, 0x43, - 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x07, 0x00, 0x1a, 0x01, 0x00, 0x25, - 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x69, 0x6e, - 0x76, 0x6f, 0x6b, 0x65, 0x2f, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x48, - 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x24, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, - 0x70, 0x01, 0x00, 0x06, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x00, 0x21, - 0x00, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x19, 0x00, 0x11, - 0x00, 0x12, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x05, 0x00, 0x06, - 0x00, 0x01, 0x00, 0x13, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x05, 0x2a, 0xb7, 0x00, 0x01, 0xb1, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x08, 0x00, 0x15, 0x00, 0x06, 0x00, 0x01, 0x00, 0x13, 0x00, - 0x00, 0x00, 0x1f, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb8, - 0x00, 0x07, 0xb3, 0x00, 0x0d, 0xb1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14, - 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, - 0x00, 0x16, 0x00, 0x00, 0x00, 0x02, 0x00, 0x17, 0x00, 0x18, 0x00, 0x00, - 0x00, 0x0a, 0x00, 0x01, 0x00, 0x19, 0x00, 0x08, 0x00, 0x1b, 0x00, 0x19 - }; - - static { - MethodHandle defineClass = null, hasFullAccess = null; - try { - defineClass = MethodHandles.lookup().findVirtual(MethodHandles.Lookup.class, "defineClass", - MethodType.methodType(Class.class, byte[].class)); - hasFullAccess = MethodHandles.lookup().findVirtual(MethodHandles.Lookup.class, "hasFullPrivilegeAccess", - MethodType.methodType(boolean.class)); - } catch (ReflectiveOperationException ign) { } - DEFINE_CLASS = defineClass; - HAS_FULL_ACCESS = hasFullAccess; - } - - @Override - public MethodHandles.Lookup apply(MethodHandles.Lookup lookup) { - try { - return grantAccess(lookup); - } catch (IOException | ReflectiveOperationException | RuntimeException e) { - e.printStackTrace(); - return lookup; - } - } - - private static MethodHandles.Lookup grantAccess(MethodHandles.Lookup lookup) throws IOException, ReflectiveOperationException { - // Fast path: on JDK 9+, privateLookupIn always yields a full-privilege - // lookup for any bean class Blackbird can see, so this branch is taken - // for every real-world call and the companion-class slow path below - // is effectively unreachable. Verified by `blackbird-tests` - // CrossLoaderAccessTest; see that test's javadoc for the full rationale. - if (DEFINE_CLASS == null || hasFullAccess(lookup)) { - return lookup; - } - // Legacy slow path, retained while we finish verifying it has no live - // callers in any supported JDK / lookup configuration. Scheduled for - // removal — see `accessClassIn` javadoc. - return (MethodHandles.Lookup) accessClassIn(lookup).getField("LOOKUP").get(null); - } - - private static boolean hasFullAccess(MethodHandles.Lookup lookup) { - try { - return HAS_FULL_ACCESS != null && (boolean) HAS_FULL_ACCESS.invokeExact(lookup); - } catch (RuntimeException | Error e) { - throw e; - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - /** - * Defines a {@code $$JacksonBlackbirdAccess} companion class in the same - * package as {@code lookup.lookupClass()} and returns it. The companion - * class exposes a {@code public static final MethodHandles.Lookup LOOKUP} - * field (initialized from a class-initializer calling - * {@link MethodHandles#lookup()}), which callers then use as a - * full-privilege lookup for operations in that package. - * - * @deprecated Since 3.2. This is legacy Java 8 / pre-{@code privateLookupIn} - * fallback logic that is no longer reachable for any known input on - * JDK 9+. The {@link #grantAccess} fast path always wins because - * {@link MethodHandles#privateLookupIn} returns a lookup with - * {@link MethodHandles.Lookup#hasFullPrivilegeAccess()} {@code == true} - * for any bean class Blackbird can see. The - * {@code jackson-module-blackbird-tests} module - * {@code CrossLoaderAccessTest} pins this behavior. If a real live - * caller for this slow path is identified before 4.0, remove the - * {@link Deprecated} marker and keep the method; otherwise, delete - * along with {@link #CLASS_NAME}, {@link #HEADER}, and {@link #FOOTER}. - */ - @Deprecated(since = "3.2", forRemoval = true) - private static Class accessClassIn(MethodHandles.Lookup lookup) throws IOException, ReflectiveOperationException { - Package pkg = lookup.lookupClass().getPackage(); - final String pkgName = pkg.getName(); - String accessClassName = pkgName.isEmpty() ? CLASS_NAME : (pkgName + "." + CLASS_NAME); - ClassLoader lookupClassLoader = lookup.lookupClass().getClassLoader(); - try { - return Class.forName(accessClassName, true, lookupClassLoader); - } catch (ClassNotFoundException ign) { } - synchronized (CrossLoaderAccess.class) { - try { - return Class.forName(accessClassName, true, lookupClassLoader); - } catch (ClassNotFoundException ign) { } - String fqcn = pkgName.isEmpty() ? CLASS_NAME - : (pkgName.replace('.', '/') + "/" + CLASS_NAME); - - ByteArrayOutputStream classBytes = new ByteArrayOutputStream(HEADER.length + FOOTER.length + fqcn.length() + 16); - DataOutputStream dataOut = new DataOutputStream(classBytes); - for (int b : HEADER) { - dataOut.writeByte(b); - } - dataOut.writeUTF(fqcn); - for (int b : FOOTER) { - dataOut.writeByte(b); - } - try { - return (Class) DEFINE_CLASS.invokeExact(lookup, classBytes.toByteArray()); - } catch (RuntimeException | Error | IOException | ReflectiveOperationException e) { - throw e; - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/ClassFileEmitters.java b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/ClassFileEmitters.java new file mode 100644 index 000000000..3b687e9a4 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/ClassFileEmitters.java @@ -0,0 +1,125 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +/** + * Locates the class-file emitters, {@code BeanReaderGenerator} and + * {@code BeanWriterGenerator}, and the shared write helpers that + * {@code BeanWriterGenerator} emits. They use the ClassFile API + * ({@code java.lang.classfile}) and compile at release 25 into + * {@code META-INF/versions/25} of the multi-release jar, so the rest of the + * module can stay at release 17. On an older runtime the JVM does not see + * them, and Blackbird cannot generate codecs (see + * {@code BlackbirdModule.setupModule}). The base classes find them by name + * because they cannot reference them at compile time. + */ +public final class ClassFileEmitters +{ + /** + * The largest generated method, in bytecode bytes, that the emitters + * return; a larger class comes back as {@code null} and the bean keeps + * the stock (de)serializer. HotSpot does not JIT-compile methods over + * 8000 bytes ({@code -XX:+DontCompileHugeMethods}, the default), and an + * interpreted codec is several times slower than stock; the margin + * leaves room for the HotSpot limit to be counted differently. + */ + public static final int MAX_METHOD_BYTES = 7000; + + private static final MethodHandle READER = find("BeanReaderGenerator", "emit", + MethodType.methodType(byte[].class, ReaderCodegen.Plan.class)); + private static final MethodHandle WRITER = find("BeanWriterGenerator", "emit", + MethodType.methodType(byte[].class, WriterCodegen.Plan.class)); + private static final MethodHandle WRITE_HELPERS = find("BeanWriterGenerator", + "emitWriteHelpers", MethodType.methodType(byte[].class)); + + private static final LinkageError INIT_FAILURE = initialize(READER, WRITER, WRITE_HELPERS); + + private ClassFileEmitters() {} + + /** + * Whether this runtime can use the release-25 emitters: false on Java 17 + * to 24, on any Java version when the jar was repackaged without its + * {@code META-INF/versions/25} entries, and when an emitter class failed + * to initialize (see {@link #initFailure()}). + */ + public static boolean available() { + return (READER != null) && (WRITER != null) && (WRITE_HELPERS != null) + && (INIT_FAILURE == null); + } + + /** + * The error that the initialization of an emitter class threw, for + * example a {@link NoClassDefFoundError} for a class that a jackson-core + * or jackson-databind of another version does not have; or null. + */ + public static LinkageError initFailure() { + return INIT_FAILURE; + } + + // Both return null when a generated method is over MAX_METHOD_BYTES. + static byte[] emit(ReaderCodegen.Plan plan) { + return invoke(READER, plan); + } + + static byte[] emit(WriterCodegen.Plan plan) { + return invoke(WRITER, plan); + } + + // The class WriterCodegen.HELPERS_CLASS, never over MAX_METHOD_BYTES. + static byte[] emitWriteHelpers() { + return invoke(WRITE_HELPERS); + } + + private static byte[] invoke(MethodHandle emitter, Object... args) { + if (!available()) { + throw new IllegalStateException("Blackbird class-file emitters are not available" + + " on this runtime", INIT_FAILURE); + } + try { + return (byte[]) emitter.invokeWithArguments(args); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable t) { + throw new IllegalStateException(t); + } + } + + // A class from META-INF/versions/25 is absent on Java 17-24 + // (ClassNotFoundException); if a repackaged jar moved it to the base + // directory, loading it there fails with UnsupportedClassVersionError. + private static MethodHandle find(String simpleName, String method, MethodType type) { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + Class emitter = lookup.findClass( + ClassFileEmitters.class.getPackageName() + "." + simpleName); + return lookup.findStatic(emitter, method, type); + } catch (ReflectiveOperationException | LinkageError e) { + return null; + } + } + + // Found emitters are initialized here, on the shallow stack of module + // registration (the first use of this class), not inside the first + // generation: a throwable there (say, a StackOverflowError deep in a + // recursive resolution) would leave the emitter class unusable for the + // rest of the JVM. A LinkageError makes Blackbird unavailable, so that it + // cannot fail module discovery for every other module; a + // VirtualMachineError propagates. + private static LinkageError initialize(MethodHandle... emitters) { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + for (MethodHandle emitter : emitters) { + if (emitter != null) { + lookup.ensureInitialized(lookup.revealDirect(emitter).getDeclaringClass()); + } + } + return null; + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } catch (LinkageError e) { + return e; + } + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/CodegenDebug.java b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/CodegenDebug.java new file mode 100644 index 000000000..acfc80cb7 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/CodegenDebug.java @@ -0,0 +1,54 @@ +package tools.jackson.module.blackbird.codegen; + +import java.util.function.Supplier; + +/** + * The debug switches for code generation, read once. {@link #CODEGEN_PROPERTY} + * enables the generation trace on stderr (gate decisions, generated class + * names); {@link #DUMP_DIR_PROPERTY} names a directory that receives every + * generated class file (see {@link CodegenDump}). + */ +public final class CodegenDebug +{ + public static final String CODEGEN_PROPERTY = "blackbird.debug.codegen"; + public static final String DUMP_DIR_PROPERTY = "blackbird.debug.dumpDir"; + + public static final boolean ENABLED = Boolean.getBoolean(CODEGEN_PROPERTY); + + private CodegenDebug() {} + + // A supplier, so that no caller builds its message while tracing is off. + public static void log(Supplier message) { + if (ENABLED) { + System.err.println("bbdebug " + message.get()); + } + } + + // A constant message: nothing to defer. + public static void log(String message) { + if (ENABLED) { + System.err.println("bbdebug " + message); + } + } + + /** + * Traces a whole-bean demotion. The modifiers see every type the mapper + * resolves, and the great majority are scalars, collections and other JDK + * types that could never be beans; tracing those buries the one bean the + * trace is being read for, so they are left out. + */ + public static void logSkip(String side, Class beanClass, String reason) { + if (ENABLED && !isBulkType(beanClass)) { + log(() -> side + " skip " + beanClass.getName() + ": " + reason); + } + } + + private static boolean isBulkType(Class type) { + if (type.isPrimitive() || type.isArray() || type.isEnum()) { + return true; + } + String name = type.getName(); + return name.startsWith("java.") || name.startsWith("javax.") + || name.startsWith("jdk.") || name.startsWith("sun."); + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/CodegenDump.java b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/CodegenDump.java new file mode 100644 index 000000000..b9a00043d --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/CodegenDump.java @@ -0,0 +1,43 @@ +package tools.jackson.module.blackbird.codegen; + +import java.io.IOException; +import java.lang.System.Logger.Level; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Debug aid: when the {@code blackbird.debug.dumpDir} system property names a + * directory, every generated class's bytes are written there as + * {@code -.class} before the hidden class is defined, + * for inspection with javap or a decompiler. The shared write helpers are + * written as {@code .class}. A dump failure never affects + * generation. + */ +final class CodegenDump +{ + private CodegenDump() {} + + // Read per class (generation is rare and costly anyway), so that tests + // can turn dumping on and off. + // A hidden bean class's name has a '/' ("Foo/0x...") that a file name + // cannot. + static void dump(Class beanClass, String kind, byte[] bytes) { + dump(beanClass.getName().replace('/', '$') + "-" + kind, bytes); + } + + static void dump(String fileStem, byte[] bytes) { + String dir = System.getProperty(CodegenDebug.DUMP_DIR_PROPERTY); + if (dir == null) { + return; + } + try { + Path path = Path.of(dir); + Files.createDirectories(path); + Files.write(path.resolve(fileStem + ".class"), bytes); + } catch (IOException | RuntimeException e) { + // RuntimeException: an invalid path, a security manager's refusal + System.getLogger("tools.jackson.module.blackbird").log(Level.DEBUG, + "Generated-class dump failed for " + fileStem, e); + } + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/CodegenFallbacks.java b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/CodegenFallbacks.java new file mode 100644 index 000000000..73265b59d --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/CodegenFallbacks.java @@ -0,0 +1,59 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.System.Logger.Level; + +/** + * Reports codec-generation fallbacks. Both kinds leave the stock + * (de)serializer in place, so they log at DEBUG ({@code FINE} through + * java.util.logging) and stay silent by default. Gate failures are expected + * world diversity: exotic classes make reflective gate checks throw. + * Generation failures are Blackbird bugs: the type passed every gate and the + * generator still failed. With the + * {@code tools.jackson.module.blackbird.failOnCodegenError} system property + * set, generation failures are rethrown instead of demoted, which the + * Blackbird test suites enable so a masked generation bug fails the build. + * A generation that runs out of stack is not reported here: the factories + * demote it silently (rethrow it in strict mode), since nothing more can + * safely run on that stack. + */ +public final class CodegenFallbacks +{ + private static final System.Logger LOGGER = + System.getLogger("tools.jackson.module.blackbird"); + + public static final String FAIL_ON_ERROR_PROPERTY = + "tools.jackson.module.blackbird.failOnCodegenError"; + + /** + * Strict mode: {@link #FAIL_ON_ERROR_PROPERTY}, read once, so every + * check in the JVM agrees. + */ + public static final boolean FAIL_ON_ERROR = Boolean.getBoolean(FAIL_ON_ERROR_PROPERTY); + + private CodegenFallbacks() {} + + public static void gateFailure(Class beanClass, Throwable cause) { + if (LOGGER.isLoggable(Level.DEBUG)) { + LOGGER.log(Level.DEBUG, "Blackbird gate check failed for " + beanClass.getName() + + "; the stock (de)serializer stays in place", cause); + } + } + + public static void generationFailure(Class beanClass, Throwable cause) { + if (FAIL_ON_ERROR) { + if (cause instanceof RuntimeException re) { + throw re; + } + if (cause instanceof Error err) { + throw err; + } + throw new IllegalStateException( + "Blackbird codec generation failed for " + beanClass.getName(), cause); + } + if (LOGGER.isLoggable(Level.DEBUG)) { + LOGGER.log(Level.DEBUG, "Blackbird codec generation failed for " + + beanClass.getName() + "; the stock (de)serializer stays in place." + + " This is likely a jackson-module-blackbird bug; please report it.", cause); + } + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/Descs.java b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/Descs.java new file mode 100644 index 000000000..6823e1335 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/Descs.java @@ -0,0 +1,37 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicConstantDesc; + +import tools.jackson.module.blackbird.internal.ClassDataBootstrap; + +/** + * Class descriptors for generated code, derived from class literals so that + * every referenced type is compile-checked and follows renames. Two build + * facts rely on the literals as well: javac's {@code --patch-module} test + * build emits only compile-time-referenced classes into the test module, and + * a descriptor built from a name would silently point at a class that build + * left out. + */ +final class Descs +{ + // Generated code resolves class-data entries by NAME through the + // classDataEntry bootstrap of ClassDataBootstrap (the JDK classDataAt + // bootstrap rejects any condy name but "_"); see ReaderCodegen.dataName. + private static final DirectMethodHandleDesc BSM_DATA_ENTRY = + ConstantDescs.ofConstantBootstrap(of(ClassDataBootstrap.class), + "classDataEntry", ConstantDescs.CD_Object); + + private Descs() {} + + static ClassDesc of(Class cls) { + return cls.describeConstable().orElseThrow(); + } + + /** The constant that loads the class-data entry {@code name} as {@code type}. */ + static DynamicConstantDesc classDataEntry(String name, ClassDesc type) { + return DynamicConstantDesc.ofNamed(BSM_DATA_ENTRY, name, type); + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/MemberHandles.java b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/MemberHandles.java new file mode 100644 index 000000000..6e336ec24 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/MemberHandles.java @@ -0,0 +1,107 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Member access for generated code: every member is unreflected and adapted + * to an erased shape (primitives kept, references widened to Object), so + * generated descriptors never name a bean class. Databind calls fixAccess on + * every member before any modifier runs, and unreflection of a member whose + * accessible flag is set does no access check, so this reaches exactly what + * stock databind can invoke - private members, non-public classes, foreign + * classloaders, and JPMS packages open only to tools.jackson.databind + * included. Unreflection uses the public lookup, so this class grants no + * access of its own: a member nobody made accessible unreflects only when it + * is public in an exported package. An IllegalAccessException here means + * databind could not open the member either (CAN_OVERRIDE_ACCESS_MODIFIERS + * disabled, or a module not open to databind); the caller demotes to the + * stock path, which fails the same way at first use. + *

+ * Varargs members unreflect to variable-arity handles, whose {@code asType} + * would collect an erased trailing {@code Object} into a fresh array instead + * of passing the array through; generated code always passes the array, as + * stock does, so such handles are made fixed-arity first. + */ +public final class MemberHandles +{ + private static final MethodHandles.Lookup LOOKUP = MethodHandles.publicLookup(); + + private MemberHandles() {} + + public static Class erase(Class type) { + return type.isPrimitive() ? type : Object.class; + } + + /** Setter adapted to {@code (Object, eV)void}; a fluent return is discarded. */ + public static MethodHandle setter(Method setter, Class valueType) + throws IllegalAccessException { + return LOOKUP.unreflect(setter).asFixedArity() + .asType(MethodType.methodType(void.class, Object.class, erase(valueType))); + } + + /** + * Builder setter adapted to {@code (Object, eV)Object}: the fluent return + * when there is one, the receiver for a void setter, so generated code + * applies every setter uniformly. Generated code keeps the receiver when + * a fluent setter returns null, as stock does. + */ + public static MethodHandle builderSetter(Method setter, Class valueType) + throws IllegalAccessException { + MethodHandle raw = LOOKUP.unreflect(setter).asFixedArity(); + Class ev = erase(valueType); + if (setter.getReturnType() == void.class) { + MethodHandle keepReceiver = MethodHandles.dropArguments( + MethodHandles.identity(Object.class), 1, ev); + return MethodHandles.foldArguments(keepReceiver, + raw.asType(MethodType.methodType(void.class, Object.class, ev))); + } + return raw.asType(MethodType.methodType(Object.class, Object.class, ev)); + } + + /** Field store adapted to {@code (Object, eV)void}. */ + public static MethodHandle fieldSetter(Field field) throws IllegalAccessException { + return LOOKUP.unreflectSetter(field) + .asType(MethodType.methodType(void.class, Object.class, erase(field.getType()))); + } + + /** Getter adapted to {@code (Object)eV}. */ + public static MethodHandle getter(Method getter) throws IllegalAccessException { + return LOOKUP.unreflect(getter).asType( + MethodType.methodType(erase(getter.getReturnType()), Object.class)); + } + + /** Field load adapted to {@code (Object)eV}. */ + public static MethodHandle fieldGetter(Field field) throws IllegalAccessException { + return LOOKUP.unreflectGetter(field) + .asType(MethodType.methodType(erase(field.getType()), Object.class)); + } + + /** No-arg constructor adapted to {@code ()Object}. */ + public static MethodHandle defaultConstructor(Constructor ctor) + throws IllegalAccessException { + return LOOKUP.unreflectConstructor(ctor) + .asType(MethodType.methodType(Object.class)); + } + + /** Constructor adapted to {@code (e1..en)Object}. */ + public static MethodHandle constructor(Constructor ctor) throws IllegalAccessException { + MethodHandle raw = LOOKUP.unreflectConstructor(ctor).asFixedArity(); + Class[] params = ctor.getParameterTypes(); + Class[] erased = new Class[params.length]; + for (int i = 0; i < params.length; i++) { + erased[i] = erase(params[i]); + } + return raw.asType(MethodType.methodType(Object.class, erased)); + } + + /** Method adapted to {@code (Object)Object} (build methods). */ + public static MethodHandle unary(Method method) throws IllegalAccessException { + return LOOKUP.unreflect(method) + .asType(MethodType.methodType(Object.class, Object.class)); + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/ReaderCodegen.java b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/ReaderCodegen.java new file mode 100644 index 000000000..8b74ccc74 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/ReaderCodegen.java @@ -0,0 +1,236 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +import tools.jackson.core.sym.PropertyNameMatcher; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.SettableBeanProperty; +import tools.jackson.databind.deser.ValueInstantiator; +import tools.jackson.databind.deser.bean.BeanDeserializer; +import tools.jackson.databind.deser.bean.BeanDeserializerBase; +import tools.jackson.databind.deser.bean.BeanPropertyMap; +import tools.jackson.databind.deser.bean.BuilderBasedDeserializer; + +/** + * Defines the hidden-class deserializer for one bean: assembles the class + * data (the name matcher, stock properties, child codecs and member handles, + * as named constants), has {@code BeanReaderGenerator} emit the class bytes, + * and defines the class in this package. Member access and construction go + * through constant MethodHandles with erased descriptors (see + * {@link MemberHandles}), so the generated bytecode never names the bean + * class and every codec defines in the module's own context, whatever the + * bean's package, module, or classloader. + */ +public final class ReaderCodegen +{ + public enum Kind { STRING, INT, LONG, BOOLEAN, CHILD, STOCK } + + // How a generated codec treats an active view. NONE: views cannot affect + // this bean (the stock (de)serializer would ignore them too), no view code. + // DELEGATE: an active view hands the whole call to the stock fallback + // (beans with more than 64 properties). MASK: the codec resolves a cached + // per-view visibility bitmask and each property arm tests its bit, so + // view-active calls keep the generated fast path. + public enum ViewStrategy { NONE, DELEGATE, MASK } + + // One arm per property. handle stores the value into the bean: + // (Object, eV)void in POJO mode, (Object, eV)Object in builder mode (the + // returned builder is kept), and null in record mode (values collect into + // locals) and for STOCK arms. type is the record component type in record + // mode and the child value type for CHILD; child is the linked generated + // codec for CHILD (a GeneratedReaderBase or GeneratedBuilderBase). + public record GenProp(String name, Kind kind, SettableBeanProperty stock, + Class type, ValueDeserializer child, MethodHandle handle) { + public GenProp(String name, Kind kind, SettableBeanProperty stock) { + this(name, kind, stock, null, null, null); + } + } + + /** How the generated deserialize method obtains the value. */ + public enum Mode { + /** POJO, built by the no-arg constructor handle. */ + CONSTRUCTOR, + /** POJO, built by ValueInstantiator.createUsingDefault. */ + INSTANTIATOR, + /** Builder created by the instantiator, finished by the build method. */ + BUILDER, + /** Record: typed locals, then the canonical constructor handle. */ + RECORD + } + + /** + * The construction side of a codec: the handle is the no-arg constructor + * ({@code ()Object}), the build method ({@code (Object)Object}) or the + * canonical constructor ({@code (e1..en)Object}); the instantiator + * creates the bean (INSTANTIATOR) or the builder (BUILDER). + */ + public record Construction(Mode mode, MethodHandle handle, ValueInstantiator instantiator) { + public static Construction constructor(MethodHandle ctor) { + return new Construction(Mode.CONSTRUCTOR, ctor, null); + } + + public static Construction instantiator(ValueInstantiator inst) { + return new Construction(Mode.INSTANTIATOR, null, inst); + } + + public static Construction builder(ValueInstantiator inst, MethodHandle build) { + return new Construction(Mode.BUILDER, build, inst); + } + + public static Construction record(MethodHandle ctor) { + return new Construction(Mode.RECORD, ctor, null); + } + } + + /** + * Everything the bytecode emitter needs. The String arrays hold the + * class-data entry name per arm ({@code null} where an arm has no such + * entry); matchArms gives the arm for each index of the name matcher (an + * arm can have several names, aliases, or none, an ignored record + * component); localNames are the record component names (record mode + * only), which name the component locals in the debug table. + * inputNames tells whether a matched name can differ from the + * property's own name (aliases, case-insensitive matching), which + * builder-mode error messages then need. + * vanillaLoop (POJO mode only) tells whether stock reads the bean with + * its vanilla loop, which ends quietly on an odd token after an unknown + * name. + */ + record Plan(String className, List props, String[] stockName, + String[] childName, String[] handleName, Mode mode, boolean injectables, + boolean vanillaLoop, ViewStrategy views, int[] matchArms, String[] localNames, boolean inputNames) {} + + private ReaderCodegen() {} + + /** + * Generates and defines the codec, or returns {@code null} when the + * generated code would be too large for the JIT to compile (see + * {@link ClassFileEmitters#MAX_METHOD_BYTES}). + */ + @SuppressWarnings("unchecked") + public static ValueDeserializer generate(Class beanClass, List props, + PropertyNameMatcher matcher, int[] matchArms, BeanDeserializerBase source, + BeanPropertyMap beanProps, Construction construction, boolean injectables, + boolean vanillaLoop, ViewStrategy views, String[] localNames) + throws ReflectiveOperationException { + Map classData = new LinkedHashMap<>(); + classData.put("matcher", matcher); + // Tier-A properties also carry their SettableBeanProperty: their + // VALUE_NULL branch runs through it, since null handling is + // configuration-dependent per property. + String[] stockName = new String[props.size()]; + String[] childName = new String[props.size()]; + String[] handleName = new String[props.size()]; + StringJoiner layout = new StringJoiner(","); + Mode mode = construction.mode(); + for (int i = 0; i < props.size(); i++) { + GenProp gp = props.get(i); + // Outside record mode only STOCK arms go without a store; the + // emitter relies on it, and a factory slip fails generation + // (a test failure in strict mode) instead of quietly going stock. + if (mode != Mode.RECORD && gp.kind() != Kind.STOCK && gp.handle() == null) { + throw new IllegalStateException("no store handle for " + gp.kind() + + " property '" + gp.name() + "' of " + beanClass.getName()); + } + layout.add(gp.name() + "=" + gp.kind()); + stockName[i] = dataName(classData, gp.name() + "Prop"); + classData.put(stockName[i], gp.stock()); + if (gp.child() != null) { + childName[i] = dataName(classData, gp.name() + "Codec"); + classData.put(childName[i], gp.child()); + } + if (gp.handle() != null) { + handleName[i] = dataName(classData, gp.name() + "Set"); + classData.put(handleName[i], gp.handle()); + } + } + switch (mode) { + case CONSTRUCTOR, RECORD -> classData.put("constructor", construction.handle()); + case INSTANTIATOR -> classData.put("instantiator", construction.instantiator()); + case BUILDER -> { + classData.put("instantiator", construction.instantiator()); + classData.put("buildMethod", construction.handle()); + } + } + + MethodHandles.Lookup definer = MethodHandles.lookup(); + // A hidden class must be named in its define context's package. + String className = definer.lookupClass().getPackageName() + + ".BBReader_" + codecName(beanClass); + byte[] bytes = ClassFileEmitters.emit(new Plan(className, props, stockName, childName, + handleName, mode, injectables, vanillaLoop, views, matchArms, localNames, + beanProps.hasAliases() || beanProps.isCaseInsensitive())); + if (bytes == null) { + CodegenDebug.log("reader gate: a generated method is over " + + ClassFileEmitters.MAX_METHOD_BYTES + " bytes"); + return null; + } + CodegenDump.dump(beanClass, "reader", bytes); + // No ClassOption.STRONG: the codec instance held by the mapper's + // deserializer cache anchors the class, so codecs unload with the + // mapper instead of pinning metaspace for the loader's lifetime. + // Module-context defines never legitimately fail: any throw here is a + // generator bug, surfaced through the placeholder's failure handling. + MethodHandles.Lookup hidden = definer.defineHiddenClassWithClassData( + bytes, Map.copyOf(classData), true); + // The codec is a copy of the resolved source (see the generated + // reader base classes). + Class sourceType = (mode == Mode.BUILDER) + ? BuilderBasedDeserializer.class : BeanDeserializer.class; + MethodHandle ctor = hidden.findConstructor(hidden.lookupClass(), + MethodType.methodType(void.class, sourceType, BeanPropertyMap.class, + String.class)); + try { + return (ValueDeserializer) ctor.invoke(source, beanProps, layout.toString()); + } catch (RuntimeException | Error e) { + // As they are: tryGenerate treats StackOverflowError and other + // VirtualMachineErrors specially. + throw e; + } catch (Throwable t) { + throw new IllegalStateException("cannot instantiate generated codec", t); + } + } + + // The bean's short name without Class.getSimpleName(): that call consults + // InnerClasses metadata, which raises IncompatibleClassChangeError for a + // member class redefined in a foreign classloader, and such beans + // accelerate. Class.getName() reads no metadata. A hidden bean class is + // named "Foo/0x..." and '/' is not allowed in a binary name. + static String codecName(Class beanClass) { + String name = beanClass.getName(); + return name.substring(name.lastIndexOf('.') + 1).replace('/', '$'); + } + + // Condy names are JVM member names ('.', ';', '[', '/', '<', '>' forbidden; + // DynamicConstantDesc validates them as method names) and JSON property + // names are arbitrary, so sanitize and dedupe. + static String dataName(Map data, String base) { + String name = unqualifiedName(base); + String candidate = name; + for (int n = 2; data.containsKey(candidate); n++) { + candidate = name + "$" + n; + } + return candidate; + } + + // A JVM member name (JVMS 4.2.2, angle brackets excluded too) derived + // from an arbitrary string. + static String unqualifiedName(String base) { + if (base.isEmpty()) { + return "$"; + } + StringBuilder sb = new StringBuilder(base.length()); + for (int i = 0; i < base.length(); i++) { + char c = base.charAt(i); + sb.append(c == '.' || c == ';' || c == '[' || c == '/' + || c == '<' || c == '>' ? '$' : c); + } + return sb.toString(); + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/WriterCodegen.java b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/WriterCodegen.java new file mode 100644 index 000000000..e92016cd8 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/codegen/WriterCodegen.java @@ -0,0 +1,166 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +import tools.jackson.core.SerializableString; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.ser.PropertyWriter; +import tools.jackson.databind.ser.bean.BeanSerializerBase; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.ViewStrategy; +import tools.jackson.module.blackbird.internal.GeneratedWriterBase; + +/** + * Defines the hidden-class serializer for one bean: assembles the class data + * (pre-encoded names, stock writers, child writers and getter handles, as + * named constants), has {@code BeanWriterGenerator} emit the class bytes, and + * defines the class in this package. Property values load through constant + * (Object)eV MethodHandles (see {@link MemberHandles}), so the generated + * bytecode never names the bean class. + */ +public final class WriterCodegen +{ + public enum WKind { STRING, INT, LONG, BOOLEAN, CHILD, STOCK } + + // handle loads the property value from the bean, erased to (Object)eV + // (a getter call or a field read); null for STOCK. + public record GenWProp(WKind kind, PropertyWriter stock, + SerializableString name, GeneratedWriterBase child, MethodHandle handle) {} + + /** + * Everything the bytecode emitter needs; the String arrays hold the + * class-data entry name per property ({@code null} where a property has + * no such entry). nativePolyWrites: emit a serializeWithType override + * matching stock BeanSerializerBase's WritableTypeId flow; false (a + * {@code @JsonTypeId} property exists, whose value feeds the type id) + * keeps the base forwarding. + */ + record Plan(String className, List props, String[] stockName, + String[] nameName, String[] childName, String[] selfName, String[] handleName, + ViewStrategy views, boolean includeByDefault, boolean nativePolyWrites) {} + + /** + * The binary name of the class that holds the write helpers of all + * generated writers. It is defined once in this package, before the + * first writer, and the writers call it by name. + */ + static final String HELPERS_CLASS = WriterCodegen.class.getPackageName() + ".WriteHelpers"; + + private WriterCodegen() {} + + /** + * Generates and defines the writer, or returns {@code null} when the + * generated code would be too large for the JIT to compile (see + * {@link ClassFileEmitters#MAX_METHOD_BYTES}). + */ + @SuppressWarnings("unchecked") + public static ValueSerializer generate(Class beanClass, List props, + BeanSerializerBase fallback, ViewStrategy views, boolean includeByDefault, + boolean nativePolyWrites) + throws ReflectiveOperationException { + Map classData = new LinkedHashMap<>(); + String[] stockName = new String[props.size()]; + String[] nameName = new String[props.size()]; + String[] childName = new String[props.size()]; + String[] selfName = new String[props.size()]; + String[] handleName = new String[props.size()]; + StringJoiner layout = new StringJoiner(","); + for (int i = 0; i < props.size(); i++) { + GenWProp p = props.get(i); + String base = p.stock().getName(); + layout.add(base + "=" + p.kind()); + stockName[i] = ReaderCodegen.dataName(classData, base + "Writer"); + classData.put(stockName[i], p.stock()); + if (p.name() != null) { + nameName[i] = ReaderCodegen.dataName(classData, base + "Name"); + classData.put(nameName[i], p.name()); + } + if (p.child() != null) { + childName[i] = ReaderCodegen.dataName(classData, base + "Codec"); + classData.put(childName[i], p.child()); + selfName[i] = ReaderCodegen.dataName(classData, base + "Self"); + classData.put(selfName[i], GeneratedWriterBase.selfReferenceWriter( + (BeanPropertyWriter) p.stock())); + } + if (p.handle() != null) { + handleName[i] = ReaderCodegen.dataName(classData, base + "Get"); + classData.put(handleName[i], p.handle()); + } + } + + HelpersHolder.ensureDefined(); + MethodHandles.Lookup definer = MethodHandles.lookup(); + // A hidden class must be named in its define context's package. + String className = definer.lookupClass().getPackageName() + + ".BBWriter_" + ReaderCodegen.codecName(beanClass); + byte[] bytes = ClassFileEmitters.emit(new Plan(className, props, stockName, nameName, + childName, selfName, handleName, views, includeByDefault, nativePolyWrites)); + if (bytes == null) { + CodegenDebug.log("writer gate: a generated method is over " + + ClassFileEmitters.MAX_METHOD_BYTES + " bytes"); + return null; + } + CodegenDump.dump(beanClass, "writer", bytes); + // Module-context defines never legitimately fail: any throw here is a + // generator bug, surfaced through the modifier's failure handling. + MethodHandles.Lookup hidden = definer.defineHiddenClassWithClassData( + bytes, Map.copyOf(classData), true); + MethodHandle ctor = hidden.findConstructor(hidden.lookupClass(), + MethodType.methodType(void.class, BeanSerializerBase.class, String.class)); + try { + return (ValueSerializer) ctor.invoke(fallback, layout.toString()); + } catch (RuntimeException | Error e) { + // As they are: tryGenerate treats StackOverflowError and other + // VirtualMachineErrors specially. + throw e; + } catch (Throwable t) { + throw new IllegalStateException("cannot instantiate generated writer", t); + } + } + + // A normal class, not a hidden one, so that the hidden writers can name + // it. It is defined at most once per class loader of Blackbird (a second + // define of the same name would fail), on first use rather than in a + // static initializer: a failed attempt (say, a throwable from deep in a + // recursive resolution) must not leave the class unusable for every + // later writer, so the next generation tries again. + private static final class HelpersHolder + { + private static volatile Class _defined; + + static void ensureDefined() throws IllegalAccessException { + if (_defined == null) { + define(); + } + } + + private static synchronized void define() throws IllegalAccessException { + if (_defined == null) { + byte[] bytes = ClassFileEmitters.emitWriteHelpers(); + CodegenDump.dump(HELPERS_CLASS, bytes); + try { + _defined = MethodHandles.lookup().defineClass(bytes); + } catch (LinkageError e) { + // An earlier attempt may have defined the class and then + // failed before recording it: use that definition. + _defined = _alreadyDefined(e); + } + } + } + + private static Class _alreadyDefined(LinkageError defineFailure) { + try { + return Class.forName(HELPERS_CLASS, false, WriterCodegen.class.getClassLoader()); + } catch (ClassNotFoundException | LinkageError e) { + defineFailure.addSuppressed(e); + throw defineFailure; + } + } + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBBuilderPlaceholder.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBBuilderPlaceholder.java new file mode 100644 index 000000000..c5510f185 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBBuilderPlaceholder.java @@ -0,0 +1,102 @@ +package tools.jackson.module.blackbird.deser; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.BeanProperty; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.bean.BeanDeserializerBase; +import tools.jackson.databind.deser.bean.BuilderBasedDeserializer; +import tools.jackson.module.blackbird.codegen.CodegenDebug; + +/** + * The builder-based counterpart of {@link BBReaderPlaceholder}: a copy of + * the stock {@link BuilderBasedDeserializer} that generates the builder + * codec when resolved, and hands out an exact-class copy of the resolved + * stock state when no codec is generated. + */ +final class BBBuilderPlaceholder extends BuilderBasedDeserializer +{ + private final boolean _declaresViews; + + private volatile ValueDeserializer _codec; + + // Set once resolution generated no codec + private volatile boolean _demoted; + + // A plain BuilderBasedDeserializer with this placeholder's resolved and + // contextualized state, made on first demoted contextualization + private volatile BeanDeserializerBase _stock; + + BBBuilderPlaceholder(BuilderBasedDeserializer src, boolean declaresViews) { + super(src); + _declaresViews = declaresViews; + } + + @Override + public void resolve(DeserializationContext ctxt) { + CodegenDebug.log(() -> "reader resolve " + handledType().getName()); + super.resolve(ctxt); + // A builder without a build method (the builder is the value) keeps + // the stock path. + ValueDeserializer codec = null; + if (_buildMethod == null) { + CodegenDebug.log("reader gate: builder without build method"); + } else { + codec = BBReaderFactory.tryGenerate(new BBReaderFactory.Source(this, _anySetter, + _injectables != null, _vanillaProcessing, _beanProperties, _propertyBasedCreator, + _nonStandardCreation, + (_delegateDeserializer != null) || (_arrayDelegateDeserializer != null), + _externalTypeIdHandler != null, _unwrappedPropertyHandler != null, + _serializationShape == JsonFormat.Shape.ARRAY, _buildMethod), + ctxt, _declaresViews); + } + _codec = codec; + _demoted = (codec == null); + } + + // As in BBReaderPlaceholder: contextualize this resolved instance, then + // hand out the codec or the stock copy (made after contextualization) + // when that leaves it as it is. + @Override + public ValueDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) { + ValueDeserializer contextual = super.createContextual(ctxt, property); + if (contextual != this) { + return contextual; + } + ValueDeserializer codec = _codec; + if (codec != null) { + return codec; + } + if (!_demoted) { + return this; + } + BeanDeserializerBase stock = _stock; + if (stock == null) { + stock = withBeanProperties(_beanProperties); + _stock = stock; + } + return stock; + } + + // Stock createContextual initializes the name matcher of the property + // map on every contextualization; generation already did, for this map + // and this mapper's stream factory (BBReaderFactory), so with a codec + // the map is not rebuilt: only this instance's cached view of it is set. + @Override + protected void initNameMatcher(DeserializationContext ctxt) { + if (_codec == null) { + super.initNameMatcher(ctxt); + return; + } + _propertyNameMatcher = _beanProperties.getNameMatcher(); + _propertiesByIndex = _beanProperties.getNameMatcherProperties(); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) { + ValueDeserializer codec = _codec; + return (codec != null) ? codec.deserialize(p, ctxt) : super.deserialize(p, ctxt); + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBDeserializerModifier.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBDeserializerModifier.java index 4a3c98670..94bd63025 100644 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBDeserializerModifier.java +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBDeserializerModifier.java @@ -1,375 +1,103 @@ package tools.jackson.module.blackbird.deser; -import java.lang.invoke.LambdaConversionException; -import java.lang.invoke.LambdaMetafactory; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.Member; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.ObjIntConsumer; -import java.util.function.ObjLongConsumer; -import java.util.function.UnaryOperator; -import java.util.logging.Level; -import java.util.logging.Logger; - -import tools.jackson.databind.*; -import tools.jackson.databind.deser.*; -import tools.jackson.databind.deser.impl.MethodProperty; -import tools.jackson.databind.deser.std.StdValueInstantiator; -import tools.jackson.databind.introspect.*; -import tools.jackson.databind.util.ClassUtil; -import tools.jackson.module.blackbird.util.ReflectionHack; +import tools.jackson.databind.BeanDescription; +import tools.jackson.databind.DeserializationConfig; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.ValueDeserializerModifier; +import tools.jackson.databind.deser.bean.BeanDeserializer; +import tools.jackson.databind.deser.bean.BuilderBasedDeserializer; +import tools.jackson.databind.introspect.BeanPropertyDefinition; +import tools.jackson.module.blackbird.codegen.CodegenDebug; +import tools.jackson.module.blackbird.codegen.CodegenFallbacks; + +/** + * Wraps eligible stock bean deserializers in a placeholder that generates a + * per-bean codec at resolve time (ClassFile API hidden class). Every gate that + * fails leaves the stock deserializer in place, so behavior never changes for + * beans the generator does not fully understand. Most gates run at resolve + * time, on the resolved stock deserializer (see BBReaderFactory); this + * modifier only filters out what cannot become a codec at all. + */ public class BBDeserializerModifier extends ValueDeserializerModifier { private static final long serialVersionUID = 1L; - private static final Logger LOG = Logger.getLogger(BBDeserializerModifier.class.getName()); - - private final Function, Lookup> _lookups; - private final UnaryOperator _accessGrant; - - public BBDeserializerModifier(Function, MethodHandles.Lookup> lookups, UnaryOperator accessGrant) - { - _lookups = lookups; - _accessGrant = accessGrant; - } - - /* - /********************************************************************** - /* BeanDeserializerModifier methods - /********************************************************************** - */ + // Member access rides databind's own fixAccess (see MemberHandles), so + // no lookup is needed. + public BBDeserializerModifier() { } @Override - public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, - BeanDescription.Supplier beanDescRef, BeanDeserializerBuilder builder) + public ValueDeserializer modifyDeserializer(DeserializationConfig config, + BeanDescription.Supplier beanDescRef, ValueDeserializer deserializer) { - final Class beanClass = beanDescRef.getBeanClass(); - MethodHandles.Lookup lookup = _lookups.apply(beanClass); - if (lookup == null) { - return builder; - } + // Gate failures of any kind leave the stock deserializer in place. The + // reflective gates can throw for exotic classes (a bean from a foreign + // classloader with inconsistent InnerClasses metadata raises + // IncompatibleClassChangeError from getEnclosingClass), and an + // acceleration modifier must never break a bean stock databind handles. try { - lookup = ReflectionHack.privateLookupIn(beanClass, lookup); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - /* Hmmh. Can we access stuff from private classes? - * Possibly, if we can use parent class loader. - * (should probably skip all non-public?) - */ - if (Modifier.isPrivate(beanClass.getModifiers())) { // TODO?? - return builder; - } - lookup = _accessGrant.apply(lookup); - // databind sets these for a builder's deserializer before this; a builder - // without a build method (`buildMethodName=""`) has the config - final boolean forBuilder = (builder.getBuildMethod() != null) - || (builder.getBuilderConfig() != null); - List> newProps = forBuilder - ? findOptimizableProperties(lookup, config, builder.getProperties(), true) - : findOptimizableProperties(lookup, config, builder.getProperties()); - // and if we found any, create mutator proxy, replace property objects - if (!newProps.isEmpty()) { - for (OptimizedSettableBeanProperty prop : newProps) { - builder.addOrReplaceProperty(prop, true); - } - } - // Second thing: see if we could (re)generate Creator(s): - ValueInstantiator inst = builder.getValueInstantiator(); - /* Hmmh. Probably better to require exact default implementation - * and not sub-class; chances are sub-class uses its own - * construction anyway. - */ - if (inst.getClass() == StdValueInstantiator.class) { - // also, only override if using default creator (no-arg ctor, no-arg static factory) - if (inst.canCreateUsingDefault() || inst.canCreateFromObjectWith()) { - inst = new CreatorOptimizer(beanClass, lookup, (StdValueInstantiator) inst).createOptimized(); - if (inst != null) { - builder.setValueInstantiator(inst); - } - } - } - - // also: may want to replace actual BeanDeserializer as well? For this, need to replace builder - // (but only if builder is the original standard one; don't want to break other impls) - if (builder.getClass() == BeanDeserializerBuilder.class) { - return new SuperSonicDeserializerBuilder(builder); - } - return builder; - } - - /* - /********************************************************************** - /* Internal methods - /********************************************************************** - */ - - protected List> findOptimizableProperties( - Lookup lookup, DeserializationConfig config, - Iterator propIterator) - { - return findOptimizableProperties(lookup, config, propIterator, false); - } - - protected List> findOptimizableProperties( - Lookup lookup, DeserializationConfig config, - Iterator propIterator, boolean forBuilder) - { - ArrayList> newProps = new ArrayList>(); - - // Ok, then, find any properties for which we could generate accessors - while (propIterator.hasNext()) { - try { - nextProperty(propIterator.next(), lookup, config, forBuilder, newProps); - } catch (Throwable e) { - if (e instanceof Error) { - throw (Error) e; - } - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } - throw new RuntimeException(e); - } + return doModify(config, beanDescRef, deserializer); + } catch (RuntimeException | LinkageError e) { + CodegenFallbacks.gateFailure(beanDescRef.getBeanClass(), e); + return deserializer; } - return newProps; } - @SuppressWarnings("unchecked") - private void nextProperty(SettableBeanProperty prop, - Lookup lookup, DeserializationConfig config, boolean forBuilder, - ArrayList> newProps) throws Throwable + private ValueDeserializer doModify(DeserializationConfig config, + BeanDescription.Supplier beanDescRef, ValueDeserializer deserializer) { - AnnotatedMember member = prop.getMember(); - Member jdkMember = member.getMember(); - - // if we ever support virtual properties, this would be null, so check, skip - if (jdkMember == null) { - return; - } - // First: we can't access private fields or methods.... - if (Modifier.isPrivate(jdkMember.getModifiers())) { - return; - } - // (although, interestingly enough, can seem to access private classes...) - - // 30-Jul-2012, tatu: [module-afterburner#6]: Needs to skip custom deserializers, if any. - if (prop.hasValueDeserializer()) { - if (!isDefaultDeserializer(prop.getValueDeserializer())) { - return; - } - } - - MethodHandle setter; - Class type; - if (jdkMember instanceof Method && prop instanceof MethodProperty) { - final Method method = (Method) jdkMember; - try { - setter = lookup.unreflect(_withoutAccessOverride(method)); - } catch (IllegalAccessException | NoSuchMethodException | SecurityException e) { - // e.g. an inherited protected setter, where the lookup class isn't the - // subclass (as with `CrossLoaderAccess` on some JDKs); reflection can still call it - _logSkipped(method, lookup, e); - return; - } - type = ((AnnotatedMethod) member).getRawParameterType(0); - // Only these get a generated setter, so don't check (or load) anything for others - if (type.isPrimitive() && (type != Integer.TYPE) && (type != Long.TYPE) && (type != Boolean.TYPE)) { - return; + if (deserializer.getClass() != BeanDeserializer.class + && deserializer.getClass() != BuilderBasedDeserializer.class) { + return skip(beanDescRef, deserializer, "not a stock bean deserializer"); + } + if (Boolean.TRUE.equals(config.getDefaultMergeable())) { + return skip(beanDescRef, deserializer, "mapper default mergeable"); + } + BeanDescription beanDesc = beanDescRef.get(); + Class beanClass = beanDesc.getBeanClass(); + // Non-static inner classes construct against an enclosing instance, + // which the generated loop does not model. The static check runs + // first: for a static member class redefined in a foreign classloader, + // getEnclosingClass raises IncompatibleClassChangeError, and such + // beans accelerate. + if (!Modifier.isStatic(beanClass.getModifiers()) + && beanClass.getEnclosingClass() != null) { + return skip(beanDescRef, deserializer, "non-static inner class"); + } + if (deserializer.getClass() == BeanDeserializer.class && !beanClass.isRecord() + && Modifier.isAbstract(beanClass.getModifiers())) { + return skip(beanDescRef, deserializer, "abstract"); + } + // Whether the bean or any property declares @JsonView explicitly: the + // resolved properties cannot tell a declared view from the empty view + // set that disabled DEFAULT_VIEW_INCLUSION forces onto unannotated + // properties, so the property definitions decide here. + boolean declaresViews = config.getAnnotationIntrospector() + .findViews(config, beanDesc.getClassInfo()) != null; + for (BeanPropertyDefinition def : beanDesc.findProperties()) { + if (def.findViews() != null) { + declaresViews = true; } - // A generated setter that would fail on every call (like one taking a - // package-private type from another package) is left to reflection - if (!ReflectionHack.canLinkAccessor(lookup, method, setter)) { - _logSkipped(method, lookup, null); - return; + if (def.getPrimaryMember() != null + && config.getAnnotationIntrospector() + .findUnwrappingNameTransformer(config, def.getPrimaryMember()) != null) { + return skip(beanDescRef, deserializer, "unwrapped property"); } - // Builder setters used to go through `MethodProperty`, whose method handle - // applies Java's access rules (modules included) unless Jackson made the - // setter accessible; the generated setter ignores those rules, so it may only - // call what `MethodProperty` could. Other builder setters are left to it - // altogether, as databind also calls `set()` on builders. - if (forBuilder && !isMadeAccessible(config, prop, method) - && !isCallableByDatabind(method)) { - return; + if (def.getMetadata() != null && def.getMetadata().getMergeInfo() != null) { + return skip(beanDescRef, deserializer, "property merge"); } - } else { - return; - //setter = lookup.unreflectGetter((Field) jdkMember); } - - final Class receiverType = setter.type().parameterType(0); - - // A setter that returns a value gets a generated thunk, which a plain lambda - // (one class each, shared by all properties) adapts to the consumer interface; - // a void setter gets a generated consumer, which one adapts to a thunk that - // returns null, so the same builder is kept (as `MethodProperty` does). Only - // builders use the thunk (for `setAndReturn`); others leave that to reflection. - if (type.isPrimitive()) { - if (type == Integer.TYPE) { - ObjIntBiFunction returning = createThunk(lookup, ObjIntBiFunction.class, int.class, setter); - ObjIntConsumer consumer = (returning == null) - ? createConsumer(lookup, ObjIntConsumer.class, int.class, setter) - : (bean, v) -> returning.apply(bean, v); - ObjIntBiFunction thunk = !forBuilder ? null : (returning != null) ? returning - : (bean, v) -> { consumer.accept(bean, v); return null; }; - newProps.add(new SettableIntProperty(prop, consumer, - thunk, receiverType, forBuilder)); - } else if (type == Long.TYPE) { - ObjLongBiFunction returning = createThunk(lookup, ObjLongBiFunction.class, long.class, setter); - ObjLongConsumer consumer = (returning == null) - ? createConsumer(lookup, ObjLongConsumer.class, long.class, setter) - : (bean, v) -> returning.apply(bean, v); - ObjLongBiFunction thunk = !forBuilder ? null : (returning != null) ? returning - : (bean, v) -> { consumer.accept(bean, v); return null; }; - newProps.add(new SettableLongProperty(prop, consumer, - thunk, receiverType, forBuilder)); - } else if (type == Boolean.TYPE) { - ObjBooleanBiFunction returning = createThunk(lookup, ObjBooleanBiFunction.class, boolean.class, setter); - ObjBooleanConsumer consumer = (returning == null) - ? createConsumer(lookup, ObjBooleanConsumer.class, boolean.class, setter) - : (bean, v) -> returning.apply(bean, v); - ObjBooleanBiFunction thunk = !forBuilder ? null : (returning != null) ? returning - : (bean, v) -> { consumer.accept(bean, v); return null; }; - newProps.add(new SettableBooleanProperty(prop, consumer, - thunk, receiverType, forBuilder)); - } - } else { - BiFunction returning = createThunk(lookup, BiFunction.class, Object.class, setter); - if (type == String.class) { - BiConsumer consumer = (returning == null) - ? createConsumer(lookup, BiConsumer.class, Object.class, setter) - : (bean, v) -> returning.apply(bean, v); - BiFunction thunk = !forBuilder ? null : (returning != null) ? returning - : (bean, v) -> { consumer.accept(bean, (String) v); return null; }; - newProps.add(new SettableStringProperty(prop, consumer, - thunk, receiverType, forBuilder)); - } else { - BiConsumer consumer = (returning == null) - ? createConsumer(lookup, BiConsumer.class, Object.class, setter) - : (bean, v) -> returning.apply(bean, v); - BiFunction thunk = !forBuilder ? null : (returning != null) ? returning - : (bean, v) -> { consumer.accept(bean, v); return null; }; - newProps.add(new SettableObjectProperty(prop, consumer, - thunk, receiverType, type, forBuilder)); - } - } - } - - // Whether Jackson makes the setter accessible: `BeanDeserializerBuilder` has the - // property fix its access when building the deserializer (only if allowed to - // override access modifiers), so ask it to do so now - @SuppressWarnings("deprecation") - private static boolean isMadeAccessible(DeserializationConfig config, - SettableBeanProperty prop, Method setter) { - if (!config.canOverrideAccessModifiers()) { - return false; - } - try { - prop.fixAccess(config); - } catch (IllegalArgumentException e) { - // databind reports this when building the deserializer - return false; - } - return setter.isAccessible(); - } - - // Returns null for void setters, which have nothing to return - static T createThunk(Lookup lookup, Class thunkType, Class valueType, MethodHandle setter) - throws Throwable, LambdaConversionException { - if (setter.type().returnType() == void.class) { - return null; + if (deserializer instanceof BuilderBasedDeserializer builder) { + return new BBBuilderPlaceholder(builder, declaresViews); } - return thunkType.cast(LambdaMetafactory.metafactory( - lookup, - "apply", - MethodType.methodType(thunkType), - MethodType.methodType(Object.class, Object.class, valueType), - setter, - setter.type()) - .getTarget().invoke()); - } - - // Only for void setters, which can implement the consumer directly - @SuppressWarnings("unchecked") - private static T createConsumer(Lookup lookup, Class iface, Class valueType, MethodHandle setter) - throws Throwable, LambdaConversionException { - return (T) iface.cast(LambdaMetafactory.metafactory( - lookup, - "accept", - MethodType.methodType(iface), - MethodType.methodType(void.class, Object.class, valueType), - setter, - setter.type()) - .getTarget().invoke()); - } - - // Whether databind can call a public setter it didn't make accessible: its own - // lookup can, if databind's module reads the setter's and that exports the package - // to it (as with core reflection, but without its implied readability) - private static boolean isCallableByDatabind(Method setter) { - Class declaringClass = setter.getDeclaringClass(); - if (!Modifier.isPublic(setter.getModifiers()) || !Modifier.isPublic(declaringClass.getModifiers())) { - return false; - } - Module databind = MethodProperty.class.getModule(); - Module module = declaringClass.getModule(); - return databind.canRead(module) && module.isExported(declaringClass.getPackageName(), databind); - } - - // `unreflect` skips its access checks for a method made accessible (as databind - // does early for injection or back references), but the generated accessor - // can't skip them, so check a copy that isn't - @SuppressWarnings("deprecation") - private static Method _withoutAccessOverride(Method method) throws NoSuchMethodException { - if (!method.isAccessible()) { - return method; - } - return method.getDeclaringClass().getDeclaredMethod(method.getName(), method.getParameterTypes()); - } - - private static void _logSkipped(Method setter, Lookup lookup, Throwable cause) { - if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, String.format( - "Not optimizing setter %s: a generated accessor in %s %s; using reflection", - setter, lookup.lookupClass().getName(), - (cause == null) ? "couldn't link to it" : "can't access it"), cause); - } - } - - /** - * Helper method used to check whether given deserializer is the default - * deserializer implementation: this is necessary to avoid overriding other - * kinds of deserializers. - */ - protected boolean isDefaultDeserializer(ValueDeserializer deser) { - return ClassUtil.isJacksonStdImpl(deser) - // 07-May-2018, tatu: Probably can't happen but just in case - || (deser instanceof SuperSonicBeanDeserializer); - - } - - // Generated thunks for setters that return a value (such as builder setters) - - @FunctionalInterface - public interface ObjIntBiFunction { - Object apply(Object bean, int value); - } - - @FunctionalInterface - public interface ObjLongBiFunction { - Object apply(Object bean, long value); + return new BBReaderPlaceholder((BeanDeserializer) deserializer, declaresViews); } - @FunctionalInterface - public interface ObjBooleanBiFunction { - Object apply(Object bean, boolean value); + private static ValueDeserializer skip(BeanDescription.Supplier beanDescRef, + ValueDeserializer deserializer, String reason) { + CodegenDebug.logSkip("reader", beanDescRef.getBeanClass(), reason); + return deserializer; } } diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBReaderFactory.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBReaderFactory.java new file mode 100644 index 000000000..c94a9dd0b --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBReaderFactory.java @@ -0,0 +1,563 @@ +package tools.jackson.module.blackbird.deser; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.RecordComponent; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import tools.jackson.core.sym.PropertyNameMatcher; +import tools.jackson.core.util.Named; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.PropertyName; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.CreatorProperty; +import tools.jackson.databind.deser.SettableAnyProperty; +import tools.jackson.databind.deser.SettableBeanProperty; +import tools.jackson.databind.deser.bean.BeanDeserializerBase; +import tools.jackson.databind.deser.bean.BeanPropertyMap; +import tools.jackson.databind.deser.bean.PropertyBasedCreator; +import tools.jackson.databind.deser.impl.MethodProperty; +import tools.jackson.databind.deser.jdk.NumberDeserializers; +import tools.jackson.databind.deser.jdk.StringDeserializer; +import tools.jackson.databind.deser.std.StdValueInstantiator; +import tools.jackson.databind.introspect.AnnotatedConstructor; +import tools.jackson.databind.introspect.AnnotatedField; +import tools.jackson.databind.introspect.AnnotatedMethod; +import tools.jackson.module.blackbird.codegen.CodegenDebug; +import tools.jackson.module.blackbird.codegen.CodegenFallbacks; +import tools.jackson.module.blackbird.codegen.MemberHandles; +import tools.jackson.module.blackbird.codegen.ReaderCodegen; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.Construction; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.GenProp; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.Kind; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.ViewStrategy; +import tools.jackson.module.blackbird.internal.GeneratedBuilderBase; +import tools.jackson.module.blackbird.internal.GeneratedReaderBase; + +/** + * Builds a generated codec from a resolved stock bean deserializer, or returns + * null when the bean does not qualify. The codec handles a bean only when + * every feature of the bean has a generated path that matches stock; anything + * else keeps the stock deserializer (a gate) or the stock property inside the + * codec (a STOCK arm). Every decision stock databind has already made - which + * names are ignored or included, how names match (aliases, case + * insensitivity), which creator builds the bean - is read from the resolved + * stock deserializer, not computed again. Member access goes through + * {@link MemberHandles}, which reaches exactly what stock databind can + * invoke, so accessibility never gates a bean the stock path handles - an + * access failure demotes to stock, which fails the same way. + */ +final class BBReaderFactory +{ + // Exact classes: a subclass may read differently + private static final Set> STOCK_SCALAR_DESERS = Set.of( + StringDeserializer.class, + NumberDeserializers.IntegerDeserializer.class, + NumberDeserializers.LongDeserializer.class, + NumberDeserializers.BooleanDeserializer.class); + + private BBReaderFactory() {} + + /** + * The resolved stock state a codec is generated from, read by the + * placeholder (a stock deserializer subclass) from its own fields. + * buildMethod is non-null exactly for builder-based beans. vanilla is + * the resolved _vanillaProcessing: whether stock takes its vanilla loop. + * asArray: the bean's own shape is ARRAY. + */ + record Source(BeanDeserializerBase deser, SettableAnyProperty anySetter, + boolean injectables, boolean vanilla, BeanPropertyMap beanProperties, PropertyBasedCreator creator, + boolean nonStandardCreation, boolean hasDelegateCreator, boolean hasExternalTypeId, + boolean hasUnwrapped, boolean asArray, AnnotatedMethod buildMethod) {} + + // CodegenFallbacks.FAIL_ON_ERROR, copied here so that the stack overflow + // handler of tryGenerate reads a field of this initialized class. + // Reading CodegenFallbacks there would instead run its static + // initializer (a logger lookup) on the nearly exhausted stack, if + // nothing has used it yet; this copy initializes it along with this + // class, early. + private static final boolean FAIL_ON_STACK_OVERFLOW = CodegenFallbacks.FAIL_ON_ERROR; + + static ValueDeserializer tryGenerate(Source stock, DeserializationContext ctxt, + boolean declaresViews) { + BeanDeserializerBase delegate = stock.deser(); + ValueDeserializer codec; + try { + codec = generate(stock, ctxt, declaresViews); + } catch (StackOverflowError e) { + // Generation ran out of stack deep in a recursive resolution: not + // a generator bug, and the stock path, which needs no generation, + // can still complete the call. Nothing more (not even logging) + // runs on the nearly exhausted stack. In strict mode (the test + // suites) it more likely is unbounded recursion in the + // generator, so it fails the test. The check only reads a field + // of this (initialized) class. + if (FAIL_ON_STACK_OVERFLOW) { + throw e; + } + return null; + } catch (VirtualMachineError e) { + // Out of memory or a broken JVM, not a generator bug: never demote + throw e; + } catch (Throwable t) { + if (CodegenDebug.ENABLED) { + CodegenDebug.log(() -> "reader tryGenerate " + delegate.handledType().getName() + " threw:"); + t.printStackTrace(); + } + CodegenFallbacks.generationFailure(delegate.handledType(), t); + return null; + } + // Outside the try: tracing must not turn into a demotion of a codec + // that generated fine. + CodegenDebug.log(() -> "reader tryGenerate " + delegate.handledType().getName() + + " -> " + (codec == null ? "null (gated)" : codec.getClass().getName())); + return codec; + } + + private static ValueDeserializer gate(String reason) { + CodegenDebug.log(() -> "reader gate: " + reason); + return null; + } + + private static ValueDeserializer generate(Source stock, DeserializationContext ctxt, + boolean declaresViews) + throws ReflectiveOperationException { + BeanDeserializerBase delegate = stock.deser(); + // Deliberately no delegate.hasViews() gate: 3.x disables + // DEFAULT_VIEW_INCLUSION by default, which marks every bean as needing + // view processing; the generated codec instead checks + // ctxt.getActiveView() per call and delegates when a view is active. + // Stock contextualizes an array-shaped bean to its as-array + // deserializer, which reads with its own loop and calls back into + // the placeholder only on stock paths (unwrapping, copies, the + // builder update overload), never into a codec. + if (stock.asArray()) { + return gate("array shape"); + } + if (delegate.getObjectIdReader(ctxt) != null) { + return gate("object id"); + } + Class beanClass = delegate.handledType(); + if (Modifier.isAbstract(beanClass.getModifiers())) { + return gate("abstract"); + } + if (stock.buildMethod() != null) { + return generateBuilder(delegate, ctxt, beanClass, stock, declaresViews); + } + if (beanClass.isRecord()) { + return generateRecord(delegate, ctxt, beanClass, stock, declaresViews); + } + // Stock builds the bean with the default creator and runs the + // property loop only without "non-standard creation": a delegating + // or property-based creator (also beside a default constructor), an + // external type id, or unwrapped properties all take other stock + // paths, which the generated loop does not have. + if (stock.nonStandardCreation()) { + return gate("non-standard creation (creator, external type id or unwrapped)"); + } + // A constant constructor handle is only equivalent when the + // instantiator is the stock one and its default creator IS the + // no-arg constructor (its failure handling is mirrored for exactly + // that instantiator). Everything else that still reports + // canCreateUsingDefault - a no-arg @JsonCreator factory, a custom + // instantiator, a constructor the lookup cannot unreflect - constructs + // through the instantiator held in class data instead. + Construction construction = Construction.instantiator(delegate.getValueInstantiator()); + if (delegate.getValueInstantiator().getClass() == StdValueInstantiator.class + && delegate.getValueInstantiator().getDefaultCreator() + instanceof AnnotatedConstructor ac) { + try { + construction = Construction.constructor( + MemberHandles.defaultConstructor(ac.getAnnotated())); + } catch (IllegalAccessException e) { + CodegenDebug.log("reader demote: ctor access, instantiator mode"); + } + } + return generateWithStockMatcher(delegate, ctxt, beanClass, stock, construction, + declaresViews, null); + } + + // Builder-based beans: the stock ValueInstantiator creates the builder, + // properties apply through normalized (Object, eV)Object setter handles, + // and the build method - a constant (Object)Object handle - produces the + // value. + private static ValueDeserializer generateBuilder(BeanDeserializerBase delegate, + DeserializationContext ctxt, Class builderClass, Source stock, + boolean declaresViews) + throws ReflectiveOperationException { + if (stock.nonStandardCreation()) { + return gate("builder with non-standard creation"); + } + Method build = stock.buildMethod().getAnnotated(); + if (build == null) { + return gate("builder without build method"); + } + // The builder class itself may be private: the codec never names it + // (the instantiator creates it, and the handles are erased). + if (build.getParameterCount() != 0) { + return gate("builder shape"); + } + MethodHandle buildMH; + try { + buildMH = MemberHandles.unary(build); + } catch (IllegalAccessException e) { + return gate("build method access"); + } + return generateWithStockMatcher(delegate, ctxt, builderClass, stock, + Construction.builder(delegate.getValueInstantiator(), buildMH), declaresViews, + builderClass); + } + + // POJO and builder codecs reuse the stock name matcher: the bean + // property map's matcher, with aliases after the primary names and the + // stock case-insensitivity. Initialized here, during resolve; the + // placeholders then skip stock createContextual's rebuild of it (see + // BBReaderPlaceholder.initNameMatcher). + private static ValueDeserializer generateWithStockMatcher( + BeanDeserializerBase delegate, DeserializationContext ctxt, Class beanClass, + Source stock, Construction construction, + boolean declaresViews, Class builderClass) + throws ReflectiveOperationException { + BeanPropertyMap beanProps = stock.beanProperties(); + beanProps.initMatcher(ctxt.tokenStreamFactory()); + SettableBeanProperty[] byIndex = beanProps.getNameMatcherProperties(); + int primaries = beanProps.size(); + if (primaries == 0) { + return gate("no props"); + } + List props = new ArrayList<>(primaries); + for (int i = 0; i < primaries; i++) { + SettableBeanProperty prop = byIndex[i]; + if (prop.getMetadata().getMergeInfo() != null) { + return gate("merge prop " + prop.getName()); + } + props.add(builderClass == null ? classify(prop) + : classifyBuilder(prop, builderClass)); + } + int[] matchArms = matchArms(byIndex, primaries); + if (matchArms == null) { + return gate("matcher layout"); + } + return ReaderCodegen.generate(beanClass, props, beanProps.getNameMatcher(), matchArms, + delegate, beanProps, construction, stock.injectables(), + (builderClass == null) && stock.vanilla(), + viewStrategy(ctxt, delegate, props, declaresViews), null); + } + + // The stock matcher has one index per primary name (the arms, in order), + // then alias entries that each hold their primary property; returns the + // arm per matcher index. + private static int[] matchArms(SettableBeanProperty[] byIndex, int primaries) { + Map arm = new IdentityHashMap<>(); + int[] out = new int[byIndex.length]; + for (int i = 0; i < byIndex.length; i++) { + Integer a = (i < primaries) ? Integer.valueOf(i) : arm.get(byIndex[i]); + if (a == null) { + return null; + } + arm.putIfAbsent(byIndex[i], a); + out[i] = a; + } + return out; + } + + // With DEFAULT_VIEW_INCLUSION on, unannotated properties have no views, + // so the resolved hasViews() (which also sees views assigned in code, by + // updateBuilder or a property definition) tells exactly whether views + // can affect the bean; without any, no per-arm view code is emitted + // (NONE). With inclusion off, disabled inclusion forces the empty set + // onto every unannotated property, so hasViews() cannot tell a declared + // view apart; beans that declare no @JsonView (read from the property + // definitions at modify time) have every property hidden under an active + // view, so the degenerate view-active call DELEGATEs to stock and the + // no-view hot path stays free of mask tests. Otherwise MASK (the fast + // path under views) up to 64 properties, DELEGATE beyond. Mask + // visibility is read per property from SettableBeanProperty.visibleInView, + // so semantics stay exactly stock. + private static ViewStrategy viewStrategy(DeserializationContext ctxt, + BeanDeserializerBase delegate, List props, boolean declaresViews) { + if (ctxt.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)) { + if (!delegate.hasViews()) { + return ViewStrategy.NONE; + } + } else if (!declaresViews) { + return ViewStrategy.DELEGATE; + } + return (props.size() > 64) ? ViewStrategy.DELEGATE : ViewStrategy.MASK; + } + + // Records collect components into typed locals and construct through the + // canonical constructor; every property must be a plain CreatorProperty + // whose creator index and type line up with the record components. + private static ValueDeserializer generateRecord(BeanDeserializerBase delegate, + DeserializationContext ctxt, Class beanClass, Source stock, + boolean declaresViews) + throws ReflectiveOperationException { + PropertyBasedCreator creator = stock.creator(); + // Stock prefers a delegating creator to the property-based one, and + // external type ids, unwrapped properties, any-setters (also as a + // creator parameter) and injectables take paths the typed-locals loop + // does not model. + if (creator == null || stock.hasDelegateCreator() || stock.hasExternalTypeId() + || stock.hasUnwrapped() || stock.anySetter() != null + || stock.injectables()) { + return gate("record creation is not the plain canonical constructor"); + } + // The generated code constructs through the canonical constructor and + // mirrors the failure handling of the stock instantiator. A + // @JsonCreator factory or a custom instantiator must construct + // through the instantiator, so such records stay on the stock path. + // An AnnotatedConstructor that also passes the per-component index + // and type match below is the canonical constructor: a second + // constructor with the same signature cannot exist. + if (delegate.getValueInstantiator().getClass() != StdValueInstantiator.class + || !(delegate.getValueInstantiator().getWithArgsCreator() + instanceof AnnotatedConstructor ctor)) { + return gate("record creator is not the constructor"); + } + RecordComponent[] comps = beanClass.getRecordComponents(); + // The seen-component mask in the generated code is one long. + if (comps.length > 64 || comps.length == 0) { + return gate("record size"); + } + SettableBeanProperty[] creatorProps = creator.allPropertiesInOrder(); + if (creatorProps.length != comps.length) { + return gate("record creator arity"); + } + // Stock reads names through the creator, then through the bean + // properties for non-creator ones; records whose bean properties are + // not all creator properties keep the stock path. + for (SettableBeanProperty prop : stock.beanProperties()) { + if (!(prop instanceof CreatorProperty)) { + return gate("record with non-creator property " + prop.getName()); + } + } + for (int i = 0; i < comps.length; i++) { + SettableBeanProperty prop = creatorProps[i]; + // Wrapper classes (managed references among them) do work at + // construction that the typed-locals loop does not. + if (prop == null || prop.getClass() != CreatorProperty.class + || prop.getCreatorIndex() != i + || prop.getMetadata().getMergeInfo() != null + || prop.getInjectionDefinition() != null + || prop.getType().getRawClass() != comps[i].getType()) { + return gate("record component " + i); + } + } + MethodHandle recordCtor; + try { + recordCtor = MemberHandles.constructor(ctor.getAnnotated()); + } catch (IllegalAccessException e) { + // The lookup cannot reach the canonical constructor, so databind + // could not open it either: demote, stock fails the same way at + // first use. + return gate("record ctor access"); + } + List props = new ArrayList<>(comps.length); + String[] localNames = new String[comps.length]; + for (int i = 0; i < comps.length; i++) { + Class compType = comps[i].getType(); + SettableBeanProperty prop = creatorProps[i]; + localNames[i] = comps[i].getName(); + ValueDeserializer valueDeser = prop.getValueDeserializer(); + Kind kind; + ValueDeserializer child = null; + if (prop.getValueTypeDeserializer() != null) { + kind = Kind.STOCK; + } else if (isCodec(valueDeser)) { + kind = Kind.CHILD; + child = valueDeser; + } else { + kind = scalarKind(compType); + if (kind == null || valueDeser == null + || !STOCK_SCALAR_DESERS.contains(valueDeser.getClass())) { + kind = Kind.STOCK; + } + } + props.add(new GenProp(prop.getName(), kind, prop, compType, child, null)); + } + // The stock fallback paths of the codec read non-creator names + // through the bean property matcher, which createContextual would + // initialize next. + BeanPropertyMap beanProps = stock.beanProperties(); + beanProps.initMatcher(ctxt.tokenStreamFactory()); + RecordNames names = recordNames(ctxt, creator, beanProps, props); + return ReaderCodegen.generate(beanClass, props, names.matcher(), names.matchArms(), + delegate, beanProps, Construction.record(recordCtor), false, false, + viewStrategy(ctxt, delegate, props, declaresViews), localNames); + } + + private record RecordNames(PropertyNameMatcher matcher, int[] matchArms) {} + + // Stock reads a record's names through PropertyBasedCreator + // .findCreatorProperty: a map of primary names and aliases (lower-cased + // with Locale.ROOT when case-insensitive) without the names to ignore. + // The matcher here holds exactly the names that map finds for each + // component. A component whose name it does not find (an ignored one) + // gets no name: the document's value takes the unknown path, as in + // stock, and the component keeps the stock absent value. + private static RecordNames recordNames(DeserializationContext ctxt, + PropertyBasedCreator creator, BeanPropertyMap beanProps, List props) { + List names = new ArrayList<>(); + List arms = new ArrayList<>(); + for (int i = 0; i < props.size(); i++) { + SettableBeanProperty prop = props.get(i).stock(); + if (creator.findCreatorProperty(prop.getName()) == prop) { + names.add(Named.fromString(prop.getName())); + arms.add(i); + } + } + for (int i = 0; i < props.size(); i++) { + SettableBeanProperty prop = props.get(i).stock(); + for (PropertyName alias : prop.findAliases(ctxt.getConfig())) { + String name = alias.getSimpleName(); + if (creator.findCreatorProperty(name) == prop) { + names.add(Named.fromString(name)); + arms.add(i); + } + } + } + // false: the matcher interns the names itself. + PropertyNameMatcher matcher = beanProps.isCaseInsensitive() + ? ctxt.tokenStreamFactory().constructCINameMatcher(names, false, Locale.ROOT) + : ctxt.tokenStreamFactory().constructNameMatcher(names, false); + int[] matchArms = new int[arms.size()]; + for (int k = 0; k < matchArms.length; k++) { + matchArms[k] = arms.get(k); + } + return new RecordNames(matcher, matchArms); + } + + // Generated stores are only equivalent for the plain stock property + // class (MethodProperty, which backs setters and fields alike): + // subclasses and wrappers (managed and back references, object id + // references, setterless and merging properties, user wrappers from + // updateBuilder) do their work in deserializeAndSet, so they keep it. + // A value type deserializer (polymorphic values, default typing) is + // applied only by the stock property. + private static GenProp classify(SettableBeanProperty prop) { + if (prop.getClass() != MethodProperty.class || prop.getValueTypeDeserializer() != null) { + return stock(prop); + } + Class raw = prop.getType().getRawClass(); + MethodHandle handle = storeHandle(prop, raw); + if (handle == null) { + return stock(prop); + } + return withValueKind(prop, raw, handle); + } + + // Builder setters must return void or the builder class exactly (the + // normalized handle keeps the fluent return as the new builder, which + // is what stock deserializeSetAndReturn does with a non-null result). + private static GenProp classifyBuilder(SettableBeanProperty prop, Class builderClass) { + if (prop.getClass() != MethodProperty.class || prop.getValueTypeDeserializer() != null) { + return stock(prop); + } + Class raw = prop.getType().getRawClass(); + Method setter = setterOf(prop, raw); + if (setter == null + || (setter.getReturnType() != void.class + && setter.getReturnType() != builderClass)) { + return stock(prop); + } + MethodHandle handle; + try { + handle = MemberHandles.builderSetter(setter, raw); + } catch (IllegalAccessException | RuntimeException e) { + return stock(prop); + } + return withValueKind(prop, raw, handle); + } + + private static GenProp withValueKind(SettableBeanProperty prop, Class raw, + MethodHandle handle) { + ValueDeserializer valueDeser = prop.getValueDeserializer(); + if (isCodec(valueDeser)) { + return new GenProp(prop.getName(), Kind.CHILD, prop, raw, valueDeser, handle); + } + Kind kind = scalarKind(raw); + if (kind == null || valueDeser == null + || !STOCK_SCALAR_DESERS.contains(valueDeser.getClass())) { + return stock(prop); + } + return new GenProp(prop.getName(), kind, prop, raw, null, handle); + } + + // The property's store as an erased (Object, eV)void handle: a setter + // call or a field put, whichever backs the property. Final fields stay on + // the stock path so their (stock-defined) behavior is preserved exactly; + // an access failure demotes the property to the stock path. + private static MethodHandle storeHandle(SettableBeanProperty prop, Class raw) { + try { + Method setter = setterOf(prop, raw); + if (setter != null) { + return MemberHandles.setter(setter, raw); + } + Field field = fieldOf(prop, raw); + if (field != null && !Modifier.isFinal(field.getModifiers())) { + return MemberHandles.fieldSetter(field); + } + return null; + } catch (IllegalAccessException | RuntimeException e) { + return null; + } + } + + private static Field fieldOf(SettableBeanProperty prop, Class raw) { + if (!(prop.getMember() instanceof AnnotatedField af)) { + return null; + } + Field field = af.getAnnotated(); + if (field == null || Modifier.isStatic(field.getModifiers()) + || field.getType() != raw) { + return null; + } + return field; + } + + private static Method setterOf(SettableBeanProperty prop, Class raw) { + if (!(prop.getMember() instanceof AnnotatedMethod am)) { + return null; + } + Method setter = am.getAnnotated(); + if (setter == null || setter.getParameterCount() != 1 + || setter.getParameterTypes()[0] != raw) { + return null; + } + return setter; + } + + private static GenProp stock(SettableBeanProperty prop) { + CodegenDebug.log(() -> "reader prop stock: " + prop.getName()); + return new GenProp(prop.getName(), Kind.STOCK, prop); + } + + private static boolean isCodec(ValueDeserializer deser) { + return (deser instanceof GeneratedReaderBase) || (deser instanceof GeneratedBuilderBase); + } + + private static Kind scalarKind(Class raw) { + if (raw == String.class) { + return Kind.STRING; + } + if (raw == int.class) { + return Kind.INT; + } + if (raw == long.class) { + return Kind.LONG; + } + if (raw == boolean.class) { + return Kind.BOOLEAN; + } + return null; + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBReaderPlaceholder.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBReaderPlaceholder.java new file mode 100644 index 000000000..f359b96f4 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/BBReaderPlaceholder.java @@ -0,0 +1,132 @@ +package tools.jackson.module.blackbird.deser; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.BeanProperty; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.bean.BeanDeserializer; +import tools.jackson.databind.deser.bean.BeanDeserializerBase; +import tools.jackson.databind.util.NameTransformer; +import tools.jackson.module.blackbird.codegen.CodegenDebug; + +/** + * Takes the place of a stock {@link BeanDeserializer} (POJOs and records) + * from the modifier on: it is a copy of that deserializer, so other + * modifiers and databind's own checks see a {@code BeanDeserializer}, and + * it behaves as stock until resolution. Resolving it resolves the stock + * state and generates the codec, which needs the resolved properties and + * the context's TokenStreamFactory. Contextualization then hands out the + * codec, so steady-state call sites reference it with no extra hop. When + * no codec is generated, contextualization hands out an exact-class copy of + * the resolved stock state instead, so a demoted bean costs nothing over + * stock. + */ +final class BBReaderPlaceholder extends BeanDeserializer +{ + // Whether the bean or any property declares @JsonView explicitly, read + // from the property definitions at modify time (the resolved properties + // cannot distinguish a declared view from the empty view set that + // disabled DEFAULT_VIEW_INCLUSION forces onto unannotated properties). + private final boolean _declaresViews; + + private volatile ValueDeserializer _codec; + + // Set once resolution generated no codec + private volatile boolean _demoted; + + // A plain BeanDeserializer with this placeholder's resolved and + // contextualized state, made on first demoted contextualization + private volatile BeanDeserializerBase _stock; + + BBReaderPlaceholder(BeanDeserializer src, boolean declaresViews) { + super(src); + _declaresViews = declaresViews; + } + + @Override + public void resolve(DeserializationContext ctxt) { + CodegenDebug.log(() -> "reader resolve " + handledType().getName()); + super.resolve(ctxt); + ValueDeserializer codec = BBReaderFactory.tryGenerate(new BBReaderFactory.Source( + this, _anySetter, _injectables != null, _vanillaProcessing, _beanProperties, + _propertyBasedCreator, _nonStandardCreation, + (_delegateDeserializer != null) || (_arrayDelegateDeserializer != null), + _externalTypeIdHandler != null, _unwrappedPropertyHandler != null, + _serializationShape == JsonFormat.Shape.ARRAY, null), + ctxt, _declaresViews); + _codec = codec; + _demoted = (codec == null); + } + + // Contextualization is the stock one; when it leaves this deserializer + // as it is, the codec (or, when demoted, the stock copy) takes its place. + // It runs on this resolved instance, never on the copy: copies do not + // carry _propertiesContextualized, so contextualizing one would redo + // the property contextualization that resolution already did. The copy + // caches the name matcher when constructed, and contextualization + // initializes the matcher, so the copy is made after that (a racing + // duplicate copy is harmless). + @Override + public ValueDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) { + ValueDeserializer contextual = super.createContextual(ctxt, property); + if (contextual != this) { + return contextual; + } + ValueDeserializer codec = _codec; + if (codec != null) { + return codec; + } + if (!_demoted) { + return this; + } + BeanDeserializerBase stock = _stock; + if (stock == null) { + stock = withBeanProperties(_beanProperties); + _stock = stock; + } + return stock; + } + + // Stock createContextual initializes the name matcher of the property + // map on every contextualization; generation already did, for this map + // and this mapper's stream factory (BBReaderFactory), so with a codec + // the map is not rebuilt: only this instance's cached view of it is set. + @Override + protected void initNameMatcher(DeserializationContext ctxt) { + if (_codec == null) { + super.initNameMatcher(ctxt); + return; + } + _propNameMatcher = _beanProperties.getNameMatcher(); + _propsByIndex = _beanProperties.getNameMatcherProperties(); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) { + ValueDeserializer codec = _codec; + return (codec != null) ? codec.deserialize(p, ctxt) : super.deserialize(p, ctxt); + } + + // BeanDeserializer unwraps only for its exact class (subclasses "MUST + // override"); an exact-class copy of this state does what it would. The + // field guards against recursive unwrapping, as in stock. The copy is + // fresh on purpose, not the shared demoted _stock: stock unwrapping uses + // the instance's own _currentlyTransforming as its guard, which a shared + // instance would race on. Unwrapping runs once per unwrapped property, + // while the outer bean resolves. + @Override + public ValueDeserializer unwrappingDeserializer(DeserializationContext ctxt, + NameTransformer transformer) { + if (_currentlyTransforming == transformer) { + return this; + } + _currentlyTransforming = transformer; + try { + return withBeanProperties(_beanProperties).unwrappingDeserializer(ctxt, transformer); + } finally { + _currentlyTransforming = null; + } + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/CreatorOptimizer.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/CreatorOptimizer.java deleted file mode 100644 index adfce2ee6..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/CreatorOptimizer.java +++ /dev/null @@ -1,154 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -import java.lang.invoke.LambdaConversionException; -import java.lang.invoke.LambdaMetafactory; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import tools.jackson.databind.deser.ValueInstantiator; -import tools.jackson.databind.deser.std.StdValueInstantiator; -import tools.jackson.databind.introspect.AnnotatedWithParams; -import tools.jackson.module.blackbird.util.Unchecked; - -import static java.lang.invoke.MethodHandles.lookup; -import static java.lang.invoke.MethodType.methodType; - -/** - * Helper class that tries to generate {@link ValueInstantiator} class - * that calls constructors and/or factory methods directly, instead - * of using Reflection. - */ -public class CreatorOptimizer -{ - protected final Class _valueClass; - - protected final StdValueInstantiator _originalInstantiator; - - private final MethodHandles.Lookup _lookup; - - public CreatorOptimizer(Class valueClass, MethodHandles.Lookup lookup, StdValueInstantiator orig) - { - _valueClass = valueClass; - _originalInstantiator = orig; - _lookup = lookup; - } - - public ValueInstantiator createOptimized() - { - /* [Issue#11]: Need to avoid optimizing if we use delegate-based creators. */ - if (_originalInstantiator.canCreateUsingDelegate()) { - return null; - } - - if (_lookup == null) { - return null; - } - - AnnotatedWithParams argsCreator = _originalInstantiator.getWithArgsCreator(); - Function optimizedArgsCreator = null; - if (argsCreator != null) { - // The LambdaMetafactory requires a specifying interface, which is not possible to provide - // for methods with arbitrary parameter lists. So we have to use a spread invoker instead, - // which is not a valid target for the metafactory. Instead, we wrap it in a trampoline to - // avoid creating megamorphic code paths and hope that code inlining covers up our reflective sins. - MethodHandle argsCreatorHandle = directHandle(argsCreator.getAnnotated()); - if (argsCreatorHandle != null) { - optimizedArgsCreator = Unchecked.supplier(() -> - (Function) LambdaMetafactory.metafactory( - _lookup, - "apply", - methodType(Function.class, MethodHandle.class), - methodType(Object.class, Object.class), - lookup().findStatic( - CreatorOptimizer.class, - "invokeTrampoline", - methodType(Object.class, MethodHandle.class, Object[].class)), - methodType(Object.class, Object[].class)) - .getTarget().invokeExact( - argsCreatorHandle - .asFixedArity() - .asSpreader( - Object[].class, - argsCreatorHandle.type().parameterCount()) - .asType(methodType(Object.class, Object[].class)))) - .get(); - } - } - - AnnotatedWithParams defaultCreator = _originalInstantiator.getDefaultCreator(); - Supplier optimizedFactory = null; - if (defaultCreator != null) { - MethodHandle defaultCreatorHandle = directHandle(defaultCreator.getAnnotated()); - if (defaultCreatorHandle != null) { - optimizedFactory = refusedAsNull(Unchecked.supplier(() -> - (Supplier) LambdaMetafactory.metafactory( - _lookup, - "get", - methodType(Supplier.class), - methodType(Object.class), - defaultCreatorHandle, - defaultCreatorHandle.type()) - .getTarget().invokeExact())); - } - } - if (optimizedArgsCreator != null || optimizedFactory != null) { - return new OptimizedValueInstantiator(_originalInstantiator, optimizedFactory, optimizedArgsCreator); - } - return null; - } - - // The metafactory refuses a handle it can't crack from the lookup, as for a bean - // in a module the lookup's module doesn't read (like a child loader's unnamed - // module, when Blackbird is a named module); reflection can still call it - private static T refusedAsNull(Supplier factory) { - try { - return factory.get(); - } catch (Exception e) { - if (e instanceof LambdaConversionException) { - return null; - } - throw e; - } - } - - private MethodHandle directHandle(AnnotatedElement element) { - return Stream.concat( - Stream.of(element) - .filter(Constructor.class::isInstance) - .map(Constructor.class::cast) - .filter(c -> !Modifier.isPrivate(c.getModifiers())) - .flatMap(t -> { - try { - return Stream.of(_lookup.unreflectConstructor(t)); - } catch (IllegalAccessException e) { - return Stream.empty(); - } - }), - Stream.of(element) - .filter(Method.class::isInstance) - .map(Method.class::cast) - .filter(m -> { - int mods = m.getModifiers(); - return Modifier.isStatic(mods) && !Modifier.isPrivate(mods); - }) - .flatMap(t -> { - try { - return Stream.of(_lookup.unreflect(t)); - } catch (IllegalAccessException e) { - return Stream.empty(); - } - })) - .findFirst().orElse(null); - } - - public static Object invokeTrampoline(MethodHandle delegate, Object[] args) throws Throwable { - return delegate.invokeExact(args); - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/ObjBooleanConsumer.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/ObjBooleanConsumer.java deleted file mode 100644 index 43411f9e4..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/ObjBooleanConsumer.java +++ /dev/null @@ -1,6 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -@FunctionalInterface -public interface ObjBooleanConsumer { - void accept(Object bean, boolean value); -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/OptimizedSettableBeanProperty.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/OptimizedSettableBeanProperty.java deleted file mode 100644 index 67594d087..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/OptimizedSettableBeanProperty.java +++ /dev/null @@ -1,145 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -import tools.jackson.core.*; - -import tools.jackson.databind.*; -import tools.jackson.databind.deser.*; -import tools.jackson.databind.deser.impl.NullsConstantProvider; -import tools.jackson.databind.util.ClassUtil; - -/** - * Base class for concrete type-specific {@link SettableBeanProperty} - * implementations. - */ -abstract class OptimizedSettableBeanProperty> - extends SettableBeanProperty.Delegating -{ - private static final long serialVersionUID = 1L; - - /** - * @since 2.9 - */ - final protected boolean _skipNulls; - - /** - * Type the generated builder setter casts the instance to: the setter's declaring - * class, or the builder's own class for an inherited protected setter. A builder - * setter can return an instance of another type, which reflection may still accept. - */ - protected final Class _receiverType; - - /** - * Whether this is a property of a builder; if so, {@link #set} passes instances - * the generated setter can't accept (see {@link #_receiverType}) to the delegate. - */ - protected final boolean _forBuilder; - - /* - /********************************************************************** - /* Life-cycle - /********************************************************************** - */ - - protected OptimizedSettableBeanProperty(SettableBeanProperty src, Class receiverType, - boolean forBuilder) - { - super(src); - _skipNulls = NullsConstantProvider.isSkipper(_nullProvider); - _receiverType = receiverType; - _forBuilder = forBuilder; - } - - // Base impl of `withName()` fine as-is: - // public SettableBeanProperty withName(PropertyName name); - - // But value deserializer handling needs some more care - @Override - public final SettableBeanProperty withValueDeserializer(ValueDeserializer deser) { - SettableBeanProperty newDelegate = delegate.withValueDeserializer(deser); - if (newDelegate == delegate) { - return this; - } - if (!_isDefaultDeserializer(deser)) { - return newDelegate; - } - return withDelegate(newDelegate); - } - - @Override - protected abstract SettableBeanProperty withDelegate(SettableBeanProperty d); - - /* - /********************************************************************** - /* Deserialization, regular - /********************************************************************** - */ - - @Override - public abstract void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object arg2) - throws JacksonException; - - @Override - public abstract void set(DeserializationContext ctxt, Object bean, Object value); - - /* - /********************************************************************** - /* Deserialization, builders - /********************************************************************** - */ - - // These use the generated accessor and keep the builder it returns (the same - // one for a void setter); setters only reflection may call go through the delegate. - - @Override - public abstract Object deserializeSetAndReturn(JsonParser p, - DeserializationContext ctxt, Object instance) - throws JacksonException; - - @Override - public abstract Object setAndReturn(DeserializationContext ctxt, Object instance, Object value); - - /* - /********************************************************************** - /* Error handling - /********************************************************************** - */ - - /** - * Helper method called when an exception is thrown from mutator, to be thrown - * by the caller (which keeps the compiler aware it never returns): anything the - * generated setter throws is reported the way {@code MethodProperty} reports it, - * and never retried. Telling a failure of the generated accessor itself from one - * thrown by the setter would mean guessing from the stack trace, and a retry could - * run the setter twice; setup instead leaves the setters it can't call to reflection. - * The parser is only used for the error location: the context's parser outside of - * {@link #deserializeSetAndReturn} and {@link #deserializeAndSet}, as it is - * for {@code MethodProperty}. - */ - protected JacksonException _reportProblem(JsonParser p, Object value, Throwable e) - throws JacksonException - { - // `MethodProperty` calls the setter through a method handle, which throws - // whatever the setter throws, and translates that; doing the same keeps type, - // message, cause and path - _throwAsJacksonE(p, e, value); - // never gets here, as `_throwAsJacksonE` always throws - return DatabindException.from(p, ClassUtil.exceptionMessage(e), e); - } - - /* - /********************************************************************** - /* Helper methods - /********************************************************************** - */ - - /** - * Helper method used to check whether given deserializer is the default - * deserializer implementation: this is necessary to avoid overriding custom - * deserializers. - */ - protected boolean _isDefaultDeserializer(ValueDeserializer deser) { - return (deser == null) - || (deser instanceof SuperSonicBeanDeserializer) - || ClassUtil.isJacksonStdImpl(deser); - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/OptimizedValueInstantiator.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/OptimizedValueInstantiator.java deleted file mode 100644 index 37c555c32..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/OptimizedValueInstantiator.java +++ /dev/null @@ -1,55 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -import java.util.function.Function; -import java.util.function.Supplier; - -import tools.jackson.core.JacksonException; -import tools.jackson.databind.DeserializationContext; -import tools.jackson.databind.deser.std.StdValueInstantiator; - -class OptimizedValueInstantiator extends StdValueInstantiator -{ - private final Supplier _optimizedDefaultCreator; - private final Function _optimizedArgsCreator; - - protected OptimizedValueInstantiator(StdValueInstantiator original, - Supplier defaultCreator, Function argsCreator) { - super(original); - this._optimizedDefaultCreator = defaultCreator; - this._optimizedArgsCreator = argsCreator; - } - - @Override - public boolean canCreateUsingDefault() { - return _optimizedDefaultCreator != null || super.canCreateUsingDefault(); - } - - @Override - public boolean canCreateFromObjectWith() { - return _optimizedArgsCreator != null || super.canCreateFromObjectWith(); - } - - @Override - public Object createUsingDefault(DeserializationContext ctxt) throws JacksonException { - if (_optimizedDefaultCreator != null) { - try { - return _optimizedDefaultCreator.get(); - } catch (Exception e) { - return ctxt.handleInstantiationProblem(_valueClass, null, e); - } - } - return super.createUsingDefault(ctxt); - } - - @Override - public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) throws JacksonException { - if (_optimizedArgsCreator != null) { - try { - return _optimizedArgsCreator.apply(args); - } catch (Exception e) { - return ctxt.handleInstantiationProblem(_valueClass, args, e); - } - } - return super.createFromObjectWith(ctxt, args); - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableBooleanProperty.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableBooleanProperty.java deleted file mode 100644 index 8507783ba..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableBooleanProperty.java +++ /dev/null @@ -1,109 +0,0 @@ -package tools.jackson.module.blackbird.deser; - - -import tools.jackson.core.*; -import tools.jackson.databind.*; -import tools.jackson.databind.deser.SettableBeanProperty; -import tools.jackson.module.blackbird.deser.BBDeserializerModifier.ObjBooleanBiFunction; - -final class SettableBooleanProperty - extends OptimizedSettableBeanProperty -{ - private static final long serialVersionUID = 1L; - private ObjBooleanConsumer _optimizedSetter; - private ObjBooleanBiFunction _optimizedSetterAndReturn; - - public SettableBooleanProperty(SettableBeanProperty src, ObjBooleanConsumer optimizedSetter, - ObjBooleanBiFunction optimizedSetterAndReturn, Class receiverType, boolean forBuilder) - { - super(src, receiverType, forBuilder); - _optimizedSetter = optimizedSetter; - _optimizedSetterAndReturn = optimizedSetterAndReturn; - } - - @Override - protected SettableBeanProperty withDelegate(SettableBeanProperty del) { - return new SettableBooleanProperty(del, _optimizedSetter, _optimizedSetterAndReturn, _receiverType, _forBuilder); - } - - /* - /********************************************************************** - /* Deserialization - /********************************************************************** - */ - - @Override - public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object bean) throws JacksonException { - boolean b; - JsonToken t = p.currentToken(); - if (t == JsonToken.VALUE_TRUE) { - b = true; - } else if (t == JsonToken.VALUE_FALSE) { - b = false; - } else { - delegate.deserializeAndSet(p, ctxt, bean); - return; - } - try { - _optimizedSetter.accept(bean, b); - } catch (Throwable e) { - throw _reportProblem(p, b, e); - } - } - - @Override - public Object deserializeSetAndReturn(JsonParser p, - DeserializationContext ctxt, Object instance) throws JacksonException - { - if ((_optimizedSetterAndReturn != null) && _receiverType.isInstance(instance)) { - JsonToken t = p.currentToken(); - if (t == JsonToken.VALUE_TRUE) { - return _setAndReturn(p, instance, true); - } - if (t == JsonToken.VALUE_FALSE) { - return _setAndReturn(p, instance, false); - } - } - return delegate.deserializeSetAndReturn(p, ctxt, instance); - } - - @Override - public void set(DeserializationContext ctxt, Object bean, Object value) throws JacksonException { - // the delegate skips a null, or reports it; and anything else (another type, - // which reflection may widen or reject) or a builder the generated setter - // can't take is reported, or set, as reflection does - if (!(value instanceof Boolean) || (_forBuilder && !_receiverType.isInstance(bean))) { - delegate.set(ctxt, bean, value); - return; - } - // not optimal (due to boxing), but better than using reflection: - final boolean b = ((Boolean) value).booleanValue(); - try { - _optimizedSetter.accept(bean, b); - } catch (Throwable e) { - throw _reportProblem(ctxt.getParser(), b, e); - } - } - - @Override - public Object setAndReturn(DeserializationContext ctxt, Object instance, Object value) throws JacksonException { - // a setter only reflection may call, anything else (null, or another type that - // reflection may widen or reject), or an instance the generated setter can't take, - // is left to the delegate - if ((_optimizedSetterAndReturn == null) || !(value instanceof Boolean) - || !_receiverType.isInstance(instance)) { - return delegate.setAndReturn(ctxt, instance, value); - } - // not optimal (due to boxing), but better than using reflection: - return _setAndReturn(ctxt.getParser(), instance, ((Boolean) value).booleanValue()); - } - - private Object _setAndReturn(JsonParser p, Object instance, boolean b) throws JacksonException { - try { - Object result = _optimizedSetterAndReturn.apply(instance, b); - return (result == null) ? instance : result; - } catch (Throwable e) { - throw _reportProblem(p, b, e); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableIntProperty.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableIntProperty.java deleted file mode 100644 index 8dcdf4d4f..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableIntProperty.java +++ /dev/null @@ -1,103 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -import java.util.function.ObjIntConsumer; - -import tools.jackson.core.*; -import tools.jackson.databind.*; -import tools.jackson.databind.deser.SettableBeanProperty; -import tools.jackson.module.blackbird.deser.BBDeserializerModifier.ObjIntBiFunction; - -final class SettableIntProperty - extends OptimizedSettableBeanProperty -{ - private static final long serialVersionUID = 1L; - private ObjIntConsumer _optimizedSetter; - private ObjIntBiFunction _optimizedSetterAndReturn; - - public SettableIntProperty(SettableBeanProperty src, ObjIntConsumer optimizedSetter, - ObjIntBiFunction optimizedSetterAndReturn, Class receiverType, boolean forBuilder) - { - super(src, receiverType, forBuilder); - _optimizedSetter = optimizedSetter; - _optimizedSetterAndReturn = optimizedSetterAndReturn; - } - - @Override - protected SettableBeanProperty withDelegate(SettableBeanProperty del) { - return new SettableIntProperty(del, _optimizedSetter, _optimizedSetterAndReturn, _receiverType, _forBuilder); - } - - /* - /********************************************************************** - /* Deserialization - /********************************************************************** - */ - - @Override - public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, - Object bean) throws JacksonException - { - if (!p.isExpectedNumberIntToken()) { - delegate.deserializeAndSet(p, ctxt, bean); - return; - } - final int v = p.getIntValue(); - try { - _optimizedSetter.accept(bean, v); - } catch (Throwable e) { - throw _reportProblem(p, v, e); - } - } - - @Override - public Object deserializeSetAndReturn(JsonParser p, - DeserializationContext ctxt, Object instance) - throws JacksonException - { - if ((_optimizedSetterAndReturn != null) && _receiverType.isInstance(instance) - && p.isExpectedNumberIntToken()) { - return _setAndReturn(p, instance, p.getIntValue()); - } - return delegate.deserializeSetAndReturn(p, ctxt, instance); - } - - @Override - public void set(DeserializationContext ctxt, Object bean, Object value) throws JacksonException { - // the delegate skips a null, or reports it; and anything else (another type, - // which reflection may widen or reject) or a builder the generated setter - // can't take is reported, or set, as reflection does - if (!(value instanceof Integer) || (_forBuilder && !_receiverType.isInstance(bean))) { - delegate.set(ctxt, bean, value); - return; - } - // not optimal (due to boxing), but better than using reflection: - int v = ((Number) value).intValue(); - try { - _optimizedSetter.accept(bean, v); - } catch (Throwable e) { - throw _reportProblem(ctxt.getParser(), v, e); - } - } - - @Override - public Object setAndReturn(DeserializationContext ctxt, Object instance, Object value) throws JacksonException { - // a setter only reflection may call, anything else (null, or another type that - // reflection may widen or reject), or an instance the generated setter can't take, - // is left to the delegate - if ((_optimizedSetterAndReturn == null) || !(value instanceof Integer) - || !_receiverType.isInstance(instance)) { - return delegate.setAndReturn(ctxt, instance, value); - } - // not optimal (due to boxing), but better than using reflection: - return _setAndReturn(ctxt.getParser(), instance, ((Integer) value).intValue()); - } - - private Object _setAndReturn(JsonParser p, Object instance, int v) throws JacksonException { - try { - Object result = _optimizedSetterAndReturn.apply(instance, v); - return (result == null) ? instance : result; - } catch (Throwable e) { - throw _reportProblem(p, v, e); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableLongProperty.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableLongProperty.java deleted file mode 100644 index 43a6ed056..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableLongProperty.java +++ /dev/null @@ -1,102 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -import java.util.function.ObjLongConsumer; - -import tools.jackson.core.*; -import tools.jackson.databind.*; -import tools.jackson.databind.deser.SettableBeanProperty; -import tools.jackson.module.blackbird.deser.BBDeserializerModifier.ObjLongBiFunction; - -final class SettableLongProperty - extends OptimizedSettableBeanProperty -{ - private static final long serialVersionUID = 1L; - private ObjLongConsumer _optimizedSetter; - private ObjLongBiFunction _optimizedSetterAndReturn; - - public SettableLongProperty(SettableBeanProperty src, ObjLongConsumer optimizedSetter, - ObjLongBiFunction optimizedSetterAndReturn, Class receiverType, boolean forBuilder) - { - super(src, receiverType, forBuilder); - _optimizedSetter = optimizedSetter; - _optimizedSetterAndReturn = optimizedSetterAndReturn; - } - - @Override - protected SettableBeanProperty withDelegate(SettableBeanProperty del) { - return new SettableLongProperty(del, _optimizedSetter, _optimizedSetterAndReturn, _receiverType, _forBuilder); - } - - /* - /********************************************************************** - /* Deserialization - /********************************************************************** - */ - - @Override - public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, - Object bean) throws JacksonException - { - if (!p.isExpectedNumberIntToken()) { - delegate.deserializeAndSet(p, ctxt, bean); - return; - } - final long v = p.getLongValue(); - try { - _optimizedSetter.accept(bean, v); - } catch (Throwable e) { - throw _reportProblem(p, v, e); - } - } - - @Override - public Object deserializeSetAndReturn(JsonParser p, - DeserializationContext ctxt, Object instance) throws JacksonException - { - if ((_optimizedSetterAndReturn != null) && _receiverType.isInstance(instance) - && p.isExpectedNumberIntToken()) { - return _setAndReturn(p, instance, p.getLongValue()); - } - return delegate.deserializeSetAndReturn(p, ctxt, instance); - } - - @Override - public void set(DeserializationContext ctxt, Object bean, Object value) throws JacksonException { - // the delegate skips a null, or reports it; and anything else (another type, - // which reflection may widen or reject) or a builder the generated setter - // can't take is reported, or set, as reflection does - if (!(value instanceof Long) || (_forBuilder && !_receiverType.isInstance(bean))) { - delegate.set(ctxt, bean, value); - return; - } - // not optimal (due to boxing), but better than using reflection: - final long v = ((Number) value).longValue(); - try { - _optimizedSetter.accept(bean, v); - } catch (Throwable e) { - throw _reportProblem(ctxt.getParser(), v, e); - } - } - - @Override - public Object setAndReturn(DeserializationContext ctxt, Object instance, Object value) throws JacksonException { - // a setter only reflection may call, anything else (null, or another type that - // reflection may widen or reject), or an instance the generated setter can't take, - // is left to the delegate - if ((_optimizedSetterAndReturn == null) || !(value instanceof Long) - || !_receiverType.isInstance(instance)) { - return delegate.setAndReturn(ctxt, instance, value); - } - // not optimal (due to boxing), but better than using reflection: - return _setAndReturn(ctxt.getParser(), instance, ((Long) value).longValue()); - } - - private Object _setAndReturn(JsonParser p, Object instance, long v) throws JacksonException { - try { - Object result = _optimizedSetterAndReturn.apply(instance, v); - return (result == null) ? instance : result; - } catch (Throwable e) { - throw _reportProblem(p, v, e); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableObjectProperty.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableObjectProperty.java deleted file mode 100644 index 00f7d6b2f..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableObjectProperty.java +++ /dev/null @@ -1,150 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -import java.util.function.BiConsumer; -import java.util.function.BiFunction; - -import tools.jackson.core.*; -import tools.jackson.databind.*; -import tools.jackson.databind.deser.SettableBeanProperty; - -final class SettableObjectProperty - extends OptimizedSettableBeanProperty -{ - private static final long serialVersionUID = 1L; - private BiConsumer _optimizedSetter; - private BiFunction _optimizedSetterAndReturn; - private final Class _setterValueType; - - public SettableObjectProperty(SettableBeanProperty src, BiConsumer optimizedSetter, - BiFunction optimizedSetterAndReturn, Class receiverType, - Class setterValueType, boolean forBuilder) - { - super(src, receiverType, forBuilder); - this._optimizedSetter = optimizedSetter; - this._optimizedSetterAndReturn = optimizedSetterAndReturn; - _setterValueType = setterValueType; - } - - @Override - protected SettableBeanProperty withDelegate(SettableBeanProperty del) { - return new SettableObjectProperty(del, _optimizedSetter, _optimizedSetterAndReturn, _receiverType, - _setterValueType, _forBuilder); - } - - /* - /********************************************************************** - /* Deserialization - /********************************************************************** - */ - - @Override - public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, - Object bean) throws JacksonException - { - Object value; - if (p.hasToken(JsonToken.VALUE_NULL)) { - if (_skipNulls) { - return; - } - value = _nullProvider.getNullValue(ctxt); - } else if (_valueTypeDeserializer == null) { - value = _valueDeserializer.deserialize(p, ctxt); - // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null - if (value == null) { - if (_skipNulls) { - return; - } - value = _nullProvider.getNullValue(ctxt); - } - } else { - value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); - } - // Do not call set(): MethodProperty passes nulls returned by polymorphic - // deserialization to the setter, even with Nulls.SKIP. - _set(ctxt, p, bean, value); - } - - @Override - public Object deserializeSetAndReturn(JsonParser p, - DeserializationContext ctxt, Object instance) throws JacksonException - { - if ((_optimizedSetterAndReturn == null) || !_receiverType.isInstance(instance)) { - return delegate.deserializeSetAndReturn(p, ctxt, instance); - } - Object value; - if (p.hasToken(JsonToken.VALUE_NULL)) { - if (_skipNulls) { - return instance; - } - value = _nullProvider.getNullValue(ctxt); - } else if (_valueTypeDeserializer == null) { - value = _valueDeserializer.deserialize(p, ctxt); - // 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null - if (value == null) { - if (_skipNulls) { - return instance; - } - value = _nullProvider.getNullValue(ctxt); - } - } else { - value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); - } - // a deserializer can give a value of another type (e.g. from a creator declared - // to return `Object`), which the generated setter can't take; the delegate - // reports it as it would - if ((value != null) && !_setterValueType.isInstance(value)) { - return delegate.setAndReturn(ctxt, instance, value); - } - return _setAndReturn(p, instance, value); - } - - @Override - public void set(DeserializationContext ctxt, Object bean, Object v) throws JacksonException { - // as in `MethodProperty`, only here: `deserializeAndSet` already skipped - // the nulls it should - if ((v == null) && _skipNulls) { - return; - } - _set(ctxt, ctxt.getParser(), bean, v); - } - - private void _set(DeserializationContext ctxt, JsonParser p, Object bean, Object v) throws JacksonException { - // a value of another type (see `deserializeSetAndReturn`), or a builder the - // generated setter can't take, is left to the delegate, which reports it - if (((v != null) && !_setterValueType.isInstance(v)) - || (_forBuilder && !_receiverType.isInstance(bean))) { - delegate.set(ctxt, bean, v); - return; - } - try { - _optimizedSetter.accept(bean, v); - } catch (Throwable e) { - throw _reportProblem(p, v, e); - } - } - - @Override - public Object setAndReturn(DeserializationContext ctxt, Object instance, Object value) throws JacksonException { - // a value of another type, or an instance the generated setter can't take, - // is left to the delegate, which reports it - if ((_optimizedSetterAndReturn == null) || !_receiverType.isInstance(instance) - || ((value != null) && !_setterValueType.isInstance(value))) { - return delegate.setAndReturn(ctxt, instance, value); - } - // as in `MethodProperty`, only here: `deserializeSetAndReturn` already skipped - // the nulls it should - if ((value == null) && _skipNulls) { - return instance; - } - return _setAndReturn(ctxt.getParser(), instance, value); - } - - private Object _setAndReturn(JsonParser p, Object instance, Object value) throws JacksonException { - try { - Object result = _optimizedSetterAndReturn.apply(instance, value); - return (result == null) ? instance : result; - } catch (Throwable e) { - throw _reportProblem(p, value, e); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableStringProperty.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableStringProperty.java deleted file mode 100644 index 0fc8d2df6..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SettableStringProperty.java +++ /dev/null @@ -1,110 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -import java.util.function.BiConsumer; -import java.util.function.BiFunction; - -import tools.jackson.core.*; -import tools.jackson.databind.*; -import tools.jackson.databind.deser.SettableBeanProperty; - -final class SettableStringProperty - extends OptimizedSettableBeanProperty -{ - private static final long serialVersionUID = 1L; - private final BiConsumer _optimizedSetter; - private final BiFunction _optimizedSetterAndReturn; - - public SettableStringProperty(SettableBeanProperty src, BiConsumer optimizedSetter, - BiFunction optimizedSetterAndReturn, Class receiverType, boolean forBuilder) - { - super(src, receiverType, forBuilder); - _optimizedSetter = optimizedSetter; - _optimizedSetterAndReturn = optimizedSetterAndReturn; - } - - @Override - protected SettableBeanProperty withDelegate(SettableBeanProperty del) { - return new SettableStringProperty(del, _optimizedSetter, _optimizedSetterAndReturn, _receiverType, _forBuilder); - } - - /* - /********************************************************************** - /* Deserialization - /********************************************************************** - */ - - // Copied from StdDeserializer.StringDeserializer: - @Override - public void deserializeAndSet(JsonParser p, DeserializationContext ctxt, Object bean) throws JacksonException - { - if (!p.hasToken(JsonToken.VALUE_STRING)) { - delegate.deserializeAndSet(p, ctxt, bean); - return; - } - _set(p, bean, p.getString()); - } - - @Override - public Object deserializeSetAndReturn(JsonParser p, DeserializationContext ctxt, Object instance) throws JacksonException - { - if ((_optimizedSetterAndReturn != null) && _receiverType.isInstance(instance) - && p.hasToken(JsonToken.VALUE_STRING)) { - String text = p.getString(); - // some parsers give null here (e.g. for a `TextNode(null)`), which needs - // the delegate's null handling - if (text != null) { - return _setAndReturn(p, instance, text); - } - } - return delegate.deserializeSetAndReturn(p, ctxt, instance); - } - - @Override - public void set(DeserializationContext ctxt, Object bean, Object value) throws JacksonException { - // as in `MethodProperty` - if ((value == null) && _skipNulls) { - return; - } - // a value of another type, or a builder the generated setter can't take, - // is left to the delegate, which reports it - if (((value != null) && !(value instanceof String)) - || (_forBuilder && !_receiverType.isInstance(bean))) { - delegate.set(ctxt, bean, value); - return; - } - _set(ctxt.getParser(), bean, value); - } - - private void _set(JsonParser p, Object bean, Object value) throws JacksonException { - try { - _optimizedSetter.accept(bean, (String) value); - } catch (Throwable e) { - throw _reportProblem(p, value, e); - } - } - - @Override - public Object setAndReturn(DeserializationContext ctxt, Object instance, Object value) throws JacksonException { - // a value of another type, or an instance the generated setter can't take, - // is left to the delegate, which reports it - if ((_optimizedSetterAndReturn == null) || !_receiverType.isInstance(instance) - || ((value != null) && !(value instanceof String))) { - return delegate.setAndReturn(ctxt, instance, value); - } - // as in `MethodProperty`, only here: `deserializeSetAndReturn` already skipped - // the nulls it should - if ((value == null) && _skipNulls) { - return instance; - } - return _setAndReturn(ctxt.getParser(), instance, value); - } - - private Object _setAndReturn(JsonParser p, Object instance, Object value) throws JacksonException { - try { - Object result = _optimizedSetterAndReturn.apply(instance, value); - return (result == null) ? instance : result; - } catch (Throwable e) { - throw _reportProblem(p, value, e); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SuperSonicBeanDeserializer.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SuperSonicBeanDeserializer.java deleted file mode 100644 index 6b7e34117..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SuperSonicBeanDeserializer.java +++ /dev/null @@ -1,342 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -import java.util.*; - -import tools.jackson.core.*; -import tools.jackson.core.io.SerializedString; - -import tools.jackson.databind.*; -import tools.jackson.databind.deser.*; -import tools.jackson.databind.deser.bean.BeanDeserializer; -import tools.jackson.databind.deser.bean.BeanPropertyMap; -import tools.jackson.databind.deser.bean.PropertyBasedCreator; -import tools.jackson.databind.deser.impl.UnwrappedPropertyHandler; -import tools.jackson.databind.util.NameTransformer; - -final class SuperSonicBeanDeserializer extends BeanDeserializer -{ - /** - * Names of properties being deserialized, in ordered they are - * expected to have been written (as per serialization settings); - * used for speculative order-based optimizations - */ - protected final SerializedString[] _orderedPropertyNames; - - /** - * Properties matching names in {@code _orderedPropertyNames}, - * assigned after resolution when property instances are finalized. - */ - protected SettableBeanProperty[] _orderedProperties; - - /* - /********************************************************************** - /* Life-cycle, construction, initialization - /********************************************************************** - */ - - public SuperSonicBeanDeserializer(BeanDeserializer src, List props) - { - super(src); - final int len = props.size(); - _orderedPropertyNames = new SerializedString[len]; - for (int i = 0; i < len; ++i) { - _orderedPropertyNames[i] = new SerializedString(props.get(i).getName()); - } - } - - protected SuperSonicBeanDeserializer(SuperSonicBeanDeserializer src, - UnwrappedPropertyHandler unwrapHandler, PropertyBasedCreator propertyBasedCreator, - BeanPropertyMap renamedProperties, boolean ignoreAllUnknown) - { - super(src, unwrapHandler, propertyBasedCreator, renamedProperties, ignoreAllUnknown); - _orderedPropertyNames = src._orderedPropertyNames; - _orderedProperties = src._orderedProperties; - } - - @Override - public ValueDeserializer unwrappingDeserializer(DeserializationContext ctxt, - NameTransformer transformer) - { - // 17-Dec-2020, tatu: Was like so: -// return new SuperSonicBeanDeserializer(this, unwrapper); - // but Afterburner 3.0 had this: - - // NOTE: copied verbatim from `BeanDeserializer` - - if (_currentlyTransforming == transformer) { // from [databind#383] - return this; - } - _currentlyTransforming = transformer; - try { - UnwrappedPropertyHandler uwHandler = _unwrappedPropertyHandler; - if (uwHandler != null) { - uwHandler = uwHandler.renameAll(ctxt, transformer); - } - PropertyBasedCreator pbCreator = _propertyBasedCreator; - if (pbCreator != null) { - pbCreator = pbCreator.renameAll(ctxt, transformer); - } - return new SuperSonicBeanDeserializer(this, uwHandler, pbCreator, - _beanProperties.renameAll(ctxt, transformer), true); - } finally { _currentlyTransforming = null; } - } - - // // Others, let's just leave as is; will not be optimized? - - //public BeanDeserializer withObjectIdReader(ObjectIdReader oir) { - - //public BeanDeserializer withIgnorableProperties(HashSet ignorableProps) - - //protected BeanDeserializerBase asArrayDeserializer() - - /* - /********************************************************************** - /* BenaDeserializer overrides - /********************************************************************** - */ - - /** - * This method is overridden as we need to know expected order of - * properties. - */ - @Override - public void resolve(DeserializationContext ctxt) - { - super.resolve(ctxt); - // Ok, now; need to find actual property instances to go with order - // defined based on property names. - - // 20-Sep-2014, tatu: As per [afterburner#43], use of `JsonTypeInfo.As.EXTERNAL_PROPERTY` - // will "hide" matching property, leading to no match below. - // But since we don't use optimized path if that case, let's just bail out. - if (_externalTypeIdHandler != null || _unwrappedPropertyHandler != null) { - // should we assign empty array or... ? - return; - } - - int len = _orderedPropertyNames.length; - ArrayList props = new ArrayList(len); - int i = 0; - for (; i < len; ++i) { - SettableBeanProperty prop = _beanProperties.findDefinition(_orderedPropertyNames[i].toString()); - if (prop != null) { - props.add(prop); - } - } - // should usually get at least one property; let's for now consider it an error if not - // (may need to revisit in future) - if (i == 0) { - throw new IllegalStateException("Blackbird internal error: BeanDeserializer for " - +_beanType+" has no properties that match expected ordering (should have "+len+") -- can not create optimized deserializer"); - } - _orderedProperties = props.toArray(new SettableBeanProperty[0]); - } - - @Override - public Object deserialize(JsonParser p, DeserializationContext ctxt) - throws JacksonException - { - if (!_vanillaProcessing || (_objectIdReader != null)) { - // should we ever get here? Just in case - return super.deserialize(p, ctxt); - } - // common case first: - if (!p.isExpectedStartObjectToken()) { - return _deserializeOther(p, ctxt, p.currentToken()); - } - if (_nonStandardCreation) { - p.nextToken(); - if (_unwrappedPropertyHandler != null) { - return deserializeWithUnwrapped(p, ctxt); - } - if (_externalTypeIdHandler != null) { - return deserializeWithExternalTypeId(p, ctxt); - } - Object bean = deserializeFromObjectUsingNonDefault(p, ctxt); - if (_injectables != null) { - injectValues(ctxt, bean); - } - return bean; - } - final Object bean = _valueInstantiator.createUsingDefault(ctxt); - // [databind#631]: Assign current value, to be accessible by custom serializers - p.assignCurrentValue(bean); - if (p.canReadObjectId()) { - Object id = p.getObjectId(); - if (id != null) { - _handleTypedObjectId(p, ctxt, bean, id); - } - } - if (_injectables != null) { - injectValues(ctxt, bean); - } - for (int i = 0, len = _orderedProperties.length; i < len; ++i) { - SettableBeanProperty prop = _orderedProperties[i]; - if (!p.nextName(_orderedPropertyNames[i])) { // miss... - if (p.currentToken() == JsonToken.END_OBJECT) { - return bean; - } - // we likely point to PROPERTY_NAME, so can just call parent impl - return super.deserialize(p, ctxt, bean); - } - p.nextToken(); // skip field, returns value token - try { - prop.deserializeAndSet(p, ctxt, bean); - } catch (Exception e) { - wrapAndThrow(e, bean, prop.getName(), ctxt); - } - } - // also, need to ensure we get closing END_OBJECT... - if (p.nextToken() != JsonToken.END_OBJECT) { - return super.deserialize(p, ctxt, bean); - } - return bean; - } - - // much of below is cut'n pasted from BeanSerializer - @Override - public final Object deserialize(JsonParser p, DeserializationContext ctxt, Object bean) - throws JacksonException - { - // [modules-base#339]: Need to delegate to base implementation for - // proper @JsonView handling during deserialization - if (_needViewProcesing) { - return super.deserialize(p, ctxt, bean); - } - // [databind#631]: Assign current value, to be accessible by custom deserializers - p.assignCurrentValue(bean); - if (_injectables != null) { - injectValues(ctxt, bean); - } - if (_unwrappedPropertyHandler != null) { - return deserializeWithUnwrapped(p, ctxt, bean); - } - if (_externalTypeIdHandler != null) { - return deserializeWithExternalTypeId(p, ctxt, bean); - } - SettableBeanProperty prop = _orderedProperties[0]; - // First: verify that first name is expected - if (p.isExpectedStartObjectToken()) { - if (!p.nextName(_orderedPropertyNames[0])) { - return super.deserialize(p, ctxt, bean); - } - } else if (!p.hasToken(JsonToken.PROPERTY_NAME) - || !prop.getName().equals(p.currentName())) { - // no, something funky, use base impl for special cases - return super.deserialize(p, ctxt, bean); - } - p.nextToken(); - try { - prop.deserializeAndSet(p, ctxt, bean); - } catch (Exception e) { - wrapAndThrow(e, bean, prop.getName(), ctxt); - } - // then rest of properties - for (int i = 1, len = _orderedProperties.length; i < len; ++i) { - if (!p.nextName(_orderedPropertyNames[i])) { // miss... - if (p.hasToken(JsonToken.END_OBJECT)) { - return bean; - } - // we likely point to PROPERTY_NAME, so can just call parent impl - return super.deserialize(p, ctxt, bean); - } - prop = _orderedProperties[i]; - p.nextToken(); // skip field, returns value token - try { - prop.deserializeAndSet(p, ctxt, bean); - } catch (Exception e) { - wrapAndThrow(e, bean, prop.getName(), ctxt); - } - } - // also, need to ensure we get closing END_OBJECT... - if (p.nextToken() != JsonToken.END_OBJECT) { - return super.deserialize(p, ctxt, bean); - } - return bean; - } - - // much of below is cut'n pasted from BeanSerializer - @Override - public final Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) - throws JacksonException - { - // [modules-base#339]: Need to delegate to base implementation for - // proper @JsonView handling during deserialization - if (_needViewProcesing) { - return super.deserializeFromObject(p, ctxt); - } - // See BeanDeserializer.deserializeFromObject [databind#622] - // Allow Object Id references to come in as JSON Objects as well... - if ((_objectIdReader != null) && _objectIdReader.maySerializeAsObject()) { - if (p.hasTokenId(JsonTokenId.ID_PROPERTY_NAME) - && _objectIdReader.isValidReferencePropertyName(p.currentName(), p)) { - return deserializeFromObjectId(p, ctxt); - } - } - if (_nonStandardCreation) { - if (_unwrappedPropertyHandler != null) { - return deserializeWithUnwrapped(p, ctxt); - } - if (_externalTypeIdHandler != null) { - return deserializeWithExternalTypeId(p, ctxt); - } - Object bean = deserializeFromObjectUsingNonDefault(p, ctxt); - if (_injectables != null) { - injectValues(ctxt, bean); - } - return bean; - } - final Object bean = _valueInstantiator.createUsingDefault(ctxt); - // [databind#631]: Assign current value, to be accessible by custom serializers - p.assignCurrentValue(bean); - if (p.canReadObjectId()) { - Object id = p.getObjectId(); - if (id != null) { - _handleTypedObjectId(p, ctxt, bean, id); - } - } - if (_injectables != null) { - injectValues(ctxt, bean); - } - SettableBeanProperty prop = _orderedProperties[0]; - // First: verify that first name is expected - if (p.isExpectedStartObjectToken()) { - if (!p.nextName(_orderedPropertyNames[0])) { - return super.deserialize(p, ctxt, bean); - } - } else if (!p.hasToken(JsonToken.PROPERTY_NAME) - || !prop.getName().equals(p.currentName())) { - return super.deserialize(p, ctxt, bean); - } - // and deserialize - p.nextToken(); - try { - prop.deserializeAndSet(p, ctxt, bean); - } catch (Exception e) { - wrapAndThrow(e, bean, prop.getName(), ctxt); - } - - // then rest of properties - for (int i = 1, len = _orderedProperties.length; i < len; ++i) { - prop = _orderedProperties[i]; - if (!p.nextName(_orderedPropertyNames[i])) { // miss... - if (p.hasToken(JsonToken.END_OBJECT)) { - return bean; - } - // we likely point to PROPERTY_NAME, so can just call parent impl - return super.deserialize(p, ctxt, bean); - } - p.nextToken(); // skip field, returns value token - try { - prop.deserializeAndSet(p, ctxt, bean); - } catch (Exception e) { - wrapAndThrow(e, bean, prop.getName(), ctxt); - } - } - // also, need to ensure we get closing END_OBJECT... - if (p.nextToken() != JsonToken.END_OBJECT) { - return super.deserialize(p, ctxt, bean); - } - return bean; - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SuperSonicDeserializerBuilder.java b/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SuperSonicDeserializerBuilder.java deleted file mode 100644 index e56cf341c..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/deser/SuperSonicDeserializerBuilder.java +++ /dev/null @@ -1,35 +0,0 @@ -package tools.jackson.module.blackbird.deser; - -import java.util.*; - -import tools.jackson.databind.ValueDeserializer; -import tools.jackson.databind.deser.*; -import tools.jackson.databind.deser.bean.BeanDeserializer; - -class SuperSonicDeserializerBuilder extends BeanDeserializerBuilder -{ - public SuperSonicDeserializerBuilder(BeanDeserializerBuilder base) { - super(base); - } - - @Override - public ValueDeserializer build() - { - BeanDeserializer deser = (BeanDeserializer) super.build(); - // only create custom one, if existing one is standard deserializer; - if (deser.getClass() == BeanDeserializer.class) { - BeanDeserializer beanDeser = (BeanDeserializer) deser; - Iterator it = getProperties(); - // also: only build custom one for non-empty beans: - if (it.hasNext()) { - // So let's find actual order of properties, necessary for optimal access - ArrayList props = new ArrayList(); - do { - props.add(it.next()); - } while (it.hasNext()); - return new SuperSonicBeanDeserializer(beanDeser, props); - } - } - return deser; - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/internal/ClassDataBootstrap.java b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/ClassDataBootstrap.java new file mode 100644 index 000000000..2865e68f9 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/ClassDataBootstrap.java @@ -0,0 +1,32 @@ +package tools.jackson.module.blackbird.internal; + +import java.lang.constant.ConstantDescs; +import java.lang.invoke.MethodHandles; +import java.util.Map; + +/** + * Constant bootstrap for generated code (reader, builder and writer codecs). + * Not API. + */ +public final class ClassDataBootstrap +{ + private ClassDataBootstrap() {} + + // Resolves one named entry of the hidden class's class data, a Map built + // at generation time. The JDK's classDataAt bootstrap requires the condy + // name to be "_", so named entries need this owner. The generated + // classes are defined in this module (in the codegen package), so they + // resolve it like any other class of the module. A condy links once and + // then constant-folds the same as classDataAt; the names exist for dump + // readability and to remove positional-index bookkeeping from the + // generators. + public static Object classDataEntry(MethodHandles.Lookup lookup, String name, Class type) + throws IllegalAccessException { + Map data = MethodHandles.classData(lookup, ConstantDescs.DEFAULT_NAME, Map.class); + Object value = data.get(name); + if (value == null) { + throw new IllegalStateException("no class data entry named " + name); + } + return type.cast(value); + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/internal/DiscoveredBlackbirdModule.java b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/DiscoveredBlackbirdModule.java new file mode 100644 index 000000000..49e6abfa6 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/DiscoveredBlackbirdModule.java @@ -0,0 +1,36 @@ +package tools.jackson.module.blackbird.internal; + +import tools.jackson.module.blackbird.BlackbirdModule; +import tools.jackson.module.blackbird.codegen.ClassFileEmitters; + +/** + * The service provider that module discovery instantiates + * ({@code MapperBuilder.findAndAddModules()}, or any other reader of + * {@code META-INF/services/tools.jackson.databind.JacksonModule}). On a + * runtime where Blackbird cannot generate codecs (for example Java 17 to + * 24), this instance registers nothing and logs one warning, so discovery + * keeps working for every other module; an explicitly registered + * {@link BlackbirdModule} fails instead. Not API: register + * {@code new BlackbirdModule()}. + */ +public final class DiscoveredBlackbirdModule extends BlackbirdModule +{ + private static final long serialVersionUID = 1L; + + // Databind keeps one module per registration id, the one added last. + // Where Blackbird works, the id is that of an explicit registration, so + // discovery plus an explicit registration installs Blackbird once. Where + // it cannot work, the id is distinct, so that discovery cannot replace an + // explicit registration, which must fail. + @Override + public Object getRegistrationId() { + return ClassFileEmitters.available() + ? BlackbirdModule.class.getName() + : DiscoveredBlackbirdModule.class.getName(); + } + + @Override + public String getModuleName() { + return BlackbirdModule.class.getSimpleName(); + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/internal/GeneratedBuilderBase.java b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/GeneratedBuilderBase.java new file mode 100644 index 000000000..907197137 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/GeneratedBuilderBase.java @@ -0,0 +1,123 @@ +package tools.jackson.module.blackbird.internal; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.SettableBeanProperty; +import tools.jackson.databind.deser.bean.BeanPropertyMap; +import tools.jackson.databind.deser.bean.BuilderBasedDeserializer; + +/** + * Superclass for generated builder codecs: a copy of the resolved stock + * {@link BuilderBasedDeserializer} whose {@code deserialize} method the + * generated class overrides; the counterpart of {@link GeneratedReaderBase}, + * whose comments apply. Not API. + */ +public abstract class GeneratedBuilderBase extends BuilderBasedDeserializer +{ + private final String _layout; + + private final ViewMasks _viewMasks = new ViewMasks(this::_computeViewMask); + + protected GeneratedBuilderBase(BuilderBasedDeserializer src, BeanPropertyMap props, + String layout) { + super(src, props); + _layout = layout; + _propertiesContextualized = true; + } + + /** The generated arms, as "name=KIND" entries in arm order. */ + public final String codecLayout() { + return _layout; + } + + @Override + public String toString() { + return "BBReader[" + handledType().getName() + ": " + _layout + "]"; + } + + @Override + public void resolve(DeserializationContext ctxt) { } + + protected final Object _stockDeserialize(JsonParser p, DeserializationContext ctxt) { + return super.deserialize(p, ctxt); + } + + // The build method threw: what BuilderBasedDeserializer.finishBuild does. + protected final Object _buildFailure(DeserializationContext ctxt, Throwable t) { + return wrapInstantiationProblem(ctxt, t); + } + + // The generated code calls the build method itself only on an instance + // of the builder class; anything else a with-method or a problem handler + // produced (the built target, null, another type) goes to the stock + // finishBuild, which returns the target as-is and reports the rest. + protected final boolean _isBuilder(Object builder) { + return handledType().isInstance(builder); + } + + protected final Object _finishBuild(DeserializationContext ctxt, Object builder) { + return finishBuild(ctxt, builder); + } + + // The property name that stock reports for a failing property: its + // general loop (deserializeFromObject) reports the name as written in + // the input (an alias, another case), its vanilla loop and view loop + // the property's own name. inputName is the name the generated arm read, + // or null for a bean without aliases or case-insensitive matching, where + // the two always agree; startObject tells whether the call entered on + // START_OBJECT (the stock vanilla loop's entry) or on a property name. + // Resolved here, on the failure path, so the entry pays nothing for it. + private String _failedName(SettableBeanProperty prop, String inputName, + boolean startObject, DeserializationContext ctxt) { + if ((inputName == null) + || (_vanillaProcessing && startObject) + || (_needViewProcesing && ctxt.getActiveView() != null)) { + return prop.getName(); + } + return inputName; + } + + protected final RuntimeException _propertyException(Throwable t, Object builder, + SettableBeanProperty prop, String inputName, boolean startObject, + DeserializationContext ctxt) { + throw wrapAndThrow(t, builder, _failedName(prop, inputName, startObject, ctxt), ctxt); + } + + protected final RuntimeException _storeFailure(Throwable t, Object builder, + Object childValue, SettableBeanProperty prop, JsonParser p, + String inputName, boolean startObject, DeserializationContext ctxt) { + throw wrapAndThrow(ReaderSupport.storeException(t, childValue, prop, p), builder, + _failedName(prop, inputName, startObject, ctxt), ctxt); + } + + protected final void _injectValues(DeserializationContext ctxt, Object builder) { + injectValues(ctxt, builder); + } + + protected final void _handleUnknown(JsonParser p, DeserializationContext ctxt, + Object builder) { + String name = p.currentName(); + p.nextToken(); + handleUnknownVanilla(p, ctxt, builder, name); + } + + // As in the stock vanilla loop, whatever the unexpected-token handling + // returns is then built. + protected final Object _unexpectedToken(JsonParser p, DeserializationContext ctxt, + Object builder) { + return finishBuild(ctxt, _handleUnexpectedWithin(p, ctxt, builder)); + } + + protected final long _viewMask(Class view) { + return _viewMasks.maskFor(view); + } + + protected long _computeViewMask(Class view) { + throw new UnsupportedOperationException("codec has no view mask"); + } + + protected final void _hiddenView(JsonParser p, DeserializationContext ctxt, + SettableBeanProperty prop) { + handleUnexpectedView(p, ctxt, prop, ctxt.getActiveView()); + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/internal/GeneratedReaderBase.java b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/GeneratedReaderBase.java new file mode 100644 index 000000000..34ef61ab7 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/GeneratedReaderBase.java @@ -0,0 +1,312 @@ +package tools.jackson.module.blackbird.internal; + +import java.util.ArrayList; +import java.util.List; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.ReadableObjectId; +import tools.jackson.databind.deser.SettableBeanProperty; +import tools.jackson.databind.deser.UnresolvedForwardReference; +import tools.jackson.databind.deser.bean.BeanDeserializer; +import tools.jackson.databind.deser.bean.BeanPropertyMap; +import tools.jackson.databind.deser.bean.PropertyValueBuffer; +import tools.jackson.databind.deser.std.StdValueInstantiator; +import tools.jackson.databind.util.IgnorePropertiesUtil; +import tools.jackson.databind.util.NameTransformer; +import tools.jackson.databind.util.TokenBuffer; + +/** + * Superclass for generated POJO and record codecs (builder codecs extend + * {@link GeneratedBuilderBase}). A codec is a copy of the resolved stock + * {@link BeanDeserializer} whose {@code deserialize} method the generated + * class overrides with the per-bean loop: everything else, and every input + * the loop does not model, is the stock implementation, and databind sees a + * {@code BeanDeserializer} wherever it checks for one. The helpers below are + * the cold paths of the generated loop; each one calls the stock code for + * the same situation. Not API. + */ +public abstract class GeneratedReaderBase extends BeanDeserializer +{ + // One "name=KIND" entry per arm, for diagnostics and tests. + private final String _layout; + + private final ViewMasks _viewMasks = new ViewMasks(this::_computeViewMask); + + protected GeneratedReaderBase(BeanDeserializer src, BeanPropertyMap props, String layout) { + // Built from the resolved deserializer, with the bean property map + // whose name matcher is initialized. + super(src, props); + _layout = layout; + _propertiesContextualized = true; + } + + /** The generated arms, as "name=KIND" entries in arm order. */ + public final String codecLayout() { + return _layout; + } + + @Override + public String toString() { + return "BBReader[" + handledType().getName() + ": " + _layout + "]"; + } + + // A copy of a resolved deserializer: nothing left to resolve. + @Override + public void resolve(DeserializationContext ctxt) { } + + // The generated deserialize hands inputs it does not model here. + protected final Object _stockDeserialize(JsonParser p, DeserializationContext ctxt) { + return super.deserialize(p, ctxt); + } + + // BeanDeserializer unwraps only for its exact class (subclasses "MUST + // override"); an exact-class copy of this state does what it would. The + // field guards against recursive unwrapping, as in stock. + @Override + public ValueDeserializer unwrappingDeserializer(DeserializationContext ctxt, + NameTransformer transformer) { + if (_currentlyTransforming == transformer) { + return this; + } + _currentlyTransforming = transformer; + try { + return withBeanProperties(_beanProperties).unwrappingDeserializer(ctxt, transformer); + } finally { + _currentlyTransforming = null; + } + } + + /* + /********************************************************************** + /* Failures + /********************************************************************** + */ + + // The no-arg constructor handle threw: what StdValueInstantiator + // .createUsingDefault does (the factory uses the handle only with exactly + // that instantiator). Returns a problem handler's replacement value. + protected final Object _instantiationFailure(DeserializationContext ctxt, Throwable t) { + return ReaderSupport.creatorProblem((StdValueInstantiator) _valueInstantiator, ctxt, + null, t); + } + + // Called from the generated exception handler that covers every property + // arm: the stock loop's wrapAndThrow, so property errors carry the + // reference path. Always throws; the return type only satisfies the + // generated athrow. + protected final RuntimeException _propertyException(Throwable t, Object beanOrNull, + SettableBeanProperty prop, DeserializationContext ctxt) { + Object ref = (beanOrNull == null) ? handledType() : beanOrNull; + throw wrapAndThrow(t, ref, prop.getName(), ctxt); + } + + // A generated store (setter call or field write) threw: what the stock + // property throws for it, through the stock loop's wrapAndThrow. + protected final RuntimeException _storeFailure(Throwable t, Object bean, + Object childValue, SettableBeanProperty prop, JsonParser p, + DeserializationContext ctxt) { + throw wrapAndThrow(ReaderSupport.storeException(t, childValue, prop, p), bean, + prop.getName(), ctxt); + } + + /* + /********************************************************************** + /* POJO loop helpers + /********************************************************************** + */ + + // Right after construction, before the property loop: the placement + // stock deserializeFromObject uses, so values from the document override + // injected values exactly like stock. + protected final void _injectValues(DeserializationContext ctxt, Object bean) { + injectValues(ctxt, bean); + } + + // A name the matcher does not know: stock handleUnknownVanilla (ignored + // names, then the any-setter, then unknown-property handling). + protected final void _handleUnknown(JsonParser p, DeserializationContext ctxt, + Object bean) { + String name = p.currentName(); + p.nextToken(); + handleUnknownVanilla(p, ctxt, bean, name); + } + + // Neither a name nor the end of the object where a name was expected. + protected final Object _unexpectedToken(JsonParser p, DeserializationContext ctxt, + Object bean) { + return _handleUnexpectedWithin(p, ctxt, bean); + } + + /* + /********************************************************************** + /* Record loop helpers + /********************************************************************** + */ + + // A name no component takes: the order of the stock property-based + // loop. Ignored names go through ignored-property handling at once; + // other names are skipped when stock would skip them, and otherwise + // buffered, to be reported against the record once it exists (see + // _recordFinish). Returns the buffer, created on first use. + protected final Object _recordUnknown(JsonParser p, DeserializationContext ctxt, + Object buffered) { + String name = p.currentName(); + p.nextToken(); + if (IgnorePropertiesUtil.shouldIgnore(name, _ignorableProps, _includableProps)) { + handleIgnoredProperty(p, ctxt, handledType(), name); + return buffered; + } + if (_shouldSkipUnknowns(ctxt)) { + p.skipChildren(); + return buffered; + } + TokenBuffer tb = (buffered == null) ? ctxt.bufferForInputBuffering(p) + : (TokenBuffer) buffered; + tb.writeName(name); + tb.copyCurrentStructure(p); + return tb; + } + + // A component value that is a forward Object Id reference: what the + // stock property-based loop does ([databind#3030]). The component gets + // null, and the reference resolves against the record once it exists + // (see _recordFinish). Returns the referrings, a list + // created on first use. + protected final Object _recordForwardRef(DeserializationContext ctxt, + SettableBeanProperty prop, UnresolvedForwardReference ref, Object referrings) { + RecordReferring referring = new RecordReferring(ctxt, ref, prop); + ref.getRoid().appendReferring(referring); + @SuppressWarnings("unchecked") + List list = (referrings == null) ? new ArrayList<>() + : (List) referrings; + list.add(referring); + return list; + } + + // After construction, with forward-reference referrings and/or buffered + // unknown properties (either may be null): what stock + // _deserializeUsingPropertyBased does. For a built record the references + // resolve against it, then the unknown properties replay. For a null one + // (a creator or problem handler returned null), as stock + // _handleNullFromPropsBasedCreator, the unknown properties are handled + // first, and a reference that resolved before construction is dropped + // ([databind#6325]). + @SuppressWarnings("unchecked") + protected final Object _recordFinish(DeserializationContext ctxt, Object bean, + Object referrings, Object buffered) { + if (bean == null && buffered != null) { + handleUnknownProperties(ctxt, null, (TokenBuffer) buffered); + } + if (referrings != null) { + for (RecordReferring referring : (List) referrings) { + referring.setBean(bean); + } + } + if (bean == null || buffered == null) { + return bean; + } + return handleUnknownProperties(ctxt, bean, (TokenBuffer) buffered); + } + + // The stock BeanDeserializer.BeanReferring, which is not accessible; keep + // in step with it (BBCodecStockChecksTest compares the two). + private static final class RecordReferring extends ReadableObjectId.Referring { + private final DeserializationContext _context; + private final SettableBeanProperty _prop; + private Object _bean; + private Object _resolvedValue; + + RecordReferring(DeserializationContext ctxt, UnresolvedForwardReference ref, + SettableBeanProperty prop) { + super(ref, prop.getType()); + _context = ctxt; + _prop = prop; + } + + void setBean(Object bean) { + _bean = bean; + // [databind#6325]: no bean (the creator returned null) to apply + // the reference to, so it is dropped. + if (bean == null) { + _resolvedValue = null; + return; + } + if (_resolvedValue != null) { + _prop.set(_context, _bean, _resolvedValue); + _resolvedValue = null; + } + } + + @Override + public void handleResolvedForwardReference(DeserializationContext ctxt, + Object id, Object value) { + if (_bean == null) { + _resolvedValue = value; + } else { + _prop.set(ctxt, _bean, value); + } + } + } + + // Record codecs call the canonical constructor directly only when the + // document supplied every component, every primitive component received + // its exact wrapper (not null or another type from a custom + // deserializer), and FAIL_ON_NULL_CREATOR_PROPERTIES is off. Everything + // else ends here: values holds the components (boxed, or the raw stock + // value, in creator-index order) and seen marks the ones the document + // supplied. Without a constructor failure, the stock creator completes the + // arguments (absent values, required and FAIL_ON_MISSING / + // FAIL_ON_NULL_CREATOR_PROPERTIES checks) and constructs; a failure from + // the fast path goes where StdValueInstantiator sends it. Either way a + // failure ends in wrapInstantiationProblem, as in the stock loop. + protected final Object _recordBuild(JsonParser p, DeserializationContext ctxt, long seen, + Object[] values, Throwable ctorFailure) { + if (ctorFailure != null) { + try { + return ReaderSupport.creatorProblem((StdValueInstantiator) _valueInstantiator, + ctxt, values, ctorFailure); + } catch (Exception e) { + return wrapInstantiationProblem(ctxt, e); + } + } + SettableBeanProperty[] props = _propertyBasedCreator.allPropertiesInOrder(); + PropertyValueBuffer buffer = _propertyBasedCreator.startBuilding(p, ctxt, null, false); + for (int i = 0; i < props.length; i++) { + if ((seen & (1L << i)) != 0) { + buffer.assignParameter(props[i], values[i]); + } + } + try { + return _propertyBasedCreator.build(ctxt, buffer); + } catch (Exception e) { + return wrapInstantiationProblem(ctxt, e); + } + } + + /* + /********************************************************************** + /* Views and class data + /********************************************************************** + */ + + // Called by generated code once per view-active call. + protected final long _viewMask(Class view) { + return _viewMasks.maskFor(view); + } + + // Overridden by generated codecs that filter per view: bit i reports + // whether property arm i is visible in the given view. Codecs that + // delegate view-active calls whole (more than 64 properties) never call + // the mask machinery. + protected long _computeViewMask(Class view) { + throw new UnsupportedOperationException("codec has no view mask"); + } + + // An arm hidden in the active view, the parser on the value token. + protected final void _hiddenView(JsonParser p, DeserializationContext ctxt, + SettableBeanProperty prop) { + handleUnexpectedView(p, ctxt, prop, ctxt.getActiveView()); + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/internal/GeneratedWriterBase.java b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/GeneratedWriterBase.java new file mode 100644 index 000000000..677501701 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/GeneratedWriterBase.java @@ -0,0 +1,149 @@ +package tools.jackson.module.blackbird.internal; + +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.jsontype.TypeSerializer; +import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.ser.BeanSerializer; +import tools.jackson.databind.ser.PropertyWriter; +import tools.jackson.databind.ser.bean.BeanSerializerBase; + +/** + * Superclass for generated serializers: a copy of the resolved stock bean + * serializer (as a {@link BeanSerializer}) whose {@code serialize} method + * (and, for most beans, {@code serializeWithType}) the generated class + * overrides. Everything else, and every call the generated code does not + * model, is the stock implementation, and databind sees a bean serializer + * wherever it checks for one (the self-reference check of + * BeanPropertyWriter among them). Not API. + */ +public abstract class GeneratedWriterBase extends BeanSerializer +{ + // One "name=KIND" entry per property, for diagnostics and tests. + private final String _layout; + + private final ViewMasks _viewMasks = new ViewMasks(this::_computeViewMask); + + protected GeneratedWriterBase(BeanSerializerBase src, String layout) { + super(src); + _layout = layout; + } + + /** The generated properties, as "name=KIND" entries in write order. */ + public final String codecLayout() { + return _layout; + } + + @Override + public String toString() { + return "BBWriter[" + handledType().getName() + ": " + _layout + "]"; + } + + // A copy of a resolved serializer: nothing left to resolve. + @Override + public void resolve(SerializationContext ctxt) { } + + /* + /********************************************************************** + /* Support for generated code + /********************************************************************** + */ + + // The stock implementations, for calls the generated code hands back. + protected final void _stockSerialize(Object bean, JsonGenerator g, SerializationContext ctxt) { + super.serialize(bean, g, ctxt); + } + + protected final void _stockSerializeWithType(Object bean, JsonGenerator g, + SerializationContext ctxt, TypeSerializer typeSer) { + super.serializeWithType(bean, g, ctxt, typeSer); + } + + // Called from the generated handler around the property section: what + // stock _serializePropertiesNoView does with an exception from the + // property at index (the generated properties are the stock ones, in + // stock order). Always throws; the return type only satisfies the + // generated athrow. + protected final RuntimeException _propertyFailure(Throwable t, Object bean, int index, + JsonGenerator g, SerializationContext ctxt) { + String name = _props[index].getName(); + if (t instanceof StackOverflowError soe) { + throw DatabindException.from(g, "Infinite recursion (StackOverflowError)", soe) + .prependPath(bean, name); + } + wrapAndThrow(ctxt, t, bean, name); + throw new IllegalStateException("wrapAndThrow returned", t); + } + + /** + * The writer that a generated child arm passes to + * {@link #_selfReference}, built once per child property at generation + * time (class data) rather than per write. + */ + public static BeanPropertyWriter selfReferenceWriter(BeanPropertyWriter prop) { + return new SelfReferenceProbe(prop); + } + + // A child property whose value is the bean itself: the rest of stock + // BeanPropertyWriter.serializeAsProperty from the self-reference check + // on (the generated arm has read the value, which stock reads once; the + // arm's properties have no type serializer, value suppression or + // applied view). probe comes from selfReferenceWriter. + protected final void _selfReference(BeanPropertyWriter probe, Object bean, JsonGenerator g, + SerializationContext ctxt) throws Exception { + ((SelfReferenceProbe) probe).serializeSelf(bean, g, ctxt); + } + + // BeanPropertyWriter's self-reference handling and serializer state are + // protected; the copy constructor carries the writer's state over. A + // child arm exists only for a writer whose serializer is a generated + // writer (BBWriterFactory.classify), so _serializer is always set. + private static final class SelfReferenceProbe extends BeanPropertyWriter { + SelfReferenceProbe(BeanPropertyWriter base) { + super(base); + if (_serializer == null) { + throw new IllegalStateException("child property '" + _name.getValue() + + "' has no serializer"); + } + } + + void serializeSelf(Object bean, JsonGenerator g, SerializationContext ctxt) + throws Exception { + if (_handleSelfReference(bean, g, ctxt, _serializer)) { + return; + } + g.writeName(_name); + _serializer.serialize(bean, g, ctxt); + } + } + + protected final long _viewMask(Class view) { + return _viewMasks.maskFor(view); + } + + // Overridden by generated writers that filter per view: bit i reports + // whether property i is visible in the given view. + protected long _computeViewMask(Class view) { + throw new UnsupportedOperationException("writer has no view mask"); + } + + // Visibility of one property in a view, matching the rule the stock + // factory uses to build the filtered writer array: no view annotations + // means DEFAULT_VIEW_INCLUSION decides (captured at generation time), + // otherwise any declared view assignable from the active view. + protected static boolean _propVisible(PropertyWriter w, Class view, + boolean includeByDefault) { + Class[] views = (w instanceof BeanPropertyWriter bpw) ? bpw.getViews() : null; + if (views == null || views.length == 0) { + return includeByDefault; + } + for (Class v : views) { + if (v.isAssignableFrom(view)) { + return true; + } + } + return false; + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/internal/ReaderSupport.java b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/ReaderSupport.java new file mode 100644 index 000000000..4007efa4a --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/ReaderSupport.java @@ -0,0 +1,82 @@ +package tools.jackson.module.blackbird.internal; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.SettableBeanProperty; +import tools.jackson.databind.deser.std.StdValueInstantiator; +import tools.jackson.databind.util.ClassUtil; + +/** + * Cold-path helpers shared by the generated reader bases: each reproduces + * what a stock class does in the same situation, where that code is not + * reachable through inheritance. + */ +final class ReaderSupport +{ + private ReaderSupport() {} + + /** + * The exception that the stock MethodProperty throws when its setter or + * field store throws t: SettableBeanProperty._throwAsJacksonE itself, + * called through a probe. A scalar arm no longer has its value, but the + * message needs only the value's class (ClassUtil.classNameOf takes a + * Class as is), which the property type fixes; a child arm passes its + * value. + */ + static Throwable storeException(Throwable t, Object childValue, SettableBeanProperty prop, + JsonParser p) { + Class raw = prop.getType().getRawClass(); + Object value = raw.isPrimitive() ? ClassUtil.wrapperType(raw) + : (raw == String.class) ? String.class : childValue; + return PropertyProbe.storeException(prop, p, t, value); + } + + // SettableBeanProperty._throwAsJacksonE is protected; the delegating + // copy carries the property's name and type into this probe. + private static final class PropertyProbe extends SettableBeanProperty.Delegating { + private PropertyProbe(SettableBeanProperty src) { + super(src); + } + + @Override + protected SettableBeanProperty withDelegate(SettableBeanProperty d) { + return new PropertyProbe(d); + } + + static Throwable storeException(SettableBeanProperty prop, JsonParser p, Throwable t, + Object value) { + try { + new PropertyProbe(prop)._throwAsJacksonE(p, t, value); + } catch (RuntimeException | Error e) { + return e; + } + // _throwAsJacksonE always throws + return new IllegalStateException(t); + } + } + + /** + * What StdValueInstantiator does with an exception from the creator it + * calls (the instantiator must be exactly that class): problem handlers + * may supply a value, otherwise the wrapped exception is thrown. + */ + static Object creatorProblem(StdValueInstantiator inst, DeserializationContext ctxt, + Object[] args, Throwable t) { + return ctxt.handleInstantiationProblem(inst.getValueClass(), args, + InstantiatorProbe.rewrapCtorProblem(inst, ctxt, t)); + } + + // StdValueInstantiator.rewrapCtorProblem is protected; the copy + // constructor carries the instantiator's state into this probe. + private static final class InstantiatorProbe extends StdValueInstantiator { + private InstantiatorProbe(StdValueInstantiator src) { + super(src); + } + + static DatabindException rewrapCtorProblem(StdValueInstantiator src, + DeserializationContext ctxt, Throwable t) { + return new InstantiatorProbe(src).rewrapCtorProblem(ctxt, t); + } + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/internal/ViewMasks.java b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/ViewMasks.java new file mode 100644 index 000000000..4d198ff2c --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/ViewMasks.java @@ -0,0 +1,55 @@ +package tools.jackson.module.blackbird.internal; + +import java.util.function.ToLongFunction; + +/** + * Copy-on-write cache of view to visibility bitmask (bit i: property arm i + * is visible in the view) for one codec. View sets are small and stable per + * application, so lookups are a reference scan. The cached view classes + * unload with the codec and its mapper. + */ +final class ViewMasks +{ + private static final Class[] NO_VIEWS = new Class[0]; + private static final long[] NO_MASKS = new long[0]; + + private final ToLongFunction> _compute; + + private volatile Class[] _views = NO_VIEWS; + private volatile long[] _masks = NO_MASKS; + + ViewMasks(ToLongFunction> compute) { + _compute = compute; + } + + long maskFor(Class view) { + Class[] views = _views; + long[] masks = _masks; + for (int i = 0; i < views.length && i < masks.length; i++) { + if (views[i] == view) { + return masks[i]; + } + } + return add(view); + } + + private synchronized long add(Class view) { + Class[] views = _views; + for (int i = 0; i < views.length; i++) { + if (views[i] == view) { + return _masks[i]; + } + } + long mask = _compute.applyAsLong(view); + Class[] newViews = new Class[views.length + 1]; + long[] newMasks = new long[views.length + 1]; + System.arraycopy(views, 0, newViews, 0, views.length); + System.arraycopy(_masks, 0, newMasks, 0, views.length); + newViews[views.length] = view; + newMasks[views.length] = mask; + // Masks first: a reader that sees the new views array also sees them. + _masks = newMasks; + _views = newViews; + return mask; + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/internal/package-info.java b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/package-info.java new file mode 100644 index 000000000..52b0c7171 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/internal/package-info.java @@ -0,0 +1,7 @@ +/** + * Blackbird internals: the superclasses of the generated codecs and the + * module discovery provider. The package is not exported from the module + * and is not API: the generated codecs are defined in this module (in the + * codegen package), so nothing outside it links to these types. + */ +package tools.jackson.module.blackbird.internal; diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBSerializerModifier.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBSerializerModifier.java index 550cdbace..a2d2f8a23 100644 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBSerializerModifier.java +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBSerializerModifier.java @@ -1,196 +1,73 @@ package tools.jackson.module.blackbird.ser; -import java.lang.invoke.LambdaMetafactory; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.Member; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.*; -import java.util.function.Function; -import java.util.function.ToIntFunction; -import java.util.function.ToLongFunction; -import java.util.function.UnaryOperator; -import java.util.logging.Level; -import java.util.logging.Logger; - -import tools.jackson.databind.*; -import tools.jackson.databind.annotation.JacksonStdImpl; -import tools.jackson.databind.introspect.AnnotatedMember; -import tools.jackson.databind.introspect.AnnotatedMethod; -import tools.jackson.databind.ser.*; -import tools.jackson.module.blackbird.util.ReflectionHack; -import tools.jackson.module.blackbird.util.Unchecked; - -import static java.lang.invoke.MethodType.*; +import tools.jackson.databind.BeanDescription; +import tools.jackson.databind.SerializationConfig; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.ser.BeanSerializer; +import tools.jackson.databind.ser.UnrolledBeanSerializer; +import tools.jackson.databind.ser.ValueSerializerModifier; +import tools.jackson.databind.ser.bean.BeanSerializerBase; +import tools.jackson.module.blackbird.codegen.CodegenDebug; +import tools.jackson.module.blackbird.codegen.CodegenFallbacks; + +/** + * Wraps eligible stock bean serializers in a placeholder that generates a + * per-bean writer at resolve time. Every gate that fails leaves the stock + * serializer in place. + */ public class BBSerializerModifier extends ValueSerializerModifier { private static final long serialVersionUID = 1L; - private static final Logger LOG = Logger.getLogger(BBSerializerModifier.class.getName()); - - private final Function, Lookup> _lookups; - private final UnaryOperator _accessGrant; - - public BBSerializerModifier(Function, MethodHandles.Lookup> lookups, UnaryOperator accessGrant) - { - _lookups = lookups; - _accessGrant = accessGrant; - } + // Member access rides databind's own fixAccess (see MemberHandles), so + // no lookup is needed. + public BBSerializerModifier() { } @Override - public List changeProperties(SerializationConfig config, - BeanDescription.Supplier beanDescRef, List beanProperties) + public ValueSerializer modifySerializer(SerializationConfig config, + BeanDescription.Supplier beanDescRef, ValueSerializer serializer) { - final Class beanClass = beanDescRef.getBeanClass(); - - /* Hmmh. Can we access stuff from private classes? - * Possibly, if we can use parent class loader. - * (should probably skip all non-public?) - */ - if (Modifier.isPrivate(beanClass.getModifiers())) { // TODO? - return beanProperties; + // Gate failures of any kind leave the stock serializer in place. The + // reflective gates can throw for exotic classes (a bean from a foreign + // classloader with inconsistent InnerClasses metadata raises + // IncompatibleClassChangeError from getEnclosingClass), and an + // acceleration modifier must never break a bean stock databind handles. + try { + return doModify(beanDescRef, serializer); + } catch (RuntimeException | LinkageError e) { + CodegenFallbacks.gateFailure(beanDescRef.getBeanClass(), e); + return serializer; } - - findProperties(beanClass, config, beanProperties); - return beanProperties; } - protected void findProperties(Class beanClass, - SerializationConfig config, List beanProperties) + private ValueSerializer doModify(BeanDescription.Supplier beanDescRef, + ValueSerializer serializer) { - MethodHandles.Lookup lookup = _lookups.apply(beanClass); - if (lookup == null) { - return; + if (serializer.getClass() != BeanSerializer.class + && serializer.getClass() != UnrolledBeanSerializer.class) { + return skip(beanDescRef, serializer, "not a stock bean serializer"); } - - ListIterator it = beanProperties.listIterator(); - while (it.hasNext()) { - Unchecked.runnable(() -> - createProperty(it, ReflectionHack.privateLookupIn(beanClass, lookup), config)) - .run(); + // Non-static inner classes stay stock for symmetry with the reader + // side; no other class-shape gate remains, since generated writers + // never name the bean class. The static check runs first: for a + // static member class redefined in a foreign classloader, + // getEnclosingClass raises IncompatibleClassChangeError (its + // InnerClasses metadata resolves to the parent-loaded owner), and + // such beans accelerate now. + Class beanClass = beanDescRef.getBeanClass(); + if (!Modifier.isStatic(beanClass.getModifiers()) + && beanClass.getEnclosingClass() != null) { + return skip(beanDescRef, serializer, "non-static inner class"); } + return new BBWriterPlaceholder((BeanSerializerBase) serializer); } - protected void createProperty(ListIterator it, Lookup lookup, SerializationConfig config) throws Throwable { - BeanPropertyWriter bpw = it.next(); - AnnotatedMember member = bpw.getMember(); - - Member jdkMember = member.getMember(); - // 11-Sep-2015, tatu: Let's skip virtual members (related to #57) - if (jdkMember == null) { - return; - } - // We can't access private fields or methods, skip: - if (Modifier.isPrivate(jdkMember.getModifiers())) { - return; - } - // (although, interestingly enough, can seem to access private classes...) - - // 30-Jul-2012, tatu: [#6]: Needs to skip custom serializers, if any. - if (bpw.hasSerializer() && !SerializerUtil.isDefaultSerializer(bpw.getSerializer())) { - return; - } - // [#9]: also skip unwrapping stuff... - if (bpw.isUnwrapping()) { - return; - } - - if (!bpw.getClass().isAnnotationPresent(JacksonStdImpl.class)) { - return; - } - - Class type = bpw.getMember().getRawType(); - MethodHandle getter; - Method method; - if (member instanceof AnnotatedMethod) { - method = (Method) member.getMember(); - try { - getter = lookup.unreflect(method); - } catch (IllegalAccessException e) { - // e.g. an inherited protected getter, where the lookup class isn't the - // subclass (as with `CrossLoaderAccess` on some JDKs); reflection can still call it - _logSkipped(method, lookup, e); - return; - } - } else { - // TODO: currently VarHandles aren't considered direct MH - return; - //getter = lookup.unreflectGetter((Field) member.getMember()); - } - lookup = _accessGrant.apply(lookup); - // A generated getter that would fail on every call (like one whose return - // type the bean's loader shadows) is left to reflection - if (!ReflectionHack.canLinkAccessor(lookup, method, getter)) { - _logSkipped(method, lookup, null); - return; - } - - if (type.isPrimitive()) { - if (type == Integer.TYPE) { - ToIntFunction accessor = (ToIntFunction) LambdaMetafactory.metafactory( - lookup, - "applyAsInt", - methodType(ToIntFunction.class), - methodType(int.class, Object.class), - getter, - getter.type()) - .getTarget().invokeExact(); - it.set(new IntPropertyWriter(bpw, accessor, null)); - } else if (type == Long.TYPE) { - ToLongFunction accessor = (ToLongFunction) LambdaMetafactory.metafactory( - lookup, - "applyAsLong", - methodType(ToLongFunction.class), - methodType(long.class, Object.class), - getter, - getter.type()) - .getTarget().invokeExact(); - it.set(new LongPropertyWriter(bpw, accessor, null)); - } else if (type == Boolean.TYPE) { - ToBooleanFunction accessor = (ToBooleanFunction) LambdaMetafactory.metafactory( - lookup, - "applyAsBoolean", - methodType(ToBooleanFunction.class), - methodType(boolean.class, Object.class), - getter, - getter.type()) - .getTarget().invokeExact(); - it.set(new BooleanPropertyWriter(bpw, accessor, null)); - } - } else { - if (type == String.class) { - Function accessor = (Function) LambdaMetafactory.metafactory( - lookup, - "apply", - methodType(Function.class), - methodType(Object.class, Object.class), - getter, - getter.type()) - .getTarget().invokeExact(); - it.set(new StringPropertyWriter(bpw, accessor, null)); - } else { - Function accessor = (Function) LambdaMetafactory.metafactory( - lookup, - "apply", - methodType(Function.class), - methodType(Object.class, Object.class), - getter, - getter.type()) - .getTarget().invokeExact(); - it.set(new ObjectPropertyWriter(bpw, accessor, null)); - } - } + private static ValueSerializer skip(BeanDescription.Supplier beanDescRef, + ValueSerializer serializer, String reason) { + CodegenDebug.logSkip("writer", beanDescRef.getBeanClass(), reason); + return serializer; } - private static void _logSkipped(Method getter, Lookup lookup, Throwable cause) { - if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, String.format( - "Not optimizing getter %s: a generated accessor in %s %s; using reflection", - getter, lookup.lookupClass().getName(), - (cause == null) ? "couldn't link to it" : "can't access it"), cause); - } - } } diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBWriterFactory.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBWriterFactory.java new file mode 100644 index 000000000..7083c4b04 --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBWriterFactory.java @@ -0,0 +1,300 @@ +package tools.jackson.module.blackbird.ser; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.introspect.AnnotatedField; +import tools.jackson.databind.introspect.AnnotatedMethod; +import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.ser.PropertyWriter; +import tools.jackson.databind.ser.bean.BeanSerializerBase; +import tools.jackson.databind.ser.impl.FilteredBeanPropertyWriter; +import tools.jackson.databind.ser.jdk.BooleanSerializer; +import tools.jackson.databind.ser.jdk.NumberSerializers; +import tools.jackson.databind.ser.jdk.StringSerializer; +import tools.jackson.databind.ser.std.NullSerializer; +import tools.jackson.databind.MapperFeature; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.ViewStrategy; +import tools.jackson.module.blackbird.codegen.WriterCodegen; +import tools.jackson.module.blackbird.codegen.WriterCodegen.GenWProp; +import tools.jackson.module.blackbird.codegen.WriterCodegen.WKind; +import tools.jackson.module.blackbird.codegen.MemberHandles; +import tools.jackson.module.blackbird.internal.GeneratedWriterBase; +import tools.jackson.module.blackbird.codegen.CodegenDebug; +import tools.jackson.module.blackbird.codegen.CodegenFallbacks; + +/** + * Builds a generated serializer from a resolved stock bean serializer, or + * returns null when the bean does not qualify. Same philosophy as the + * deserializer side: anything not cheaply verifiable rides the stock + * PropertyWriter from generated code, and member access goes through + * {@link MemberHandles} - databind ran fixAccess on every accessor before + * this factory sees it, so an unreflect failure means the stock path could + * not read the property either, and the property demotes. + */ +final class BBWriterFactory +{ + // Exact classes: a subclass may write differently + private static final Set> STOCK_SCALAR_SERS = Set.of( + StringSerializer.class, + NumberSerializers.IntegerSerializer.class, + NumberSerializers.LongSerializer.class, + BooleanSerializer.class); + + private BBWriterFactory() {} + + // CodegenFallbacks.FAIL_ON_ERROR, copied here so that the stack overflow + // handler of tryGenerate reads a field of this initialized class. + // Reading CodegenFallbacks there would instead run its static + // initializer (a logger lookup) on the nearly exhausted stack, if + // nothing has used it yet; this copy initializes it along with this + // class, early. + private static final boolean FAIL_ON_STACK_OVERFLOW = CodegenFallbacks.FAIL_ON_ERROR; + + // props and filtered: the resolved _props and _filteredProps of delegate, + // which are protected. asArray: the bean's own shape is ARRAY. + static ValueSerializer tryGenerate(BeanSerializerBase delegate, + BeanPropertyWriter[] props, BeanPropertyWriter[] filtered, boolean hasTypeId, + boolean asArray, SerializationContext ctxt) { + ValueSerializer codec; + try { + codec = generate(delegate, props, filtered, hasTypeId, + asArray, ctxt); + } catch (StackOverflowError e) { + // Generation ran out of stack deep in a recursive resolution: not + // a generator bug, and the stock path, which needs no generation, + // can still complete the call. Nothing more (not even logging) + // runs on the nearly exhausted stack. In strict mode (the test + // suites) it more likely is unbounded recursion in the + // generator, so it fails the test. The check only reads a field + // of this (initialized) class. + if (FAIL_ON_STACK_OVERFLOW) { + throw e; + } + return null; + } catch (VirtualMachineError e) { + // Out of memory or a broken JVM, not a generator bug: never demote + throw e; + } catch (Throwable t) { + if (CodegenDebug.ENABLED) { + CodegenDebug.log(() -> "writer tryGenerate " + delegate.handledType().getName() + " threw:"); + t.printStackTrace(); + } + CodegenFallbacks.generationFailure(delegate.handledType(), t); + return null; + } + // Outside the try: tracing must not turn into a demotion of a codec + // that generated fine. + CodegenDebug.log(() -> "writer tryGenerate " + delegate.handledType().getName() + + " -> " + (codec == null ? "null (gated)" : codec.getClass().getName())); + return codec; + } + + private static ValueSerializer gate(String reason) { + CodegenDebug.log(() -> "writer gate: " + reason); + return null; + } + + private static ValueSerializer generate(BeanSerializerBase delegate, + BeanPropertyWriter[] beanProps, BeanPropertyWriter[] filtered, boolean hasTypeId, + boolean asArray, SerializationContext ctxt) + throws ReflectiveOperationException { + // Stock contextualizes an array-shaped bean to its as-array + // serializer, which writes with its own loop and calls back into the + // placeholder only on stock paths (unwrapping, copies), never into a + // writer codec. + if (asArray) { + return gate("array shape"); + } + if (delegate.usesObjectId() || delegate.getFilterId() != null) { + return gate("object id or filter id"); + } + Class beanClass = delegate.handledType(); + List props = new ArrayList<>(); + for (Iterator it = delegate.properties(); it.hasNext(); ) { + PropertyWriter writer = it.next(); + props.add(classify(writer)); + } + if (props.isEmpty()) { + return gate("no props"); + } + boolean includeByDefault = ctxt.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION); + // A @JsonTypeId property replaces the resolver-computed type id, which + // the generated serializeWithType does not model; such beans keep the + // base class's whole-call forwarding for typed writes. + boolean nativePolyWrites = !hasTypeId; + // null when the writer would be too large to JIT-compile. + return WriterCodegen.generate(beanClass, props, delegate, + viewStrategy(beanProps, filtered, props, includeByDefault), includeByDefault, + nativePolyWrites); + } + + // Under an active view stock writes the resolved filtered-writer array + // (_filteredProps, or every property when there is none). The generated + // code derives visibility from each property's views instead, which is + // only the same as long as the array is the one processViews builds: + // with no array, views cannot affect the output at all (NONE); an array + // anything else built (a filtered writer replaced, nulled or added by + // updateBuilder, a processViews or constructFilteredBeanWriter override) + // DELEGATEs the view-active call to stock. Otherwise, as processViews: + // no property declares a view, so with default inclusion off an active + // view hides every property and the rare view-active call DELEGATEs to + // stock's write-nothing path, keeping the no-view hot path free of + // per-property tests. Properties with views take MASK (fast path under + // views) up to 64 properties, DELEGATE beyond. Visibility is read per + // property from BeanPropertyWriter.getViews. + private static ViewStrategy viewStrategy(BeanPropertyWriter[] beanProps, + BeanPropertyWriter[] filtered, List props, boolean includeByDefault) { + if (filtered == null) { + return ViewStrategy.NONE; + } + if (!isStockViewArray(beanProps, filtered, includeByDefault)) { + CodegenDebug.log("writer views: non-stock filtered writers, delegating"); + return ViewStrategy.DELEGATE; + } + boolean viewsFound = false; + for (GenWProp p : props) { + if (p.stock() instanceof BeanPropertyWriter bpw + && bpw.getViews() != null && bpw.getViews().length > 0) { + viewsFound = true; + break; + } + } + if (!viewsFound) { + return includeByDefault ? ViewStrategy.NONE : ViewStrategy.DELEGATE; + } + return (props.size() > 64) ? ViewStrategy.DELEGATE : ViewStrategy.MASK; + } + + // Whether filtered is what BeanSerializerFactory.processViews builds + // from props: an unannotated property as itself (default inclusion on) + // or left out (off), one with views as a stock FilteredBeanPropertyWriter + // with the same name and views. Which writer a stock filtered writer + // wraps is not visible (its delegate field is in a private class of a + // package that databind does not open), so one built by + // constructViewBased around a different writer for the same property + // (say, a wrapper that changes the value) is taken as stock: under an + // active view the generated writer then writes props[i], not the wrapper. + private static boolean isStockViewArray(BeanPropertyWriter[] props, + BeanPropertyWriter[] filtered, boolean includeByDefault) { + if (filtered.length != props.length) { + return false; + } + for (int i = 0; i < props.length; i++) { + BeanPropertyWriter prop = props[i]; + BeanPropertyWriter f = filtered[i]; + Class[] views = prop.getViews(); + if (views == null || views.length == 0) { + if (f != (includeByDefault ? prop : null)) { + return false; + } + } else if (f == null + || f.getClass().getEnclosingClass() != FilteredBeanPropertyWriter.class + || !f.getName().equals(prop.getName()) + || !Arrays.equals(f.getViews(), views)) { + return false; + } + } + return true; + } + + // A generated arm writes the value directly, so every per-property + // feature of BeanPropertyWriter.serializeAsProperty that it skips must be + // absent: a value type serializer (polymorphic values, default typing), + // an applied view, suppression of nulls or values, and, for values that + // can be null, a null serializer other than the default one (the arm + // writes JSON null itself). + private static GenWProp classify(PropertyWriter writer) { + if (writer.getClass() != BeanPropertyWriter.class) { + return stock(writer); + } + BeanPropertyWriter bpw = (BeanPropertyWriter) writer; + WriterProbe.State state = WriterProbe.stateOf(bpw); + if (bpw.willSuppressNulls() || state.suppressableValue() != null + || bpw.getTypeSerializer() != null || state.applyView() != null) { + return stock(writer); + } + Class raw; + MethodHandle handle; + try { + if (bpw.getMember() instanceof AnnotatedMethod am + && am.getAnnotated() != null + && am.getAnnotated().getParameterCount() == 0) { + Method getter = am.getAnnotated(); + raw = getter.getReturnType(); + handle = MemberHandles.getter(getter); + } else if (bpw.getMember() instanceof AnnotatedField af + && af.getAnnotated() != null + && !Modifier.isStatic(af.getAnnotated().getModifiers())) { + Field field = af.getAnnotated(); + raw = field.getType(); + handle = MemberHandles.fieldGetter(field); + } else { + return stock(writer); + } + } catch (IllegalAccessException | RuntimeException e) { + return stock(writer); + } + if (!raw.isPrimitive() && (state.nullSerializer() == null + || state.nullSerializer().getClass() != NullSerializer.class)) { + return stock(writer); + } + ValueSerializer valueSer = bpw.getSerializer(); + if (valueSer instanceof GeneratedWriterBase child && !raw.isPrimitive()) { + return new GenWProp(WKind.CHILD, writer, bpw.getSerializedName(), child, handle); + } + WKind kind = scalarKind(raw); + if (kind == null || valueSer == null + || !STOCK_SCALAR_SERS.contains(valueSer.getClass())) { + return stock(writer); + } + return new GenWProp(kind, writer, bpw.getSerializedName(), null, handle); + } + + // BeanPropertyWriter keeps these in protected fields with no accessor; + // the copy constructor carries them into this probe, whose own inherited + // fields are readable. + private static final class WriterProbe extends BeanPropertyWriter { + record State(Object suppressableValue, ValueSerializer nullSerializer, + Class applyView) {} + + private WriterProbe(BeanPropertyWriter base) { + super(base); + } + + static State stateOf(BeanPropertyWriter base) { + WriterProbe p = new WriterProbe(base); + return new State(p._suppressableValue, p._nullSerializer, p._applyView); + } + } + + private static GenWProp stock(PropertyWriter writer) { + CodegenDebug.log(() -> "writer prop stock: " + writer.getName()); + return new GenWProp(WKind.STOCK, writer, null, null, null); + } + + private static WKind scalarKind(Class raw) { + if (raw == String.class) { + return WKind.STRING; + } + if (raw == int.class) { + return WKind.INT; + } + if (raw == long.class) { + return WKind.LONG; + } + if (raw == boolean.class) { + return WKind.BOOLEAN; + } + return null; + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBWriterPlaceholder.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBWriterPlaceholder.java new file mode 100644 index 000000000..37e7a252b --- /dev/null +++ b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BBWriterPlaceholder.java @@ -0,0 +1,78 @@ +package tools.jackson.module.blackbird.ser; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.BeanProperty; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.ser.BeanSerializer; +import tools.jackson.databind.ser.bean.BeanSerializerBase; +import tools.jackson.module.blackbird.codegen.CodegenDebug; + +/** + * Takes the place of a stock bean serializer from the modifier on: it is a + * copy of that serializer (as a {@link BeanSerializer}), so other modifiers + * and databind's own checks see a bean serializer, and it behaves as stock + * until resolution. Resolving it resolves the stock state and generates the + * writer, which contextualization then hands out. When no writer is + * generated, contextualization hands out the stock serializer instead, so + * a demoted bean costs nothing over stock. + */ +final class BBWriterPlaceholder extends BeanSerializer +{ + // The serializer this copies (BeanSerializer or UnrolledBeanSerializer). + // The copy shares its property writers, and resolution only assigns + // serializers to those writers, so resolving this resolves the original + // serializer as well. + private final BeanSerializerBase _original; + + private volatile ValueSerializer _codec; + + // Set (to _original) once resolution generated no writer + private volatile BeanSerializerBase _stock; + + BBWriterPlaceholder(BeanSerializerBase src) { + super(src); + _original = src; + } + + @Override + public void resolve(SerializationContext ctxt) { + CodegenDebug.log(() -> "writer resolve " + handledType().getName()); + super.resolve(ctxt); + ValueSerializer codec = BBWriterFactory.tryGenerate(this, _props, _filteredProps, + _typeId != null, _serializationShape == JsonFormat.Shape.ARRAY, ctxt); + _codec = codec; + if (codec == null) { + _stock = _original; + } + } + + // Contextualization is the stock one, on this resolved instance; when it + // leaves this serializer as it is, the generated writer (or, when + // demoted, the original serializer) takes its place. + @Override + public ValueSerializer createContextual(SerializationContext ctxt, BeanProperty property) { + ValueSerializer contextual = super.createContextual(ctxt, property); + if (contextual != this) { + return contextual; + } + ValueSerializer codec = _codec; + if (codec != null) { + return codec; + } + BeanSerializerBase stock = _stock; + return (stock != null) ? stock : this; + } + + @Override + public void serialize(Object value, JsonGenerator g, SerializationContext ctxt) { + ValueSerializer codec = _codec; + if (codec != null) { + codec.serialize(value, g, ctxt); + } else { + super.serialize(value, g, ctxt); + } + } +} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BooleanPropertyWriter.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BooleanPropertyWriter.java deleted file mode 100644 index 7ebdf15b2..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/BooleanPropertyWriter.java +++ /dev/null @@ -1,97 +0,0 @@ -package tools.jackson.module.blackbird.ser; - -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.ValueSerializer; -import tools.jackson.databind.PropertyName; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ser.BeanPropertyWriter; - -final class BooleanPropertyWriter - extends OptimizedBeanPropertyWriter -{ - private static final long serialVersionUID = 1L; - - private final boolean _suppressableSet; - private final boolean _suppressableBoolean; - - private final ToBooleanFunction _acc; - - public BooleanPropertyWriter(BeanPropertyWriter src, ToBooleanFunction acc, ValueSerializer ser) { - super(src, ser); - _acc = acc; - - if (_suppressableValue instanceof Boolean) { - _suppressableBoolean = ((Boolean)_suppressableValue).booleanValue(); - _suppressableSet = true; - } else { - _suppressableBoolean = false; - _suppressableSet = false; - } - } - - protected BooleanPropertyWriter(BooleanPropertyWriter base, PropertyName name) { - super(base, name); - _suppressableSet = base._suppressableSet; - _suppressableBoolean = base._suppressableBoolean; - _acc = base._acc; - } - - @Override - protected BeanPropertyWriter _new(PropertyName newName) { - return new BooleanPropertyWriter(this, newName); - } - - @Override - public BeanPropertyWriter withSerializer(ValueSerializer ser) { - return new BooleanPropertyWriter(this, _acc, ser); - } - - /* - /********************************************************************** - /* Overrides - /********************************************************************** - */ - - @Override - public final void serializeAsProperty(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsProperty(bean, g, ctxt); - return; - } - boolean value; - try { - value = _acc.applyAsBoolean(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, false); - return; - } - if (!_suppressableSet || _suppressableBoolean != value) { - g.writeName(_fastName); - g.writeBoolean(value); - } - } - - @Override - public final void serializeAsElement(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsElement(bean, g, ctxt); - return; - } - boolean value; - try { - value = _acc.applyAsBoolean(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, true); - return; - } - if (!_suppressableSet || _suppressableBoolean != value) { - g.writeBoolean(value); - } else { // important: MUST output a placeholder - serializeAsOmittedElement(bean, g, ctxt); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/IntPropertyWriter.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/IntPropertyWriter.java deleted file mode 100644 index 078d80035..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/IntPropertyWriter.java +++ /dev/null @@ -1,99 +0,0 @@ -package tools.jackson.module.blackbird.ser; - -import java.util.function.ToIntFunction; - -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.ValueSerializer; -import tools.jackson.databind.PropertyName; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ser.BeanPropertyWriter; - -final class IntPropertyWriter - extends OptimizedBeanPropertyWriter -{ - private static final long serialVersionUID = 1L; - - private final int _suppressableInt; - private final boolean _suppressableIntSet; - - private final ToIntFunction _acc; - - public IntPropertyWriter(BeanPropertyWriter src, ToIntFunction acc, ValueSerializer ser) { - super(src, ser); - _acc = acc; - - if (_suppressableValue instanceof Integer) { - _suppressableInt = (Integer)_suppressableValue; - _suppressableIntSet = true; - } else { - _suppressableInt = 0; - _suppressableIntSet = false; - } - } - - protected IntPropertyWriter(IntPropertyWriter base, PropertyName name) { - super(base, name); - _suppressableInt = base._suppressableInt; - _suppressableIntSet = base._suppressableIntSet; - _acc = base._acc; - } - - @Override - protected BeanPropertyWriter _new(PropertyName newName) { - return new IntPropertyWriter(this, newName); - } - - @Override - public BeanPropertyWriter withSerializer(ValueSerializer ser) { - return new IntPropertyWriter(this, _acc, ser); - } - - /* - /********************************************************************** - /* Overrides - /********************************************************************** - */ - - @Override - public final void serializeAsProperty(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsProperty(bean, g, ctxt); - return; - } - int value; - try { - value = _acc.applyAsInt(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, false); - return; - } - if (!_suppressableIntSet || _suppressableInt != value) { - g.writeName(_fastName); - g.writeNumber(value); - } - } - - @Override - public final void serializeAsElement(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsElement(bean, g, ctxt); - return; - } - int value; - try { - value = _acc.applyAsInt(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, true); - return; - } - if (!_suppressableIntSet || _suppressableInt != value) { - g.writeNumber(value); - } else { // important: MUST output a placeholder - serializeAsOmittedElement(bean, g, ctxt); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/LongPropertyWriter.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/LongPropertyWriter.java deleted file mode 100644 index 02db15de6..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/LongPropertyWriter.java +++ /dev/null @@ -1,99 +0,0 @@ -package tools.jackson.module.blackbird.ser; - -import java.util.function.ToLongFunction; - -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.ValueSerializer; -import tools.jackson.databind.PropertyName; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ser.BeanPropertyWriter; - -final class LongPropertyWriter - extends OptimizedBeanPropertyWriter -{ - private static final long serialVersionUID = 1L; - - private final long _suppressableLong; - private final boolean _suppressableSet; - - private final ToLongFunction _acc; - - public LongPropertyWriter(BeanPropertyWriter src, ToLongFunction acc, ValueSerializer ser) { - super(src, ser); - _acc = acc; - - if (_suppressableValue instanceof Long) { - _suppressableLong = (Long)_suppressableValue; - _suppressableSet = true; - } else { - _suppressableLong = 0L; - _suppressableSet = false; - } - } - - protected LongPropertyWriter(LongPropertyWriter base, PropertyName name) { - super(base, name); - _suppressableSet = base._suppressableSet; - _suppressableLong = base._suppressableLong; - _acc = base._acc; - } - - @Override - protected BeanPropertyWriter _new(PropertyName newName) { - return new LongPropertyWriter(this, newName); - } - - @Override - public BeanPropertyWriter withSerializer(ValueSerializer ser) { - return new LongPropertyWriter(this, _acc, ser); - } - - /* - /********************************************************************** - /* Overrides - /********************************************************************** - */ - - @Override - public final void serializeAsProperty(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsProperty(bean, g, ctxt); - return; - } - long value; - try { - value = _acc.applyAsLong(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, false); - return; - } - if (!_suppressableSet || _suppressableLong != value) { - g.writeName(_fastName); - g.writeNumber(value); - } - } - - @Override - public final void serializeAsElement(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsElement(bean, g, ctxt); - return; - } - long value; - try { - value = _acc.applyAsLong(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, false); - return; - } - if (!_suppressableSet || _suppressableLong != value) { - g.writeNumber(value); - } else { // important: MUST output a placeholder - serializeAsOmittedElement(bean, g, ctxt); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/ObjectPropertyWriter.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/ObjectPropertyWriter.java deleted file mode 100644 index 2a0a63c6f..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/ObjectPropertyWriter.java +++ /dev/null @@ -1,162 +0,0 @@ -package tools.jackson.module.blackbird.ser; - -import java.util.function.Function; - -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.ValueSerializer; -import tools.jackson.databind.PropertyName; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ser.BeanPropertyWriter; -import tools.jackson.databind.ser.impl.PropertySerializerMap; - -final class ObjectPropertyWriter - extends OptimizedBeanPropertyWriter -{ - private static final long serialVersionUID = 1L; - private final Function _acc; - - public ObjectPropertyWriter(BeanPropertyWriter src, Function acc, ValueSerializer ser) { - super(src, ser); - _acc = acc; - } - - protected ObjectPropertyWriter(ObjectPropertyWriter base, PropertyName name) { - super(base, name); - _acc = base._acc; - } - - @Override - protected BeanPropertyWriter _new(PropertyName newName) { - return new ObjectPropertyWriter(this, newName); - } - - @Override - public BeanPropertyWriter withSerializer(ValueSerializer ser) { - return new ObjectPropertyWriter(this, _acc, ser); - } - - /* - /********************************************************************** - /* Overrides - /********************************************************************** - */ - - @Override - public final void serializeAsProperty(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsProperty(bean, g, ctxt); - return; - } - Object value; - try { - value = _acc.apply(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, false); - return; - } - // Null (etc) handling; copied from super-class impl - if (value == null) { - // 20-Jun-2022, tatu: Defer checking of null, see [databind#3481] - if ((_suppressableValue != null) - && ctxt.includeFilterSuppressNulls(_suppressableValue)) { - return; - } - if (_nullSerializer != null) { - g.writeName(_fastName); - _nullSerializer.serialize(null, g, ctxt); - } - return; - } - ValueSerializer ser = _serializer; - if (ser == null) { - Class cls = value.getClass(); - PropertySerializerMap map = _dynamicSerializers; - ser = map.serializerFor(cls); - if (ser == null) { - ser = _findAndAddDynamic(map, cls, ctxt); - } - } - if (_suppressableValue != null) { - if (MARKER_FOR_EMPTY == _suppressableValue) { - if (ser.isEmpty(ctxt, value)) { - return; - } - } else if (_suppressableValue.equals(value)) { - return; - } - } - if (value == bean) { - // three choices: exception; handled by call; or pass-through - if (_handleSelfReference(bean, g, ctxt, ser)) { - return; - } - } - g.writeName(_fastName); - if (_typeSerializer == null) { - ser.serialize(value, g, ctxt); - } else { - ser.serializeWithType(value, g, ctxt, _typeSerializer); - } - } - - @Override - public final void serializeAsElement(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsElement(bean, g, ctxt); - return; - } - Object value; - try { - value = _acc.apply(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, true); - return; - } - // Null (etc) handling; copied from super-class impl - if (value == null) { - if (_nullSerializer != null) { - _nullSerializer.serialize(null, g, ctxt); - } else if (_suppressNulls) { - serializeAsOmittedElement(bean, g, ctxt); - } else { - ctxt.defaultSerializeNullValue(g); - } - return; - } - ValueSerializer ser = _serializer; - if (ser == null) { - Class cls = value.getClass(); - PropertySerializerMap map = _dynamicSerializers; - ser = map.serializerFor(cls); - if (ser == null) { - ser = _findAndAddDynamic(map, cls, ctxt); - } - } - if (_suppressableValue != null) { - if (MARKER_FOR_EMPTY == _suppressableValue) { - if (ser.isEmpty(ctxt, value)) { - serializeAsOmittedElement(bean, g, ctxt); - return; - } - } else if (_suppressableValue.equals(value)) { - serializeAsOmittedElement(bean, g, ctxt); - return; - } - } - if (value == bean) { - // three choices: exception; handled by call; or pass-through - if (_handleSelfReference(bean, g, ctxt, ser)) { - return; - } - } - if (_typeSerializer == null) { - ser.serialize(value, g, ctxt); - } else { - ser.serializeWithType(value, g, ctxt, _typeSerializer); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/OptimizedBeanPropertyWriter.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/OptimizedBeanPropertyWriter.java deleted file mode 100644 index ada422a1e..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/OptimizedBeanPropertyWriter.java +++ /dev/null @@ -1,130 +0,0 @@ -package tools.jackson.module.blackbird.ser; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import tools.jackson.core.JsonGenerator; -import tools.jackson.core.SerializableString; - -import tools.jackson.databind.*; -import tools.jackson.databind.jsontype.TypeSerializer; -import tools.jackson.databind.ser.BeanPropertyWriter; - -/** - * Intermediate base class that is used for concrete - * per-type implementations - */ -@SuppressWarnings("serial") -abstract class OptimizedBeanPropertyWriter> - extends BeanPropertyWriter -{ - /** - * Locally stored version of efficiently serializable name. - * Used to work around earlier problems with typing between - * interface, implementation - */ - protected final SerializableString _fastName; - - protected final BeanPropertyWriter fallbackWriter; - // Not volatile to prevent overhead, worst case is we trip the exception a few extra times - protected boolean broken = false; - - protected OptimizedBeanPropertyWriter(BeanPropertyWriter src, ValueSerializer ser) - { - super(src); - this.fallbackWriter = unwrapFallbackWriter(src); - // either use the passed on serializer or the original one - _serializer = (ser != null) ? ser : src.getSerializer(); - _fastName = src.getSerializedName(); - } - - protected OptimizedBeanPropertyWriter(OptimizedBeanPropertyWriter base, PropertyName name) { - super(base, name); - // 24-Oct-2017, tatu: Should this be changed too? - fallbackWriter = base.fallbackWriter; - _serializer = base._serializer; - _fastName = getSerializedName(); - } - - private BeanPropertyWriter unwrapFallbackWriter(BeanPropertyWriter srcIn) - { - while (srcIn instanceof OptimizedBeanPropertyWriter) { - srcIn = ((OptimizedBeanPropertyWriter)srcIn).fallbackWriter; - } - return srcIn; - } - - // Ensure it gets defined - @Override - protected abstract BeanPropertyWriter _new(PropertyName newName); - - @Override - public void assignTypeSerializer(TypeSerializer typeSer) { - super.assignTypeSerializer(typeSer); - if (fallbackWriter != null) { - fallbackWriter.assignTypeSerializer(typeSer); - } - // 04-Oct-2015, tatu: Should we handle this wrt [module-afterburner#59]? - // Seems unlikely, as String/long/int/boolean are final types; and for - // basic 'Object' we delegate to deserializer as expected - } - - @Override - public void assignSerializer(ValueSerializer ser) { - super.assignSerializer(ser); - if (fallbackWriter != null) { - fallbackWriter.assignSerializer(ser); - } - // 04-Oct-2015, tatu: To fix [module-afterburner#59], need to disable use of - // fully optimized variant - if (!SerializerUtil.isDefaultSerializer(ser)) { - broken = true; - } - } - - @Override - public void assignNullSerializer(ValueSerializer nullSer) { - super.assignNullSerializer(nullSer); - if (fallbackWriter != null) { - fallbackWriter.assignNullSerializer(nullSer); - } - } - - public abstract BeanPropertyWriter withSerializer(ValueSerializer ser); - - @Override - public abstract void serializeAsProperty(Object bean, JsonGenerator g, SerializationContext ctxt) throws Exception; - - @Override - public abstract void serializeAsElement(Object bean, JsonGenerator g, SerializationContext ctxt) throws Exception; - - // note: synchronized used to try to minimize race conditions; also, should NOT - // be a performance problem - protected synchronized void _handleProblem(Object bean, JsonGenerator g, SerializationContext ctxt, - Throwable t, boolean element) throws Exception - { - if ((t instanceof IllegalAccessError) - || (t instanceof SecurityException)) { - _reportProblem(bean, t); - if (element) { - fallbackWriter.serializeAsElement(bean, g, ctxt); - } else { - fallbackWriter.serializeAsProperty(bean, g, ctxt); - } - return; - } - if (t instanceof Error) { - throw (Error) t; - } - throw (Exception) t; - } - - protected void _reportProblem(Object bean, Throwable e) - { - broken = true; - String msg = String.format("Disabling Blackbird serialization for %s (field %s; mutator %s), due to access error (type %s, message=%s)%n", - bean.getClass(), _name, getClass().getName(), - e.getClass().getName(), e.getMessage()); - Logger.getLogger(OptimizedBeanPropertyWriter.class.getName()).log(Level.WARNING, msg, e); - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/SerializerUtil.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/SerializerUtil.java deleted file mode 100644 index 74aa75657..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/SerializerUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package tools.jackson.module.blackbird.ser; - -import tools.jackson.databind.ValueSerializer; -import tools.jackson.databind.ser.std.ToStringSerializer; -import tools.jackson.databind.util.ClassUtil; - -/** - * Helper class that contains utility methods needed by various other classes - * in this package. - * - * @since 2.12 - */ -class SerializerUtil -{ - /** - * Helper method used to check whether given serializer is the default - * serializer implementation: this is necessary to avoid overriding other - * kinds of serializers. - */ - public static boolean isDefaultSerializer(ValueSerializer ser) - { - if (ser == null) { - return true; - } - if (ClassUtil.isJacksonStdImpl(ser)) { - // 20-Nov-2020, tatu: As per [modules-base#117], need to consider - // one standard serializer that should not be replaced... - if (ser instanceof ToStringSerializer) { - return false; - } - return true; - } - return false; - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/StringPropertyWriter.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/StringPropertyWriter.java deleted file mode 100644 index 33b274019..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/StringPropertyWriter.java +++ /dev/null @@ -1,123 +0,0 @@ -package tools.jackson.module.blackbird.ser; - -import java.util.function.Function; - -import tools.jackson.core.JsonGenerator; -import tools.jackson.databind.ValueSerializer; -import tools.jackson.databind.PropertyName; -import tools.jackson.databind.SerializationContext; -import tools.jackson.databind.ser.BeanPropertyWriter; - -final class StringPropertyWriter - extends OptimizedBeanPropertyWriter -{ - private static final long serialVersionUID = 1L; - - private final Function _acc; - - public StringPropertyWriter(BeanPropertyWriter src, Function acc, ValueSerializer ser) { - super(src, ser); - _acc = acc; - } - - protected StringPropertyWriter(StringPropertyWriter base, PropertyName name) { - super(base, name); - _acc = base._acc; - } - - @Override - protected BeanPropertyWriter _new(PropertyName newName) { - return new StringPropertyWriter(this, newName); - } - - @Override - public BeanPropertyWriter withSerializer(ValueSerializer ser) { - return new StringPropertyWriter(this, _acc, ser); - } - - /* - /********************************************************************** - /* Overrides - /********************************************************************** - */ - - @Override - public final void serializeAsProperty(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsProperty(bean, g, ctxt); - return; - } - String value; - try { - value = _acc.apply(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, false); - return; - } - // Null (etc) handling; copied from super-class impl - if (value == null) { - // 20-Jun-2022, tatu: Defer checking of null, see [databind#3481] - if ((_suppressableValue != null) - && ctxt.includeFilterSuppressNulls(_suppressableValue)) { - return; - } - if (_nullSerializer != null) { - g.writeName(_fastName); - _nullSerializer.serialize(null, g, ctxt); - } - return; - } - if (_suppressableValue != null) { - if (MARKER_FOR_EMPTY == _suppressableValue) { - if (value.length() == 0) { - return; - } - } else if (_suppressableValue.equals(value)) { - return; - } - } - g.writeName(_fastName); - g.writeString(value); - } - - @Override - public final void serializeAsElement(Object bean, JsonGenerator g, SerializationContext ctxt) - throws Exception - { - if (broken) { - fallbackWriter.serializeAsElement(bean, g, ctxt); - return; - } - - String value; - try { - value = _acc.apply(bean); - } catch (Throwable t) { - _handleProblem(bean, g, ctxt, t, true); - return; - } - // Null (etc) handling; copied from super-class impl - if (value == null) { - if (_suppressNulls) { - serializeAsOmittedElement(bean, g, ctxt); - } else { - ctxt.defaultSerializeNullValue(g); - } - return; - } - if (_suppressableValue != null) { - if (MARKER_FOR_EMPTY == _suppressableValue) { - if (value.length() == 0) { - serializeAsOmittedElement(bean, g, ctxt); - return; - } - } else if (_suppressableValue.equals(value)) { - serializeAsOmittedElement(bean, g, ctxt); - return; - } - } - g.writeString(value); - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/ToBooleanFunction.java b/blackbird/src/main/java/tools/jackson/module/blackbird/ser/ToBooleanFunction.java deleted file mode 100644 index be50ab935..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/ser/ToBooleanFunction.java +++ /dev/null @@ -1,6 +0,0 @@ -package tools.jackson.module.blackbird.ser; - -@FunctionalInterface -public interface ToBooleanFunction { - boolean applyAsBoolean(Object bean); -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/util/CheckedFunction.java b/blackbird/src/main/java/tools/jackson/module/blackbird/util/CheckedFunction.java deleted file mode 100644 index 53d45863c..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/util/CheckedFunction.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c), Data Geekery GmbH, contact@datageekery.com - * - * 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. - */ -package tools.jackson.module.blackbird.util; - -@FunctionalInterface -public interface CheckedFunction { - T apply(X x) throws Throwable; -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/util/CheckedSupplier.java b/blackbird/src/main/java/tools/jackson/module/blackbird/util/CheckedSupplier.java deleted file mode 100644 index c6b3c8b39..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/util/CheckedSupplier.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c), Data Geekery GmbH, contact@datageekery.com - * - * 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. - */ -package tools.jackson.module.blackbird.util; - -@FunctionalInterface -public interface CheckedSupplier { - T get() throws Throwable; -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/util/ReflectionHack.java b/blackbird/src/main/java/tools/jackson/module/blackbird/util/ReflectionHack.java deleted file mode 100644 index ed21cc319..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/util/ReflectionHack.java +++ /dev/null @@ -1,235 +0,0 @@ -package tools.jackson.module.blackbird.util; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.logging.Level; -import java.util.logging.Logger; - -import tools.jackson.module.blackbird.BlackbirdModule; - -import java.lang.invoke.MethodType; - -/** - * Allow private access to fields across using a JVM version-appropriate strategy - * without having a compile-time dependency on Java 9+. - * - * This entire class can be replaced by a direct call to JDK 9+ - * {@link MethodHandles}{@code .privateLookupIn} - * once Jackson targets Java >= 9. - */ -public class ReflectionHack { - @SuppressWarnings("synthetic-access") - public static Lookup privateLookupIn(Class lookup, MethodHandles.Lookup orig) throws IllegalAccessException { - if (Java9Up.FACTORY != null) { - return Java9Up.privateLookupIn(lookup, orig); - } - return Java8.privateLookupIn(lookup); - } - - /** - * Whether an accessor spun with {@code lookup} can call {@code handle}, unreflected - * from {@code method}, without failing on every call. It casts to each parameter - * type (including the receiver), so those must be accessible from the lookup - * class; and the call names every type in the method's signature, so those must - * also be the same classes from the lookup class's loader (see - * {@link #isSameClassWithoutLoading}). - */ - public static boolean canLinkAccessor(Lookup lookup, Method method, MethodHandle handle) { - final MethodType type = handle.type(); - final Class declaringClass = method.getDeclaringClass(); - final boolean sameLoader; - try { - sameLoader = (declaringClass.getClassLoader() == lookup.lookupClass().getClassLoader()); - } catch (SecurityException e) { - return false; - } - // Reflection resolved the method's signature through its declaring class's - // loader; if that's the lookup class's loader, the accessor resolves the same - // classes, so only access needs checking - if (sameLoader) { - for (Class paramType : type.parameterArray()) { - if (!isAccessible(lookup, paramType)) { - return false; - } - } - return true; - } - // For an inherited protected method the handle's receiver is narrowed to the - // lookup class, but on Java 8 calling it still names the declaring class (which - // is otherwise the receiver, checked below). Newer JDKs don't name or load it, - // so only check it there, and without loading it. - if ((Java9Up.FACTORY == null) - && (type.parameterCount() > 0) && (type.parameterType(0) != declaringClass) - && !isSameClassWithoutLoading(lookup, declaringClass)) { - return false; - } - for (Class paramType : type.parameterArray()) { - if (!canAccessClass(lookup, paramType)) { - return false; - } - } - // The return type isn't cast to, but the JVM still requires it to be the same - // class from the accessor's loader. The accessor never loads it, so neither - // may this check. - return isSameClassWithoutLoading(lookup, type.returnType()); - } - - /** - * Whether code defined with {@code lookup}, such as a lambda it spins, can use - * {@code type} where it must also access it, as in a cast: it must be the same - * class from the lookup class's loader (see {@link #isSameClassWithoutLoading}) - * and accessible from it. If not, that code fails when it runs, not when it is - * created. - */ - public static boolean canAccessClass(Lookup lookup, Class type) { - return isSameClassWithoutLoading(lookup, type) && isAccessible(lookup, type); - } - - // Whether `type` is accessible from the lookup class, wherever it was loaded - private static boolean isAccessible(Lookup lookup, Class type) { - type = elementType(type); - if (type.isPrimitive()) { - return true; - } - try { - if (Java9Up.ACCESS_CLASS != null) { - return Java9Up.canAccessClass(lookup, type); - } - return canAccessClassWithoutModules(lookup.lookupClass(), type); - } catch (SecurityException e) { - // e.g. a security manager denying `getClassLoader` - return false; - } - } - - /** - * Whether {@code type}, as code defined with {@code lookup} refers to it, is known - * to be the same class from the lookup class's loader, without asking that loader. - * Asking could make it define its own copy, which can't be undone and breaks code - * that relies on it never doing so. So only a class that loader defined itself - * qualifies, or a {@code java.*} one (which only the JDK's loaders can define); - * types from other loaders, even JDK {@code javax.*} ones, don't. - */ - public static boolean isSameClassWithoutLoading(Lookup lookup, Class type) { - type = elementType(type); - if (type.isPrimitive() || type.getName().startsWith("java.")) { - return true; - } - try { - return type.getClassLoader() == lookup.lookupClass().getClassLoader(); - } catch (SecurityException e) { - return false; - } - } - - private static Class elementType(Class type) { - while (type.isArray()) { - type = type.getComponentType(); - } - return type; - } - - // Without modules, public classes are accessible from anywhere, others only - // from the same runtime package (which includes the bean's own class, often - // package-private). `getModifiers` reports the declared modifiers of nested - // classes, so a protected one is (safely) treated as inaccessible from other - // packages, though the JVM would allow it. - static boolean canAccessClassWithoutModules(Class lookupClass, Class type) { - if (Modifier.isPublic(type.getModifiers())) { - return true; - } - return (type.getClassLoader() == lookupClass.getClassLoader()) - && packageName(type).equals(packageName(lookupClass)); - } - - private static String packageName(Class type) { - String name = type.getName(); - int dot = name.lastIndexOf('.'); - return (dot < 0) ? "" : name.substring(0, dot); - } - - static class Java9Up { - static final MethodHandle FACTORY; - static final MethodHandle ACCESS_CLASS; - - static { - FACTORY = init(); - ACCESS_CLASS = (FACTORY == null) ? null : initAccessClass(); - } - - private static MethodHandle init() { - try { - return MethodHandles.lookup().findStatic( - MethodHandles.class, - "privateLookupIn", - MethodType.methodType(Lookup.class, Class.class, MethodHandles.Lookup.class)); - } catch (ReflectiveOperationException e) { - Logger l = Logger.getLogger(BlackbirdModule.class.getName()); - // 02-Nov-2022, tatu: as per [modules-base#187] only log exception at trace - l.log(Level.WARNING, - "Unable to find Java 9+ MethodHandles.privateLookupIn. Blackbird is not performing optimally!"); - l.log(Level.FINE, - "Failure reason for `MethodHandles.privateLookupIn()` not being found: "+e.getMessage(), e); - return null; - } - } - - private static MethodHandle initAccessClass() { - try { - return MethodHandles.lookup().findVirtual( - MethodHandles.Lookup.class, - "accessClass", - MethodType.methodType(Class.class, Class.class)); - } catch (ReflectiveOperationException e) { - return null; - } - } - - public static Lookup privateLookupIn(Class lookup, Lookup orig) { - try { - return (Lookup) FACTORY.invokeExact(lookup, orig); - } catch (ReflectiveOperationException e) { - return orig; - } catch (Throwable t) { - throw Sneaky.throwAnyway(t); - } - } - - public static boolean canAccessClass(Lookup lookup, Class type) { - try { - Class unused = (Class) ACCESS_CLASS.invokeExact(lookup, type); - return true; - } catch (IllegalAccessException e) { - return false; - } catch (Throwable t) { - throw Sneaky.throwAnyway(t); - } - } - } - - static class Java8 { - private static final Constructor FACTORY; - - static { - try { - FACTORY = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); - } catch (ReflectiveOperationException e) { - throw new RuntimeException( - "Unable to use private Lookup constructor", - e); - } - - if (!FACTORY.isAccessible()) { - FACTORY.setAccessible(true); - } - } - - public static Lookup privateLookupIn(Class lookup) { - return Unchecked.function(FACTORY::newInstance).apply(lookup); - } - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/util/Sneaky.java b/blackbird/src/main/java/tools/jackson/module/blackbird/util/Sneaky.java deleted file mode 100644 index 75cdd6713..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/util/Sneaky.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c), Data Geekery GmbH, contact@datageekery.com - * - * 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. - */ -package tools.jackson.module.blackbird.util; - -import java.lang.reflect.InvocationTargetException; - -public class Sneaky { - private Sneaky() { - throw new RuntimeException(); - } - - /** - * Will always throw an exception, so the caller should also always throw the dummy return value to make sure the control flow remains clear. - */ - public static RuntimeException throwAnyway(Throwable t) { - if (t instanceof InterruptedException) { - Thread.currentThread().interrupt(); - } - - if (t instanceof InvocationTargetException) { - throw throwAnyway(t.getCause()); - } - - throwEvadingChecks(t); - - // never reached - return null; - } - - @SuppressWarnings("unchecked") - private static void throwEvadingChecks(Throwable throwable) throws E { - throw (E) throwable; - } -} diff --git a/blackbird/src/main/java/tools/jackson/module/blackbird/util/Unchecked.java b/blackbird/src/main/java/tools/jackson/module/blackbird/util/Unchecked.java deleted file mode 100644 index 76ea1186e..000000000 --- a/blackbird/src/main/java/tools/jackson/module/blackbird/util/Unchecked.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c), Data Geekery GmbH, contact@datageekery.com - * - * 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. - */ -package tools.jackson.module.blackbird.util; - -import java.util.concurrent.Callable; -import java.util.function.Function; -import java.util.function.Supplier; - -public class Unchecked { - private Unchecked() { - throw new RuntimeException(); - } - - @SuppressWarnings("PMD.DoNotUseThreads") - public static Runnable runnable(CheckedRunnable checkedRunnable) { - return () -> { - try { - checkedRunnable.run(); - } catch (Throwable t) { - throw Sneaky.throwAnyway(t); - } - }; - } - - public static Supplier supplier(CheckedSupplier checkedSupplier) { - return () -> { - try { - return checkedSupplier.get(); - } catch (Throwable t) { - throw Sneaky.throwAnyway(t); - } - }; - } - - public static Function function(CheckedFunction checkedFunction) { - return x -> { - try { - return checkedFunction.apply(x); - } catch (Throwable t) { - throw Sneaky.throwAnyway(t); - } - }; - } - - public interface SneakyCallable extends Callable { - @Override - T call(); // no 'throws Exception' - } - - public interface CheckedRunnable { - void run() throws Throwable; - } -} diff --git a/blackbird/src/main/java25/tools/jackson/module/blackbird/codegen/BeanReaderGenerator.java b/blackbird/src/main/java25/tools/jackson/module/blackbird/codegen/BeanReaderGenerator.java new file mode 100644 index 000000000..8f817a81a --- /dev/null +++ b/blackbird/src/main/java25/tools/jackson/module/blackbird/codegen/BeanReaderGenerator.java @@ -0,0 +1,1272 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassHierarchyResolver; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.Label; +import java.lang.classfile.MethodModel; +import java.lang.classfile.attribute.CodeAttribute; +import java.lang.classfile.attribute.MethodParameterInfo; +import java.lang.classfile.attribute.MethodParametersAttribute; +import java.lang.classfile.instruction.SwitchCase; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.core.sym.PropertyNameMatcher; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.deser.NullValueProvider; +import tools.jackson.databind.deser.SettableBeanProperty; +import tools.jackson.databind.deser.UnresolvedForwardReference; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.ValueInstantiator; +import tools.jackson.databind.deser.bean.BeanDeserializer; +import tools.jackson.databind.deser.bean.BeanPropertyMap; +import tools.jackson.databind.deser.bean.BuilderBasedDeserializer; +import tools.jackson.databind.deser.impl.NullsConstantProvider; +import tools.jackson.databind.util.ClassUtil; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.GenProp; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.Kind; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.Mode; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.ViewStrategy; +import tools.jackson.module.blackbird.internal.GeneratedBuilderBase; +import tools.jackson.module.blackbird.internal.GeneratedReaderBase; + +/** + * Emits the class file of a hidden-class deserializer for one bean (see + * {@link ReaderCodegen}, which defines it): a loop on nextNameMatch, a + * tableswitch on the property index, inlined scalar reads for eligible + * properties, and the stock SettableBeanProperty (a named classData constant) + * for everything else. Compiled at release 25 into META-INF/versions/25, + * since it uses the ClassFile API; reached through {@link ClassFileEmitters}. + */ +final class BeanReaderGenerator +{ + private static final ClassDesc CD_JSON_PARSER = Descs.of(JsonParser.class); + private static final ClassDesc CD_JSON_TOKEN = Descs.of(JsonToken.class); + private static final ClassDesc CD_DESER_CONTEXT = Descs.of(DeserializationContext.class); + private static final ClassDesc CD_DESER_FEATURE = Descs.of(DeserializationFeature.class); + private static final ClassDesc CD_NAME_MATCHER = Descs.of(PropertyNameMatcher.class); + private static final ClassDesc CD_SETTABLE_PROP = Descs.of(SettableBeanProperty.class); + private static final ClassDesc CD_NULL_PROVIDER = Descs.of(NullValueProvider.class); + private static final ClassDesc CD_FORWARD_REF = Descs.of(UnresolvedForwardReference.class); + private static final ClassDesc CD_NULLS_CONSTANT = Descs.of(NullsConstantProvider.class); + private static final ClassDesc CD_READER_BASE = Descs.of(GeneratedReaderBase.class); + private static final ClassDesc CD_BUILDER_BASE = Descs.of(GeneratedBuilderBase.class); + private static final ClassDesc CD_VALUE_DESERIALIZER = Descs.of(ValueDeserializer.class); + private static final ClassDesc CD_BEAN_PROPERTY_MAP = Descs.of(BeanPropertyMap.class); + private static final ClassDesc CD_VALUE_INSTANTIATOR = Descs.of(ValueInstantiator.class); + private static final ClassDesc CD_ISE = Descs.of(IllegalStateException.class); + private static final ClassDesc CD_RTE = Descs.of(RuntimeException.class); + private static final ClassDesc CD_EXCEPTION = ConstantDescs.CD_Exception; + private static final ClassDesc CD_THROWABLE = ConstantDescs.CD_Throwable; + private static final ClassDesc CD_OBJECT = ConstantDescs.CD_Object; + + private static final MethodTypeDesc MTD_READER_CTOR = MethodTypeDesc.of( + ConstantDescs.CD_void, Descs.of(BeanDeserializer.class), CD_BEAN_PROPERTY_MAP, + ConstantDescs.CD_String); + private static final MethodTypeDesc MTD_BUILDER_CTOR = MethodTypeDesc.of( + ConstantDescs.CD_void, Descs.of(BuilderBasedDeserializer.class), CD_BEAN_PROPERTY_MAP, + ConstantDescs.CD_String); + private static final MethodTypeDesc MTD_UNEXPECTED = MethodTypeDesc.of(CD_OBJECT, + CD_JSON_PARSER, CD_DESER_CONTEXT, CD_OBJECT); + private static final MethodTypeDesc MTD_NEXT_NAME_MATCH = + MethodTypeDesc.of(ConstantDescs.CD_int, CD_NAME_MATCHER); + private static final MethodTypeDesc MTD_NEXT_TOKEN = MethodTypeDesc.of(CD_JSON_TOKEN); + private static final MethodTypeDesc MTD_GET_STRING = MethodTypeDesc.of(ConstantDescs.CD_String); + private static final MethodTypeDesc MTD_GET_INT = MethodTypeDesc.of(ConstantDescs.CD_int); + private static final MethodTypeDesc MTD_GET_LONG = MethodTypeDesc.of(ConstantDescs.CD_long); + private static final MethodTypeDesc MTD_GET_BOOLEAN = MethodTypeDesc.of(ConstantDescs.CD_boolean); + private static final MethodTypeDesc MTD_ASSIGN_CURRENT = + MethodTypeDesc.of(ConstantDescs.CD_void, CD_OBJECT); + private static final MethodTypeDesc MTD_DESERIALIZE_AND_SET = MethodTypeDesc.of( + ConstantDescs.CD_void, CD_JSON_PARSER, CD_DESER_CONTEXT, CD_OBJECT); + private static final MethodTypeDesc MTD_DESERIALIZE = + MethodTypeDesc.of(CD_OBJECT, CD_JSON_PARSER, CD_DESER_CONTEXT); + private static final MethodTypeDesc MTD_DESER_SET_RETURN = MethodTypeDesc.of( + CD_OBJECT, CD_JSON_PARSER, CD_DESER_CONTEXT, CD_OBJECT); + private static final MethodTypeDesc MTD_CREATE_DEFAULT = + MethodTypeDesc.of(CD_OBJECT, CD_DESER_CONTEXT); + private static final MethodTypeDesc MTD_NEW_INSTANCE = MethodTypeDesc.of(CD_OBJECT); + private static final MethodTypeDesc MTD_GET_NULL_PROVIDER = MethodTypeDesc.of(CD_NULL_PROVIDER); + private static final MethodTypeDesc MTD_GET_NULL_VALUE = MethodTypeDesc.of(CD_OBJECT, + CD_DESER_CONTEXT); + private static final MethodTypeDesc MTD_IS_SKIPPER = MethodTypeDesc.of( + ConstantDescs.CD_boolean, CD_NULL_PROVIDER); + private static final MethodTypeDesc MTD_BUILD_INVOKE = MethodTypeDesc.of(CD_OBJECT, CD_OBJECT); + private static final MethodTypeDesc MTD_IS_BUILDER = + MethodTypeDesc.of(ConstantDescs.CD_boolean, CD_OBJECT); + private static final MethodTypeDesc MTD_FINISH_BUILD = + MethodTypeDesc.of(CD_OBJECT, CD_DESER_CONTEXT, CD_OBJECT); + private static final MethodTypeDesc MTD_INJECT = MethodTypeDesc.of( + ConstantDescs.CD_void, CD_DESER_CONTEXT, CD_OBJECT); + private static final MethodTypeDesc MTD_HANDLE_UNKNOWN = MethodTypeDesc.of( + ConstantDescs.CD_void, CD_JSON_PARSER, CD_DESER_CONTEXT, CD_OBJECT); + private static final MethodTypeDesc MTD_RECORD_UNKNOWN = MethodTypeDesc.of( + CD_OBJECT, CD_JSON_PARSER, CD_DESER_CONTEXT, CD_OBJECT); + private static final MethodTypeDesc MTD_RECORD_FINISH = MethodTypeDesc.of( + CD_OBJECT, CD_DESER_CONTEXT, CD_OBJECT, CD_OBJECT, CD_OBJECT); + private static final MethodTypeDesc MTD_RECORD_BUILD = MethodTypeDesc.of(CD_OBJECT, + CD_JSON_PARSER, CD_DESER_CONTEXT, ConstantDescs.CD_long, + CD_OBJECT.arrayType(), CD_THROWABLE); + private static final MethodTypeDesc MTD_RECORD_FORWARD_REF = MethodTypeDesc.of(CD_OBJECT, + CD_DESER_CONTEXT, CD_SETTABLE_PROP, CD_FORWARD_REF, CD_OBJECT); + private static final MethodTypeDesc MTD_CREATION_FAILURE = + MethodTypeDesc.of(CD_OBJECT, CD_DESER_CONTEXT, CD_THROWABLE); + private static final MethodTypeDesc MTD_PROP_WRAP = MethodTypeDesc.of( + CD_RTE, CD_THROWABLE, CD_OBJECT, CD_SETTABLE_PROP, CD_DESER_CONTEXT); + private static final MethodTypeDesc MTD_STORE_FAILURE = MethodTypeDesc.of(CD_RTE, + CD_THROWABLE, CD_OBJECT, CD_OBJECT, CD_SETTABLE_PROP, CD_JSON_PARSER, + CD_DESER_CONTEXT); + // Builder codecs: the same with the input name and the entry kind (see + // GeneratedBuilderBase._failedName) before the DeserializationContext. + private static final MethodTypeDesc MTD_BUILDER_PROP_WRAP = MethodTypeDesc.of( + CD_RTE, CD_THROWABLE, CD_OBJECT, CD_SETTABLE_PROP, ConstantDescs.CD_String, + ConstantDescs.CD_boolean, CD_DESER_CONTEXT); + private static final MethodTypeDesc MTD_BUILDER_STORE_FAILURE = MethodTypeDesc.of(CD_RTE, + CD_THROWABLE, CD_OBJECT, CD_OBJECT, CD_SETTABLE_PROP, CD_JSON_PARSER, + ConstantDescs.CD_String, ConstantDescs.CD_boolean, CD_DESER_CONTEXT); + private static final MethodTypeDesc MTD_IS_ENABLED = + MethodTypeDesc.of(ConstantDescs.CD_boolean, CD_DESER_FEATURE); + private static final MethodTypeDesc MTD_GET_ACTIVE_VIEW = + MethodTypeDesc.of(ConstantDescs.CD_Class); + private static final MethodTypeDesc MTD_VIEW_MASK = + MethodTypeDesc.of(ConstantDescs.CD_long, ConstantDescs.CD_Class); + private static final MethodTypeDesc MTD_HIDDEN_VIEW = MethodTypeDesc.of( + ConstantDescs.CD_void, CD_JSON_PARSER, CD_DESER_CONTEXT, CD_SETTABLE_PROP); + private static final MethodTypeDesc MTD_VISIBLE_IN_VIEW = + MethodTypeDesc.of(ConstantDescs.CD_boolean, ConstantDescs.CD_Class); + + // Stack map frames can need the supertypes of the types the code uses. + // The default resolver reads class files through the system class + // loader, which does not see Jackson in an application server, an OSGi + // container or a fat jar. The lookup of this module sees it. + static final ClassFile CLASS_FILE = ClassFile.of( + ClassFile.ClassHierarchyResolverOption.of(ClassHierarchyResolver.defaultResolver() + .orElse(ClassHierarchyResolver.ofClassLoading(MethodHandles.lookup())))); + + private BeanReaderGenerator() {} + + // Builder codecs extend GeneratedBuilderBase, all others + // GeneratedReaderBase; both declare the helpers the generated code calls. + private static ClassDesc baseOf(ReaderCodegen.Plan plan) { + return (plan.mode() == Mode.BUILDER) ? CD_BUILDER_BASE : CD_READER_BASE; + } + + static byte[] emit(ReaderCodegen.Plan plan) { + return withinSizeLimit(build(plan)); + } + + // The generated class, or null when one of its methods is too large for + // the JIT (see ClassFileEmitters.MAX_METHOD_BYTES); measured on the + // emitted bytes rather than estimated. + static byte[] withinSizeLimit(byte[] bytes) { + for (MethodModel method : ClassFile.of().parse(bytes).methods()) { + Optional code = method.findAttribute(Attributes.code()); + if (code.isPresent() + && code.get().codeLength() > ClassFileEmitters.MAX_METHOD_BYTES) { + return null; + } + } + return bytes; + } + + private static byte[] build(ReaderCodegen.Plan plan) { + ClassDesc thisClass = ClassDesc.of(plan.className()); + ClassDesc base = baseOf(plan); + MethodTypeDesc ctor = (plan.mode() == Mode.BUILDER) ? MTD_BUILDER_CTOR : MTD_READER_CTOR; + return CLASS_FILE.build(thisClass, clb -> { + clb.withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_FINAL | ClassFile.ACC_SUPER); + clb.withSuperclass(base); + clb.withMethod(ConstantDescs.INIT_NAME, ctor, ClassFile.ACC_PUBLIC, + mb -> mb.with(params("src", "beanProperties", "layout")) + .withCode(cob -> cob.aload(0).aload(1).aload(2).aload(3) + .invokespecial(base, ConstantDescs.INIT_NAME, ctor) + .return_())); + clb.withMethod("deserialize", MTD_DESERIALIZE, ClassFile.ACC_PUBLIC, + mb -> mb.with(params("p", "ctxt")).withCode(cob -> { + if (plan.mode() == Mode.RECORD) { + buildRecordDeserialize(cob, plan); + } else { + buildDeserialize(cob, plan); + } + })); + if (plan.views() == ViewStrategy.MASK) { + emitComputeViewMask(clb, plan.props(), plan.stockName()); + } + }); + } + + static void ldcData(CodeBuilder cob, String name, ClassDesc type) { + cob.ldc(Descs.classDataEntry(name, type)); + } + + // Debug metadata: parameter names and named locals make dumped codecs + // (the blackbird.debug.dumpDir output) read like source in a decompiler; + // the verifier ignores both attributes. + static MethodParametersAttribute params(String... names) { + MethodParameterInfo[] infos = new MethodParameterInfo[names.length]; + for (int i = 0; i < names.length; i++) { + infos[i] = MethodParameterInfo.ofParameter(Optional.of(names[i]), 0); + } + return MethodParametersAttribute.of(infos); + } + + // Overrides GeneratedReaderBase._computeViewMask: bit i set when arm i's + // stock property is visible in the view, which keeps visibility semantics + // (matchers, default-view inclusion) exactly the stock ones. + private static void emitComputeViewMask(java.lang.classfile.ClassBuilder clb, + List props, String[] stockName) { + clb.withMethod("_computeViewMask", MTD_VIEW_MASK, ClassFile.ACC_PROTECTED, + mb -> mb.with(params("activeView")).withCode(cob -> { + final int maskSlot = 2; + Label scopeStart = cob.newBoundLabel(); + cob.lconst_0().lstore(maskSlot); + for (int i = 0; i < props.size(); i++) { + Label skip = cob.newLabel(); + ldcData(cob, stockName[i], CD_SETTABLE_PROP); + cob.aload(1); + cob.invokevirtual(CD_SETTABLE_PROP, "visibleInView", MTD_VISIBLE_IN_VIEW); + cob.ifeq(skip); + cob.lload(maskSlot); + cob.loadConstant(1L << i); + cob.lor(); + cob.lstore(maskSlot); + cob.labelBinding(skip); + } + cob.lload(maskSlot).lreturn(); + Label scopeEnd = cob.newBoundLabel(); + cob.localVariable(1, "activeView", ConstantDescs.CD_Class, scopeStart, scopeEnd); + cob.localVariable(maskSlot, "mask", ConstantDescs.CD_long, scopeStart, scopeEnd); + })); + } + + // MASK strategy: resolves the visibility bitmask for the call (all-ones + // when no view is active) into maskSlot. + private static void emitViewMask(CodeBuilder cob, ClassDesc base, int ctxt, int maskSlot) { + Label nullView = cob.newLabel(); + Label haveMask = cob.newLabel(); + cob.aload(ctxt).invokevirtual(CD_DESER_CONTEXT, "getActiveView", MTD_GET_ACTIVE_VIEW); + cob.dup(); + cob.ifnull(nullView); + cob.aload(0); + cob.swap(); + cob.invokevirtual(base, "_viewMask", MTD_VIEW_MASK); + cob.lstore(maskSlot); + cob.goto_(haveMask); + cob.labelBinding(nullView); + cob.pop(); + cob.loadConstant(-1L); + cob.lstore(maskSlot); + cob.labelBinding(haveMask); + } + + // MASK strategy, start of each arm: a hidden property consumes its value + // with stock semantics (advance to the value token, then the base helper + // reports or skips) and continues the loop without touching the bean. + private static void emitArmViewCheck(CodeBuilder cob, ClassDesc base, int parser, + int ctxt, int maskSlot, + int matcherSlot, int ixSlot, int armIndex, String stockName, Label loop) { + Label visible = cob.newLabel(); + cob.lload(maskSlot); + cob.loadConstant(1L << armIndex); + cob.land(); + cob.lconst_0(); + cob.lcmp(); + cob.ifne(visible); + // The fused match already advanced to the value token, so _hiddenView + // consumes the current value without advancing. + cob.aload(0).aload(parser).aload(ctxt); + ldcData(cob, stockName, CD_SETTABLE_PROP); + cob.invokevirtual(base, "_hiddenView", MTD_HIDDEN_VIEW); + nextNameMatch(cob, parser, matcherSlot, ixSlot); + cob.goto_(loop); + cob.labelBinding(visible); + } + + // The loop head shared by all modes: dispatch on the match index (arms, + // end of object, unknown name, anything else) with a tableswitch that + // sends each matcher index to its arm. Returns the case label per arm, + // null for an arm no name reaches (its code is not emitted). + private static Label[] emitLoopHead(CodeBuilder cob, ReaderCodegen.Plan plan, int ixSlot, + Label loop, Label endObject, Label unknown, Label oddToken, Label defaultCase) { + List props = plan.props(); + int[] matchArms = plan.matchArms(); + Label switchPart = cob.newLabel(); + cob.labelBinding(loop); + cob.iload(ixSlot).ifge(switchPart); + cob.iload(ixSlot).iconst_m1().if_icmpeq(endObject); + cob.iload(ixSlot).ldc(-2).if_icmpeq(unknown); + cob.goto_(oddToken); + + cob.labelBinding(switchPart); + Label[] caseLabels = new Label[props.size()]; + List cases = new ArrayList<>(matchArms.length); + for (int k = 0; k < matchArms.length; k++) { + int arm = matchArms[k]; + if (caseLabels[arm] == null) { + caseLabels[arm] = cob.newLabel(); + } + cases.add(SwitchCase.of(k, caseLabels[arm])); + } + cob.iload(ixSlot); + if (cases.isEmpty()) { + cob.pop(); + cob.goto_(defaultCase); + } else { + cob.tableswitch(0, matchArms.length - 1, defaultCase, cases); + } + return caseLabels; + } + + // POJO and builder modes. The receiver local holds the bean, or the + // builder whose setters may return a replacement builder (kept, like + // stock deserializeSetAndReturn); the build method ends builder mode. + private static void buildDeserialize(CodeBuilder cob, ReaderCodegen.Plan plan) { + final boolean builderMode = plan.mode() == Mode.BUILDER; + final ClassDesc base = baseOf(plan); + final List props = plan.props(); + final ViewStrategy views = plan.views(); + final int parser = 1; + final int ctxt = 2; + final int beanSlot = 3; + final int matcherSlot = 4; + final int ixSlot = 5; + final int propSlot = 6; + final int excSlot = 7; + final int maskSlot = 8; + final int valueSlot = 10; + // Builder mode with names that can differ from the property's own + // (see GeneratedBuilderBase._failedName): each arm reads the input + // name, and the first match records the entry token. + final boolean inputNames = builderMode && plan.inputNames(); + final int inputNameSlot = 11; + final int startObjectSlot = 12; + // Set once a name has gone to unknown-name handling; tracked only + // where stock reads with its vanilla loop. + final boolean trackUnknown = plan.vanillaLoop(); + final int afterUnknownSlot = 13; + final Names names = !builderMode ? null + : inputNames ? new Names(inputNameSlot, startObjectSlot) : Names.NONE; + Label propHandler = cob.newLabel(); + Label storeHandler = cob.newLabel(); + + // A failing setter or field store gets the stock property's message, + // so each generated store has its own exception region. The regions + // nest inside the arm regions, and the JVM takes the first matching + // exception-table entry, so they register before any arm region. + Label[] storeStart = new Label[props.size()]; + Label[] storeEnd = new Label[props.size()]; + boolean anyStore = false; + for (int i = 0; i < props.size(); i++) { + if (props.get(i).handle() != null) { + storeStart[i] = cob.newLabel(); + storeEnd[i] = cob.newLabel(); + cob.exceptionCatch(storeStart[i], storeEnd[i], storeHandler, CD_EXCEPTION); + anyStore = true; + } + } + + Label scopeStart = cob.newBoundLabel(); + Label delegate = emitEntryGuard(cob, parser, ctxt, views); + if (views == ViewStrategy.MASK) { + emitViewMask(cob, base, ctxt, maskSlot); + } + if (anyStore) { + // Read by the store handler; child arms set it before storing. + cob.aconst_null().astore(valueSlot); + } + + Label ctorHandler = null; + Label haveBean = null; + if (plan.mode() == Mode.CONSTRUCTOR) { + ctorHandler = cob.newLabel(); + haveBean = cob.newLabel(); + Label ctorStart = cob.newLabel(); + Label ctorEnd = cob.newLabel(); + cob.exceptionCatch(ctorStart, ctorEnd, ctorHandler, CD_EXCEPTION); + cob.labelBinding(ctorStart); + ldcData(cob, "constructor", ConstantDescs.CD_MethodHandle); + cob.invokevirtual(ConstantDescs.CD_MethodHandle, "invokeExact", MTD_NEW_INSTANCE); + cob.labelBinding(ctorEnd); + cob.labelBinding(haveBean); + } else { + ldcData(cob, "instantiator", CD_VALUE_INSTANTIATOR); + cob.aload(ctxt); + cob.invokevirtual(CD_VALUE_INSTANTIATOR, "createUsingDefault", MTD_CREATE_DEFAULT); + } + cob.astore(beanSlot); + if (!builderMode) { + cob.aload(parser).aload(beanSlot) + .invokevirtual(CD_JSON_PARSER, "assignCurrentValue", MTD_ASSIGN_CURRENT); + } + if (plan.injectables()) { + cob.aload(0).aload(ctxt).aload(beanSlot) + .invokevirtual(base, "_injectValues", MTD_INJECT); + } + + if (trackUnknown) { + cob.iconst_0().istore(afterUnknownSlot); + } + ldcData(cob, "matcher", CD_NAME_MATCHER); + cob.astore(matcherSlot); + + Label loop = cob.newLabel(); + Label endObject = cob.newLabel(); + Label unknown = cob.newLabel(); + Label oddToken = cob.newLabel(); + Label defaultCase = cob.newLabel(); + + emitFirstMatch(cob, parser, matcherSlot, ixSlot, inputNames ? startObjectSlot : -1); + Label[] caseLabels = emitLoopHead(cob, plan, ixSlot, loop, endObject, unknown, + oddToken, defaultCase); + + for (int i = 0; i < props.size(); i++) { + cob.labelBinding(caseLabels[i]); + if (views == ViewStrategy.MASK) { + emitArmViewCheck(cob, base, parser, ctxt, maskSlot, matcherSlot, ixSlot, i, + plan.stockName()[i], loop); + } + if (inputNames) { + // As stock's general loop: the name of the value token. + cob.aload(parser).invokevirtual(CD_JSON_PARSER, "currentName", MTD_GET_STRING); + cob.astore(inputNameSlot); + } + Label armEnd = beginArm(cob, plan.stockName()[i], propSlot, propHandler); + GenProp prop = props.get(i); + // Null exactly for STOCK arms (see ReaderCodegen.generate). + Store store = (prop.handle() == null) ? null + : new Store(plan.handleName()[i], storeStart[i], storeEnd[i]); + String stockName = plan.stockName()[i]; + switch (prop.kind()) { + case STRING -> emitScalar(cob, beanSlot, parser, ctxt, "getString", + MTD_GET_STRING, CD_OBJECT, stockName, builderMode, + store, "VALUE_STRING"); + case INT -> emitScalar(cob, beanSlot, parser, ctxt, "getIntValue", + MTD_GET_INT, ConstantDescs.CD_int, stockName, builderMode, + store, "VALUE_NUMBER_INT"); + case LONG -> emitScalar(cob, beanSlot, parser, ctxt, "getLongValue", + MTD_GET_LONG, ConstantDescs.CD_long, stockName, builderMode, + store, "VALUE_NUMBER_INT"); + case BOOLEAN -> emitScalar(cob, beanSlot, parser, ctxt, "getBooleanValue", + MTD_GET_BOOLEAN, ConstantDescs.CD_boolean, stockName, builderMode, + store, null); + case CHILD -> emitChildArm(cob, beanSlot, parser, ctxt, valueSlot, + plan.childName()[i], stockName, builderMode, store); + case STOCK -> + // The fused match left the value token current; the stock + // property consumes it. + emitStockSet(cob, parser, ctxt, beanSlot, stockName, builderMode); + } + cob.labelBinding(armEnd); + nextNameMatch(cob, parser, matcherSlot, ixSlot); + cob.goto_(loop); + } + + cob.labelBinding(defaultCase); + throwIse(cob, "bad property index"); + + cob.labelBinding(endObject); + if (builderMode) { + emitBuild(cob, base, ctxt, beanSlot, excSlot); + } else { + cob.aload(beanSlot).areturn(); + } + + cob.labelBinding(unknown); + if (trackUnknown) { + cob.iconst_1().istore(afterUnknownSlot); + } + cob.aload(0).aload(parser).aload(ctxt).aload(beanSlot); + cob.invokevirtual(base, "_handleUnknown", MTD_HANDLE_UNKNOWN); + nextNameMatch(cob, parser, matcherSlot, ixSlot); + cob.goto_(loop); + + cob.labelBinding(oddToken); + if (trackUnknown) { + // Once an unknown name has been seen, stock's vanilla loop + // (_vanillaDeserializeWithUnknown) returns the bean as it is. + Label beforeUnknown = cob.newLabel(); + cob.iload(afterUnknownSlot).ifeq(beforeUnknown); + cob.aload(beanSlot).areturn(); + cob.labelBinding(beforeUnknown); + } + cob.aload(0).aload(parser).aload(ctxt).aload(beanSlot) + .invokevirtual(base, "_unexpectedToken", MTD_UNEXPECTED) + .areturn(); + + emitPropertyHandler(cob, base, propHandler, ctxt, propSlot, excSlot, beanSlot, names); + if (anyStore) { + cob.labelBinding(storeHandler); + cob.astore(excSlot); + cob.aload(0).aload(excSlot).aload(beanSlot).aload(valueSlot).aload(propSlot) + .aload(parser); + if (builderMode) { + names.load(cob); + cob.aload(ctxt); + cob.invokevirtual(base, "_storeFailure", MTD_BUILDER_STORE_FAILURE); + } else { + cob.aload(ctxt); + cob.invokevirtual(base, "_storeFailure", MTD_STORE_FAILURE); + } + cob.athrow(); + } + if (ctorHandler != null) { + // StdValueInstantiator.createUsingDefault semantics; a problem + // handler's replacement value continues as the bean. + cob.labelBinding(ctorHandler); + cob.astore(excSlot); + cob.aload(0).aload(ctxt).aload(excSlot); + cob.invokevirtual(base, "_instantiationFailure", MTD_CREATION_FAILURE); + cob.goto_(haveBean); + } + emitDelegateTail(cob, base, parser, ctxt, delegate); + + Label scopeEnd = cob.newBoundLabel(); + cob.localVariable(parser, "p", CD_JSON_PARSER, scopeStart, scopeEnd); + cob.localVariable(ctxt, "ctxt", CD_DESER_CONTEXT, scopeStart, scopeEnd); + cob.localVariable(beanSlot, builderMode ? "builder" : "bean", CD_OBJECT, + scopeStart, scopeEnd); + cob.localVariable(matcherSlot, "matcher", CD_NAME_MATCHER, scopeStart, scopeEnd); + cob.localVariable(ixSlot, "ix", ConstantDescs.CD_int, scopeStart, scopeEnd); + cob.localVariable(propSlot, "prop", CD_SETTABLE_PROP, scopeStart, scopeEnd); + cob.localVariable(excSlot, "e", CD_THROWABLE, scopeStart, scopeEnd); + if (views == ViewStrategy.MASK) { + cob.localVariable(maskSlot, "viewMask", ConstantDescs.CD_long, scopeStart, scopeEnd); + } + if (anyStore) { + cob.localVariable(valueSlot, "value", CD_OBJECT, scopeStart, scopeEnd); + } + if (inputNames) { + cob.localVariable(inputNameSlot, "inputName", ConstantDescs.CD_String, + scopeStart, scopeEnd); + cob.localVariable(startObjectSlot, "startObject", ConstantDescs.CD_boolean, + scopeStart, scopeEnd); + } + if (trackUnknown) { + cob.localVariable(afterUnknownSlot, "afterUnknown", ConstantDescs.CD_boolean, + scopeStart, scopeEnd); + } + } + + // BuilderBasedDeserializer.finishBuild: a failing build method goes + // through wrapInstantiationProblem. A receiver that is not a builder + // instance (see GeneratedBuilderBase._isBuilder) takes the stock call. + private static void emitBuild(CodeBuilder cob, ClassDesc base, int ctxt, int builderSlot, + int excSlot) { + Label start = cob.newLabel(); + Label end = cob.newLabel(); + Label handler = cob.newLabel(); + Label notBuilder = cob.newLabel(); + cob.aload(0).aload(builderSlot); + cob.invokevirtual(base, "_isBuilder", MTD_IS_BUILDER); + cob.ifeq(notBuilder); + cob.exceptionCatch(start, end, handler, CD_EXCEPTION); + cob.labelBinding(start); + ldcData(cob, "buildMethod", ConstantDescs.CD_MethodHandle); + cob.aload(builderSlot); + cob.invokevirtual(ConstantDescs.CD_MethodHandle, "invokeExact", MTD_BUILD_INVOKE); + cob.labelBinding(end); + cob.areturn(); + cob.labelBinding(handler); + cob.astore(excSlot); + cob.aload(0).aload(ctxt).aload(excSlot); + cob.invokevirtual(base, "_buildFailure", MTD_CREATION_FAILURE); + cob.areturn(); + cob.labelBinding(notBuilder); + cob.aload(0).aload(ctxt).aload(builderSlot); + cob.invokevirtual(base, "_finishBuild", MTD_FINISH_BUILD); + cob.areturn(); + } + + // A generated store: the class-data name of its (Object, eV)void handle + // ((Object, eV)Object in builder mode, whose non-null result becomes the + // new receiver) and the labels of its exception region. + private record Store(String handleName, Label start, Label end) {} + + private static void emitStore(CodeBuilder cob, Store store, int receiverSlot, + ClassDesc erasedDesc, boolean builderMode) { + cob.labelBinding(store.start()); + if (builderMode) { + cob.invokevirtual(ConstantDescs.CD_MethodHandle, "invokeExact", MethodTypeDesc.of( + CD_OBJECT, CD_OBJECT, erasedDesc)); + cob.labelBinding(store.end()); + // Stock deserializeSetAndReturn keeps the builder when a fluent + // setter returns null. + Label replace = cob.newLabel(); + cob.dup(); + cob.ifnonnull(replace); + cob.pop(); + cob.aload(receiverSlot); + cob.labelBinding(replace); + cob.astore(receiverSlot); + } else { + cob.invokevirtual(ConstantDescs.CD_MethodHandle, "invokeExact", MethodTypeDesc.of( + ConstantDescs.CD_void, CD_OBJECT, erasedDesc)); + cob.labelBinding(store.end()); + } + } + + // The null branch (and any other unexpected token) enters the stock + // property with the parser on that token, which is the position its + // deserializeAndSet expects. + private static void emitScalar(CodeBuilder cob, int receiverSlot, int parser, int ctxt, + String getter, MethodTypeDesc getterType, ClassDesc erasedDesc, + String stockName, boolean builderMode, Store store, String expectedToken) { + Label useStock = cob.newLabel(); + Label done = cob.newLabel(); + emitExpectedTokenCheck(cob, parser, expectedToken, useStock); + ldcData(cob, store.handleName(), ConstantDescs.CD_MethodHandle); + cob.aload(receiverSlot); + cob.aload(parser).invokevirtual(CD_JSON_PARSER, getter, getterType); + emitStore(cob, store, receiverSlot, erasedDesc, builderMode); + cob.goto_(done); + cob.labelBinding(useStock); + emitStockSet(cob, parser, ctxt, receiverSlot, stockName, builderMode); + cob.labelBinding(done); + } + + // A child bean on START_OBJECT goes to the linked child codec, anything + // else to the stock property. The child value passes through valueSlot so + // that a failing store can name its class, as stock does. A null from the + // child codec (a null build result, or a problem handler's) gets what + // stock deserializeAndSet gives it ([databind#2023]): skipped for a + // skipping null provider, otherwise replaced by the provider's value. + private static void emitChildArm(CodeBuilder cob, int receiverSlot, int parser, int ctxt, + int valueSlot, String childName, String stockName, boolean builderMode, + Store store) { + Label childStock = cob.newLabel(); + Label childDone = cob.newLabel(); + Label haveValue = cob.newLabel(); + // The fused match left the value token current. + cob.aload(parser).invokevirtual(CD_JSON_PARSER, "currentToken", MTD_NEXT_TOKEN); + cob.getstatic(CD_JSON_TOKEN, "START_OBJECT", CD_JSON_TOKEN); + cob.if_acmpne(childStock); + emitChildCall(cob, parser, ctxt, childName); + cob.astore(valueSlot); + cob.aload(valueSlot).ifnonnull(haveValue); + emitNullProvider(cob, stockName); + cob.invokestatic(CD_NULLS_CONSTANT, "isSkipper", MTD_IS_SKIPPER); + cob.ifne(childDone); + emitNullValue(cob, ctxt, stockName); + cob.astore(valueSlot); + cob.labelBinding(haveValue); + ldcData(cob, store.handleName(), ConstantDescs.CD_MethodHandle); + cob.aload(receiverSlot); + cob.aload(valueSlot); + emitStore(cob, store, receiverSlot, CD_OBJECT, builderMode); + cob.goto_(childDone); + cob.labelBinding(childStock); + emitStockSet(cob, parser, ctxt, receiverSlot, stockName, builderMode); + cob.labelBinding(childDone); + } + + private static void emitNullProvider(CodeBuilder cob, String stockName) { + ldcData(cob, stockName, CD_SETTABLE_PROP); + cob.invokevirtual(CD_SETTABLE_PROP, "getNullValueProvider", MTD_GET_NULL_PROVIDER); + } + + // The stock property's null value (its null provider's, which applies + // Nulls.FAIL, AS_EMPTY and the null config overrides). + private static void emitNullValue(CodeBuilder cob, int ctxt, String stockName) { + emitNullProvider(cob, stockName); + cob.aload(ctxt); + cob.invokeinterface(CD_NULL_PROVIDER, "getNullValue", MTD_GET_NULL_VALUE); + } + + // Child codecs are reader or builder codecs; the constant receiver makes + // the call monomorphic either way. + private static void emitChildCall(CodeBuilder cob, int parser, int ctxt, String childName) { + ldcData(cob, childName, CD_VALUE_DESERIALIZER); + cob.aload(parser).aload(ctxt); + cob.invokevirtual(CD_VALUE_DESERIALIZER, "deserialize", MTD_DESERIALIZE); + } + + // The stock property reads and stores the value: deserializeAndSet, or + // in builder mode deserializeSetAndReturn, whose result (a fluent + // builder may return a replacement instance) becomes the new builder. + private static void emitStockSet(CodeBuilder cob, int parser, int ctxt, int receiverSlot, + String stockName, boolean builderMode) { + ldcData(cob, stockName, CD_SETTABLE_PROP); + cob.aload(parser).aload(ctxt).aload(receiverSlot); + if (builderMode) { + cob.invokevirtual(CD_SETTABLE_PROP, "deserializeSetAndReturn", MTD_DESER_SET_RETURN); + cob.astore(receiverSlot); + } else { + cob.invokevirtual(CD_SETTABLE_PROP, "deserializeAndSet", MTD_DESERIALIZE_AND_SET); + } + } + + // Record mode: components collect into typed locals in canonical order. + // At the end of the object the canonical constructor runs directly when + // the document supplied every component and nothing needs stock + // argument handling; otherwise, and when the constructor throws, the base + // finishes through the stock creator (see GeneratedReaderBase + // ._recordBuild). Names no component takes are handled, or buffered and + // replayed against the record, the way the stock property-based loop + // handles them. + private static void buildRecordDeserialize(CodeBuilder cob, ReaderCodegen.Plan plan) { + final ClassDesc base = CD_READER_BASE; + final List props = plan.props(); + final ViewStrategy views = plan.views(); + final int n = props.size(); + final int parser = 1; + final int ctxt = 2; + + int next = 3; + int[] componentSlot = new int[n]; + boolean anyPrimitive = false; + for (int i = 0; i < n; i++) { + componentSlot[i] = next; + Class t = props.get(i).type(); + next += (t == long.class || t == double.class) ? 2 : 1; + anyPrimitive |= t.isPrimitive(); + } + final int matcherSlot = next++; + final int ixSlot = next++; + final int seenSlot = next; + next += 2; + final int rawPrimSlot = next; + next += 2; + final int maskSlot = next; + next += 2; + final int propSlot = next++; + final int excSlot = next++; + final int unknownSlot = next++; + final int beanSlot = next++; + final int pendingSlot = next++; + final int referringsSlot = next++; + // Primitive components: the stock value when it is not exactly the + // wrapper (see emitStockValueToLocal). + int[] rawSlot = new int[n]; + for (int i = 0; i < n; i++) { + rawSlot[i] = props.get(i).type().isPrimitive() ? next++ : -1; + } + Label propHandler = cob.newLabel(); + + Label scopeStart = cob.newBoundLabel(); + Label delegate = emitEntryGuard(cob, parser, ctxt, views); + if (views == ViewStrategy.MASK) { + emitViewMask(cob, base, ctxt, maskSlot); + } + + for (int i = 0; i < n; i++) { + Class t = props.get(i).type(); + if (t == long.class) { + cob.lconst_0().lstore(componentSlot[i]); + } else if (t == double.class) { + cob.dconst_0().dstore(componentSlot[i]); + } else if (t == float.class) { + cob.fconst_0().fstore(componentSlot[i]); + } else if (t.isPrimitive()) { + cob.iconst_0().istore(componentSlot[i]); + } else { + cob.aconst_null().astore(componentSlot[i]); + } + if (rawSlot[i] >= 0) { + cob.aconst_null().astore(rawSlot[i]); + } + } + cob.lconst_0().lstore(seenSlot); + cob.lconst_0().lstore(rawPrimSlot); + cob.aconst_null().astore(unknownSlot); + cob.aconst_null().astore(referringsSlot); + + ldcData(cob, "matcher", CD_NAME_MATCHER); + cob.astore(matcherSlot); + + Label loop = cob.newLabel(); + Label endObject = cob.newLabel(); + Label unknown = cob.newLabel(); + Label oddToken = cob.newLabel(); + Label defaultCase = cob.newLabel(); + + emitFirstMatch(cob, parser, matcherSlot, ixSlot, -1); + Label[] caseLabels = emitLoopHead(cob, plan, ixSlot, loop, endObject, unknown, + oddToken, defaultCase); + + for (int i = 0; i < n; i++) { + if (caseLabels[i] == null) { + continue; + } + cob.labelBinding(caseLabels[i]); + if (views == ViewStrategy.MASK) { + // A hidden component stays unseen, so the stock creator gives + // it the absent value, as for a stock hidden creator property. + emitArmViewCheck(cob, base, parser, ctxt, maskSlot, matcherSlot, ixSlot, i, + plan.stockName()[i], loop); + } + GenProp prop = props.get(i); + // A stock component value may be a forward Object Id reference, + // which the stock loop catches; the region registers before the + // arm's, so that it takes precedence. + Label fwdStart = null; + Label fwdEnd = null; + Label fwdHandler = null; + if (prop.kind() == Kind.STOCK) { + fwdStart = cob.newLabel(); + fwdEnd = cob.newLabel(); + fwdHandler = cob.newLabel(); + cob.exceptionCatch(fwdStart, fwdEnd, fwdHandler, CD_FORWARD_REF); + } + Label armEnd = beginArm(cob, plan.stockName()[i], propSlot, propHandler); + Class t = prop.type(); + String stockName = plan.stockName()[i]; + ComponentLocal local = new ComponentLocal(componentSlot[i], t, rawPrimSlot, rawSlot[i], i); + switch (prop.kind()) { + case STRING -> emitRecordScalar(cob, parser, ctxt, local, + "getString", MTD_GET_STRING, stockName, "VALUE_STRING"); + case INT -> emitRecordScalar(cob, parser, ctxt, local, + "getIntValue", MTD_GET_INT, stockName, "VALUE_NUMBER_INT"); + case LONG -> emitRecordScalar(cob, parser, ctxt, local, + "getLongValue", MTD_GET_LONG, stockName, "VALUE_NUMBER_INT"); + case BOOLEAN -> emitRecordScalar(cob, parser, ctxt, local, + "getBooleanValue", MTD_GET_BOOLEAN, stockName, null); + case CHILD -> { + Label childStock = cob.newLabel(); + Label childDone = cob.newLabel(); + cob.aload(parser).invokevirtual(CD_JSON_PARSER, "currentToken", MTD_NEXT_TOKEN); + cob.getstatic(CD_JSON_TOKEN, "START_OBJECT", CD_JSON_TOKEN); + cob.if_acmpne(childStock); + emitChildCall(cob, parser, ctxt, plan.childName()[i]); + cob.astore(componentSlot[i]); + // A null child value: the null provider's value, as stock + // SettableBeanProperty.deserialize gives a creator property. + cob.aload(componentSlot[i]).ifnonnull(childDone); + emitNullValue(cob, ctxt, stockName); + cob.astore(componentSlot[i]); + cob.goto_(childDone); + cob.labelBinding(childStock); + emitStockValueToLocal(cob, parser, ctxt, local, stockName); + cob.labelBinding(childDone); + } + case STOCK -> { + cob.labelBinding(fwdStart); + emitStockValueToLocal(cob, parser, ctxt, local, stockName); + cob.labelBinding(fwdEnd); + } + } + cob.labelBinding(armEnd); + cob.lload(seenSlot); + cob.loadConstant(1L << i); + cob.lor(); + cob.lstore(seenSlot); + nextNameMatch(cob, parser, matcherSlot, ixSlot); + cob.goto_(loop); + if (fwdHandler != null) { + // The component is assigned null, as in stock. + cob.labelBinding(fwdHandler); + cob.astore(excSlot); + cob.aload(0).aload(ctxt).aload(propSlot).aload(excSlot) + .checkcast(CD_FORWARD_REF).aload(referringsSlot); + cob.invokevirtual(base, "_recordForwardRef", MTD_RECORD_FORWARD_REF); + cob.astore(referringsSlot); + if (t.isPrimitive()) { + cob.aconst_null().astore(rawSlot[i]); + emitRawPrimitive(cob, local, true); + } else { + cob.aconst_null().astore(componentSlot[i]); + } + cob.goto_(armEnd); + } + } + + cob.labelBinding(defaultCase); + throwIse(cob, "bad property index"); + + cob.labelBinding(endObject); + Label slow = cob.newLabel(); + Label finish = cob.newLabel(); + cob.aconst_null().astore(pendingSlot); + long allSeen = (n == 64) ? -1L : (1L << n) - 1; + cob.lload(seenSlot); + cob.loadConstant(allSeen); + cob.lcmp(); + cob.ifne(slow); + if (anyPrimitive) { + cob.lload(rawPrimSlot); + cob.lconst_0(); + cob.lcmp(); + cob.ifne(slow); + } + cob.aload(ctxt); + cob.getstatic(CD_DESER_FEATURE, "FAIL_ON_NULL_CREATOR_PROPERTIES", CD_DESER_FEATURE); + cob.invokevirtual(CD_DESER_CONTEXT, "isEnabled", MTD_IS_ENABLED); + cob.ifne(slow); + // The constructor handle is erased ((e1..en)Object, see MemberHandles), + // so the call descriptor uses Object for reference components. + Label ctorStart = cob.newLabel(); + Label ctorEnd = cob.newLabel(); + Label ctorHandler = cob.newLabel(); + cob.exceptionCatch(ctorStart, ctorEnd, ctorHandler, CD_EXCEPTION); + cob.labelBinding(ctorStart); + ldcData(cob, "constructor", ConstantDescs.CD_MethodHandle); + ClassDesc[] paramDescs = new ClassDesc[n]; + for (int i = 0; i < n; i++) { + Class t = props.get(i).type(); + paramDescs[i] = t.isPrimitive() ? Descs.of(t) : CD_OBJECT; + loadLocal(cob, t, componentSlot[i]); + } + cob.invokevirtual(ConstantDescs.CD_MethodHandle, "invokeExact", + MethodTypeDesc.of(CD_OBJECT, paramDescs)); + cob.labelBinding(ctorEnd); + cob.astore(beanSlot); + cob.goto_(finish); + // The constructor failed with every component present: the base + // reports it the way the stock instantiator would. + cob.labelBinding(ctorHandler); + cob.astore(pendingSlot); + cob.labelBinding(slow); + cob.aload(0).aload(parser).aload(ctxt).lload(seenSlot); + cob.loadConstant(n); + cob.anewarray(CD_OBJECT); + for (int i = 0; i < n; i++) { + Class t = props.get(i).type(); + cob.dup(); + cob.loadConstant(i); + if (t.isPrimitive()) { + // A raw stock value goes to the stock creator as it is. + Label boxIt = cob.newLabel(); + Label stored = cob.newLabel(); + cob.lload(rawPrimSlot).loadConstant(1L << i).land().lconst_0().lcmp(); + cob.ifeq(boxIt); + cob.aload(rawSlot[i]); + cob.goto_(stored); + cob.labelBinding(boxIt); + loadLocal(cob, t, componentSlot[i]); + ClassDesc boxDesc = Descs.of(ClassUtil.wrapperType(t)); + cob.invokestatic(boxDesc, "valueOf", MethodTypeDesc.of(boxDesc, Descs.of(t))); + cob.labelBinding(stored); + } else { + loadLocal(cob, t, componentSlot[i]); + } + cob.aastore(); + } + cob.aload(pendingSlot); + cob.invokevirtual(base, "_recordBuild", MTD_RECORD_BUILD); + cob.astore(beanSlot); + cob.labelBinding(finish); + // Forward references and buffered unknown properties, in stock's + // order (see _recordFinish); neither on the common path. + Label finishRest = cob.newLabel(); + Label finished = cob.newLabel(); + cob.aload(referringsSlot).ifnonnull(finishRest); + cob.aload(unknownSlot).ifnull(finished); + cob.labelBinding(finishRest); + cob.aload(0).aload(ctxt).aload(beanSlot).aload(referringsSlot).aload(unknownSlot); + cob.invokevirtual(base, "_recordFinish", MTD_RECORD_FINISH); + cob.astore(beanSlot); + cob.labelBinding(finished); + cob.aload(beanSlot).areturn(); + + cob.labelBinding(unknown); + cob.aload(0).aload(parser).aload(ctxt).aload(unknownSlot); + cob.invokevirtual(base, "_recordUnknown", MTD_RECORD_UNKNOWN); + cob.astore(unknownSlot); + nextNameMatch(cob, parser, matcherSlot, ixSlot); + cob.goto_(loop); + + // Stock's property-based loop runs only while it sees names: any + // other token ends it, and the record builds as at END_OBJECT. + cob.labelBinding(oddToken); + cob.goto_(endObject); + + emitPropertyHandler(cob, base, propHandler, ctxt, propSlot, excSlot, -1, null); + emitDelegateTail(cob, base, parser, ctxt, delegate); + + Label scopeEnd = cob.newBoundLabel(); + cob.localVariable(parser, "p", CD_JSON_PARSER, scopeStart, scopeEnd); + cob.localVariable(ctxt, "ctxt", CD_DESER_CONTEXT, scopeStart, scopeEnd); + for (int i = 0; i < n; i++) { + Class t = props.get(i).type(); + // Record component names: Java identifiers, unlike JSON names. + cob.localVariable(componentSlot[i], plan.localNames()[i], + t.isPrimitive() ? Descs.of(t) : CD_OBJECT, scopeStart, scopeEnd); + } + cob.localVariable(matcherSlot, "matcher", CD_NAME_MATCHER, scopeStart, scopeEnd); + cob.localVariable(ixSlot, "ix", ConstantDescs.CD_int, scopeStart, scopeEnd); + cob.localVariable(seenSlot, "seen", ConstantDescs.CD_long, scopeStart, scopeEnd); + cob.localVariable(rawPrimSlot, "rawPrimitives", ConstantDescs.CD_long, + scopeStart, scopeEnd); + cob.localVariable(propSlot, "prop", CD_SETTABLE_PROP, scopeStart, scopeEnd); + cob.localVariable(excSlot, "e", CD_THROWABLE, scopeStart, scopeEnd); + cob.localVariable(unknownSlot, "unknown", CD_OBJECT, scopeStart, scopeEnd); + cob.localVariable(beanSlot, "bean", CD_OBJECT, scopeStart, scopeEnd); + cob.localVariable(pendingSlot, "ctorFailure", CD_THROWABLE, scopeStart, scopeEnd); + cob.localVariable(referringsSlot, "referrings", CD_OBJECT, scopeStart, scopeEnd); + if (views == ViewStrategy.MASK) { + cob.localVariable(maskSlot, "viewMask", ConstantDescs.CD_long, scopeStart, scopeEnd); + } + } + + // A record component local: its slot and type, and (primitive components + // only) its raw-value slot and its bit in the rawPrimitives mask. + private record ComponentLocal(int slot, Class type, int rawPrimSlot, int rawSlot, + int index) {} + + private static void emitRecordScalar(CodeBuilder cob, int parser, int ctxt, + ComponentLocal local, String getter, MethodTypeDesc getterType, String stockName, + String expectedToken) { + Label useStock = cob.newLabel(); + Label done = cob.newLabel(); + emitExpectedTokenCheck(cob, parser, expectedToken, useStock); + cob.aload(parser).invokevirtual(CD_JSON_PARSER, getter, getterType); + storeLocal(cob, local.type(), local.slot()); + if (local.type().isPrimitive()) { + emitRawPrimitive(cob, local, false); + } + cob.goto_(done); + cob.labelBinding(useStock); + emitStockValueToLocal(cob, parser, ctxt, local, stockName); + cob.labelBinding(done); + } + + // Reads the whole value through the stock property (parser positioned on + // the value token). Reference components stay Object, since the erased + // constructor handle casts on invocation. Primitive components unbox an + // exact wrapper; anything else a custom deserializer may return (null, + // another wrapper type) is kept raw and marks the component, so that the + // stock creator receives it as it would without Blackbird, and widens it, + // or fails in the creator call, as stock does. + private static void emitStockValueToLocal(CodeBuilder cob, int parser, int ctxt, + ComponentLocal local, String stockName) { + ldcData(cob, stockName, CD_SETTABLE_PROP); + cob.aload(parser).aload(ctxt); + cob.invokevirtual(CD_SETTABLE_PROP, "deserialize", MTD_DESERIALIZE); + Class type = local.type(); + if (!type.isPrimitive()) { + cob.astore(local.slot()); + return; + } + Label raw = cob.newLabel(); + Label done = cob.newLabel(); + ClassDesc boxDesc = Descs.of(ClassUtil.wrapperType(type)); + cob.dup(); + cob.instanceOf(boxDesc); + cob.ifeq(raw); + cob.checkcast(boxDesc); + cob.invokevirtual(boxDesc, type.getName() + "Value", MethodTypeDesc.of(Descs.of(type))); + storeLocal(cob, type, local.slot()); + emitRawPrimitive(cob, local, false); + cob.goto_(done); + cob.labelBinding(raw); + cob.astore(local.rawSlot()); + emitRawPrimitive(cob, local, true); + cob.labelBinding(done); + } + + // Sets or clears the component's bit in the rawPrimitives mask: a later + // value for a duplicate name replaces an earlier one, as in stock. + private static void emitRawPrimitive(CodeBuilder cob, ComponentLocal local, boolean isRaw) { + long bit = 1L << local.index(); + cob.lload(local.rawPrimSlot()); + if (isRaw) { + cob.loadConstant(bit); + cob.lor(); + } else { + cob.loadConstant(~bit); + cob.land(); + } + cob.lstore(local.rawPrimSlot()); + } + + private static void storeLocal(CodeBuilder cob, Class type, int slot) { + if (type == long.class) { + cob.lstore(slot); + } else if (type == double.class) { + cob.dstore(slot); + } else if (type == float.class) { + cob.fstore(slot); + } else if (type.isPrimitive()) { + cob.istore(slot); + } else { + cob.astore(slot); + } + } + + private static void loadLocal(CodeBuilder cob, Class type, int slot) { + if (type == long.class) { + cob.lload(slot); + } else if (type == double.class) { + cob.dload(slot); + } else if (type == float.class) { + cob.fload(slot); + } else if (type.isPrimitive()) { + cob.iload(slot); + } else { + cob.aload(slot); + } + } + + // Guards the generated loop's entry: anything the loop does not model - a + // stream positioned on neither START_OBJECT nor PROPERTY_NAME (the + // AsProperty polymorphic path hands subtype deserializers a stream on the + // property after the type id) and, under the DELEGATE strategy only, an + // active view - branches to the returned label, whose stock-delegation + // tail the caller emits at the end of the method so the main body + // decompiles un-nested. The MASK strategy keeps view-active calls on the + // generated path through the visibility bitmask. + private static Label emitEntryGuard(CodeBuilder cob, int parser, int ctxt, + ViewStrategy views) { + Label delegate = cob.newLabel(); + Label entryOk = cob.newLabel(); + cob.aload(parser).invokevirtual(CD_JSON_PARSER, "currentToken", MTD_NEXT_TOKEN); + cob.getstatic(CD_JSON_TOKEN, "START_OBJECT", CD_JSON_TOKEN); + cob.if_acmpeq(entryOk); + cob.aload(parser).invokevirtual(CD_JSON_PARSER, "currentToken", MTD_NEXT_TOKEN); + cob.getstatic(CD_JSON_TOKEN, "PROPERTY_NAME", CD_JSON_TOKEN); + cob.if_acmpne(delegate); + cob.labelBinding(entryOk); + if (views == ViewStrategy.DELEGATE) { + cob.aload(ctxt).invokevirtual(CD_DESER_CONTEXT, "getActiveView", + MTD_GET_ACTIVE_VIEW); + cob.ifnonnull(delegate); + } + return delegate; + } + + // The stock deserialize of the codec's own (copied) stock state. + private static void emitDelegateTail(CodeBuilder cob, ClassDesc base, int parser, int ctxt, + Label delegate) { + cob.labelBinding(delegate); + cob.aload(0).aload(parser).aload(ctxt); + cob.invokevirtual(base, "_stockDeserialize", MTD_DESERIALIZE); + cob.areturn(); + } + + // Branches to useStock unless the current (value) token is the one the + // inline read expects. Null, quoted scalars, and mismatched shapes all + // take the stock property, which owns coercion and null handling. + // expectedToken null selects the boolean pair. + private static void emitExpectedTokenCheck(CodeBuilder cob, int parser, + String expectedToken, Label useStock) { + // The fused match already advanced to the value token, so read it + // with currentToken rather than advancing again. + cob.aload(parser).invokevirtual(CD_JSON_PARSER, "currentToken", MTD_NEXT_TOKEN); + if (expectedToken != null) { + cob.getstatic(CD_JSON_TOKEN, expectedToken, CD_JSON_TOKEN); + cob.if_acmpne(useStock); + } else { + Label isTrue = cob.newLabel(); + Label fast = cob.newLabel(); + cob.dup(); + cob.getstatic(CD_JSON_TOKEN, "VALUE_TRUE", CD_JSON_TOKEN); + cob.if_acmpeq(isTrue); + cob.getstatic(CD_JSON_TOKEN, "VALUE_FALSE", CD_JSON_TOKEN); + cob.if_acmpne(useStock); + cob.goto_(fast); + cob.labelBinding(isTrue); + cob.pop(); + cob.labelBinding(fast); + } + } + + // Loads the arm's stock property into propSlot and opens its exception + // region: any Exception from a property arm is rethrown with the property + // reference prepended, like the stock loop's wrapAndThrow. + private static Label beginArm(CodeBuilder cob, String stockName, int propSlot, Label handler) { + ldcData(cob, stockName, CD_SETTABLE_PROP); + cob.astore(propSlot); + Label armStart = cob.newLabel(); + Label armEnd = cob.newLabel(); + cob.exceptionCatch(armStart, armEnd, handler, CD_EXCEPTION); + cob.labelBinding(armStart); + return armEnd; + } + + // The builder failure handlers' name arguments: the locals holding the + // input name and the entry kind, or null and false where the names + // cannot differ (NONE). + private record Names(int inputNameSlot, int startObjectSlot) { + static final Names NONE = new Names(-1, -1); + + void load(CodeBuilder cob) { + if (inputNameSlot < 0) { + cob.aconst_null().iconst_0(); + } else { + cob.aload(inputNameSlot).iload(startObjectSlot); + } + } + } + + // receiverSlot < 0 selects aconst_null (record mode: no instance yet); + // non-null names (builder mode) passes the name arguments. + private static void emitPropertyHandler(CodeBuilder cob, ClassDesc base, Label handler, + int ctxt, + int propSlot, int excSlot, int receiverSlot, Names names) { + cob.labelBinding(handler); + cob.astore(excSlot); + cob.aload(0); + cob.aload(excSlot); + if (receiverSlot < 0) { + cob.aconst_null(); + } else { + cob.aload(receiverSlot); + } + cob.aload(propSlot); + if (names != null) { + names.load(cob); + cob.aload(ctxt); + cob.invokevirtual(base, "_propertyException", MTD_BUILDER_PROP_WRAP); + } else { + cob.aload(ctxt); + cob.invokevirtual(base, "_propertyException", MTD_PROP_WRAP); + } + cob.athrow(); + } + + // The fused name match: on a non-negative result the current token is the + // property's value token, so the arms consume it directly; negative + // results leave the stream on the name exactly as nextNameMatch does. + private static void nextNameMatch(CodeBuilder cob, int parser, int matcherSlot, int ixSlot) { + cob.aload(parser).aload(matcherSlot) + .invokevirtual(CD_JSON_PARSER, "nextNameMatchAndToken", MTD_NEXT_NAME_MATCH) + .istore(ixSlot); + } + + // First match of the loop. The entry guard admits START_OBJECT and + // PROPERTY_NAME; a name entry matches the CURRENT name, mirroring stock + // BeanDeserializer's currentNameMatch loop head. The fused arms consume + // the current token, so a matched name entry advances to its value here; + // fusing the entry itself would double-advance (the current name is + // already consumed). Negative results stay on the name, as the unknown + // arm expects. startObjectSlot >= 0 records which entry was taken. + private static void emitFirstMatch(CodeBuilder cob, int parser, int matcherSlot, int ixSlot, + int startObjectSlot) { + Label nameEntry = cob.newLabel(); + Label done = cob.newLabel(); + cob.aload(parser).invokevirtual(CD_JSON_PARSER, "currentToken", MTD_NEXT_TOKEN); + cob.getstatic(CD_JSON_TOKEN, "PROPERTY_NAME", CD_JSON_TOKEN); + cob.if_acmpeq(nameEntry); + if (startObjectSlot >= 0) { + cob.iconst_1().istore(startObjectSlot); + } + nextNameMatch(cob, parser, matcherSlot, ixSlot); + cob.goto_(done); + cob.labelBinding(nameEntry); + if (startObjectSlot >= 0) { + cob.iconst_0().istore(startObjectSlot); + } + cob.aload(parser).aload(matcherSlot) + .invokevirtual(CD_JSON_PARSER, "currentNameMatch", MTD_NEXT_NAME_MATCH) + .istore(ixSlot); + cob.iload(ixSlot).iflt(done); + cob.aload(parser).invokevirtual(CD_JSON_PARSER, "nextToken", MTD_NEXT_TOKEN).pop(); + cob.labelBinding(done); + } + + private static void throwIse(CodeBuilder cob, String message) { + cob.new_(CD_ISE).dup().ldc(message) + .invokespecial(CD_ISE, ConstantDescs.INIT_NAME, + MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String)) + .athrow(); + } +} diff --git a/blackbird/src/main/java25/tools/jackson/module/blackbird/codegen/BeanWriterGenerator.java b/blackbird/src/main/java25/tools/jackson/module/blackbird/codegen/BeanWriterGenerator.java new file mode 100644 index 000000000..2af5b3121 --- /dev/null +++ b/blackbird/src/main/java25/tools/jackson/module/blackbird/codegen/BeanWriterGenerator.java @@ -0,0 +1,550 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.classfile.ClassFile; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.Label; +import java.lang.classfile.attribute.MethodParametersAttribute; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.List; + +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonToken; +import tools.jackson.core.SerializableString; +import tools.jackson.core.type.WritableTypeId; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.jsontype.TypeSerializer; +import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.ser.PropertyWriter; +import tools.jackson.databind.ser.bean.BeanSerializerBase; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.ViewStrategy; +import tools.jackson.module.blackbird.codegen.WriterCodegen.GenWProp; +import tools.jackson.module.blackbird.codegen.WriterCodegen.WKind; +import tools.jackson.module.blackbird.internal.GeneratedWriterBase; + +/** + * Emits the class file of a hidden-class serializer for one bean (see + * {@link WriterCodegen}, which defines it): a straight-line sequence of + * writeName/write-value pairs with pre-encoded name constants for eligible + * properties and the stock PropertyWriter (a named classData constant) for + * everything else. Beans that declare views resolve a per-view visibility + * bitmask and each property tests its bit, so view-active writes stay on the + * generated path; beans with more than 64 properties delegate view-active + * calls to the stock serializer instead. Compiled at release 25 into + * META-INF/versions/25, since it uses the ClassFile API; reached through + * {@link ClassFileEmitters}. + */ +final class BeanWriterGenerator +{ + private static final ClassDesc CD_JSON_GENERATOR = Descs.of(JsonGenerator.class); + private static final ClassDesc CD_SER_CONTEXT = Descs.of(SerializationContext.class); + private static final ClassDesc CD_SERIALIZABLE_STRING = Descs.of(SerializableString.class); + private static final ClassDesc CD_PROPERTY_WRITER = Descs.of(PropertyWriter.class); + private static final ClassDesc CD_BEAN_PROPERTY_WRITER = Descs.of(BeanPropertyWriter.class); + private static final ClassDesc CD_WRITER_BASE = + Descs.of(GeneratedWriterBase.class); + private static final ClassDesc CD_BEAN_SER_BASE = Descs.of(BeanSerializerBase.class); + private static final ClassDesc CD_WRITE_HELPERS = ClassDesc.of(WriterCodegen.HELPERS_CLASS); + + private static final MethodTypeDesc MTD_SERIALIZE = MethodTypeDesc.of(ConstantDescs.CD_void, + ConstantDescs.CD_Object, CD_JSON_GENERATOR, CD_SER_CONTEXT); + private static final MethodTypeDesc MTD_WRITE_NAME = + MethodTypeDesc.of(CD_JSON_GENERATOR, CD_SERIALIZABLE_STRING); + private static final MethodTypeDesc MTD_WRITE_STRING = + MethodTypeDesc.of(CD_JSON_GENERATOR, ConstantDescs.CD_String); + private static final MethodTypeDesc MTD_WRITE_INT = + MethodTypeDesc.of(CD_JSON_GENERATOR, ConstantDescs.CD_int); + private static final MethodTypeDesc MTD_WRITE_LONG = + MethodTypeDesc.of(CD_JSON_GENERATOR, ConstantDescs.CD_long); + private static final MethodTypeDesc MTD_WRITE_BOOLEAN = + MethodTypeDesc.of(CD_JSON_GENERATOR, ConstantDescs.CD_boolean); + private static final MethodTypeDesc MTD_WRITE_NULL = MethodTypeDesc.of(CD_JSON_GENERATOR); + private static final MethodTypeDesc MTD_WRITE_START_OBJECT = + MethodTypeDesc.of(CD_JSON_GENERATOR, ConstantDescs.CD_Object); + private static final MethodTypeDesc MTD_WRITE_END = MethodTypeDesc.of(CD_JSON_GENERATOR); + private static final MethodTypeDesc MTD_SERIALIZE_AS_PROPERTY = MethodTypeDesc.of( + ConstantDescs.CD_void, ConstantDescs.CD_Object, CD_JSON_GENERATOR, CD_SER_CONTEXT); + private static final MethodTypeDesc MTD_SELF_REFERENCE = MethodTypeDesc.of( + ConstantDescs.CD_void, CD_BEAN_PROPERTY_WRITER, ConstantDescs.CD_Object, + CD_JSON_GENERATOR, CD_SER_CONTEXT); + private static final MethodTypeDesc MTD_CTOR = MethodTypeDesc.of(ConstantDescs.CD_void, + CD_BEAN_SER_BASE, ConstantDescs.CD_String); + private static final MethodTypeDesc MTD_PROP_FAILURE = MethodTypeDesc.of( + Descs.of(RuntimeException.class), ConstantDescs.CD_Throwable, + ConstantDescs.CD_Object, ConstantDescs.CD_int, CD_JSON_GENERATOR, CD_SER_CONTEXT); + private static final ClassDesc CD_STACK_OVERFLOW = Descs.of(StackOverflowError.class); + private static final MethodTypeDesc MTD_GET_ACTIVE_VIEW = + MethodTypeDesc.of(ConstantDescs.CD_Class); + private static final MethodTypeDesc MTD_VIEW_MASK = + MethodTypeDesc.of(ConstantDescs.CD_long, ConstantDescs.CD_Class); + private static final MethodTypeDesc MTD_PROP_VISIBLE = MethodTypeDesc.of( + ConstantDescs.CD_boolean, CD_PROPERTY_WRITER, ConstantDescs.CD_Class, + ConstantDescs.CD_boolean); + private static final ClassDesc CD_JSON_TOKEN = Descs.of(JsonToken.class); + private static final ClassDesc CD_TYPE_SERIALIZER = + Descs.of(TypeSerializer.class); + private static final ClassDesc CD_WRITABLE_TYPE_ID = + Descs.of(WritableTypeId.class); + private static final MethodTypeDesc MTD_TYPE_ID = MethodTypeDesc.of( + CD_WRITABLE_TYPE_ID, ConstantDescs.CD_Object, CD_JSON_TOKEN); + private static final MethodTypeDesc MTD_WRITE_TYPE_PART = MethodTypeDesc.of( + CD_WRITABLE_TYPE_ID, CD_JSON_GENERATOR, CD_SER_CONTEXT, CD_WRITABLE_TYPE_ID); + private static final MethodTypeDesc MTD_ASSIGN_CURRENT = + MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object); + private static final MethodTypeDesc MTD_SERIALIZE_WITH_TYPE = MethodTypeDesc.of( + ConstantDescs.CD_void, ConstantDescs.CD_Object, CD_JSON_GENERATOR, CD_SER_CONTEXT, + CD_TYPE_SERIALIZER); + + private BeanWriterGenerator() {} + + private static void ldcData(CodeBuilder cob, String name, ClassDesc type) { + cob.ldc(Descs.classDataEntry(name, type)); + } + + static byte[] emit(WriterCodegen.Plan plan) { + return BeanReaderGenerator.withinSizeLimit(buildClass(plan.className(), plan.props(), + plan.stockName(), plan.nameName(), plan.childName(), plan.selfName(), + plan.handleName(), plan.views(), plan.includeByDefault(), plan.nativePolyWrites())); + } + + // HotSpot C2 measure. The nops make each write helper larger than + // FreqInlineSize (325 bytes, C2) and C1MaxInlineSize (35 bytes, C1), so + // neither compiler inlines it into serialize() and it compiles once as + // its own unit. On aarch64, C2 copies of writeName and the NumberOutput + // digit code inlined into writers that hold list loops spill loop state + // to the stack. All writers share one WriteHelpers class: with a copy per + // writer class, each copy compiles separately, and on x86_64 the extra + // machine code causes instruction-cache misses. With the padding, + // RecordBenchmark.recordSer is 24-45% faster and PojoBenchmark.mediaItemSer + // 12-31% faster on aarch64 (JDK 25 and 27-ea), and + // BuilderBenchmark.builderSer 23% and mediaItemSer 6% faster on x86_64 + // (JDK 25). On x86_64, shared helpers make recordSer 12% and mediaItemSer + // 7% faster than a copy per writer class. Graal uses other inlining rules, + // and the interpreter executes the nops until tier-up. + private static final int INLINE_PAD = 384; + + private static final MethodTypeDesc MTD_HELP_STRING = MethodTypeDesc.of(ConstantDescs.CD_void, + CD_JSON_GENERATOR, CD_SERIALIZABLE_STRING, ConstantDescs.CD_String); + private static final MethodTypeDesc MTD_HELP_INT = MethodTypeDesc.of(ConstantDescs.CD_void, + CD_JSON_GENERATOR, CD_SERIALIZABLE_STRING, ConstantDescs.CD_int); + private static final MethodTypeDesc MTD_HELP_LONG = MethodTypeDesc.of(ConstantDescs.CD_void, + CD_JSON_GENERATOR, CD_SERIALIZABLE_STRING, ConstantDescs.CD_long); + private static final MethodTypeDesc MTD_HELP_BOOLEAN = MethodTypeDesc.of(ConstantDescs.CD_void, + CD_JSON_GENERATOR, CD_SERIALIZABLE_STRING, ConstantDescs.CD_boolean); + private static final MethodTypeDesc MTD_HELP_NAME = MethodTypeDesc.of(ConstantDescs.CD_void, + CD_JSON_GENERATOR, CD_SERIALIZABLE_STRING); + + private static byte[] buildClass(String className, List props, + String[] stockName, String[] nameName, String[] childName, String[] selfName, + String[] handleName, ViewStrategy views, boolean includeByDefault, + boolean nativePolyWrites) { + ClassDesc thisClass = ClassDesc.of(className); + return BeanReaderGenerator.CLASS_FILE.build(thisClass, clb -> { + clb.withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_FINAL | ClassFile.ACC_SUPER); + clb.withSuperclass(CD_WRITER_BASE); + clb.withMethod(ConstantDescs.INIT_NAME, MTD_CTOR, ClassFile.ACC_PUBLIC, + mb -> mb.with(BeanReaderGenerator.params("fallback", "layout")) + .withCode(cob -> cob.aload(0).aload(1).aload(2) + .invokespecial(CD_WRITER_BASE, ConstantDescs.INIT_NAME, MTD_CTOR) + .return_())); + clb.withMethod("serialize", MTD_SERIALIZE, ClassFile.ACC_PUBLIC, + mb -> mb.with(BeanReaderGenerator.params("value", "g", "ctxt")) + .withCode(cob -> buildSerialize(cob, props, + stockName, nameName, childName, selfName, handleName, views))); + if (nativePolyWrites) { + clb.withMethod("serializeWithType", MTD_SERIALIZE_WITH_TYPE, ClassFile.ACC_PUBLIC, + mb -> mb.with(BeanReaderGenerator.params("value", "g", "ctxt", "typeSer")) + .withCode(cob -> buildSerializeWithType(cob, props, + stockName, nameName, childName, selfName, handleName, + views))); + } + if (views == ViewStrategy.MASK) { + emitComputeViewMask(clb, props, stockName, includeByDefault); + } + }); + } + + /** + * Emits {@link WriterCodegen#HELPERS_CLASS}, the padded write helpers + * that every generated writer calls (see {@link #INLINE_PAD}). + */ + static byte[] emitWriteHelpers() { + return BeanReaderGenerator.CLASS_FILE.build(CD_WRITE_HELPERS, clb -> { + clb.withFlags(ClassFile.ACC_FINAL | ClassFile.ACC_SUPER); + emitPadded(clb, "$str", MTD_HELP_STRING, cob -> { + cob.aload(0).aload(1) + .invokevirtual(CD_JSON_GENERATOR, "writeName", MTD_WRITE_NAME).pop(); + Label isNull = cob.newLabel(); + Label done = cob.newLabel(); + cob.aload(2).ifnull(isNull); + cob.aload(0).aload(2) + .invokevirtual(CD_JSON_GENERATOR, "writeString", MTD_WRITE_STRING).pop(); + cob.goto_(done); + cob.labelBinding(isNull); + cob.aload(0).invokevirtual(CD_JSON_GENERATOR, "writeNull", MTD_WRITE_NULL).pop(); + cob.labelBinding(done); + cob.return_(); + }); + emitPadded(clb, "$int", MTD_HELP_INT, cob -> cob.aload(0).aload(1) + .invokevirtual(CD_JSON_GENERATOR, "writeName", MTD_WRITE_NAME).pop() + .aload(0).iload(2) + .invokevirtual(CD_JSON_GENERATOR, "writeNumber", MTD_WRITE_INT).pop() + .return_()); + emitPadded(clb, "$long", MTD_HELP_LONG, cob -> cob.aload(0).aload(1) + .invokevirtual(CD_JSON_GENERATOR, "writeName", MTD_WRITE_NAME).pop() + .aload(0).lload(2) + .invokevirtual(CD_JSON_GENERATOR, "writeNumber", MTD_WRITE_LONG).pop() + .return_()); + emitPadded(clb, "$bool", MTD_HELP_BOOLEAN, cob -> cob.aload(0).aload(1) + .invokevirtual(CD_JSON_GENERATOR, "writeName", MTD_WRITE_NAME).pop() + .aload(0).iload(2) + .invokevirtual(CD_JSON_GENERATOR, "writeBoolean", MTD_WRITE_BOOLEAN).pop() + .return_()); + emitPadded(clb, "$name", MTD_HELP_NAME, cob -> cob.aload(0).aload(1) + .invokevirtual(CD_JSON_GENERATOR, "writeName", MTD_WRITE_NAME).pop() + .return_()); + }); + } + + private static void emitPadded(java.lang.classfile.ClassBuilder clb, String name, + MethodTypeDesc type, java.util.function.Consumer body) { + MethodParametersAttribute helperParams = (type.parameterCount() == 2) + ? BeanReaderGenerator.params("g", "name") + : BeanReaderGenerator.params("g", "name", "value"); + clb.withMethod(name, type, ClassFile.ACC_STATIC, + mb -> mb.with(helperParams).withCode(cob -> { + for (int i = 0; i < INLINE_PAD; i++) { + cob.nop(); + } + body.accept(cob); + })); + } + + private static void buildSerialize(CodeBuilder cob, + List props, String[] stockName, String[] nameName, String[] childName, + String[] selfName, String[] handleName, ViewStrategy views) { + final int gen = 2; + final int ctxt = 3; + final int refSlot = 5; + final int maskSlot = 6; + final int ixSlot = 8; + final int excSlot = 9; + + Label scopeStart = cob.newBoundLabel(); + Label delegate = null; + if (views == ViewStrategy.DELEGATE) { + // View-active calls (declared views with more than 64 properties, + // or the write-nothing inclusion-off case) go to the stock + // serializer, whose tail sits after the body so the main path + // decompiles un-nested. + delegate = cob.newLabel(); + cob.aload(ctxt).invokevirtual(CD_SER_CONTEXT, "getActiveView", MTD_GET_ACTIVE_VIEW); + cob.ifnonnull(delegate); + } else if (views == ViewStrategy.MASK) { + emitViewMask(cob, ctxt, maskSlot); + } + + cob.aload(gen).aload(1) + .invokevirtual(CD_JSON_GENERATOR, "writeStartObject", MTD_WRITE_START_OBJECT).pop(); + + Label handler = emitPropsWithHandler(cob, props, stockName, nameName, + childName, selfName, handleName, views, gen, ctxt, refSlot, maskSlot, ixSlot); + + cob.aload(gen).invokevirtual(CD_JSON_GENERATOR, "writeEndObject", MTD_WRITE_END).pop(); + cob.return_(); + emitPropertyHandler(cob, handler, gen, ctxt, ixSlot, excSlot); + if (delegate != null) { + cob.labelBinding(delegate); + cob.aload(0).aload(1).aload(gen).aload(ctxt); + cob.invokevirtual(CD_WRITER_BASE, "_stockSerialize", MTD_SERIALIZE); + cob.return_(); + } + + Label scopeEnd = cob.newBoundLabel(); + cob.localVariable(1, "value", ConstantDescs.CD_Object, scopeStart, scopeEnd); + cob.localVariable(gen, "g", CD_JSON_GENERATOR, scopeStart, scopeEnd); + cob.localVariable(ctxt, "ctxt", CD_SER_CONTEXT, scopeStart, scopeEnd); + // Slot entries must stay within max_locals, so name the child slot + // only when some CHILD arm actually stores it. + if (props.stream().anyMatch(pr -> pr.kind() == WKind.CHILD)) { + cob.localVariable(refSlot, "child", ConstantDescs.CD_Object, scopeStart, scopeEnd); + } + cob.localVariable(ixSlot, "propIndex", ConstantDescs.CD_int, scopeStart, scopeEnd); + cob.localVariable(excSlot, "e", ConstantDescs.CD_Throwable, scopeStart, scopeEnd); + if (views == ViewStrategy.MASK) { + cob.localVariable(maskSlot, "viewMask", ConstantDescs.CD_long, scopeStart, scopeEnd); + } + } + + // The serializeWithType override: stock BeanSerializerBase's flow with + // the object-id and filter-id branches omitted (both gate at the factory) + // and the type id always typeSer.typeId(value, START_OBJECT) - beans with + // a @JsonTypeId property keep the base forwarding instead. The props body + // is emitted again rather than shared through a method so the plain + // serialize path keeps its measured shape. + private static void buildSerializeWithType(CodeBuilder cob, + List props, String[] stockName, String[] nameName, String[] childName, + String[] selfName, String[] handleName, ViewStrategy views) { + final int gen = 2; + final int ctxt = 3; + final int typeSer = 4; + final int typeIdSlot = 5; + final int refSlot = 6; + final int maskSlot = 7; + final int ixSlot = 9; + final int excSlot = 10; + + Label scopeStart = cob.newBoundLabel(); + Label delegate = null; + if (views == ViewStrategy.DELEGATE) { + delegate = cob.newLabel(); + cob.aload(ctxt).invokevirtual(CD_SER_CONTEXT, "getActiveView", MTD_GET_ACTIVE_VIEW); + cob.ifnonnull(delegate); + } else if (views == ViewStrategy.MASK) { + emitViewMask(cob, ctxt, maskSlot); + } + + cob.aload(typeSer).aload(1); + cob.getstatic(CD_JSON_TOKEN, "START_OBJECT", CD_JSON_TOKEN); + cob.invokevirtual(CD_TYPE_SERIALIZER, "typeId", MTD_TYPE_ID); + cob.astore(typeIdSlot); + cob.aload(typeSer).aload(gen).aload(ctxt).aload(typeIdSlot); + cob.invokevirtual(CD_TYPE_SERIALIZER, "writeTypePrefix", MTD_WRITE_TYPE_PART).pop(); + cob.aload(gen).aload(1) + .invokevirtual(CD_JSON_GENERATOR, "assignCurrentValue", MTD_ASSIGN_CURRENT); + + Label handler = emitPropsWithHandler(cob, props, stockName, nameName, + childName, selfName, handleName, views, gen, ctxt, refSlot, maskSlot, ixSlot); + + cob.aload(typeSer).aload(gen).aload(ctxt).aload(typeIdSlot); + cob.invokevirtual(CD_TYPE_SERIALIZER, "writeTypeSuffix", MTD_WRITE_TYPE_PART).pop(); + cob.return_(); + emitPropertyHandler(cob, handler, gen, ctxt, ixSlot, excSlot); + if (delegate != null) { + cob.labelBinding(delegate); + cob.aload(0).aload(1).aload(gen).aload(ctxt).aload(typeSer); + cob.invokevirtual(CD_WRITER_BASE, "_stockSerializeWithType", MTD_SERIALIZE_WITH_TYPE); + cob.return_(); + } + + Label scopeEnd = cob.newBoundLabel(); + cob.localVariable(1, "value", ConstantDescs.CD_Object, scopeStart, scopeEnd); + cob.localVariable(gen, "g", CD_JSON_GENERATOR, scopeStart, scopeEnd); + cob.localVariable(ctxt, "ctxt", CD_SER_CONTEXT, scopeStart, scopeEnd); + cob.localVariable(typeSer, "typeSer", CD_TYPE_SERIALIZER, scopeStart, scopeEnd); + cob.localVariable(typeIdSlot, "typeIdDef", CD_WRITABLE_TYPE_ID, scopeStart, scopeEnd); + if (props.stream().anyMatch(pr -> pr.kind() == WKind.CHILD)) { + cob.localVariable(refSlot, "child", ConstantDescs.CD_Object, scopeStart, scopeEnd); + } + cob.localVariable(ixSlot, "propIndex", ConstantDescs.CD_int, scopeStart, scopeEnd); + cob.localVariable(excSlot, "e", ConstantDescs.CD_Throwable, scopeStart, scopeEnd); + if (views == ViewStrategy.MASK) { + cob.localVariable(maskSlot, "viewMask", ConstantDescs.CD_long, scopeStart, scopeEnd); + } + } + + // MASK strategy: resolves the visibility bitmask for the call (all-ones + // when no view is active) into maskSlot. + private static void emitViewMask(CodeBuilder cob, int ctxt, int maskSlot) { + Label nullView = cob.newLabel(); + Label haveMask = cob.newLabel(); + cob.aload(ctxt).invokevirtual(CD_SER_CONTEXT, "getActiveView", MTD_GET_ACTIVE_VIEW); + cob.dup(); + cob.ifnull(nullView); + cob.aload(0); + cob.swap(); + cob.invokevirtual(CD_WRITER_BASE, "_viewMask", MTD_VIEW_MASK); + cob.lstore(maskSlot); + cob.goto_(haveMask); + cob.labelBinding(nullView); + cob.pop(); + cob.loadConstant(-1L); + cob.lstore(maskSlot); + cob.labelBinding(haveMask); + } + + // The property section of stock _serializePropertiesNoView: an Exception + // goes through wrapAndThrow and a StackOverflowError becomes the + // "Infinite recursion" DatabindException, both with the reference of the + // property being written (ixSlot). Returns the handler label, which the + // caller binds after its return (see emitPropertyHandler). + private static Label emitPropsWithHandler(CodeBuilder cob, + List props, String[] stockName, String[] nameName, String[] childName, + String[] selfName, String[] handleName, ViewStrategy views, int gen, int ctxt, + int refSlot, int maskSlot, int ixSlot) { + Label start = cob.newLabel(); + Label end = cob.newLabel(); + Label handler = cob.newLabel(); + cob.exceptionCatch(start, end, handler, ConstantDescs.CD_Exception); + cob.exceptionCatch(start, end, handler, CD_STACK_OVERFLOW); + // Assigned before the region so the handler can read it from any + // instruction in it. + cob.iconst_0().istore(ixSlot); + cob.labelBinding(start); + emitProps(cob, props, stockName, nameName, childName, selfName, handleName, views, + gen, ctxt, refSlot, maskSlot, ixSlot); + cob.labelBinding(end); + return handler; + } + + private static void emitPropertyHandler(CodeBuilder cob, Label handler, int gen, int ctxt, + int ixSlot, int excSlot) { + cob.labelBinding(handler); + cob.astore(excSlot); + cob.aload(0).aload(excSlot).aload(1).iload(ixSlot).aload(gen).aload(ctxt); + cob.invokevirtual(CD_WRITER_BASE, "_propertyFailure", MTD_PROP_FAILURE); + cob.athrow(); + } + + // One writeName/write-value pair per property, the bean in slot 1. + private static void emitProps(CodeBuilder cob, List props, + String[] stockName, String[] nameName, String[] childName, String[] selfName, + String[] handleName, ViewStrategy views, int gen, int ctxt, int refSlot, int maskSlot, + int ixSlot) { + for (int i = 0; i < props.size(); i++) { + GenWProp prop = props.get(i); + if (i > 0) { + cob.loadConstant(i); + cob.istore(ixSlot); + } + Label hidden = null; + Label next = null; + if (views == ViewStrategy.MASK) { + hidden = cob.newLabel(); + next = hasDeclaredViews(prop) ? cob.newLabel() : hidden; + cob.lload(maskSlot); + cob.loadConstant(1L << i); + cob.land(); + cob.lconst_0(); + cob.lcmp(); + cob.ifeq(hidden); + } + switch (prop.kind()) { + case INT -> emitScalar(cob, gen, nameName[i], handleName[i], + "$int", MTD_HELP_INT, ConstantDescs.CD_int); + case LONG -> emitScalar(cob, gen, nameName[i], handleName[i], + "$long", MTD_HELP_LONG, ConstantDescs.CD_long); + case BOOLEAN -> emitScalar(cob, gen, nameName[i], handleName[i], + "$bool", MTD_HELP_BOOLEAN, ConstantDescs.CD_boolean); + case STRING -> { + // The getter handle is erased to (Object)Object; the value + // is a String at run time (java.lang, not a bean name), so + // a checkcast recovers the helper's descriptor. + cob.aload(gen); + ldcData(cob, nameName[i], CD_SERIALIZABLE_STRING); + emitLoad(cob, handleName[i], ConstantDescs.CD_Object); + cob.checkcast(ConstantDescs.CD_String); + cob.invokestatic(CD_WRITE_HELPERS, "$str", MTD_HELP_STRING); + } + case CHILD -> { + // A value that is the bean itself goes to the stock + // self-reference handling (FAIL_ON_SELF_REFERENCES, + // WRITE_SELF_REFERENCES_AS_NULL), with the value read + // here, so that the getter runs once, as in stock. + Label isNull = cob.newLabel(); + Label self = cob.newLabel(); + Label done = cob.newLabel(); + emitLoad(cob, handleName[i], ConstantDescs.CD_Object); + cob.astore(refSlot); + cob.aload(refSlot).ifnull(isNull); + cob.aload(refSlot).aload(1).if_acmpeq(self); + cob.aload(gen); + ldcData(cob, nameName[i], CD_SERIALIZABLE_STRING); + cob.invokestatic(CD_WRITE_HELPERS, "$name", MTD_HELP_NAME); + ldcData(cob, childName[i], CD_WRITER_BASE); + cob.aload(refSlot).aload(gen).aload(ctxt); + cob.invokevirtual(CD_WRITER_BASE, "serialize", MTD_SERIALIZE); + cob.goto_(done); + cob.labelBinding(isNull); + cob.aload(gen); + ldcData(cob, nameName[i], CD_SERIALIZABLE_STRING); + cob.invokestatic(CD_WRITE_HELPERS, "$name", MTD_HELP_NAME); + cob.aload(gen).invokevirtual(CD_JSON_GENERATOR, "writeNull", MTD_WRITE_NULL).pop(); + cob.goto_(done); + cob.labelBinding(self); + cob.aload(0); + ldcData(cob, selfName[i], CD_BEAN_PROPERTY_WRITER); + cob.aload(1).aload(gen).aload(ctxt); + cob.invokevirtual(CD_WRITER_BASE, "_selfReference", MTD_SELF_REFERENCE); + cob.labelBinding(done); + } + case STOCK -> { + ldcData(cob, stockName[i], CD_PROPERTY_WRITER); + cob.aload(1).aload(gen).aload(ctxt); + cob.invokevirtual(CD_PROPERTY_WRITER, "serializeAsProperty", + MTD_SERIALIZE_AS_PROPERTY); + } + } + if (next != hidden) { + // Hidden by a declared view: stock FilteredBeanPropertyWriter + // hands it to serializeAsOmittedProperty, which writes a + // placeholder on generators that cannot omit properties. + // A view-less property hidden by DEFAULT_VIEW_INCLUSION is + // left out of stock's filtered array, so writes nothing. + cob.goto_(next); + cob.labelBinding(hidden); + ldcData(cob, stockName[i], CD_PROPERTY_WRITER); + cob.aload(1).aload(gen).aload(ctxt); + cob.invokevirtual(CD_PROPERTY_WRITER, "serializeAsOmittedProperty", + MTD_SERIALIZE_AS_PROPERTY); + } + if (next != null) { + cob.labelBinding(next); + } + } + } + + private static boolean hasDeclaredViews(GenWProp prop) { + return prop.stock() instanceof BeanPropertyWriter bpw + && bpw.getViews() != null && bpw.getViews().length > 0; + } + + // Overrides GeneratedWriterBase._computeViewMask: bit i set when property + // i is visible in the view, per the same rule the stock factory uses to + // build the filtered writer array. + private static void emitComputeViewMask(java.lang.classfile.ClassBuilder clb, + List props, String[] stockName, boolean includeByDefault) { + clb.withMethod("_computeViewMask", MTD_VIEW_MASK, ClassFile.ACC_PROTECTED, + mb -> mb.with(BeanReaderGenerator.params("activeView")).withCode(cob -> { + final int maskSlot = 2; + Label scopeStart = cob.newBoundLabel(); + cob.lconst_0().lstore(maskSlot); + for (int i = 0; i < props.size(); i++) { + Label skip = cob.newLabel(); + ldcData(cob, stockName[i], CD_PROPERTY_WRITER); + cob.aload(1); + cob.loadConstant(includeByDefault ? 1 : 0); + cob.invokestatic(CD_WRITER_BASE, "_propVisible", MTD_PROP_VISIBLE); + cob.ifeq(skip); + cob.lload(maskSlot); + cob.loadConstant(1L << i); + cob.lor(); + cob.lstore(maskSlot); + cob.labelBinding(skip); + } + cob.lload(maskSlot).lreturn(); + Label scopeEnd = cob.newBoundLabel(); + cob.localVariable(1, "activeView", ConstantDescs.CD_Class, scopeStart, scopeEnd); + cob.localVariable(maskSlot, "mask", ConstantDescs.CD_long, scopeStart, scopeEnd); + })); + } + + private static void emitScalar(CodeBuilder cob, int gen, + String nameEntry, String handleEntry, String helper, + MethodTypeDesc helperType, ClassDesc erasedDesc) { + cob.aload(gen); + ldcData(cob, nameEntry, CD_SERIALIZABLE_STRING); + emitLoad(cob, handleEntry, erasedDesc); + cob.invokestatic(CD_WRITE_HELPERS, helper, helperType); + } + + // Loads the property value from the bean (parameter 1) through the + // constant (Object)eV handle. + private static void emitLoad(CodeBuilder cob, String handleEntry, ClassDesc erasedDesc) { + ldcData(cob, handleEntry, ConstantDescs.CD_MethodHandle); + cob.aload(1); + cob.invokevirtual(ConstantDescs.CD_MethodHandle, "invokeExact", + MethodTypeDesc.of(erasedDesc, ConstantDescs.CD_Object)); + } +} diff --git a/blackbird/src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule b/blackbird/src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule index 09536df46..f71b80042 100644 --- a/blackbird/src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule +++ b/blackbird/src/main/resources/META-INF/services/tools.jackson.databind.JacksonModule @@ -1,2 +1 @@ -tools.jackson.module.blackbird.BlackbirdModule - +tools.jackson.module.blackbird.internal.DiscoveredBlackbirdModule diff --git a/blackbird/src/moditect/module-info.java b/blackbird/src/moditect/module-info.java deleted file mode 100644 index bfb48f5ae..000000000 --- a/blackbird/src/moditect/module-info.java +++ /dev/null @@ -1,11 +0,0 @@ -module tools.jackson.module.blackbird { - requires java.logging; - - requires tools.jackson.core; - requires tools.jackson.databind; - - exports tools.jackson.module.blackbird; - - provides tools.jackson.databind.JacksonModule with - tools.jackson.module.blackbird.BlackbirdModule; -} diff --git a/blackbird/src/test/java/module-info.java b/blackbird/src/test/java/module-info.java index c3d7e65e5..4f3fda3d2 100644 --- a/blackbird/src/test/java/module-info.java +++ b/blackbird/src/test/java/module-info.java @@ -4,8 +4,6 @@ // Since we are not split from Main artifact, will not // need to depend on Main artifact -- but need its dependencies - requires java.logging; - requires com.fasterxml.jackson.annotation; requires tools.jackson.core; requires tools.jackson.databind; @@ -23,7 +21,6 @@ opens tools.jackson.module.blackbird; opens tools.jackson.module.blackbird.codegen; opens tools.jackson.module.blackbird.deser; - opens tools.jackson.module.blackbird.deser.access; opens tools.jackson.module.blackbird.deser.convert; opens tools.jackson.module.blackbird.deser.filter; opens tools.jackson.module.blackbird.deser.inject; @@ -36,6 +33,7 @@ opens tools.jackson.module.blackbird.deser.shadowed.owner; opens tools.jackson.module.blackbird.deser.struct; opens tools.jackson.module.blackbird.format; + opens tools.jackson.module.blackbird.internal; opens tools.jackson.module.blackbird.misc; opens tools.jackson.module.blackbird.objectid; opens tools.jackson.module.blackbird.roundtrip; @@ -44,9 +42,8 @@ opens tools.jackson.module.blackbird.ser.otherpackage; opens tools.jackson.module.blackbird.testutil; opens tools.jackson.module.blackbird.testutil.failure; - opens tools.jackson.module.blackbird.util; provides tools.jackson.databind.JacksonModule with - tools.jackson.module.blackbird.BlackbirdModule; + tools.jackson.module.blackbird.internal.DiscoveredBlackbirdModule; uses tools.jackson.databind.JacksonModule; } diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/BBCodecProgrammaticViewTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/BBCodecProgrammaticViewTest.java new file mode 100644 index 000000000..d38d13fee --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/BBCodecProgrammaticViewTest.java @@ -0,0 +1,212 @@ +package tools.jackson.module.blackbird; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonView; + +import tools.jackson.databind.BeanDescription; +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.DeserializationConfig; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.PropertyName; +import tools.jackson.databind.SerializationConfig; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.deser.BeanDeserializerBuilder; +import tools.jackson.databind.deser.SettableBeanProperty; +import tools.jackson.databind.deser.ValueDeserializerModifier; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.ser.BeanSerializerBuilder; +import tools.jackson.databind.ser.ValueSerializerModifier; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Views assigned in code rather than through {@code @JsonView}: a + * deserializer modifier that sets a property's views, and a serializer + * modifier that replaces the filtered-writer array. Stock honors both under + * an active view, so the generated codecs must too; every case compares + * against a vanilla mapper with the same modifier. + */ +public class BBCodecProgrammaticViewTest extends BlackbirdTestBase +{ + public static class Public { } + public static class Admin { } + + public static class RoleBean { + public String name; + public String role; + } + + public record RoleRecord(String name, String role) { } + + @JsonDeserialize(builder = RoleBuilt.Builder.class) + public static class RoleBuilt { + final String name, role; + + RoleBuilt(String name, String role) { + this.name = name; + this.role = role; + } + + public static class Builder { + private String name, role; + + public Builder withName(String v) { name = v; return this; } + public Builder withRole(String v) { role = v; return this; } + public RoleBuilt build() { return new RoleBuilt(name, role); } + } + } + + public static class Person { + public String name = "Bob"; + public int age = 42; + public String ssn = "123-45-6789"; + } + + public static class Account { + public String name = "acct"; + @JsonView(Admin.class) + public String secret = "s3"; + } + + private static final String ROLE_DOC = "{\"name\":\"x\",\"role\":\"admin\"}"; + + // Restricts "role" to the Admin view. + private static SimpleModule roleViews() { + return new SimpleModule("roleViews").setDeserializerModifier(new ValueDeserializerModifier() { + private static final long serialVersionUID = 1L; + + @Override + public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, + BeanDescription.Supplier beanDescRef, BeanDeserializerBuilder builder) { + SettableBeanProperty role = builder.findProperty(PropertyName.construct("role")); + if (role != null) { + role.setViews(new Class[] { Admin.class }); + } + return builder; + } + }); + } + + // Leaves "ssn" out of the filtered writers (Person); with default + // inclusion off, makes "name" visible in every view (Account). + private static SimpleModule filteredWriters() { + return new SimpleModule("filteredWriters").setSerializerModifier(new ValueSerializerModifier() { + private static final long serialVersionUID = 1L; + + @Override + public BeanSerializerBuilder updateBuilder(SerializationConfig config, + BeanDescription.Supplier beanDesc, BeanSerializerBuilder builder) { + Class raw = beanDesc.getBeanClass(); + if (raw != Person.class && raw != Account.class) { + return builder; + } + List props = builder.getProperties(); + BeanPropertyWriter[] filtered = (builder.getFilteredProperties() == null) + ? props.toArray(new BeanPropertyWriter[0]) + : builder.getFilteredProperties().clone(); + for (int i = 0; i < filtered.length; i++) { + String name = props.get(i).getName(); + if (raw == Person.class && name.equals("ssn")) { + filtered[i] = null; + } else if (raw == Account.class && name.equals("name")) { + filtered[i] = props.get(i); + } + } + builder.setFilteredProperties(filtered); + return builder; + } + }); + } + + // With default inclusion on (off by default in 3.x), unannotated + // properties carry no views, so nothing but the views set in code can + // hide "role". + @Test + public void testReaderHonorsViewsSetInUpdateBuilder() throws Exception { + ObjectMapper vanilla = JsonMapper.builder().addModule(roleViews()) + .enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build(); + ObjectMapper module = mapperBuilder().addModule(roleViews()) + .enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build(); + + RoleBean eb = vanilla.readerWithView(Public.class).forType(RoleBean.class).readValue(ROLE_DOC); + RoleBean ab = module.readerWithView(Public.class).forType(RoleBean.class).readValue(ROLE_DOC); + assertNull(eb.role); + assertEquals(eb.name, ab.name); + assertEquals(eb.role, ab.role); + assertGeneratedReader(module, RoleBean.class); + + RoleRecord er = vanilla.readerWithView(Public.class).forType(RoleRecord.class).readValue(ROLE_DOC); + RoleRecord ar = module.readerWithView(Public.class).forType(RoleRecord.class).readValue(ROLE_DOC); + assertNull(er.role()); + assertEquals(er, ar); + assertGeneratedReader(module, RoleRecord.class); + + RoleBuilt ec = vanilla.readerWithView(Public.class).forType(RoleBuilt.class).readValue(ROLE_DOC); + RoleBuilt ac = module.readerWithView(Public.class).forType(RoleBuilt.class).readValue(ROLE_DOC); + assertNull(ec.role); + assertEquals(ec.name, ac.name); + assertEquals(ec.role, ac.role); + assertGeneratedReader(module, RoleBuilt.class); + + // Admin sees the property in both. + RoleBean admin = module.readerWithView(Admin.class).forType(RoleBean.class).readValue(ROLE_DOC); + assertEquals("admin", admin.role); + } + + @Test + public void testReaderFailsOnUnexpectedViewPropertySetInCode() throws Exception { + for (Class type : new Class[] { RoleBean.class, RoleRecord.class, RoleBuilt.class }) { + ObjectMapper vanilla = JsonMapper.builder().addModule(roleViews()) + .enable(MapperFeature.DEFAULT_VIEW_INCLUSION) + .enable(DeserializationFeature.FAIL_ON_UNEXPECTED_VIEW_PROPERTIES).build(); + ObjectMapper module = mapperBuilder().addModule(roleViews()) + .enable(MapperFeature.DEFAULT_VIEW_INCLUSION) + .enable(DeserializationFeature.FAIL_ON_UNEXPECTED_VIEW_PROPERTIES).build(); + DatabindException expected = assertThrows(DatabindException.class, + () -> vanilla.readerWithView(Public.class).forType(type).readValue(ROLE_DOC)); + DatabindException actual = assertThrows(DatabindException.class, + () -> module.readerWithView(Public.class).forType(type).readValue(ROLE_DOC), + type.getSimpleName()); + assertEquals(expected.getClass(), actual.getClass()); + } + } + + // Default inclusion on: no property declares a view, so the filtered + // array exists only because the modifier set one. + @Test + public void testWriterHonorsFilteredWritersFromUpdateBuilder() throws Exception { + ObjectMapper vanilla = JsonMapper.builder().addModule(filteredWriters()) + .enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build(); + ObjectMapper module = mapperBuilder().addModule(filteredWriters()) + .enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build(); + String expected = vanilla.writerWithView(Public.class).writeValueAsString(new Person()); + assertFalse(expected.contains("ssn"), expected); + assertEquals(expected, module.writerWithView(Public.class).writeValueAsString(new Person())); + // Without a view the filtered writers do not apply. + assertEquals(vanilla.writeValueAsString(new Person()), + module.writeValueAsString(new Person())); + assertGeneratedWriter(module, Person.class); + } + + @Test + public void testWriterHonorsWriterAddedToFilteredArray() throws Exception { + ObjectMapper vanilla = JsonMapper.builder().addModule(filteredWriters()) + .disable(MapperFeature.DEFAULT_VIEW_INCLUSION).build(); + ObjectMapper module = mapperBuilder().addModule(filteredWriters()) + .disable(MapperFeature.DEFAULT_VIEW_INCLUSION).build(); + for (Class view : new Class[] { Public.class, Admin.class }) { + String expected = vanilla.writerWithView(view).writeValueAsString(new Account()); + assertTrue(expected.contains("\"name\""), expected); + assertEquals(expected, module.writerWithView(view).writeValueAsString(new Account()), + view.getSimpleName()); + } + assertGeneratedWriter(module, Account.class); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/BlackbirdTestBase.java b/blackbird/src/test/java/tools/jackson/module/blackbird/BlackbirdTestBase.java index bebd9c213..ef6467009 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/BlackbirdTestBase.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/BlackbirdTestBase.java @@ -1,18 +1,31 @@ package tools.jackson.module.blackbird; -import java.lang.invoke.MethodHandles; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.function.Function; +import java.util.Map; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import tools.jackson.core.*; +import tools.jackson.databind.BeanDescription; +import tools.jackson.databind.DeserializationConfig; +import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationConfig; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.deser.ValueDeserializerModifier; import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.databind.ser.ValueSerializerModifier; import tools.jackson.module.blackbird.testutil.NoCheckSubTypeValidator; +import tools.jackson.module.blackbird.codegen.CodegenFallbacks; +import tools.jackson.module.blackbird.internal.GeneratedBuilderBase; +import tools.jackson.module.blackbird.internal.GeneratedReaderBase; +import tools.jackson.module.blackbird.internal.GeneratedWriterBase; import static org.junit.jupiter.api.Assertions.*; @@ -215,21 +228,148 @@ protected static JsonMapper.Builder blackbirdMapperBuilder() { return mapperBuilder(); } + static { + // The suite runs strict: a codec-generation failure fails the test + // instead of demoting to the stock path, so the lenient production + // fallback cannot mask a generator bug here. Surefire sets this for + // every test class. Outside Maven (an IDE) this takes effect only if + // it runs before Blackbird's codegen classes first load, which read + // the property once. + System.setProperty(CodegenFallbacks.FAIL_ON_ERROR_PROPERTY, "true"); + } + protected static JsonMapper.Builder mapperBuilder() { return JsonMapper.builder() .polymorphicTypeValidator(new NoCheckSubTypeValidator()) .addModule(new BlackbirdModule()); } - // for tests that need the module to use particular lookups - protected static JsonMapper.Builder mapperBuilder(Function, MethodHandles.Lookup> lookups) { + protected static JsonMapper newVanillaJSONMapper() { + return new JsonMapper(); + } + + /** + * Builder for a Blackbird mapper that records, per bean class, what + * Blackbird's modifiers installed: the placeholder, or the stock + * (de)serializer when a gate skipped the bean. Either map may be null. + * Modifiers run in reverse registration order, so the capture module + * registers before Blackbird to observe its result. + */ + protected static JsonMapper.Builder capturingMapperBuilder( + Map, ValueDeserializer> desers, Map, ValueSerializer> sers) { + return capturingMapperBuilder(new BlackbirdModule(), desers, sers); + } + + /** As {@link #capturingMapperBuilder(Map, Map)}, with the given Blackbird module. */ + protected static JsonMapper.Builder capturingMapperBuilder(BlackbirdModule blackbird, + Map, ValueDeserializer> desers, Map, ValueSerializer> sers) { + SimpleModule capture = new SimpleModule("capture"); + if (desers != null) { + capture.setDeserializerModifier(new ValueDeserializerModifier() { + private static final long serialVersionUID = 1L; + @Override + public ValueDeserializer modifyDeserializer(DeserializationConfig cfg, + BeanDescription.Supplier ref, ValueDeserializer d) { + desers.put(ref.getBeanClass(), d); + return d; + } + }); + } + if (sers != null) { + capture.setSerializerModifier(new ValueSerializerModifier() { + private static final long serialVersionUID = 1L; + @Override + public ValueSerializer modifySerializer(SerializationConfig cfg, + BeanDescription.Supplier ref, ValueSerializer s) { + sers.put(ref.getBeanClass(), s); + return s; + } + }); + } return JsonMapper.builder() - .polymorphicTypeValidator(new NoCheckSubTypeValidator()) - .addModule(new BlackbirdModule(lookups)); + .addModule(capture) + .addModule(blackbird); } - protected static JsonMapper newVanillaJSONMapper() { - return new JsonMapper(); + /** Blackbird mapper recording installed deserializers; see {@link #capturingMapperBuilder}. */ + protected static ObjectMapper capturingMapper(Map, ValueDeserializer> desers) { + return capturingMapperBuilder(desers, null).build(); + } + + /** Blackbird mapper recording installed serializers; see {@link #capturingMapperBuilder}. */ + protected static ObjectMapper capturingWriterMapper(Map, ValueSerializer> sers) { + return capturingMapperBuilder(null, sers).build(); + } + + /* + /********************************************************** + /* Helper methods; codec engagement + /********************************************************** + */ + + /** The (contextualized) deserializer the mapper uses for a root value of the type. */ + protected static ValueDeserializer rootReader(ObjectMapper mapper, Class type) { + DeserializationContext ctxt = mapper._deserializationContext(); + return ctxt.findRootValueDeserializer(ctxt.constructType(type)); + } + + /** The (contextualized) serializer the mapper uses for a root value of the type. */ + protected static ValueSerializer rootWriter(ObjectMapper mapper, Class type) { + SerializationContext ctxt = mapper._serializationContext(); + return ctxt.handleRootContextualization(ctxt.findValueSerializer(type)); + } + + /** + * Whether a (de)serializer that a modifier captured resolved to a + * generated codec. The modifier captures the placeholder either way, and + * only its codec field tells a generated bean from a gated one; + * contextualization (rootReader/rootWriter) hands out the codec or, for + * a gated bean, a stock (de)serializer instead. + */ + protected static boolean hasGeneratedCodec(Object captured) { + try { + java.lang.reflect.Field codec = captured.getClass().getDeclaredField("_codec"); + codec.setAccessible(true); + return codec.get(captured) != null; + } catch (NoSuchFieldException e) { + return false; + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + + /** Asserts that the generated reader handles the type, and returns its arm layout. */ + protected static String assertGeneratedReader(ObjectMapper mapper, Class type) { + ValueDeserializer d = rootReader(mapper, type); + if (d instanceof GeneratedReaderBase r) { + return r.codecLayout(); + } + if (d instanceof GeneratedBuilderBase b) { + return b.codecLayout(); + } + return fail(type.getSimpleName() + " should use a generated reader, uses " + d); + } + + /** Asserts that the stock deserializer handles the type. */ + protected static void assertStockReader(ObjectMapper mapper, Class type) { + ValueDeserializer d = rootReader(mapper, type); + assertFalse(d instanceof GeneratedReaderBase || d instanceof GeneratedBuilderBase, + type.getSimpleName() + " should keep the stock deserializer, uses " + d); + } + + /** Asserts that the generated writer handles the type, and returns its layout. */ + protected static String assertGeneratedWriter(ObjectMapper mapper, Class type) { + ValueSerializer s = rootWriter(mapper, type); + assertTrue(s instanceof GeneratedWriterBase, + type.getSimpleName() + " should use a generated writer, uses " + s); + return ((GeneratedWriterBase) s).codecLayout(); + } + + /** Asserts that the stock serializer handles the type. */ + protected static void assertStockWriter(ObjectMapper mapper, Class type) { + ValueSerializer s = rootWriter(mapper, type); + assertFalse(s instanceof GeneratedWriterBase, + type.getSimpleName() + " should keep the stock serializer, uses " + s); } /* diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/ModuleSPIMetadataTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/ModuleSPIMetadataTest.java index af6acafe2..792ec5c70 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/ModuleSPIMetadataTest.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/ModuleSPIMetadataTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Test; import tools.jackson.databind.JacksonModule; +import tools.jackson.module.blackbird.internal.DiscoveredBlackbirdModule; import static org.junit.jupiter.api.Assertions.*; @@ -17,7 +18,9 @@ void testModuleSPIMetadata() { = ServiceLoader.load(JacksonModule.class); assertTrue(loader.iterator().hasNext(), "Expected at least one `Module` implementation to be found via `ServiceLoader`"); - final String exp = BlackbirdModule.class.getName(); + // Discovery instantiates the dedicated provider subclass (see + // DiscoveredBlackbirdModule), not BlackbirdModule itself. + final String exp = DiscoveredBlackbirdModule.class.getName(); int count = 0; try { diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/TestAccessFallback.java b/blackbird/src/test/java/tools/jackson/module/blackbird/TestAccessFallback.java index 3ea658297..acc6c95ca 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/TestAccessFallback.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/TestAccessFallback.java @@ -15,13 +15,15 @@ public BogusTestError(String msg) { } } - // Getter fails when called through the generated accessor, and works when - // called by the fallback writer + // Getter that fails on request, counting its calls. Generated writers are + // hidden classes, left out of stack traces, so the calls can't tell which + // path made them; `assertGeneratedWriter` checks that instead. public static class MyBean { private String e; - int optimizedCalls, fallbackCalls; + boolean fail; + int calls; public MyBean() { } @@ -37,17 +39,9 @@ public void setE(String e) public String getE() { - // nearest of databind's `BeanPropertyWriter` (which calls the getter through - // a method handle) or a Blackbird frame tells which path called - for (StackTraceElement elem : new Throwable().getStackTrace()) { - if (elem.getClassName().equals("tools.jackson.databind.ser.BeanPropertyWriter")) { - ++fallbackCalls; - return e; - } - if (elem.getClassName().startsWith("tools.jackson.module.blackbird.ser.")) { - ++optimizedCalls; - throw new BogusTestError("boom!"); - } + ++calls; + if (fail) { + throw new BogusTestError("boom!"); } return e; } @@ -59,17 +53,35 @@ public String getE() public void testSerializeAccess() throws Exception { ObjectMapper abMapper = newObjectMapper(); + assertGeneratedWriter(abMapper, MyBean.class); MyBean bean = new MyBean("a"); assertEquals(BEAN_JSON, abMapper.writeValueAsString(bean)); - // the generated getter failed, and the value was then written by the fallback writer - assertEquals(1, bean.optimizedCalls); - assertEquals(1, bean.fallbackCalls); + assertEquals(1, bean.calls); + } + + // A failing getter is called once, not retried through the stock writer, + // and its failure is reported as plain databind reports it + @Test + public void testFailingGetterIsNotRetried() throws Exception + { + MyBean expectedBean = new MyBean("a"); + expectedBean.fail = true; + Throwable expected = assertThrows(Throwable.class, + () -> newVanillaJSONMapper().writeValueAsString(expectedBean)); + assertEquals(1, expectedBean.calls); - // after that, the fallback writer is used directly - MyBean bean2 = new MyBean("a"); - assertEquals(BEAN_JSON, abMapper.writeValueAsString(bean2)); - assertEquals(0, bean2.optimizedCalls); - assertEquals(1, bean2.fallbackCalls); + ObjectMapper abMapper = newObjectMapper(); + // twice, as a failure must not change how later writes go + for (int i = 0; i < 2; ++i) { + MyBean bean = new MyBean("a"); + bean.fail = true; + Throwable actual = assertThrows(Throwable.class, () -> abMapper.writeValueAsString(bean)); + assertEquals(1, bean.calls); + assertEquals(expected.getClass(), actual.getClass()); + assertEquals(expected.getMessage(), actual.getMessage()); + assertEquals(String.valueOf(expected.getCause()), String.valueOf(actual.getCause())); + } + assertEquals(BEAN_JSON, abMapper.writeValueAsString(new MyBean("a"))); } // 18-Jan-2025, tatu: Fails on JDK-17/JPMS, for reasons unknown. diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/ArrayShapeNoCodecTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/ArrayShapeNoCodecTest.java new file mode 100644 index 000000000..bc4266bc2 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/ArrayShapeNoCodecTest.java @@ -0,0 +1,66 @@ +package tools.jackson.module.blackbird.codegen; + +import java.nio.file.Files; +import java.nio.file.Path; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * An array-shaped bean contextualizes to the stock as-array (de)serializer, + * which never calls a codec, so none is generated for it. Generation is + * observed through the class dump. + */ +public class ArrayShapeNoCodecTest extends BlackbirdTestBase +{ + @JsonFormat(shape = JsonFormat.Shape.ARRAY) + @JsonPropertyOrder({ "a", "b" }) + public static class AsArray { + public int a; + public String b; + } + + public static class AsObject { + public int a; + public String b; + } + + @TempDir + Path dumpDir; + + @Test + public void arrayShapeGeneratesNoCodec() throws Exception { + String old = System.setProperty(CodegenDebug.DUMP_DIR_PROPERTY, dumpDir.toString()); + try { + ObjectMapper mapper = newObjectMapper(); + AsArray v = mapper.readValue("[1,\"x\"]", AsArray.class); + assertEquals(1, v.a); + assertEquals("x", v.b); + assertEquals("[1,\"x\"]", mapper.writeValueAsString(v)); + mapper.readValue("{\"a\":1,\"b\":\"x\"}", AsObject.class); + mapper.writeValueAsString(new AsObject()); + } finally { + if (old == null) { + System.clearProperty(CodegenDebug.DUMP_DIR_PROPERTY); + } else { + System.setProperty(CodegenDebug.DUMP_DIR_PROPERTY, old); + } + } + assertFalse(Files.exists(dump(AsArray.class, "reader"))); + assertFalse(Files.exists(dump(AsArray.class, "writer"))); + assertTrue(Files.exists(dump(AsObject.class, "reader"))); + assertTrue(Files.exists(dump(AsObject.class, "writer"))); + } + + private Path dump(Class type, String kind) { + return dumpDir.resolve(type.getName() + "-" + kind + ".class"); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/CodegenDumpTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/CodegenDumpTest.java new file mode 100644 index 000000000..aab533cd4 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/CodegenDumpTest.java @@ -0,0 +1,51 @@ +package tools.jackson.module.blackbird.codegen; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * A failing class dump (here an invalid dump directory, which fails with an + * unchecked InvalidPathException rather than an IOException) must never + * affect generation. The direct {@code CodegenDump.dump} call stands for the + * dump of the shared write helpers, which runs just before they are defined: + * they are defined once per JVM, usually by an earlier test, so this test + * cannot reach that define itself. + */ +public class CodegenDumpTest extends BlackbirdTestBase +{ + public static class DumpBean { + public int a = 1; + public String b = "x"; + } + + public static class LaterBean { + public int c = 2; + } + + private static final String BAD_DIR = "bad\0dir"; + + @Test + public void testDumpFailureIsIgnored() { + String old = System.getProperty(CodegenDebug.DUMP_DIR_PROPERTY); + System.setProperty(CodegenDebug.DUMP_DIR_PROPERTY, BAD_DIR); + try { + CodegenDump.dump("Stem", new byte[0]); + ObjectMapper mapper = newObjectMapper(); + assertEquals("{\"a\":1,\"b\":\"x\"}", mapper.writeValueAsString(new DumpBean())); + assertGeneratedWriter(mapper, DumpBean.class); + } finally { + if (old == null) { + System.clearProperty(CodegenDebug.DUMP_DIR_PROPERTY); + } else { + System.setProperty(CodegenDebug.DUMP_DIR_PROPERTY, old); + } + } + ObjectMapper mapper = newObjectMapper(); + assertEquals("{\"c\":2}", mapper.writeValueAsString(new LaterBean())); + assertGeneratedWriter(mapper, LaterBean.class); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/HiddenBeanClassTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/HiddenBeanClassTest.java new file mode 100644 index 000000000..5451f095b --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/HiddenBeanClassTest.java @@ -0,0 +1,82 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * A hidden bean class (as bytecode-generating frameworks define) has a + * name like {@code Foo/0x1234}; generated codecs are named after the bean, + * and '/' must not end up in their binary name, nor in the names of their + * dumped class files. + */ +public class HiddenBeanClassTest extends BlackbirdTestBase +{ + // public class HiddenBean { public int x; public String name; } + private static Class defineHiddenBean() throws Exception { + ClassDesc self = ClassDesc.of(HiddenBeanClassTest.class.getPackageName(), "HiddenBean"); + byte[] bytes = ClassFile.of().build(self, clb -> clb + .withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_SUPER) + .withField("x", ConstantDescs.CD_int, ClassFile.ACC_PUBLIC) + .withField("name", ConstantDescs.CD_String, ClassFile.ACC_PUBLIC) + .withMethodBody(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, + ClassFile.ACC_PUBLIC, cob -> cob.aload(0) + .invokespecial(ConstantDescs.CD_Object, + ConstantDescs.INIT_NAME, ConstantDescs.MTD_void) + .return_())); + return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass(); + } + + @Test + public void testHiddenBeanClass() throws Exception { + Class type = defineHiddenBean(); + assertTrue(type.isHidden()); + assertTrue(type.getName().contains("/"), type.getName()); + + ObjectMapper mapper = newObjectMapper(); + assertGeneratedReader(mapper, type); + assertGeneratedWriter(mapper, type); + + Object bean = mapper.readValue(a2q("{'x':3,'name':'a'}"), type); + assertEquals(3, type.getField("x").getInt(bean)); + assertEquals("a", type.getField("name").get(bean)); + assertEquals(newVanillaJSONMapper().writeValueAsString(bean), + mapper.writeValueAsString(bean)); + } + + @Test + public void testHiddenBeanClassDump(@TempDir Path dir) throws Exception { + String old = System.getProperty(CodegenDebug.DUMP_DIR_PROPERTY); + System.setProperty(CodegenDebug.DUMP_DIR_PROPERTY, dir.toString()); + try { + Class type = defineHiddenBean(); + ObjectMapper mapper = newObjectMapper(); + assertGeneratedReader(mapper, type); + assertGeneratedWriter(mapper, type); + } finally { + if (old == null) { + System.clearProperty(CodegenDebug.DUMP_DIR_PROPERTY); + } else { + System.setProperty(CodegenDebug.DUMP_DIR_PROPERTY, old); + } + } + try (Stream files = Files.list(dir)) { + String names = files.map(f -> f.getFileName().toString()).sorted() + .reduce("", (a, b) -> a + " " + b); + assertTrue(names.matches(".*HiddenBean\\$0x[0-9a-f]+-reader\\.class.*"), names); + assertTrue(names.matches(".*HiddenBean\\$0x[0-9a-f]+-writer\\.class.*"), names); + } + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/PropertyNameSanitizingTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/PropertyNameSanitizingTest.java new file mode 100644 index 000000000..b68b9fac8 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/PropertyNameSanitizingTest.java @@ -0,0 +1,48 @@ +package tools.jackson.module.blackbird.codegen; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Class-data entries are named after JSON property names, which may hold + * characters the JVM forbids in member names; those must not keep a bean + * from getting its generated codecs. + */ +public class PropertyNameSanitizingTest extends BlackbirdTestBase +{ + public static class OddNames { + @JsonProperty("a") + public String angles = "x"; + @JsonProperty("") + public int init = 1; + @JsonProperty("p.q;r[s/t") + public String others = "y"; + // sanitizes to the same name as "p.q;r[s/t" + @JsonProperty("p$q$r$s$t") + public String collides = "z"; + } + + @Test + public void testForbiddenCharactersInPropertyNames() throws Exception { + ObjectMapper mapper = newObjectMapper(); + assertGeneratedReader(mapper, OddNames.class); + assertGeneratedWriter(mapper, OddNames.class); + + String json = mapper.writeValueAsString(new OddNames()); + assertEquals(newVanillaJSONMapper().writeValueAsString(new OddNames()), json); + + OddNames result = mapper.readValue(a2q( + "{'a':'1','':2,'p.q;r[s/t':'3','p$q$r$s$t':'4'}"), + OddNames.class); + assertEquals("1", result.angles); + assertEquals(2, result.init); + assertEquals("3", result.others); + assertEquals("4", result.collides); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/ReaderPlanCheckTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/ReaderPlanCheckTest.java new file mode 100644 index 000000000..3f4f5871a --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/ReaderPlanCheckTest.java @@ -0,0 +1,37 @@ +package tools.jackson.module.blackbird.codegen; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import tools.jackson.module.blackbird.BlackbirdTestBase; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.Construction; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.GenProp; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.Kind; +import tools.jackson.module.blackbird.codegen.ReaderCodegen.ViewStrategy; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Outside record mode, only STOCK arms go without a generated store. A + * factory that breaks this fails generation, so strict-mode tests report it, + * instead of the arm quietly taking the stock path. + */ +public class ReaderPlanCheckTest extends BlackbirdTestBase +{ + public static class Bean { + public int x; + } + + @Test + public void scalarArmWithoutStoreFails() { + for (Kind kind : new Kind[] { Kind.STRING, Kind.INT, Kind.LONG, Kind.BOOLEAN, + Kind.CHILD }) { + IllegalStateException e = assertThrows(IllegalStateException.class, + () -> ReaderCodegen.generate(Bean.class, List.of(new GenProp("x", kind, null)), + null, new int[] { 0 }, null, null, Construction.instantiator(null), + false, false, ViewStrategy.NONE, null)); + assertTrue(e.getMessage().contains("no store handle for " + kind), e.getMessage()); + } + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/SharedWriteHelpersTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/SharedWriteHelpersTest.java new file mode 100644 index 000000000..a5ed40fb3 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/codegen/SharedWriteHelpersTest.java @@ -0,0 +1,94 @@ +package tools.jackson.module.blackbird.codegen; + +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.MethodModel; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * All generated writers call one class of write helpers, padded past the + * HotSpot inline limits (see {@code BeanWriterGenerator.INLINE_PAD}). + */ +public class SharedWriteHelpersTest extends BlackbirdTestBase +{ + // The HotSpot default of FreqInlineSize (C2) on every platform. The C1 + // limit, C1MaxInlineSize, is 35 bytes. + private static final int FREQ_INLINE_SIZE = 325; + + private static final List HELPERS = List.of("$bool", "$int", "$long", "$name", "$str"); + + public static class Point { + public String name = "p"; + public int x = 1; + public long y = 2L; + public boolean on = true; + public Point next; + } + + public static class Tag { + public String text; + public int size = 3; + public Point at = new Point(); + } + + @Test + public void writersCallOneSharedHelperClass() throws Exception { + ObjectMapper mapper = newObjectMapper(); + assertGeneratedWriter(mapper, Point.class); + assertGeneratedWriter(mapper, Tag.class); + Class pointWriter = rootWriter(mapper, Point.class).getClass(); + Class tagWriter = rootWriter(mapper, Tag.class).getClass(); + assertEquals(List.of(), helperMethods(pointWriter)); + assertEquals(List.of(), helperMethods(tagWriter)); + + Class helpers = Class.forName(WriterCodegen.HELPERS_CLASS, false, + pointWriter.getClassLoader()); + assertFalse(helpers.isHidden()); + assertSame(pointWriter.getModule(), helpers.getModule()); + assertEquals(pointWriter.getPackageName(), helpers.getPackageName()); + assertEquals(HELPERS, helperMethods(helpers)); + } + + @Test + public void helpersWriteAsStock() { + Tag tag = new Tag(); + assertEquals(newVanillaJSONMapper().writeValueAsString(tag), + newObjectMapper().writeValueAsString(tag)); + } + + @Test + public void helpersExceedTheInlineLimits() { + ClassModel model = ClassFile.of().parse(ClassFileEmitters.emitWriteHelpers()); + Map codeLengths = new TreeMap<>(); + for (MethodModel m : model.methods()) { + codeLengths.put(m.methodName().stringValue(), + m.findAttribute(Attributes.code()).orElseThrow().codeLength()); + } + assertEquals(HELPERS, List.copyOf(codeLengths.keySet())); + codeLengths.forEach((name, length) -> assertTrue(length > FREQ_INLINE_SIZE, + name + " has " + length + " bytes of bytecode, so C2 can inline it")); + } + + // Synthetic methods are left out: a coverage agent such as JaCoCo adds + // $jacocoInit to the helper class when it is defined. + private static List helperMethods(Class type) { + return Arrays.stream(type.getDeclaredMethods()) + .filter(m -> !m.isSynthetic()) + .map(Method::getName) + .filter(n -> n.startsWith("$")) + .sorted() + .toList(); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecAnySetterTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecAnySetterTest.java new file mode 100644 index 000000000..cd183b4c5 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecAnySetterTest.java @@ -0,0 +1,146 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.exc.IgnoredPropertyException; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Any-setter parity: beans with {@code @JsonAnySetter} engage codecs, and the + * unknown arm feeds the stock any-setter in handleUnknownVanilla's exact + * order (explicit ignorals first, then the any-setter, which also consumes + * names that ignoreUnknown would otherwise skip). Every case compares against + * vanilla. Records with an any-setter stay on the stock path: their values + * buffer before construction, which the typed-locals loop does not model. + */ +public class BBCodecAnySetterTest extends BlackbirdTestBase +{ + public static class AnyBean { + public int i; + public String s; + public Map extra = new HashMap<>(); + + @JsonAnySetter + public void set(String name, Object value) { + extra.put(name, value); + } + } + + @JsonIgnoreProperties(value = { "secret" }, ignoreUnknown = true) + public static class AnyWithIgnoralsBean { + public int i; + public Map extra = new HashMap<>(); + + @JsonAnySetter + public void set(String name, Object value) { + extra.put(name, value); + } + } + + public static class AnyFieldBean { + public int i; + + @JsonAnySetter + public Map extra = new HashMap<>(); + } + + public record AnyRecord(String name) { + @JsonAnySetter + public void set(String n, Object v) { } + } + + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + private static void assertEngaged(Map, ValueDeserializer> seen, Class cls, + boolean engaged) { + ValueDeserializer d = seen.get(cls); + assertNotNull(d, "no deserializer captured for " + cls); + assertEquals(engaged, hasGeneratedCodec(d), + cls.getSimpleName() + " engagement"); + } + + @Test + public void testAnySetterCollectsUnknowns() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':1,'x':'a','s':'known','deep':{'k':[1,2]},'n':null}"); + AnyBean v = vanilla.readValue(doc, AnyBean.class); + AnyBean m = mapper.readValue(doc, AnyBean.class); + assertEngaged(seen, AnyBean.class, true); + assertEquals(v.i, m.i); + assertEquals(v.s, m.s); + assertEquals(v.extra, m.extra); + } + + @Test + public void testAnySetterBeatsStrictUnknowns() throws Exception { + // With an any-setter, nothing is "unknown": per-call + // FAIL_ON_UNKNOWN_PROPERTIES never fires, exactly like stock. + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':1,'x':'a'}"); + AnyBean v = vanilla.reader(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .forType(AnyBean.class).readValue(doc); + AnyBean m = mapper.reader(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .forType(AnyBean.class).readValue(doc); + assertEngaged(seen, AnyBean.class, true); + assertEquals(v.extra, m.extra); + } + + @Test + public void testIgnoredNameNeverReachesAnySetter() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':1,'secret':'boo','x':'a'}"); + AnyWithIgnoralsBean v = vanilla.readValue(doc, AnyWithIgnoralsBean.class); + AnyWithIgnoralsBean m = mapper.readValue(doc, AnyWithIgnoralsBean.class); + assertEngaged(seen, AnyWithIgnoralsBean.class, true); + assertEquals(v.i, m.i); + assertEquals(v.extra, m.extra); + assertFalse(m.extra.containsKey("secret")); + + IgnoredPropertyException ve = assertThrows(IgnoredPropertyException.class, + () -> vanilla.reader(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES) + .forType(AnyWithIgnoralsBean.class).readValue(doc)); + IgnoredPropertyException me = assertThrows(IgnoredPropertyException.class, + () -> mapper.reader(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES) + .forType(AnyWithIgnoralsBean.class).readValue(doc)); + assertEquals(ve.getPropertyName(), me.getPropertyName()); + } + + @Test + public void testAnySetterField() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':2,'a':1,'b':'two'}"); + AnyFieldBean v = vanilla.readValue(doc, AnyFieldBean.class); + AnyFieldBean m = mapper.readValue(doc, AnyFieldBean.class); + assertEngaged(seen, AnyFieldBean.class, true); + assertEquals(v.i, m.i); + assertEquals(v.extra, m.extra); + } + + @Test + public void testRecordWithAnySetterStaysStock() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'name':'a','other':1}"); + AnyRecord v = vanilla.readValue(doc, AnyRecord.class); + AnyRecord m = mapper.readValue(doc, AnyRecord.class); + // The placeholder wraps the record; the factory gates it at resolve. + assertStockReader(mapper, AnyRecord.class); + assertEquals(v, m); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecCompatibilityTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecCompatibilityTest.java new file mode 100644 index 000000000..087770a0b --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecCompatibilityTest.java @@ -0,0 +1,261 @@ +package tools.jackson.module.blackbird.deser; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonUnwrapped; + +import org.junit.jupiter.api.Test; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.DeserializationProblemHandler; +import tools.jackson.databind.exc.MismatchedInputException; +import tools.jackson.databind.exc.UnrecognizedPropertyException; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Behavior-parity regression tests against stock databind: quoted-scalar + * coercion, unknown-property handling under per-call config and problem + * handlers, record required and missing components, {@code @JsonUnwrapped} + * with codec-eligible children, PROPERTY_NAME entry, and modifier + * serializability. Every case compares against a vanilla mapper. + */ +public class BBCodecCompatibilityTest extends BlackbirdTestBase +{ + public static class ScalarBean { + public int i; + public long l; + public boolean b; + public String s; + } + + public record ReqRecord(@JsonProperty(required = true) String name, int count) { } + + public record PlainRecord(String name, int count) { } + + public static class Child { + public int value; + public String tag; + } + + public static class UnwrappedParent { + public int a; + @JsonUnwrapped + public Child child; + } + + public static class PrefixedParent { + public int a; + @JsonUnwrapped(prefix = "foo.") + public Child child; + } + + @JsonIgnoreProperties({ "skipMe" }) + public static class IgnoralBean { + public int i; + } + + private final ObjectMapper mapper = newObjectMapper(); + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + @Test + public void testQuotedScalarCoercion() throws Exception { + String doc = a2q("{'i':'123','l':'456','b':'true','s':'x'}"); + ScalarBean exp = vanilla.readValue(doc, ScalarBean.class); + ScalarBean act = mapper.readValue(doc, ScalarBean.class); + assertEquals(exp.i, act.i); + assertEquals(exp.l, act.l); + assertEquals(exp.b, act.b); + assertEquals(exp.s, act.s); + } + + @Test + public void testFloatToIntCoercion() throws Exception { + String doc = a2q("{'i':1.5,'l':2.5}"); + ScalarBean exp = vanilla.readValue(doc, ScalarBean.class); + ScalarBean act = mapper.readValue(doc, ScalarBean.class); + assertEquals(exp.i, act.i); + assertEquals(exp.l, act.l); + } + + @Test + public void testScalarFromNumberToken() throws Exception { + // String property fed a number token: stock coerces, so must we. + String doc = a2q("{'s':42,'b':true,'i':7}"); + ScalarBean exp = vanilla.readValue(doc, ScalarBean.class); + ScalarBean act = mapper.readValue(doc, ScalarBean.class); + assertEquals(exp.s, act.s); + assertEquals(exp.b, act.b); + assertEquals(exp.i, act.i); + } + + @Test + public void testPerCallFailOnUnknownProperties() throws Exception { + String doc = a2q("{'i':1,'nope':{'x':2},'s':'y'}"); + // Default (disabled): unknown skips, other properties land. + ScalarBean ok = mapper.readValue(doc, ScalarBean.class); + assertEquals(1, ok.i); + assertEquals("y", ok.s); + // Per-call enable must fail, exactly like vanilla. + assertThrows(UnrecognizedPropertyException.class, () -> vanilla + .readerFor(ScalarBean.class) + .with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .readValue(doc)); + assertThrows(UnrecognizedPropertyException.class, () -> mapper + .readerFor(ScalarBean.class) + .with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .readValue(doc)); + } + + @Test + public void testProblemHandlerSeesUnknownProperty() throws Exception { + List seen = new ArrayList<>(); + ObjectMapper m = mapperBuilder().addHandler(new DeserializationProblemHandler() { + @Override + public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, + ValueDeserializer deserializer, Object beanOrClass, String propertyName) { + seen.add(propertyName); + p.skipChildren(); + return true; + } + }).build(); + ScalarBean bean = m.readValue(a2q("{'i':1,'mystery':[1,2]}"), ScalarBean.class); + assertEquals(1, bean.i); + assertEquals(List.of("mystery"), seen); + } + + @Test + public void testIgnoredPropertiesKeepStockSemantics() throws Exception { + String doc = a2q("{'i':1,'skipMe':{'x':2}}"); + IgnoralBean exp = vanilla.readerFor(IgnoralBean.class) + .with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).readValue(doc); + IgnoralBean act = mapper.readerFor(IgnoralBean.class) + .with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).readValue(doc); + assertEquals(exp.i, act.i); + assertThrows(UnrecognizedPropertyException.class, () -> mapper + .readerFor(IgnoralBean.class) + .with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .readValue(a2q("{'i':1,'other':2}"))); + } + + @Test + public void testRequiredRecordComponentMissing() throws Exception { + String doc = a2q("{'count':3}"); + MismatchedInputException expEx = assertThrows(MismatchedInputException.class, + () -> vanilla.readValue(doc, ReqRecord.class)); + MismatchedInputException actEx = assertThrows(MismatchedInputException.class, + () -> mapper.readValue(doc, ReqRecord.class)); + assertTrue(expEx.getMessage().contains("Missing required creator property 'name'"), + expEx.getMessage()); + assertTrue(actEx.getMessage().contains("Missing required creator property 'name'"), + actEx.getMessage()); + // Present case unaffected. + assertEquals(vanilla.readValue(a2q("{'name':'x','count':3}"), ReqRecord.class), + mapper.readValue(a2q("{'name':'x','count':3}"), ReqRecord.class)); + } + + @Test + public void testPerCallFailOnMissingCreatorProperties() throws Exception { + String doc = a2q("{'name':'x'}"); + // Default: missing component takes its default value. + assertEquals(vanilla.readValue(doc, PlainRecord.class), + mapper.readValue(doc, PlainRecord.class)); + assertThrows(MismatchedInputException.class, () -> vanilla + .readerFor(PlainRecord.class) + .with(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) + .readValue(doc)); + assertThrows(MismatchedInputException.class, () -> mapper + .readerFor(PlainRecord.class) + .with(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) + .readValue(doc)); + } + + @Test + public void testUnwrappedChildWithCodec() throws Exception { + UnwrappedParent p = new UnwrappedParent(); + p.a = 1; + p.child = new Child(); + p.child.value = 3; + p.child.tag = "t"; + String expJson = vanilla.writeValueAsString(p); + assertEquals(expJson, mapper.writeValueAsString(p)); + + UnwrappedParent exp = vanilla.readValue(expJson, UnwrappedParent.class); + UnwrappedParent act = mapper.readValue(expJson, UnwrappedParent.class); + assertNotNull(act.child); + assertEquals(exp.a, act.a); + assertEquals(exp.child.value, act.child.value); + assertEquals(exp.child.tag, act.child.tag); + } + + @Test + public void testPrefixedUnwrappedChildWithCodec() throws Exception { + PrefixedParent p = new PrefixedParent(); + p.a = 1; + p.child = new Child(); + p.child.value = 3; + p.child.tag = "t"; + String expJson = vanilla.writeValueAsString(p); + assertEquals(expJson, mapper.writeValueAsString(p)); + + PrefixedParent exp = vanilla.readValue(expJson, PrefixedParent.class); + PrefixedParent act = mapper.readValue(expJson, PrefixedParent.class); + assertNotNull(act.child); + assertEquals(exp.a, act.a); + assertEquals(exp.child.value, act.child.value); + assertEquals(exp.child.tag, act.child.tag); + } + + @Test + public void testPropertyNameEntry() throws Exception { + String doc = a2q("{'i':7,'l':8,'b':true,'s':'y'}"); + ScalarBean exp; + try (JsonParser p = vanilla.createParser(doc)) { + p.nextToken(); + p.nextToken(); + exp = vanilla.readValue(p, ScalarBean.class); + } + ScalarBean act; + try (JsonParser p = mapper.createParser(doc)) { + p.nextToken(); + p.nextToken(); + act = mapper.readValue(p, ScalarBean.class); + } + assertEquals(exp.i, act.i); + assertEquals(exp.l, act.l); + assertEquals(exp.b, act.b); + assertEquals(exp.s, act.s); + } + + @Test + public void testMapperUsableAfterJdkSerialization() throws Exception { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream out = new ObjectOutputStream(bytes)) { + out.writeObject(newObjectMapper()); + } + ObjectMapper revived; + try (ObjectInputStream in = new ObjectInputStream( + new ByteArrayInputStream(bytes.toByteArray()))) { + revived = (ObjectMapper) in.readObject(); + } + // Building a deserializer runs the modifier's updateBuilder, which + // uses the transient ThreadLocal that deserialization nulled out. + ScalarBean bean = revived.readValue(a2q("{'i':5,'s':'z'}"), ScalarBean.class); + assertEquals(5, bean.i); + assertEquals("z", bean.s); + assertEquals(newObjectMapper().writeValueAsString(bean), + revived.writeValueAsString(bean)); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecCreatorTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecCreatorTest.java new file mode 100644 index 000000000..48c746b7d --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecCreatorTest.java @@ -0,0 +1,91 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +// The value instantiator's default creator is not always the no-arg +// constructor: a no-arg @JsonCreator factory takes its place, and databind +// then constructs through the factory. Generated POJO codecs construct +// through the stock instantiator in that case (and still engage); record +// codecs construct through the canonical constructor only, so a record with +// a factory creator stays on the stock path. +public class BBCodecCreatorTest extends BlackbirdTestBase +{ + public static class FactoryBean { + public String name; + public boolean viaFactory; + + public FactoryBean() { } + + @JsonCreator + public static FactoryBean create() { + FactoryBean bean = new FactoryBean(); + bean.viaFactory = true; + return bean; + } + } + + public record FactoryRec(String name, int count) { + @JsonCreator + public static FactoryRec create(@JsonProperty("name") String name, + @JsonProperty("count") int count) { + return new FactoryRec(name + "!", count); + } + } + + // Canonical constructor is varargs: the generated codec must pass the + // array through, not wrap it as a single element + public record VarargsRec(String name, String... tags) { } + + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + @Test + public void testRecordFactoryCreatorIsUsed() throws Exception { + FactoryRec expected = vanilla.readValue("{\"name\":\"a\",\"count\":2}", FactoryRec.class); + + FactoryRec actual = newBlackbirdMapper().readValue("{\"name\":\"a\",\"count\":2}", FactoryRec.class); + assertEquals(expected, actual, + "record construction path differs from stock (creator is the factory)"); + } + + @Test + public void testNoArgFactoryCreatorIsUsedAndEngages() throws Exception { + FactoryBean expected = vanilla.readValue("{\"name\":\"a\"}", FactoryBean.class); + + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + FactoryBean actual = mapper.readValue("{\"name\":\"a\"}", FactoryBean.class); + assertTrue(hasGeneratedCodec(seen.get(FactoryBean.class)), + "factory-creator bean did not engage a codec"); + assertEquals("a", actual.name); + assertEquals(expected.viaFactory, actual.viaFactory, + "construction path differs from stock (default creator is the factory)"); + assertTrue(actual.viaFactory); + } + + @Test + public void testVarargsRecordConstructor() throws Exception { + final String json = "{\"name\":\"a\",\"tags\":[\"x\",\"y\"]}"; + VarargsRec expected = vanilla.readValue(json, VarargsRec.class); + + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + VarargsRec actual = mapper.readValue(json, VarargsRec.class); + assertTrue(hasGeneratedCodec(seen.get(VarargsRec.class)), + "varargs record did not engage a codec"); + assertEquals(expected.name(), actual.name()); + assertArrayEquals(expected.tags(), actual.tags()); + assertArrayEquals(new String[] { "x", "y" }, actual.tags()); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecDemotionTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecDemotionTest.java new file mode 100644 index 000000000..264148900 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecDemotionTest.java @@ -0,0 +1,298 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonMerge; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; + +import org.junit.jupiter.api.Test; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.BeanProperty; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.InjectableValues; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.deser.std.StdDeserializer; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Documented demotions: beans whose features the generator does not model + * (object ids, per-property merge, injectables on records) stay on the stock + * deserializer and behave exactly like vanilla. These pins keep the demotions + * deliberate; if the generator grows support, the engagement assertion flips + * - as it did for any-setter beans and injectable POJOs, whose engagement + * pins live here too. + */ +public class BBCodecDemotionTest extends BlackbirdTestBase +{ + @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class) + public static class IdBean { + public int value; + public IdBean next; + } + + @JsonFilter("f") + public static class FilterBean { + public int value; + } + + // A builder with a creator constructor: the builder codec demotes + @JsonDeserialize(builder = CreatorBuilt.Builder.class) + public static class CreatorBuilt { + final String name; + final int n; + + CreatorBuilt(String name, int n) { + this.name = name; + this.n = n; + } + + public static class Builder { + private final String name; + private int n; + + @JsonCreator + public Builder(@JsonProperty("name") String name) { + this.name = name; + } + + public Builder withN(int n) { + this.n = n; + return this; + } + + public CreatorBuilt build() { + return new CreatorBuilt(name, n); + } + } + } + + // Counts how often databind contextualizes it + public static class CountingDeser extends StdDeserializer { + static final AtomicInteger CONTEXTUALIZED = new AtomicInteger(); + + public CountingDeser() { + super(String.class); + } + + @Override + public ValueDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) { + CONTEXTUALIZED.incrementAndGet(); + return this; + } + + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) { + return p.getValueAsString(); + } + } + + // Demoted (object id); the per-property ignoral contextualizes it into + // a new instance + @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class) + public static class CountedIdBean { + @JsonDeserialize(using = CountingDeser.class) + public String s; + } + + public static class CountedHolder { + @JsonIgnoreProperties("x") + public CountedIdBean bean; + } + + public static class MergeTarget { + public int a = 1; + public int b = 2; + } + + public static class MergeBean { + @JsonMerge + public MergeTarget child = new MergeTarget(); + } + + public static class AnySetterBean { + public int known; + public Map rest = new LinkedHashMap<>(); + + @JsonAnySetter + public void any(String name, Object value) { + rest.put(name, value); + } + } + + public static class InjectBean { + public int i; + @JacksonInject("who") + public String who; + } + + public static class InjectOverrideBean { + public int i; + @JacksonInject("who") + public String who; + @JacksonInject("count") + public int count; + } + + public record InjectRecord(String name, @JacksonInject("who") String who) { } + + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + // Stock comes in two shapes: the modifier demoted (raw stock captured), + // or the factory gated at resolve time (placeholder captured, codec never + // generated). Both mean every call runs the stock deserializer. + private static void assertStock(Map, ValueDeserializer> seen, Class cls) + throws Exception { + ValueDeserializer d = seen.get(cls); + assertNotNull(d, "no deserializer captured for " + cls); + assertFalse(hasGeneratedCodec(d), + cls.getSimpleName() + " unexpectedly generated a codec; if support" + + " was added deliberately, flip this pin to assert engagement"); + } + + @Test + public void testObjectIdBeanStaysStock() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'@id':1,'value':5,'next':1}"); + IdBean v = vanilla.readValue(doc, IdBean.class); + IdBean m = mapper.readValue(doc, IdBean.class); + assertStock(seen, IdBean.class); + assertEquals(v.value, m.value); + assertSame(m, m.next); + } + + // A demoted bean gets the stock (de)serializer itself from + // contextualization, not the placeholder that stands in for it + @Test + public void testDemotedBeanUsesStockClasses() throws Exception { + ObjectMapper mapper = newObjectMapper(); + assertSame(rootWriter(vanilla, FilterBean.class).getClass(), + rootWriter(mapper, FilterBean.class).getClass()); + assertSame(rootReader(vanilla, IdBean.class).getClass(), + rootReader(mapper, IdBean.class).getClass()); + assertSame(rootReader(vanilla, InjectRecord.class).getClass(), + rootReader(mapper, InjectRecord.class).getClass()); + assertSame(rootReader(vanilla, CreatorBuilt.class).getClass(), + rootReader(mapper, CreatorBuilt.class).getClass()); + + CreatorBuilt built = mapper.readValue(a2q("{'name':'a','n':3}"), CreatorBuilt.class); + assertEquals("a", built.name); + assertEquals(3, built.n); + } + + // Resolution already contextualized the demoted bean's properties, so a + // later contextualization into a new instance must not redo it + @Test + public void testDemotedBeanPropertiesContextualizedAsStock() throws Exception { + final String doc = a2q("{'bean':{'@id':1,'s':'v'}}"); + CountingDeser.CONTEXTUALIZED.set(0); + assertEquals("v", newVanillaJSONMapper().readValue(doc, CountedHolder.class).bean.s); + int stock = CountingDeser.CONTEXTUALIZED.get(); + + CountingDeser.CONTEXTUALIZED.set(0); + assertEquals("v", newObjectMapper().readValue(doc, CountedHolder.class).bean.s); + assertEquals(stock, CountingDeser.CONTEXTUALIZED.get()); + } + + @Test + public void testMergePropertyBeanStaysStock() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'child':{'a':9}}"); + MergeBean v = vanilla.readValue(doc, MergeBean.class); + MergeBean m = mapper.readValue(doc, MergeBean.class); + assertStock(seen, MergeBean.class); + assertEquals(v.child.a, m.child.a); + assertEquals(v.child.b, m.child.b); + assertEquals(2, m.child.b); + } + + @Test + public void testAnySetterBeanEngages() throws Exception { + // Widened from a demotion pin: the unknown arm feeds the stock + // any-setter, so these beans accelerate (BBCodecAnySetterTest has the + // full parity matrix; records with an any-setter still demote). + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'known':1,'x':'a','y':2}"); + AnySetterBean v = vanilla.readValue(doc, AnySetterBean.class); + AnySetterBean m = mapper.readValue(doc, AnySetterBean.class); + assertTrue(hasGeneratedCodec(seen.get(AnySetterBean.class)), + "any-setter bean did not engage a codec"); + assertEquals(v.known, m.known); + assertEquals(v.rest, m.rest); + assertEquals(2, m.rest.size()); + } + + @Test + public void testInjectableBeanEngages() throws Exception { + // Widened from a demotion pin: injected values apply through the base + // helper right after construction, the stock placement. + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper base = capturingMapper(seen); + InjectableValues inject = + new InjectableValues.Std().addValue("who", "injected"); + String doc = a2q("{'i':7}"); + InjectBean v = vanilla.reader(inject).forType(InjectBean.class).readValue(doc); + InjectBean m = base.reader(inject).forType(InjectBean.class).readValue(doc); + assertTrue(hasGeneratedCodec(seen.get(InjectBean.class)), + "injectable bean did not engage a codec"); + assertEquals(v.i, m.i); + assertEquals(v.who, m.who); + assertEquals("injected", m.who); + } + + @Test + public void testDocumentOverridesInjectedValue() throws Exception { + // Injection runs before the property loop, so a document value wins - + // exactly like stock. + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper base = capturingMapper(seen); + InjectableValues inject = new InjectableValues.Std() + .addValue("who", "injected").addValue("count", 41); + String doc = a2q("{'i':7,'who':'from-doc'}"); + InjectOverrideBean v = vanilla.reader(inject) + .forType(InjectOverrideBean.class).readValue(doc); + InjectOverrideBean m = base.reader(inject) + .forType(InjectOverrideBean.class).readValue(doc); + assertTrue(hasGeneratedCodec(seen.get(InjectOverrideBean.class)), + "injectable bean did not engage a codec"); + assertEquals(v.who, m.who); + assertEquals("from-doc", m.who); + assertEquals(v.count, m.count); + assertEquals(41, m.count); + } + + @Test + public void testInjectableRecordStaysStock() throws Exception { + // Record codecs have no instance until the end of the document, so + // injection cannot take its stock place; records with injectables + // demote. + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper base = capturingMapper(seen); + InjectableValues inject = + new InjectableValues.Std().addValue("who", "injected"); + String doc = a2q("{'name':'a'}"); + InjectRecord v = vanilla.reader(inject).forType(InjectRecord.class).readValue(doc); + InjectRecord m = base.reader(inject).forType(InjectRecord.class).readValue(doc); + assertStock(seen, InjectRecord.class); + assertEquals(v, m); + assertEquals("injected", m.who()); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecEngagementTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecEngagementTest.java new file mode 100644 index 000000000..8fcac9924 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecEngagementTest.java @@ -0,0 +1,247 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.exc.MismatchedInputException; + +import tools.jackson.module.blackbird.BlackbirdTestBase; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonPOJOBuilder; + +import static org.junit.jupiter.api.Assertions.*; + +public class BBCodecEngagementTest extends BlackbirdTestBase +{ + public static class PublicBean { + private String name; + private int count; + private long total; + private boolean active; + private List tags; + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public int getCount() { return count; } + public void setCount(int count) { this.count = count; } + public long getTotal() { return total; } + public void setTotal(long total) { this.total = total; } + public boolean isActive() { return active; } + public void setActive(boolean active) { this.active = active; } + public List getTags() { return tags; } + public void setTags(List tags) { this.tags = tags; } + } + + public record PublicRec(String name, int count, long total, boolean active, List tags) {} + + @JsonDeserialize(builder = BuilderBean.Builder.class) + public static class BuilderBean { + private final String name; + private final int count; + private final List tags; + + private BuilderBean(String name, int count, List tags) { + this.name = name; + this.count = count; + this.tags = tags; + } + + public String getName() { return name; } + public int getCount() { return count; } + public List getTags() { return tags; } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + private String name; + private int count; + private List tags; + + public Builder name(String name) { this.name = name; return this; } + public Builder count(int count) { this.count = count; return this; } + public Builder tags(List tags) { this.tags = tags; return this; } + public BuilderBean build() { return new BuilderBean(name, count, tags); } + } + } + + // The common shape: a private static nested builder. The codec never + // names the builder class, so it engages like a public one. + @JsonDeserialize(builder = PrivateBuilderBean.Builder.class) + public static class PrivateBuilderBean { + final String name; + final int count; + + private PrivateBuilderBean(String name, int count) { + this.name = name; + this.count = count; + } + + @JsonPOJOBuilder(withPrefix = "") + private static class Builder { + private String name; + private int count; + + private Builder name(String name) { this.name = name; return this; } + private Builder count(int count) { this.count = count; return this; } + private PrivateBuilderBean build() { return new PrivateBuilderBean(name, count); } + } + } + + public static class PublicParent { + private String label; + private PublicBean child; + private PublicRec rec; + + public String getLabel() { return label; } + public void setLabel(String label) { this.label = label; } + public PublicBean getChild() { return child; } + public void setChild(PublicBean child) { this.child = child; } + public PublicRec getRec() { return rec; } + public void setRec(PublicRec rec) { this.rec = rec; } + } + + private final ObjectMapper MAPPER = newObjectMapper(); + + @Test + public void testNestedChildCodecs() throws Exception { + String doc = "{\"label\":\"p\",\"child\":{\"name\":\"c\",\"count\":2}," + + "\"rec\":{\"name\":\"r\",\"count\":5,\"total\":6,\"active\":true,\"tags\":[]}}"; + PublicParent parent = MAPPER.readValue(doc, PublicParent.class); + assertEquals("p", parent.getLabel()); + assertEquals("c", parent.getChild().getName()); + assertEquals(2, parent.getChild().getCount()); + assertEquals(new PublicRec("r", 5, 6L, true, List.of()), parent.getRec()); + + PublicParent nulls = MAPPER.readValue("{\"child\":null,\"rec\":null}", PublicParent.class); + assertNull(nulls.getChild()); + assertNull(nulls.getRec()); + + PublicParent vanillaParent = newVanillaJSONMapper().readValue(doc, PublicParent.class); + assertEquals(vanillaParent.getRec(), parent.getRec()); + assertEquals(vanillaParent.getChild().getName(), parent.getChild().getName()); + } + + @Test + public void testGeneratedRecordCodecValues() throws Exception { + PublicRec rec = MAPPER.readValue( + "{\"name\":\"a\",\"count\":3,\"total\":9000000000,\"active\":true,\"tags\":[\"x\"]}", + PublicRec.class); + assertEquals(new PublicRec("a", 3, 9000000000L, true, List.of("x")), rec); + + PublicRec reversed = MAPPER.readValue( + "{\"tags\":[\"x\"],\"active\":true,\"total\":9000000000,\"count\":3,\"name\":\"a\"}", + PublicRec.class); + assertEquals(rec, reversed); + } + + @Test + public void testGeneratedRecordCodecNullsUnknownsMissing() throws Exception { + ObjectMapper vanilla = newVanillaJSONMapper(); + String doc = "{\"name\":null,\"junk\":{\"a\":[1]},\"count\":3}"; + assertEquals(vanilla.readValue(doc, PublicRec.class), + MAPPER.readValue(doc, PublicRec.class)); + String primNull = "{\"count\":null,\"name\":\"n\"}"; + assertThrows(MismatchedInputException.class, + () -> vanilla.readValue(primNull, PublicRec.class)); + assertThrows(MismatchedInputException.class, + () -> MAPPER.readValue(primNull, PublicRec.class)); + } + + @Test + public void testGeneratedBuilderCodecValues() throws Exception { + String doc = "{\"name\":\"b\",\"junk\":[{}],\"count\":7,\"tags\":[\"t\"]}"; + BuilderBean bean = MAPPER.readValue(doc, BuilderBean.class); + assertEquals("b", bean.getName()); + assertEquals(7, bean.getCount()); + assertEquals(List.of("t"), bean.getTags()); + BuilderBean vanillaBean = newVanillaJSONMapper().readValue(doc, BuilderBean.class); + assertEquals(vanillaBean.getName(), bean.getName()); + assertEquals(vanillaBean.getCount(), bean.getCount()); + assertEquals(vanillaBean.getTags(), bean.getTags()); + } + + // A non-object where a bean is expected reports MismatchedInputException + // from the generated codecs, same as stock. + @Test + public void testOddTokenMatchesStock() throws Exception { + for (Class type : new Class[] { + PublicBean.class, PublicRec.class, BuilderBean.class }) { + assertThrows(MismatchedInputException.class, + () -> MAPPER.readValue("[1]", type)); + assertThrows(MismatchedInputException.class, + () -> newVanillaJSONMapper().readValue("[1]", type)); + } + } + + @Test + public void testEngineEngagesForAllShapes() throws Exception { + java.util.Map, tools.jackson.databind.ValueDeserializer> seen = + new java.util.concurrent.ConcurrentHashMap<>(); + ObjectMapper m = capturingMapper(seen); + m.readValue("{\"name\":\"a\"}", PublicBean.class); + m.readValue("{\"name\":\"a\"}", PublicRec.class); + m.readValue("{\"name\":\"a\"}", BuilderBean.class); + assertEquals("BBReaderPlaceholder", + seen.get(PublicBean.class).getClass().getSimpleName()); + assertEquals("BBReaderPlaceholder", + seen.get(PublicRec.class).getClass().getSimpleName()); + // Builder flow keys the modifier by the builder class. + assertEquals("BBBuilderPlaceholder", + seen.get(BuilderBean.Builder.class).getClass().getSimpleName()); + // The placeholder only stands in until resolution; the generated + // codec is what the mapper then uses. + assertGeneratedReader(m, PublicBean.class); + assertGeneratedReader(m, PublicRec.class); + assertGeneratedReader(m, BuilderBean.class); + } + + @Test + public void testGeneratedCodecValues() throws Exception { + PublicBean bean = MAPPER.readValue( + "{\"name\":\"a\",\"count\":3,\"total\":9000000000,\"active\":true,\"tags\":[\"x\",\"y\"]}", + PublicBean.class); + assertEquals("a", bean.getName()); + assertEquals(3, bean.getCount()); + assertEquals(9000000000L, bean.getTotal()); + assertTrue(bean.isActive()); + assertEquals(List.of("x", "y"), bean.getTags()); + } + + @Test + public void testGeneratedCodecNullsAndUnknowns() throws Exception { + PublicBean bean = MAPPER.readValue( + "{\"name\":null,\"junk\":{\"deep\":[1,2]},\"active\":false}", + PublicBean.class); + assertNull(bean.getName()); + assertEquals(0, bean.getCount()); + assertFalse(bean.isActive()); + assertNull(bean.getTags()); + assertThrows(MismatchedInputException.class, + () -> MAPPER.readValue("{\"count\":null}", PublicBean.class)); + assertThrows(MismatchedInputException.class, + () -> newVanillaJSONMapper().readValue("{\"count\":null}", PublicBean.class)); + } + + @Test + public void testPrivateBuilderGenerates() throws Exception { + assertGeneratedReader(MAPPER, PrivateBuilderBean.class); + ObjectMapper vanilla = newVanillaJSONMapper(); + for (String doc : new String[] { "{\"name\":\"a\",\"count\":3}", "{\"count\":\"x\"}", + "{\"name\":null,\"junk\":[1],\"count\":4}" }) { + PrivateBuilderBean expected; + try { + expected = vanilla.readValue(doc, PrivateBuilderBean.class); + } catch (Exception e) { + Exception actual = assertThrows(Exception.class, + () -> MAPPER.readValue(doc, PrivateBuilderBean.class), doc); + assertEquals(e.getClass(), actual.getClass(), doc); + assertEquals(e.getMessage(), actual.getMessage(), doc); + continue; + } + PrivateBuilderBean actual = MAPPER.readValue(doc, PrivateBuilderBean.class); + assertEquals(expected.name, actual.name, doc); + assertEquals(expected.count, actual.count, doc); + } + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecFieldTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecFieldTest.java new file mode 100644 index 000000000..8b9889ba6 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecFieldTest.java @@ -0,0 +1,191 @@ +package tools.jackson.module.blackbird.deser; + +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import tools.jackson.databind.exc.MismatchedInputException; + +import tools.jackson.module.blackbird.BlackbirdModule; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +// Field-backed properties: the generator stores through putfield / reads +// through getfield for public fields, and reaches a non-public field through a +// handle unreflected after databind's own fixAccess. Behavior must match +// stock databind on every path. +public class BBCodecFieldTest extends BlackbirdTestBase +{ + @JsonPropertyOrder({ "name", "count", "total", "active", "tags", "child" }) + public static class FieldBean { + public String name; + public int count; + public long total; + public boolean active; + public List tags; + public FieldChild child; + } + + public static class FieldChild { + public String label; + } + + public static class FinalFieldBean { + public final String name; + public int count; + + public FinalFieldBean() { + this("", 0); + } + + @JsonCreator + public FinalFieldBean( + @JsonProperty("name") String name, + @JsonProperty("count") int count) { + this.name = name; + this.count = count; + } + } + + public static class MixedBean { + public String field; + private int viaSetter; + + public int getViaSetter() { return viaSetter; } + public void setViaSetter(int v) { viaSetter = v; } + } + + public static class PrivateFieldBean { + @JsonProperty("name") + private String name; + @JsonProperty("count") + private int count; + + public String name() { return name; } + public int count() { return count; } + } + + private final ObjectMapper MAPPER = newObjectMapper(); + private final ObjectMapper VANILLA = newVanillaJSONMapper(); + + @Test + public void testPublicFieldValues() throws Exception { + String json = a2q("{'name':'a','count':3,'total':9000000000,'active':true," + + "'tags':['x','y'],'child':{'label':'c'}}"); + FieldBean bean = MAPPER.readValue(json, FieldBean.class); + assertEquals("a", bean.name); + assertEquals(3, bean.count); + assertEquals(9000000000L, bean.total); + assertTrue(bean.active); + assertEquals(List.of("x", "y"), bean.tags); + assertEquals("c", bean.child.label); + } + + @Test + public void testPublicFieldReversedOrder() throws Exception { + String json = a2q("{'child':{'label':'c'},'tags':['x'],'active':true," + + "'total':7,'count':2,'name':'a'}"); + FieldBean bean = MAPPER.readValue(json, FieldBean.class); + FieldBean vanilla = VANILLA.readValue(json, FieldBean.class); + assertEquals(vanilla.name, bean.name); + assertEquals(vanilla.count, bean.count); + assertEquals(vanilla.child.label, bean.child.label); + } + + @Test + public void testPublicFieldNullsAndUnknowns() throws Exception { + String json = a2q("{'name':null,'extra':{'deep':1},'count':5," + + "'tags':null,'child':null}"); + FieldBean bean = MAPPER.readValue(json, FieldBean.class); + assertNull(bean.name); + assertEquals(5, bean.count); + assertNull(bean.tags); + assertNull(bean.child); + } + + @Test + public void testNullToPrimitiveFieldThrows() { + assertThrows(MismatchedInputException.class, + () -> MAPPER.readValue(a2q("{'count':null}"), FieldBean.class)); + } + + @Test + public void testPublicFieldRoundTripMatchesVanilla() throws Exception { + FieldBean bean = MAPPER.readValue(a2q("{'name':'a','count':3,'total':7," + + "'active':true,'tags':['x','y'],'child':{'label':'c'}}"), FieldBean.class); + assertEquals(VANILLA.writeValueAsString(bean), MAPPER.writeValueAsString(bean)); + } + + @Test + public void testFinalFieldMatchesVanilla() throws Exception { + String json = a2q("{'name':'a','count':3}"); + FinalFieldBean bean = MAPPER.readValue(json, FinalFieldBean.class); + assertEquals("a", bean.name); + assertEquals(3, bean.count); + assertEquals(VANILLA.writeValueAsString(bean), MAPPER.writeValueAsString(bean)); + } + + @Test + public void testMixedFieldAndSetter() throws Exception { + MixedBean bean = MAPPER.readValue(a2q("{'field':'a','viaSetter':7}"), MixedBean.class); + assertEquals("a", bean.field); + assertEquals(7, bean.getViaSetter()); + assertEquals(VANILLA.writeValueAsString(bean), MAPPER.writeValueAsString(bean)); + } + + @Test + public void testPrivateFieldAccelerates() throws Exception { + // No lookup needed: databind opens the private fields, and the + // codec reaches them through handles, not the stock path. + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper m = capturingMapper(seen); + PrivateFieldBean bean = m.readValue(a2q("{'name':'a','count':3}"), PrivateFieldBean.class); + assertTrue(hasGeneratedCodec(seen.get(PrivateFieldBean.class))); + assertEquals("a", bean.name()); + assertEquals(3, bean.count()); + String layout = assertGeneratedReader(m, PrivateFieldBean.class); + assertFalse(layout.contains("STOCK"), layout); + } + + @Test + public void testDeprecatedLookupOverrideIsIgnored() throws Exception { + // The released findLookupSupplier() override still compiles, but + // module setup no longer calls it, and acceleration does not change. + AtomicInteger calls = new AtomicInteger(); + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper m = capturingMapperBuilder(new BlackbirdModule() { + private static final long serialVersionUID = 1L; + @Override + @SuppressWarnings("deprecation") + protected Supplier findLookupSupplier() { + calls.incrementAndGet(); + return MethodHandles::lookup; + } + }, seen, null) + .build(); + PrivateFieldBean bean = m.readValue(a2q("{'name':'a','count':3}"), PrivateFieldBean.class); + assertTrue(hasGeneratedCodec(seen.get(PrivateFieldBean.class))); + assertEquals("a", bean.name()); + assertEquals(3, bean.count()); + assertEquals(0, calls.get()); + } + + @Test + public void testFieldBeanEngagesCodec() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper m = capturingMapper(seen); + m.readValue(a2q("{'name':'a'}"), FieldBean.class); + assertTrue(hasGeneratedCodec(seen.get(FieldBean.class))); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecIgnoralsTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecIgnoralsTest.java new file mode 100644 index 000000000..98a9a23d1 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecIgnoralsTest.java @@ -0,0 +1,169 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.exc.IgnoredPropertyException; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Ignoral parity: beans with {@code @JsonIgnoreProperties}, + * {@code @JsonIncludeProperties}, or {@code @JsonIgnore} now engage codecs, + * and the unknown arm consults the ignoral sets in the stock loop's exact + * order. Every case compares against vanilla. + */ +public class BBCodecIgnoralsTest extends BlackbirdTestBase +{ + @JsonIgnoreProperties(ignoreUnknown = true) + public static class LenientBean { + public int i; + public String s; + } + + @JsonIgnoreProperties({ "secret" }) + public static class IgnoredSetBean { + public int i; + public String s; + } + + @JsonIncludeProperties({ "i" }) + public static class IncludedSetBean { + public int i; + public String s; + } + + public static class JsonIgnoreBean { + public int i; + @JsonIgnore + public String hidden; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record LenientRecord(String name, int count) { } + + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + private static String message(Exception e) { + String m = e.getMessage(); + int nl = m.indexOf('\n'); + return (nl < 0) ? m : m.substring(0, nl); + } + + private static void assertEngaged(Map, ValueDeserializer> seen, Class cls) { + ValueDeserializer d = seen.get(cls); + assertNotNull(d, "no deserializer captured for " + cls); + assertTrue(hasGeneratedCodec(d), + cls.getSimpleName() + " did not engage a codec"); + } + + @Test + public void testIgnoreUnknownSkips() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':1,'nope':{'deep':[1,2]},'s':'x','more':true}"); + LenientBean v = vanilla.readValue(doc, LenientBean.class); + LenientBean m = mapper.readValue(doc, LenientBean.class); + assertEngaged(seen, LenientBean.class); + assertEquals(v.i, m.i); + assertEquals(v.s, m.s); + } + + @Test + public void testIgnoreUnknownBeatsStrictPerCall() throws Exception { + // Stock skips silently under ignoreUnknown even when + // FAIL_ON_UNKNOWN_PROPERTIES is enabled per call. + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':1,'nope':true}"); + LenientBean v = vanilla.reader(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .forType(LenientBean.class).readValue(doc); + LenientBean m = mapper.reader(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .forType(LenientBean.class).readValue(doc); + assertEngaged(seen, LenientBean.class); + assertEquals(v.i, m.i); + } + + @Test + public void testIgnoredSetSkipsAndFailOnIgnored() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':1,'secret':'boo','s':'x'}"); + IgnoredSetBean v = vanilla.readValue(doc, IgnoredSetBean.class); + IgnoredSetBean m = mapper.readValue(doc, IgnoredSetBean.class); + assertEngaged(seen, IgnoredSetBean.class); + assertEquals(v.i, m.i); + assertEquals(v.s, m.s); + + IgnoredPropertyException ve = assertThrows(IgnoredPropertyException.class, + () -> vanilla.reader(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES) + .forType(IgnoredSetBean.class).readValue(doc)); + IgnoredPropertyException me = assertThrows(IgnoredPropertyException.class, + () -> mapper.reader(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES) + .forType(IgnoredSetBean.class).readValue(doc)); + assertEquals(message(ve), message(me)); + } + + @Test + public void testIncludedSetIgnoresOthers() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':1,'s':'x'}"); + IncludedSetBean v = vanilla.readValue(doc, IncludedSetBean.class); + IncludedSetBean m = mapper.readValue(doc, IncludedSetBean.class); + assertEngaged(seen, IncludedSetBean.class); + assertEquals(v.i, m.i); + assertEquals(v.s, m.s); + assertNull(m.s); + } + + @Test + public void testJsonIgnoredNameInDocument() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':1,'hidden':'boo'}"); + JsonIgnoreBean v = vanilla.readValue(doc, JsonIgnoreBean.class); + JsonIgnoreBean m = mapper.readValue(doc, JsonIgnoreBean.class); + assertEngaged(seen, JsonIgnoreBean.class); + assertEquals(v.i, m.i); + assertNull(m.hidden); + } + + @Test + public void testUnknownStillReportedWithIgnoredSet() throws Exception { + // An unknown name outside the ignored set still runs the stock + // unknown handling (throws under per-call FAIL_ON_UNKNOWN). + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'i':1,'other':true}"); + Exception ve = assertThrows(Exception.class, + () -> vanilla.reader(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .forType(IgnoredSetBean.class).readValue(doc)); + Exception me = assertThrows(Exception.class, + () -> mapper.reader(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .forType(IgnoredSetBean.class).readValue(doc)); + assertEngaged(seen, IgnoredSetBean.class); + assertEquals(ve.getClass(), me.getClass()); + assertEquals(message(ve), message(me)); + } + + @Test + public void testIgnoreUnknownRecord() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + String doc = a2q("{'name':'a','junk':[1,{'x':2}],'count':3}"); + assertEquals(vanilla.readValue(doc, LenientRecord.class), + mapper.readValue(doc, LenientRecord.class)); + assertEngaged(seen, LenientRecord.class); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecMatcherTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecMatcherTest.java new file mode 100644 index 000000000..8d2b7d344 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecMatcherTest.java @@ -0,0 +1,147 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.exc.MismatchedInputException; +import tools.jackson.module.blackbird.BlackbirdTestBase; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Name-matching parity: case-insensitive mappers use the core CI matcher + * instead of demoting, and property aliases map to the same generated arm the + * way stock BeanPropertyMap appends them. Every case compares against vanilla. + */ +public class BBCodecMatcherTest extends BlackbirdTestBase +{ + public static class AliasBean { + @JsonAlias({ "identifier", "the_id" }) + public int id; + public String name; + } + + public record AliasRecord(@JsonAlias("nm") @JsonProperty(required = true) String name, + int count) { } + + public static class PlainBean { + public int count; + public String label; + } + + @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + public static class FormatCIBean { + public int count; + public String label; + } + + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + private static ObjectMapper capturingMapper(Map, ValueDeserializer> seen, + boolean caseInsensitive) { + JsonMapper.Builder b = capturingMapperBuilder(seen, null); + if (caseInsensitive) { + b = b.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES); + } + return b.build(); + } + + private static void assertEngaged(Map, ValueDeserializer> seen, Class cls) { + ValueDeserializer d = seen.get(cls); + assertNotNull(d, "no deserializer captured for " + cls); + assertTrue(hasGeneratedCodec(d), + cls.getSimpleName() + " did not engage a codec"); + } + + @Test + public void testCaseInsensitiveMapper() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen, true); + ObjectMapper vci = JsonMapper.builder() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + .build(); + String doc = a2q("{'COUNT':7,'Label':'x'}"); + PlainBean v = vci.readValue(doc, PlainBean.class); + PlainBean m = mapper.readValue(doc, PlainBean.class); + assertEngaged(seen, PlainBean.class); + assertEquals(v.count, m.count); + assertEquals(v.label, m.label); + assertEquals(7, m.count); + } + + @Test + public void testCaseInsensitiveUnknownStillUnknown() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen, true); + String doc = a2q("{'COUNT':7,'zzz':1}"); + PlainBean m = mapper.readValue(doc, PlainBean.class); + assertEquals(7, m.count); + assertEngaged(seen, PlainBean.class); + } + + @Test + public void testAliasNamesHitTheSameArm() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen, false); + for (String key : new String[] { "id", "identifier", "the_id" }) { + String doc = a2q("{'" + key + "':42,'name':'x'}"); + AliasBean v = vanilla.readValue(doc, AliasBean.class); + AliasBean m = mapper.readValue(doc, AliasBean.class); + assertEquals(v.id, m.id, "via " + key); + assertEquals(42, m.id, "via " + key); + assertEquals("x", m.name); + } + assertEngaged(seen, AliasBean.class); + } + + @Test + public void testAliasSatisfiesRequiredRecordComponent() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen, false); + String doc = a2q("{'nm':'a','count':3}"); + assertEquals(vanilla.readValue(doc, AliasRecord.class), + mapper.readValue(doc, AliasRecord.class)); + assertEngaged(seen, AliasRecord.class); + + assertThrows(MismatchedInputException.class, + () -> mapper.readValue(a2q("{'count':3}"), AliasRecord.class)); + } + + @Test + public void testAliasUnderCaseInsensitiveMapper() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen, true); + ObjectMapper vci = JsonMapper.builder() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) + .build(); + String doc = a2q("{'IDENTIFIER':42,'NAME':'x'}"); + AliasBean v = vci.readValue(doc, AliasBean.class); + AliasBean m = mapper.readValue(doc, AliasBean.class); + assertEngaged(seen, AliasBean.class); + assertEquals(v.id, m.id); + assertEquals(v.name, m.name); + } + + @Test + public void testClassLevelFormatCaseInsensitivity() throws Exception { + // Class-level @JsonFormat CI arrives through createContextual; the + // placeholder falls back to the stock contextual instance, so behavior + // must match vanilla whichever path serves it. + ObjectMapper mapper = newObjectMapper(); + String doc = a2q("{'CoUnT':5,'LABEL':'y'}"); + FormatCIBean v = vanilla.readValue(doc, FormatCIBean.class); + FormatCIBean m = mapper.readValue(doc, FormatCIBean.class); + assertEquals(v.count, m.count); + assertEquals(v.label, m.label); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecStockChecksTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecStockChecksTest.java new file mode 100644 index 000000000..fb484c36d --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecStockChecksTest.java @@ -0,0 +1,339 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; + +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.deser.DeserializationProblemHandler; +import tools.jackson.databind.exc.UnrecognizedPropertyException; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.module.blackbird.BlackbirdTestBase; +import tools.jackson.module.blackbird.internal.GeneratedBuilderBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Checks the stock deserializers make on rare paths, which the generated + * loops must make too: null handling of a null child value, finishBuild's + * checks on what a builder loop produced, widening of a primitive record + * component's stock value, and forward Object Id references in record + * components. Every case compares against a vanilla mapper. + */ +public class BBCodecStockChecksTest extends BlackbirdTestBase +{ + // A builder-built child whose build method returns null. + @JsonDeserialize(builder = NullChild.Builder.class) + public static class NullChild { + public static class Builder { + public int v; + public Builder withV(int v) { this.v = v; return this; } + public NullChild build() { return null; } + } + } + + public static class FailParent { + @JsonSetter(nulls = Nulls.FAIL) + public NullChild child; + public int x; + } + + public static class SkipParent { + @JsonSetter(nulls = Nulls.SKIP) + public NullChild child = new NullChild(); + public int x; + } + + public record FailRecord(@JsonSetter(nulls = Nulls.FAIL) NullChild child, int x) { } + + // A builder whose with-method may return the built value. + @JsonDeserialize(builder = Wt.Builder.class) + public static class Wt { + final int v; + + Wt(int v) { this.v = v; } + + public static class Builder { + public Object withV(int v) { return new Wt(v * 10); } + public Wt build() { return new Wt(-1); } + } + } + + // A builder property with an alias; a negative value fails in the + // with-method. + @JsonDeserialize(builder = Aliased.Builder.class) + public static class Aliased { + final int v; + + Aliased(int v) { this.v = v; } + + public static class Builder { + int v; + + @JsonAlias("alt") + public Builder withV(int v) { + if (v < 0) { + throw new IllegalArgumentException("negative"); + } + this.v = v; + return this; + } + + public Aliased build() { return new Aliased(v); } + } + } + + // A polymorphic builder subtype with an alias: a type id after the + // first property makes the codec run on the buffered property, then on + // the live stream. + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, + property = "type") + @JsonSubTypes(@JsonSubTypes.Type(value = AliasedSub.class, name = "sub")) + public interface AliasedBase { } + + @JsonDeserialize(builder = AliasedSub.Builder.class) + public static class AliasedSub implements AliasedBase { + final String a; + final int z; + + AliasedSub(String a, int z) { this.a = a; this.z = z; } + + public static class Builder { + String a; + int z; + + @JsonAlias("aa") + public Builder withA(String a) { this.a = a; return this; } + + public Builder withZ(int z) { + if (z < 0) { + throw new IllegalArgumentException("negative"); + } + this.z = z; + return this; + } + + public AliasedSub build() { return new AliasedSub(a, z); } + } + } + + // A builder whose construction a problem handler replaces with null. + @JsonDeserialize(builder = Boom.Builder.class) + public static class Boom { + public static class Builder { + public Builder() { throw new IllegalStateException("boom"); } + public Builder withV(int v) { return this; } + public Boom build() { return new Boom(); } + } + } + + // Returns an Integer whatever the target type. + public static class IntDeser extends ValueDeserializer { + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) { + return p.getIntValue(); + } + } + + public record LongRecord(@JsonDeserialize(using = IntDeser.class) long v, String s) { } + + public record DoubleRecord(@JsonDeserialize(using = IntDeser.class) double v, String s) { } + + public record BoolRecord(@JsonDeserialize(using = IntDeser.class) boolean v, String s) { } + + @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") + public static class Item { + public int id; + public String name; + } + + public record RefHolder(AtomicReference ref, String tag) { } + + // The reference resolves inside the record (items comes after ref), + // before construction; a negative tag makes the constructor throw. + public record RefPair(AtomicReference ref, List items, int tag) { + public RefPair { + if (tag < 0) { + throw new IllegalArgumentException("negative tag"); + } + } + } + + public static class RefDoc { + public RefHolder holder; + public List items; + } + + private static final ObjectMapper VANILLA = newVanillaJSONMapper(); + private static final ObjectMapper MODULE = newObjectMapper(); + + private static void assertSameOutcome(ObjectMapper vanilla, ObjectMapper module, + String doc, Class type, Function view) throws Exception { + Object expected; + try { + expected = view.apply(vanilla.readValue(doc, type)); + } catch (Exception e) { + Exception actual = assertThrows(Exception.class, () -> module.readValue(doc, type), + "stock threw " + e); + assertEquals(e.getClass(), actual.getClass(), actual.toString()); + return; + } + assertEquals(expected, view.apply(module.readValue(doc, type))); + } + + @Test + public void testNullChildValueGetsNullHandling() throws Exception { + String doc = "{\"child\":{\"v\":1},\"x\":2}"; + assertSameOutcome(VANILLA, MODULE, doc, FailParent.class, b -> b.child + "/" + b.x); + assertSameOutcome(VANILLA, MODULE, doc, SkipParent.class, + b -> (b.child != null) + "/" + b.x); + assertSameOutcome(VANILLA, MODULE, doc, FailRecord.class, r -> r.toString()); + assertGeneratedReader(MODULE, FailParent.class); + assertGeneratedReader(MODULE, SkipParent.class); + assertGeneratedReader(MODULE, FailRecord.class); + // Sanity: stock does fail, and does skip. + assertThrows(Exception.class, () -> VANILLA.readValue(doc, FailParent.class)); + assertNotNull(VANILLA.readValue(doc, SkipParent.class).child); + } + + @Test + public void testBuilderWithMethodReturningTarget() throws Exception { + assertSameOutcome(VANILLA, MODULE, "{\"v\":3}", Wt.class, w -> w.v); + assertEquals(30, MODULE.readValue("{\"v\":3}", Wt.class).v); + assertGeneratedReader(MODULE, Wt.class); + } + + @Test + public void testBuilderReplacedByNullIsReported() throws Exception { + DeserializationProblemHandler toNull = new DeserializationProblemHandler() { + @Override + public Object handleInstantiationProblem(DeserializationContext ctxt, + Class instClass, Object argument, Throwable t) { + return null; + } + }; + ObjectMapper vanilla = JsonMapper.builder().addHandler(toNull).build(); + ObjectMapper module = mapperBuilder().addHandler(toNull).build(); + assertSameOutcome(vanilla, module, "{}", Boom.class, b -> b); + assertThrows(Exception.class, () -> vanilla.readValue("{}", Boom.class)); + assertGeneratedReader(module, Boom.class); + } + + @Test + public void testPrimitiveComponentStockValueWidens() throws Exception { + assertSameOutcome(VANILLA, MODULE, "{\"v\":5,\"s\":\"x\"}", LongRecord.class, + r -> r.toString()); + assertEquals(new LongRecord(5L, "x"), + MODULE.readValue("{\"v\":5,\"s\":\"x\"}", LongRecord.class)); + assertSameOutcome(VANILLA, MODULE, "{\"v\":5,\"s\":\"x\"}", DoubleRecord.class, + r -> r.toString()); + // Integer cannot become boolean: both fail in the creator call. + assertSameOutcome(VANILLA, MODULE, "{\"v\":5,\"s\":\"x\"}", BoolRecord.class, + r -> r.toString()); + assertGeneratedReader(MODULE, LongRecord.class); + assertGeneratedReader(MODULE, BoolRecord.class); + } + + @Test + public void testForwardReferenceInRecordComponent() throws Exception { + ObjectMapper vanilla = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS).build(); + ObjectMapper module = mapperBuilder() + .disable(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS).build(); + // Dangling: the component stays null. + assertSameOutcome(vanilla, module, "{\"ref\":7,\"tag\":\"t\"}", RefHolder.class, + h -> h.ref() + "/" + h.tag()); + assertGeneratedReader(module, RefHolder.class); + // Resolved later: stock reports that a record has no fallback setter. + String doc = "{\"holder\":{\"ref\":1,\"tag\":\"t\"},\"items\":[{\"id\":1,\"name\":\"a\"}]}"; + assertSameOutcome(VANILLA, MODULE, doc, RefDoc.class, d -> d.holder + "/" + d.items); + assertThrows(Exception.class, () -> VANILLA.readValue(doc, RefDoc.class)); + } + + // A problem handler replaces the failed construction with null after the + // forward reference has resolved: with no record to apply it to, stock + // drops the resolved value ([databind#6325]). + @Test + public void testForwardReferenceResolvedBeforeNullRecord() throws Exception { + DeserializationProblemHandler toNull = new DeserializationProblemHandler() { + @Override + public Object handleInstantiationProblem(DeserializationContext ctxt, + Class instClass, Object argument, Throwable t) { + return null; + } + }; + ObjectMapper vanilla = JsonMapper.builder().addHandler(toNull).build(); + ObjectMapper module = mapperBuilder().addHandler(toNull).build(); + String doc = "{\"ref\":1,\"items\":[{\"id\":1,\"name\":\"a\"}],\"tag\":-1}"; + assertGeneratedReader(module, RefPair.class); + assertSameOutcome(vanilla, module, doc, RefPair.class, r -> r); + // With an unknown property too, stock reports that first. + String unknown = doc.replace("{\"ref\"", "{\"extra\":true,\"ref\""); + ObjectMapper strictVanilla = vanilla.rebuild() + .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).build(); + ObjectMapper strictModule = module.rebuild() + .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).build(); + assertThrows(UnrecognizedPropertyException.class, + () -> strictVanilla.readValue(unknown, RefPair.class)); + assertSameOutcome(strictVanilla, strictModule, unknown, RefPair.class, r -> r); + // Without the failure both build the record with the reference set. + String ok = doc.replace("-1", "2"); + assertSameOutcome(vanilla, module, ok, RefPair.class, + r -> r.ref().get().name + "/" + r.items().get(0).name + "/" + r.tag()); + } + + // A failing builder property is reported under the name in the input, + // as stock's general builder loop does: here the alias. + @Test + public void testBuilderFailureReportsInputName() throws Exception { + assertGeneratedReader(MODULE, Aliased.class); + for (String doc : new String[] { "{\"alt\":\"x\"}", "{\"alt\":-1}", "{\"v\":-1}" }) { + Exception expected = assertThrows(Exception.class, + () -> VANILLA.readValue(doc, Aliased.class)); + Exception actual = assertThrows(Exception.class, + () -> MODULE.readValue(doc, Aliased.class)); + assertEquals(expected.getClass(), actual.getClass(), doc); + assertEquals(expected.getMessage(), actual.getMessage(), doc); + } + Exception e = assertThrows(Exception.class, + () -> MODULE.readValue("{\"alt\":-1}", Aliased.class)); + assertTrue(e.getMessage().contains("[\"alt\"]"), e.getMessage()); + } + + // The same after a type id that follows a buffered property: the + // failing property is read from the live stream, past the buffer. + @Test + public void testBuilderFailureNameAfterBufferedTypeId() throws Exception { + String doc = "{\"aa\":\"x\",\"type\":\"sub\",\"z\":-1}"; + Exception expected = assertThrows(Exception.class, + () -> VANILLA.readValue(doc, AliasedBase.class)); + Exception actual = assertThrows(Exception.class, + () -> MODULE.readValue(doc, AliasedBase.class)); + // The subtype reader sits behind the type deserializer: the + // generated codec's failure helper shows that it reported this. + assertTrue(Arrays.stream(actual.getStackTrace()).anyMatch( + f -> f.getClassName().equals(GeneratedBuilderBase.class.getName())), + "failure should come from the generated builder codec"); + assertEquals(expected.getClass(), actual.getClass()); + assertEquals(expected.getMessage(), actual.getMessage()); + assertTrue(actual.getMessage().contains("[\"z\"]"), actual.getMessage()); + assertSameOutcome(VANILLA, MODULE, doc.replace("-1", "2"), AliasedBase.class, + b -> ((AliasedSub) b).a + "/" + ((AliasedSub) b).z); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecStrictFeaturesTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecStrictFeaturesTest.java new file mode 100644 index 000000000..74753d97a --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecStrictFeaturesTest.java @@ -0,0 +1,188 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.exc.MismatchedInputException; +import tools.jackson.databind.exc.UnrecognizedPropertyException; +import tools.jackson.module.blackbird.BlackbirdTestBase; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Strict-feature parity with vanilla, both per-call and enabled at build: + * FAIL_ON_UNKNOWN_PROPERTIES, FAIL_ON_MISSING_CREATOR_PROPERTIES, and + * FAIL_ON_NULL_CREATOR_PROPERTIES. Per-call cases warm the codec with a + * default read first, so the strict read runs through the cached generated + * codec rather than deciding generation. Every case compares against vanilla. + */ +public class BBCodecStrictFeaturesTest extends BlackbirdTestBase +{ + public static class SimpleBean { + public int i; + public String s; + } + + public record RefRecord(String name, int count) { } + + public record ReqRecord(@JsonProperty(required = true) String name, int count) { } + + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + private static String message(Exception e) { + String m = e.getMessage(); + int nl = m.indexOf('\n'); + return (nl < 0) ? m : m.substring(0, nl); + } + + /** Blackbird mapper with a capture modifier, so engagement is assertable. */ + private static ObjectMapper capturingMapper(Map, ValueDeserializer> seen, + DeserializationFeature... enable) { + JsonMapper.Builder b = capturingMapperBuilder(seen, null); + for (DeserializationFeature f : enable) { + b = b.enable(f); + } + return b.build(); + } + + private static void assertEngaged(Map, ValueDeserializer> seen, Class cls) { + ValueDeserializer d = seen.get(cls); + assertNotNull(d, "no deserializer captured for " + cls); + assertTrue(hasGeneratedCodec(d), + cls.getSimpleName() + " did not engage a codec"); + } + + // FAIL_ON_UNKNOWN_PROPERTIES enabled at build time: the bean still + // engages a codec, and the unknown property throws exactly like vanilla. + + @Test + public void testBuildTimeFailOnUnknown() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper strict = capturingMapper(seen, + DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + ObjectMapper vstrict = JsonMapper.builder() + .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + + SimpleBean ok = strict.readValue(a2q("{'i':1,'s':'x'}"), SimpleBean.class); + assertEquals(1, ok.i); + assertEngaged(seen, SimpleBean.class); + + String doc = a2q("{'i':1,'nope':true,'s':'x'}"); + UnrecognizedPropertyException ve = assertThrows(UnrecognizedPropertyException.class, + () -> vstrict.readValue(doc, SimpleBean.class)); + UnrecognizedPropertyException me = assertThrows(UnrecognizedPropertyException.class, + () -> strict.readValue(doc, SimpleBean.class)); + assertEquals(message(ve), message(me)); + assertEquals(ve.getPath().toString(), me.getPath().toString()); + } + + // FAIL_ON_MISSING_CREATOR_PROPERTIES + + @Test + public void testBuildTimeFailOnMissingCreator() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper strict = capturingMapper(seen, + DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES); + String doc = a2q("{'name':'x'}"); + MismatchedInputException ve = assertThrows(MismatchedInputException.class, + () -> vanilla.reader(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) + .forType(RefRecord.class).readValue(doc)); + MismatchedInputException me = assertThrows(MismatchedInputException.class, + () -> strict.readValue(doc, RefRecord.class)); + assertEquals(ve.getClass(), me.getClass()); + assertEngaged(seen, RefRecord.class); + } + + @Test + public void testPerCallFailOnMissingCreator() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + // Warm: the codec generates under default config and stays cached. + assertEquals(new RefRecord("a", 1), + mapper.readValue(a2q("{'name':'a','count':1}"), RefRecord.class)); + assertEngaged(seen, RefRecord.class); + + String doc = a2q("{'name':'x'}"); + MismatchedInputException ve = assertThrows(MismatchedInputException.class, + () -> vanilla.reader(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) + .forType(RefRecord.class).readValue(doc)); + MismatchedInputException me = assertThrows(MismatchedInputException.class, + () -> mapper.reader(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) + .forType(RefRecord.class).readValue(doc)); + assertEquals(ve.getClass(), me.getClass()); + } + + // FAIL_ON_NULL_CREATOR_PROPERTIES + + @Test + public void testPerCallFailOnNullCreatorExplicitNull() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + assertEquals(new RefRecord("a", 1), + mapper.readValue(a2q("{'name':'a','count':1}"), RefRecord.class)); + assertEngaged(seen, RefRecord.class); + + String doc = a2q("{'name':null,'count':3}"); + MismatchedInputException ve = assertThrows(MismatchedInputException.class, + () -> vanilla.reader(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) + .forType(RefRecord.class).readValue(doc)); + MismatchedInputException me = assertThrows(MismatchedInputException.class, + () -> mapper.reader(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) + .forType(RefRecord.class).readValue(doc)); + assertEquals(message(ve), message(me)); + } + + @Test + public void testPerCallFailOnNullCreatorMissingComponent() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingMapper(seen); + assertEquals(new RefRecord("a", 1), + mapper.readValue(a2q("{'name':'a','count':1}"), RefRecord.class)); + assertEngaged(seen, RefRecord.class); + + String doc = a2q("{'count':3}"); + MismatchedInputException ve = assertThrows(MismatchedInputException.class, + () -> vanilla.reader(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) + .forType(RefRecord.class).readValue(doc)); + MismatchedInputException me = assertThrows(MismatchedInputException.class, + () -> mapper.reader(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES) + .forType(RefRecord.class).readValue(doc)); + assertEquals(message(ve), message(me)); + } + + @Test + public void testFailOnNullCreatorOffMatchesVanilla() throws Exception { + ObjectMapper mapper = newObjectMapper(); + String doc = a2q("{'name':null,'count':3}"); + assertEquals(vanilla.readValue(doc, RefRecord.class), + mapper.readValue(doc, RefRecord.class)); + } + + @Test + public void testBuildTimeFailOnNullCreator() throws Exception { + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper strict = capturingMapper(seen, + DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES); + assertThrows(MismatchedInputException.class, + () -> strict.readValue(a2q("{'name':null,'count':3}"), RefRecord.class)); + assertEngaged(seen, RefRecord.class); + } + + // Required stays enforced regardless of features + + @Test + public void testRequiredComponentStillEnforced() throws Exception { + ObjectMapper mapper = newObjectMapper(); + assertThrows(MismatchedInputException.class, + () -> mapper.readValue(a2q("{'count':3}"), ReqRecord.class)); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecViewTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecViewTest.java new file mode 100644 index 000000000..063f6d811 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBCodecViewTest.java @@ -0,0 +1,259 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.fasterxml.jackson.annotation.JsonView; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.MapperFeature; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * View-parity regression tests: with an active view the generated codecs stay + * on the fast path and test a per-view visibility bitmask per property + * (visibility read from the stock property, so DEFAULT_VIEW_INCLUSION and + * matcher semantics are exactly stock); a hidden property consumes its value + * or is omitted like stock. Beans with more than 64 properties instead + * delegate the whole view-active call to the stock fallback. Every case + * compares against a vanilla mapper, in both directions, with and without an + * active view, so view semantics come from stock rather than hardcoded + * expectations. + */ +public class BBCodecViewTest extends BlackbirdTestBase +{ + public static class ViewA { } + public static class ViewB { } + + public static class ViewBean { + private String a; + private int b; + private String plain; + + @JsonView(ViewA.class) + public String getA() { return a; } + public void setA(String v) { a = v; } + + @JsonView(ViewB.class) + public int getB() { return b; } + public void setB(int v) { b = v; } + + public String getPlain() { return plain; } + public void setPlain(String v) { plain = v; } + } + + public static class ViewFieldBean { + @JsonView(ViewA.class) + public String a; + @JsonView(ViewB.class) + public int b; + } + + public record ViewRecord( + @JsonView(ViewA.class) String a, + @JsonView(ViewB.class) int b) { } + + public static class Parent { + private ViewBean child; + // Visible in both views, so the child itself deserializes and its own + // per-property view filtering is what the test exercises. + @JsonView({ ViewA.class, ViewB.class }) + public ViewBean getChild() { return child; } + public void setChild(ViewBean c) { child = c; } + } + + private static final ObjectMapper VANILLA = newVanillaJSONMapper(); + private static final ObjectMapper MODULE = newObjectMapper(); + + private static final String DOC = "{\"a\":\"x\",\"b\":7,\"plain\":\"p\"}"; + + private static ViewBean bean() { + ViewBean bean = new ViewBean(); + bean.setA("x"); + bean.setB(7); + bean.setPlain("p"); + return bean; + } + + @Test + public void testSerializeMatchesVanillaPerView() throws Exception { + for (Class view : new Class[] { ViewA.class, ViewB.class }) { + assertEquals(VANILLA.writerWithView(view).writeValueAsString(bean()), + MODULE.writerWithView(view).writeValueAsString(bean()), + "view " + view.getSimpleName()); + } + assertEquals(VANILLA.writeValueAsString(bean()), MODULE.writeValueAsString(bean()), + "no active view"); + } + + @Test + public void testDeserializeMatchesVanillaPerView() throws Exception { + for (Class view : new Class[] { ViewA.class, ViewB.class }) { + ViewBean expected = VANILLA.readerWithView(view).forType(ViewBean.class).readValue(DOC); + ViewBean actual = MODULE.readerWithView(view).forType(ViewBean.class).readValue(DOC); + assertEquals(expected.getA(), actual.getA(), "a under " + view.getSimpleName()); + assertEquals(expected.getB(), actual.getB(), "b under " + view.getSimpleName()); + assertEquals(expected.getPlain(), actual.getPlain(), + "plain under " + view.getSimpleName()); + } + } + + @Test + public void testViewReadConsumesSkippedValues() throws Exception { + // Non-visible properties must be consumed exactly as stock consumes + // them: a top-level array derails if the skip leaves the stream + // mispositioned. + String doc = "[" + DOC + "," + DOC + "]"; + List beans = MODULE.readerWithView(ViewA.class) + .forType(MODULE.getTypeFactory() + .constructCollectionType(List.class, ViewBean.class)) + .readValue(doc); + assertEquals(2, beans.size()); + assertEquals("x", beans.get(1).getA()); + assertEquals(0, beans.get(1).getB(), + "@JsonView(ViewB) property must stay default with ViewA active"); + } + + @Test + public void testFieldBeanMatchesVanillaPerView() throws Exception { + ViewFieldBean bean = new ViewFieldBean(); + bean.a = "x"; + bean.b = 7; + String doc = "{\"a\":\"x\",\"b\":7}"; + for (Class view : new Class[] { ViewA.class, ViewB.class }) { + assertEquals(VANILLA.writerWithView(view).writeValueAsString(bean), + MODULE.writerWithView(view).writeValueAsString(bean)); + ViewFieldBean expected = VANILLA.readerWithView(view) + .forType(ViewFieldBean.class).readValue(doc); + ViewFieldBean actual = MODULE.readerWithView(view) + .forType(ViewFieldBean.class).readValue(doc); + assertEquals(expected.a, actual.a); + assertEquals(expected.b, actual.b); + } + } + + @Test + public void testRecordMatchesVanillaPerView() throws Exception { + String doc = "{\"a\":\"x\",\"b\":7}"; + for (Class view : new Class[] { ViewA.class, ViewB.class }) { + ViewRecord expected = VANILLA.readerWithView(view) + .forType(ViewRecord.class).readValue(doc); + ViewRecord actual = MODULE.readerWithView(view) + .forType(ViewRecord.class).readValue(doc); + assertEquals(expected, actual, "record under " + view.getSimpleName()); + assertEquals(VANILLA.writerWithView(view).writeValueAsString(expected), + MODULE.writerWithView(view).writeValueAsString(actual)); + } + } + + @Test + public void testNestedCodecChildMatchesVanillaPerView() throws Exception { + Parent parent = new Parent(); + parent.setChild(bean()); + String doc = "{\"child\":" + DOC + "}"; + for (Class view : new Class[] { ViewA.class, ViewB.class }) { + assertEquals(VANILLA.writerWithView(view).writeValueAsString(parent), + MODULE.writerWithView(view).writeValueAsString(parent)); + Parent expected = VANILLA.readerWithView(view).forType(Parent.class).readValue(doc); + Parent actual = MODULE.readerWithView(view).forType(Parent.class).readValue(doc); + assertEquals(expected.getChild().getA(), actual.getChild().getA()); + assertEquals(expected.getChild().getB(), actual.getChild().getB()); + } + } + + @Test + public void testViewBeansEngageCodecs() throws Exception { + // The cases above only pin view parity if the codecs actually engage + // for these beans when no view is active. + Map, ValueDeserializer> seen = new ConcurrentHashMap<>(); + ObjectMapper m = capturingMapper(seen); + m.readValue(DOC, ViewBean.class); + m.readValue("{\"a\":\"x\",\"b\":7}", ViewRecord.class); + assertTrue(hasGeneratedCodec(seen.get(ViewBean.class))); + assertTrue(hasGeneratedCodec(seen.get(ViewRecord.class))); + } + + // A plain bean with no @JsonView annotations: with DEFAULT_VIEW_INCLUSION + // off, stock hides every property under any active view on the write side + // and keeps them all on the read side (the deser matcher is null). The + // codec must reproduce both, which forces the MASK strategy for a bean + // that carries no view annotations at all. + public static class PlainBean { + private String a; + private int b; + public String getA() { return a; } + public void setA(String v) { a = v; } + public int getB() { return b; } + public void setB(int v) { b = v; } + } + + @Test + public void testDefaultViewInclusionOffMatchesVanilla() throws Exception { + ObjectMapper vanilla = JsonMapper.builder() + .disable(MapperFeature.DEFAULT_VIEW_INCLUSION) + .build(); + ObjectMapper module = mapperBuilder() + .disable(MapperFeature.DEFAULT_VIEW_INCLUSION) + .build(); + PlainBean bean = new PlainBean(); + bean.setA("x"); + bean.setB(7); + String doc = "{\"a\":\"x\",\"b\":7}"; + assertEquals(vanilla.writerWithView(ViewA.class).writeValueAsString(bean), + module.writerWithView(ViewA.class).writeValueAsString(bean), + "write, inclusion off, active view: both should omit all"); + PlainBean ev = vanilla.readerWithView(ViewA.class).forType(PlainBean.class).readValue(doc); + PlainBean am = module.readerWithView(ViewA.class).forType(PlainBean.class).readValue(doc); + assertEquals(ev.getA(), am.getA()); + assertEquals(ev.getB(), am.getB()); + } + + // More than 64 properties: the mask cannot fit, so an active view delegates + // the whole call. Output must still match vanilla in both directions. + public static class WideViewBean { + public int p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15; + public int p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31; + public int p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47; + public int p48, p49, p50, p51, p52, p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63; + @JsonView(ViewA.class) + public int p64; + public int p65; + } + + @Test + public void testWideBeanDelegatesUnderView() throws Exception { + WideViewBean bean = new WideViewBean(); + bean.p64 = 64; + bean.p65 = 65; + for (Class view : new Class[] { ViewA.class, ViewB.class }) { + assertEquals(VANILLA.writerWithView(view).writeValueAsString(bean), + MODULE.writerWithView(view).writeValueAsString(bean), + "wide bean under " + view.getSimpleName()); + } + assertEquals(VANILLA.writeValueAsString(bean), MODULE.writeValueAsString(bean), + "wide bean, no view"); + } + + // A codec whose bean has 64 or fewer properties keeps view-active calls on + // the generated mask path (only the wide bean above delegates). This + // check pins that the mask read is byte-faithful to stock: the ViewB + // property is hidden under ViewA, the visible ones match, and the skipped + // value leaves the stream correctly positioned for the trailing property. + @Test + public void testViewActiveMaskMatchesVanilla() throws Exception { + ViewBean expected = VANILLA.readerWithView(ViewA.class) + .forType(ViewBean.class).readValue(DOC); + ViewBean actual = MODULE.readerWithView(ViewA.class) + .forType(ViewBean.class).readValue(DOC); + assertEquals(expected.getA(), actual.getA()); + assertEquals(expected.getB(), actual.getB()); + assertEquals(expected.getPlain(), actual.getPlain()); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBReaderEntryTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBReaderEntryTest.java new file mode 100644 index 000000000..50cb96b19 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBReaderEntryTest.java @@ -0,0 +1,214 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import org.junit.jupiter.api.Test; + +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.databind.BeanDescription; +import tools.jackson.databind.DeserializationConfig; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.deser.ValueDeserializerModifier; +import tools.jackson.databind.exc.MismatchedInputException; +import tools.jackson.databind.deser.std.DelegatingDeserializer; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * PROPERTY_NAME entry through generated readers. The AsProperty polymorphic + * path hands a subtype deserializer a stream positioned on the property after + * the type id (or a buffered-replay sequence starting on one), so generated + * readers accept that entry natively instead of delegating. Every behavior + * case compares against a vanilla mapper. + */ +public class BBReaderEntryTest extends BlackbirdTestBase +{ + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") + @JsonSubTypes({ + @JsonSubTypes.Type(value = Dog.class, name = "dog"), + @JsonSubTypes.Type(value = RecCat.class, name = "cat") + }) + public interface Animal { } + + public static class Dog implements Animal { + public String name; + public int age; + public boolean good; + } + + public record RecCat(@JsonProperty(required = true) String name, int lives) + implements Animal { } + + public static class Plain { + public String s; + public int i; + } + + private final ObjectMapper mapper = newObjectMapper(); + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + // Pins the databind contract the native entry depends on: the AsProperty + // type deserializer invokes the subtype deserializer with the stream on + // PROPERTY_NAME, for the id-first (live stream) and id-last (buffered + // sequence) orders both. Runs against vanilla, no Blackbird. + @Test + public void databindHandsPropertyNameEntry() throws Exception + { + ConcurrentMap entries = new ConcurrentHashMap<>(); + ObjectMapper spied = JsonMapper.builder() + .addModule(new SimpleModule("spy") { + private static final long serialVersionUID = 1L; + @Override + public void setupModule(SetupContext ctxt) { + super.setupModule(ctxt); + ctxt.addDeserializerModifier(new ValueDeserializerModifier() { + private static final long serialVersionUID = 1L; + @Override + public ValueDeserializer modifyDeserializer( + DeserializationConfig config, + BeanDescription.Supplier beanDescRef, + ValueDeserializer deser) { + if (beanDescRef.getBeanClass() != Dog.class) { + return deser; + } + return new EntrySpy(deser, entries); + } + }); + } + }) + .build(); + + spied.readValue(a2q("{'type':'dog','name':'Rex','age':3,'good':true}"), Animal.class); + assertEquals(JsonToken.PROPERTY_NAME, entries.get("entry"), "id-first entry"); + + spied.readValue(a2q("{'name':'Rex','age':3,'good':true,'type':'dog'}"), Animal.class); + assertEquals(JsonToken.PROPERTY_NAME, entries.get("entry"), "id-last (buffered) entry"); + } + + @Test + public void polymorphicTypeIdFirst() throws Exception + { + String doc = a2q("{'type':'dog','name':'Rex','age':3,'good':true}"); + Dog d = (Dog) mapper.readValue(doc, Animal.class); + Dog v = (Dog) vanilla.readValue(doc, Animal.class); + assertEquals(v.name, d.name); + assertEquals(v.age, d.age); + assertEquals(v.good, d.good); + } + + @Test + public void polymorphicTypeIdLast() throws Exception + { + String doc = a2q("{'name':'Rex','age':3,'good':true,'type':'dog'}"); + Dog d = (Dog) mapper.readValue(doc, Animal.class); + Dog v = (Dog) vanilla.readValue(doc, Animal.class); + assertEquals(v.name, d.name); + assertEquals(v.age, d.age); + assertEquals(v.good, d.good); + } + + @Test + public void polymorphicRecordSubtype() throws Exception + { + String doc = a2q("{'type':'cat','name':'Mia','lives':9}"); + assertEquals(vanilla.readValue(doc, Animal.class), mapper.readValue(doc, Animal.class)); + + String middle = a2q("{'name':'Mia','type':'cat','lives':9}"); + assertEquals(vanilla.readValue(middle, Animal.class), + mapper.readValue(middle, Animal.class)); + + // Required component missing: same failure both stacks, through the + // PROPERTY_NAME entry. + String missing = a2q("{'type':'cat','lives':9}"); + MismatchedInputException expected = assertThrows(MismatchedInputException.class, + () -> vanilla.readValue(missing, Animal.class)); + MismatchedInputException actual = assertThrows(MismatchedInputException.class, + () -> mapper.readValue(missing, Animal.class)); + assertEquals(expected.getClass(), actual.getClass()); + } + + @Test + public void polymorphicIdOnlyEmptyRemainder() throws Exception + { + // Nothing after the type id: the subtype deserializer enters at + // END_OBJECT, which stays on the stock delegation path. + String doc = a2q("{'type':'dog'}"); + Dog d = (Dog) mapper.readValue(doc, Animal.class); + Dog v = (Dog) vanilla.readValue(doc, Animal.class); + assertEquals(v.name, d.name); + assertEquals(v.age, d.age); + } + + @Test + public void readValueAtPropertyName() throws Exception + { + String doc = a2q("{'s':'x','i':7}"); + Plain p1 = readPositioned(mapper, doc); + Plain p2 = readPositioned(vanilla, doc); + assertEquals(p2.s, p1.s); + assertEquals(p2.i, p1.i); + } + + @Test + public void unknownFirstNameAtEntry() throws Exception + { + String doc = a2q("{'mystery':[1,2,{'x':3}],'s':'x','i':7}"); + Plain p1 = readPositioned(mapper, doc); + Plain p2 = readPositioned(vanilla, doc); + assertEquals(p2.s, p1.s); + assertEquals(p2.i, p1.i); + } + + @Test + public void polymorphicList() throws Exception + { + String doc = a2q("[{'type':'dog','name':'a','age':1,'good':false}," + + "{'type':'cat','name':'b','lives':2}]"); + List got = mapper.readerForListOf(Animal.class).readValue(doc); + List want = vanilla.readerForListOf(Animal.class).readValue(doc); + assertEquals(want.size(), got.size()); + assertEquals(((Dog) want.get(0)).name, ((Dog) got.get(0)).name); + assertEquals(want.get(1), got.get(1)); + } + + static class EntrySpy extends DelegatingDeserializer { + private final ConcurrentMap entries; + + EntrySpy(ValueDeserializer d, ConcurrentMap entries) { + super(d); + this.entries = entries; + } + + @Override + protected ValueDeserializer newDelegatingInstance(ValueDeserializer newDelegatee) { + return new EntrySpy(newDelegatee, entries); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) { + entries.put("entry", p.currentToken()); + return super.deserialize(p, ctxt); + } + } + + private static Plain readPositioned(ObjectMapper m, String doc) throws Exception + { + try (JsonParser p = m.createParser(doc)) { + assertEquals(JsonToken.START_OBJECT, p.nextToken()); + assertEquals(JsonToken.PROPERTY_NAME, p.nextToken()); + return m.readValue(p, Plain.class); + } + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBReaderOddTokenTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBReaderOddTokenTest.java new file mode 100644 index 000000000..1813f52e6 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBReaderOddTokenTest.java @@ -0,0 +1,140 @@ +package tools.jackson.module.blackbird.deser; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.junit.jupiter.api.Test; + +import tools.jackson.core.JsonParser; +import tools.jackson.core.ObjectReadContext; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.exc.MismatchedInputException; +import tools.jackson.databind.util.TokenBuffer; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * A token that is neither a name nor END_OBJECT where a name is expected + * (possible from a non-JSON parser or a replayed buffer). Stock's vanilla + * loop reports it as unexpected, unless an unknown name came before it: then + * _vanillaDeserializeWithUnknown returns the bean as it is. Stock takes the + * vanilla loop only with DEFAULT_VIEW_INCLUSION on (off by default in 3.x); + * otherwise its general loop reports the token either way. A record (stock's + * property-based loop, which runs only while it sees names) ends its loop on + * the token and builds as at END_OBJECT. Every case compares against a + * vanilla mapper. + */ +public class BBReaderOddTokenTest extends BlackbirdTestBase +{ + public static class Plain { + public String s; + public int i; + } + + public record Rec(String s, int i) { } + + public record RequiredRec(String s, @JsonProperty(required = true) int i) { } + + private final ObjectMapper mapper = newObjectMapper(); + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + private final ObjectMapper viewInclusionMapper = mapperBuilder() + .enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build(); + private final ObjectMapper viewInclusionVanilla = newVanillaJSONMapper().rebuild() + .enable(MapperFeature.DEFAULT_VIEW_INCLUSION).build(); + + @Test + public void oddTokenAfterUnknownReturnsBean() throws Exception + { + assertGeneratedReader(viewInclusionMapper, Plain.class); + Plain expected = read(viewInclusionVanilla, true); + Plain actual = read(viewInclusionMapper, true); + assertEquals("a", expected.s); + assertEquals("a", actual.s); + assertEquals(expected.i, actual.i); + } + + @Test + public void oddTokenAfterUnknownFailsWithoutVanillaLoop() throws Exception + { + assertGeneratedReader(mapper, Plain.class); + assertSameFailure(vanilla, mapper, true); + } + + @Test + public void oddTokenWithoutUnknownFails() throws Exception + { + assertGeneratedReader(mapper, Plain.class); + assertSameFailure(vanilla, mapper, false); + assertGeneratedReader(viewInclusionMapper, Plain.class); + assertSameFailure(viewInclusionVanilla, viewInclusionMapper, false); + } + + @Test + public void oddTokenEndsRecord() throws Exception + { + assertGeneratedReader(mapper, Rec.class); + for (boolean withUnknown : new boolean[] { false, true }) { + Rec expected = read(vanilla, withUnknown, Rec.class); + assertEquals(new Rec("a", 0), expected); + assertEquals(expected, read(mapper, withUnknown, Rec.class)); + } + } + + // The component after the token is missing, as at END_OBJECT. + @Test + public void oddTokenEndsRecordMissingRequired() throws Exception + { + assertGeneratedReader(mapper, RequiredRec.class); + assertSameFailure(vanilla, mapper, false, RequiredRec.class); + } + + private static void assertSameFailure(ObjectMapper stock, ObjectMapper bb, + boolean withUnknown) + { + assertSameFailure(stock, bb, withUnknown, Plain.class); + } + + private static void assertSameFailure(ObjectMapper stock, ObjectMapper bb, + boolean withUnknown, Class type) + { + MismatchedInputException expected = assertThrows(MismatchedInputException.class, + () -> read(stock, withUnknown, type)); + MismatchedInputException actual = assertThrows(MismatchedInputException.class, + () -> read(bb, withUnknown, type)); + assertEquals(expected.getOriginalMessage(), actual.getOriginalMessage()); + } + + private static Plain read(ObjectMapper m, boolean withUnknown) throws Exception + { + return read(m, withUnknown, Plain.class); + } + + // {"s":"a", ["x":1,] 3, "i":5}: a bare number where a name belongs. + private static T read(ObjectMapper m, boolean withUnknown, Class type) + throws Exception + { + TokenBuffer tb = TokenBuffer.forGeneration(); + tb.writeStartObject(); + tb.writeName("s"); + tb.writeString("a"); + if (withUnknown) { + tb.writeName("x"); + tb.writeNumber(1); + } + tb.writeNumber(3); + tb.writeName("i"); + tb.writeNumber(5); + tb.writeEndObject(); + try (JsonParser p = tb.asParser(ObjectReadContext.empty())) { + return m.readerFor(type) + // Stock's early return leaves the rest of the object + // unread. + .without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, + DeserializationFeature.FAIL_ON_TRAILING_TOKENS) + .readValue(p); + } + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBReaderUpdatingTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBReaderUpdatingTest.java new file mode 100644 index 000000000..19d51c98f --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/BBReaderUpdatingTest.java @@ -0,0 +1,60 @@ +package tools.jackson.module.blackbird.deser; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * readerForUpdating parity: the generated reader forwards update-value reads + * to the stock deserializer, so updating must behave exactly like vanilla. + */ +public class BBReaderUpdatingTest extends BlackbirdTestBase +{ + public static class Mutable { + private int count; + private String name; + private String keep; + + public int getCount() { return count; } + public void setCount(int c) { count = c; } + public String getName() { return name; } + public void setName(String n) { name = n; } + public String getKeep() { return keep; } + public void setKeep(String k) { keep = k; } + } + + private final ObjectMapper mapper = newObjectMapper(); + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + @Test + public void testUpdatingReadMatchesVanilla() throws Exception { + String doc = a2q("{'count':42,'name':'updated'}"); + + Mutable mine = new Mutable(); + mine.setKeep("original"); + mine.setName("old"); + Object result = mapper.readerForUpdating(mine).readValue(doc); + assertSame(mine, result); + + Mutable theirs = new Mutable(); + theirs.setKeep("original"); + theirs.setName("old"); + vanilla.readerForUpdating(theirs).readValue(doc); + + assertEquals(theirs.getCount(), mine.getCount()); + assertEquals(theirs.getName(), mine.getName()); + assertEquals(theirs.getKeep(), mine.getKeep()); + assertEquals("original", mine.getKeep()); + } + + @Test + public void testPlainReadStillGenerated() throws Exception { + Mutable m = mapper.readValue(a2q("{'count':7,'name':'n','keep':'k'}"), Mutable.class); + assertEquals(7, m.getCount()); + assertEquals("n", m.getName()); + assertEquals("k", m.getKeep()); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/OptionalDeser355Test.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/OptionalDeser355Test.java index 0c8948940..b076c73a2 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/OptionalDeser355Test.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/OptionalDeser355Test.java @@ -10,16 +10,12 @@ import tools.jackson.databind.*; import tools.jackson.databind.annotation.JsonDeserialize; import tools.jackson.databind.deser.SettableBeanProperty; -import tools.jackson.databind.deser.ValueDeserializerModifier; import tools.jackson.databind.deser.bean.BeanDeserializerBase; import tools.jackson.databind.deser.impl.MethodProperty; import tools.jackson.databind.ext.jdk8.Jdk8OptionalDeserializer; -import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.jsontype.TypeDeserializer; -import tools.jackson.databind.module.SimpleModule; import tools.jackson.databind.type.TypeFactory; -import tools.jackson.module.blackbird.BlackbirdModule; import tools.jackson.module.blackbird.BlackbirdTestBase; import static org.junit.jupiter.api.Assertions.*; @@ -99,48 +95,38 @@ public CustomOptionalDeserializer withResolved(TypeDeserializer typeDeser, } } - static class DeserializerCapture extends ValueDeserializerModifier { - private static final long serialVersionUID = 1L; - + static class DeserializerCapture { private final Map, ValueDeserializer> deserializers = new ConcurrentHashMap<>(); - @Override - public ValueDeserializer modifyDeserializer(DeserializationConfig config, - BeanDescription.Supplier beanDescRef, ValueDeserializer deserializer) { - deserializers.put(beanDescRef.getBeanClass(), deserializer); - return deserializer; - } - - public SettableBeanProperty propertyFor(Class beanType, String propertyName) { + public ValueDeserializer deserializerFor(Class beanType) { ValueDeserializer deserializer = deserializers.get(beanType); - BeanDeserializerBase beanDeserializer = assertInstanceOf( - BeanDeserializerBase.class, deserializer); - Iterator properties = beanDeserializer.properties(); - while (properties.hasNext()) { - SettableBeanProperty property = properties.next(); - if (property.getName().equals(propertyName)) { - return property; - } - } - return fail("No property '"+propertyName+"' found for "+beanType.getName()); + assertNotNull(deserializer, "No deserializer captured for " + beanType.getName()); + return deserializer; } } + // The new engine never swaps individual properties: eligible beans - + // these package-private ones included - get a whole generated codec, and + // every Optional-typed property rides its stock SettableBeanProperty + // inside it (the issue-355 revert cannot recur by construction). The + // original per-property assertions named the old engine's classes; their + // equivalent here is codec engagement plus behavior. @Test - public void testKeepsOptimizedPropertyForBuiltInOptionalDeserializer() throws Exception { + public void testOptionalPropertyWithBuiltInDeserializer() throws Exception { DeserializerCapture capture = new DeserializerCapture(); ObjectMapper mapper = mapperWithCapture(capture); OptionalBean bean = mapper.readValue("{\"value\":\"test\"}", OptionalBean.class); assertEquals(Optional.of("test"), bean.getValue()); - assertInstanceOf(SettableObjectProperty.class, - capture.propertyFor(OptionalBean.class, "value")); + assertTrue(hasGeneratedCodec(capture.deserializerFor(OptionalBean.class))); + assertEquals(Optional.empty(), + mapper.readValue("{\"value\":null}", OptionalBean.class).getValue()); } @Test - public void testFallsBackForCustomOptionalDeserializer() throws Exception { + public void testOptionalPropertyWithCustomDeserializer() throws Exception { DeserializerCapture capture = new DeserializerCapture(); ObjectMapper mapper = mapperWithCapture(capture); @@ -149,18 +135,11 @@ public void testFallsBackForCustomOptionalDeserializer() throws Exception { assertEquals(Optional.of("test"), bean.getValue()); assertEquals(Optional.of(42), bean.getNumber()); - assertInstanceOf(MethodProperty.class, - capture.propertyFor(CustomOptionalBean.class, "value")); - assertInstanceOf(MethodProperty.class, - capture.propertyFor(CustomOptionalBean.class, "number")); + // The custom-deserializer properties ride the codec's stock arms. + assertTrue(hasGeneratedCodec(capture.deserializerFor(CustomOptionalBean.class))); } private ObjectMapper mapperWithCapture(DeserializerCapture capture) { - SimpleModule captureModule = new SimpleModule() - .setDeserializerModifier(capture); - return JsonMapper.builder() - .addModule(new BlackbirdModule()) - .addModule(captureModule) - .build(); + return capturingMapper(capture.deserializers); } } diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/PackagePrivateBeanTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/PackagePrivateBeanTest.java new file mode 100644 index 000000000..357adb36c --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/PackagePrivateBeanTest.java @@ -0,0 +1,178 @@ +package tools.jackson.module.blackbird.deser; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.exc.MismatchedInputException; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Non-public classes accelerate: package-private, protected, and private + * static nested beans (and their non-private members) get generated codecs. + * Member access rides databind's fixAccess through unreflected constant + * handles, so the generated class never names the bean and accessibility + * never gates a bean stock databind handles. Every behavioral check compares + * against a vanilla mapper. + */ +public class PackagePrivateBeanTest extends BlackbirdTestBase +{ + static class PkgBean { + private int count; + private String name; + + public int getCount() { return count; } + public void setCount(int v) { count = v; } + public String getName() { return name; } + public void setName(String v) { name = v; } + } + + static class PkgMembersBean { + int direct; + private long viaSetter; + + int getDirect() { return direct; } + void setDirect(int v) { direct = v; } + long getViaSetter() { return viaSetter; } + void setViaSetter(long v) { viaSetter = v; } + } + + private static class PrivateBean { + public int x; + private String s; + + // Explicit annotations: private accessors are not auto-detected, and + // this pin is exactly about reaching them once databind selects them. + @JsonProperty("s") + private String getS() { return s; } + @JsonProperty("s") + private void setS(String v) { s = v; } + } + + protected static class ProtectedBean { + public int x; + public String s; + } + + record PkgRecord(@JsonProperty(required = true) String id, int qty) { } + + static class Capture { + final Map, ValueDeserializer> desers = new ConcurrentHashMap<>(); + final Map, ValueSerializer> sers = new ConcurrentHashMap<>(); + } + + private static ObjectMapper mapperWith(Capture capture) { + return capturingMapperBuilder(capture.desers, capture.sers).build(); + } + + // The placeholder resolves its codec at resolve time; a null codec means + // the generator gated the bean, so the assertion distinguishes "engaged + // and generated" from "engaged but silently demoted". + private static void assertCodecGenerated(Object placeholder) throws Exception { + assertEquals("BBReaderPlaceholder", placeholder.getClass().getSimpleName(), + "expected the deserializer placeholder, got " + + placeholder.getClass().getName()); + assertTrue(hasGeneratedCodec(placeholder), "placeholder engaged but no codec generated"); + } + + private static void assertWriterGenerated(Object placeholder) throws Exception { + assertEquals("BBWriterPlaceholder", placeholder.getClass().getSimpleName(), + "expected the serializer placeholder, got " + + placeholder.getClass().getName()); + assertTrue(hasGeneratedCodec(placeholder), "placeholder engaged but no writer generated"); + } + + @Test + public void testPackagePrivateBeanAccelerates() throws Exception { + Capture capture = new Capture(); + ObjectMapper mapper = mapperWith(capture); + ObjectMapper vanilla = newVanillaJSONMapper(); + + String doc = a2q("{'count':7,'name':'x'}"); + PkgBean act = mapper.readValue(doc, PkgBean.class); + PkgBean exp = vanilla.readValue(doc, PkgBean.class); + assertEquals(exp.getCount(), act.getCount()); + assertEquals(exp.getName(), act.getName()); + assertEquals(vanilla.writeValueAsString(exp), mapper.writeValueAsString(act)); + + assertCodecGenerated(capture.desers.get(PkgBean.class)); + assertWriterGenerated(capture.sers.get(PkgBean.class)); + } + + @Test + public void testPackagePrivateMembersAccelerate() throws Exception { + Capture capture = new Capture(); + ObjectMapper mapper = mapperWith(capture); + ObjectMapper vanilla = newVanillaJSONMapper(); + + String doc = a2q("{'direct':3,'viaSetter':9000000000}"); + PkgMembersBean act = mapper.readValue(doc, PkgMembersBean.class); + PkgMembersBean exp = vanilla.readValue(doc, PkgMembersBean.class); + assertEquals(exp.getDirect(), act.getDirect()); + assertEquals(exp.getViaSetter(), act.getViaSetter()); + assertEquals(vanilla.writeValueAsString(exp), mapper.writeValueAsString(act)); + + assertCodecGenerated(capture.desers.get(PkgMembersBean.class)); + } + + @Test + public void testPrivateClassAccelerates() throws Exception { + // Widened from a demotion pin: private classes, private setters, and + // the private constructor all reach the codec through fixAccess'd + // handles now. + Capture capture = new Capture(); + ObjectMapper mapper = mapperWith(capture); + ObjectMapper vanilla = newVanillaJSONMapper(); + + String doc = a2q("{'x':5,'s':'in'}"); + PrivateBean act = mapper.readValue(doc, PrivateBean.class); + PrivateBean exp = vanilla.readValue(doc, PrivateBean.class); + assertEquals(exp.x, act.x); + assertEquals(exp.getS(), act.getS()); + assertEquals("in", act.getS()); + assertEquals(vanilla.writeValueAsString(exp), mapper.writeValueAsString(act)); + + assertCodecGenerated(capture.desers.get(PrivateBean.class)); + assertWriterGenerated(capture.sers.get(PrivateBean.class)); + } + + @Test + public void testProtectedNestedBeanAccelerates() throws Exception { + Capture capture = new Capture(); + ObjectMapper mapper = mapperWith(capture); + ObjectMapper vanilla = newVanillaJSONMapper(); + + String doc = a2q("{'x':1,'s':'y'}"); + ProtectedBean act = mapper.readValue(doc, ProtectedBean.class); + assertEquals(1, act.x); + assertEquals("y", act.s); + assertEquals(vanilla.writeValueAsString(act), mapper.writeValueAsString(act)); + + assertCodecGenerated(capture.desers.get(ProtectedBean.class)); + } + + @Test + public void testPackagePrivateRecord() throws Exception { + Capture capture = new Capture(); + ObjectMapper mapper = mapperWith(capture); + ObjectMapper vanilla = newVanillaJSONMapper(); + + String doc = a2q("{'id':'a','qty':2}"); + assertEquals(vanilla.readValue(doc, PkgRecord.class), + mapper.readValue(doc, PkgRecord.class)); + assertCodecGenerated(capture.desers.get(PkgRecord.class)); + + MismatchedInputException e = assertThrows(MismatchedInputException.class, + () -> mapper.readValue(a2q("{'qty':2}"), PkgRecord.class)); + assertTrue(e.getMessage().contains("Missing required creator property 'id'"), + e.getMessage()); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestBuilders.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestBuilders.java index 71f0f9b70..7af21292f 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestBuilders.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestBuilders.java @@ -4,8 +4,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Supplier; import org.junit.jupiter.api.Test; @@ -15,7 +13,6 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; -import tools.jackson.core.JsonParser; import tools.jackson.databind.DatabindException; import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.DeserializationFeature; @@ -25,7 +22,6 @@ import tools.jackson.databind.PropertyName; import tools.jackson.databind.annotation.JsonDeserialize; import tools.jackson.databind.annotation.JsonPOJOBuilder; -import tools.jackson.databind.deser.DeserializationContextExt; import tools.jackson.databind.deser.SettableBeanProperty; import tools.jackson.databind.deser.bean.BeanDeserializerBase; import tools.jackson.databind.deser.impl.MethodProperty; @@ -807,12 +803,16 @@ public void testSimpleBuilder() throws Exception assertEquals(expected.foo, actual.foo); } + // Only the `List` property, which generated readers leave to its stock + // `MethodProperty`, goes through reflection; the scalar ones are generated + private static final int STOCK_PROPERTIES = 1; + @Test public void testMutableBuilderSkipsReflection() throws Exception { Values v = MAPPER.readValue(VALUES_JSON, MutableBuilt.class); _verifyValues(v); - assertEquals(0, v.reflectiveCalls); + assertEquals(STOCK_PROPERTIES, v.reflectiveCalls); } @Test @@ -820,7 +820,7 @@ public void testCopyingBuilderSkipsReflection() throws Exception { Values v = MAPPER.readValue(VALUES_JSON, CopyingBuilt.class); _verifyValues(v); - assertEquals(0, v.reflectiveCalls); + assertEquals(STOCK_PROPERTIES, v.reflectiveCalls); } // Sanity check that the reflection probe works at all @@ -870,7 +870,7 @@ public void testVoidSetterBuilder() throws Exception { Values v = MAPPER.readValue(VALUES_JSON, VoidSetterBuilt.class); _verifyValues(v); - assertEquals(0, v.reflectiveCalls); + assertEquals(STOCK_PROPERTIES, v.reflectiveCalls); // as reflection would, with Jackson's own access rules assertEquals(5, newVanillaJSONMapper().readValue(VALUES_JSON, VoidSetterBuilt.class).reflectiveCalls); } @@ -931,13 +931,16 @@ public void testThrowingSetterMatchesVanilla() throws Exception String desc = "wrap=" + wrap + ", " + json + ", " + expected; assertEquals(2, setterCalls, desc); _assertSameFailure(desc, expected, actual); - // and the Blackbird failure came from the generated accessor + // and the Blackbird failure came from the generated reader, except + // for the `List` property, which stays on the stock path Throwable root = actual; while (root.getCause() != null) { root = root.getCause(); } - assertTrue(Arrays.stream(root.getStackTrace()) - .noneMatch(e -> e.getClassName().equals(MethodProperty.class.getName())), desc); + if (!json.contains("list")) { + assertTrue(Arrays.stream(root.getStackTrace()) + .noneMatch(e -> e.getClassName().equals(MethodProperty.class.getName())), desc); + } } } } @@ -996,8 +999,8 @@ public void testReplacedBuilderOfBaseClass() throws Exception @Test public void testUnusableBuilderInstanceMatchesVanilla() throws Exception { - assertTrue(_findProperty(MAPPER, UnrelatedBuilt.class, "x") instanceof OptimizedSettableBeanProperty); - assertTrue(_findProperty(MAPPER, NullCreatorBuilt.class, "x") instanceof OptimizedSettableBeanProperty); + assertGeneratedReader(MAPPER, UnrelatedBuilt.class); + assertGeneratedReader(MAPPER, NullCreatorBuilt.class); for (boolean wrap : new boolean[] { true, false }) { ObjectMapper vanilla = JsonMapper.builder() .configure(DeserializationFeature.WRAP_EXCEPTIONS, wrap).build(); @@ -1019,46 +1022,6 @@ public void testUnusableBuilderInstanceMatchesVanilla() throws Exception } } - // A failure of the generated accessor itself (simulated by the anonymous classes) - // is reported like the setter's (an `Error` as it is, as `MethodProperty` does), - // without trying the setter again through reflection - @Test - public void testAccessorFailureIsNotRetried() throws Exception - { - ObjectMapper vanilla = newVanillaJSONMapper(); - BeanDeserializerBase deser = (BeanDeserializerBase) vanilla._deserializationContext() - .findRootValueDeserializer(vanilla.constructType(ThrowingBuilt.class)); - IllegalAccessError failure = new IllegalAccessError("simulated"); - SettableStringProperty prop = new SettableStringProperty(deser.findProperty(PropertyName.construct("str")), - new BiConsumer() { - @Override - public void accept(Object bean, String v) { - throw failure; - } - }, - new BiFunction() { - @Override - public Object apply(Object bean, Object v) { - throw failure; - } - }, - ThrowingBuilder.class, true); - toThrow = () -> new IllegalStateException("thrown by setter"); - setterCalls = 0; - - assertSame(failure, assertThrows(IllegalAccessError.class, - () -> prop.setAndReturn(CTXT, new ThrowingBuilder(), "foo"))); - try (JsonParser p = vanilla.createParser(a2q("{'str':'foo'}"))) { - p.nextToken(); - p.nextToken(); - p.nextToken(); - DeserializationContextExt ctxt = vanilla._deserializationContext().assignParser(p); - assertSame(failure, assertThrows(IllegalAccessError.class, - () -> prop.deserializeSetAndReturn(p, ctxt, new ThrowingBuilder()))); - } - assertEquals(0, setterCalls); - } - @Test public void testInheritedPackagePrivateSetter() throws Exception { @@ -1080,8 +1043,8 @@ public void testInjectedPackagePrivateSetterInOtherPackage() throws Exception } } - // As `deserializeSetAndReturn` does, `setAndReturn` leaves a builder the generated - // setter can't take to reflection, and skips nulls + // `setAndReturn` with a builder the setter can't take fails as reflection does, + // and skips nulls @Test public void testSetAndReturnWithUnusableBuilder() throws Exception { @@ -1100,7 +1063,6 @@ public void testSetAndReturnWithUnusableBuilder() throws Exception } for (String name : new String[] { "str", "list" }) { SettableBeanProperty prop = _findProperty(MAPPER, SkipNullsBuilt.class, name); - assertTrue(prop instanceof OptimizedSettableBeanProperty, name); SkipNullsBuilder builder = new SkipNullsBuilder(); assertSame(builder, prop.setAndReturn(CTXT, builder, null)); assertEquals("default", builder.str); @@ -1111,10 +1073,7 @@ public void testSetAndReturnWithUnusableBuilder() throws Exception @Test public void testNullReturningBuilderForAllTypes() throws Exception { - for (String name : new String[] { "l", "b", "list" }) { - assertTrue(_findProperty(MAPPER, NullReturningAllBuilt.class, name) instanceof OptimizedSettableBeanProperty, - name); - } + assertGeneratedReader(MAPPER, NullReturningAllBuilt.class); NullReturningAllBuilt v = MAPPER.readValue(a2q("{'l':7,'b':true,'list':['x']}"), NullReturningAllBuilt.class); assertEquals(7L, v.l); assertTrue(v.b); @@ -1134,9 +1093,7 @@ public void testBuilderNullHandlingMatchesVanilla() throws Exception .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT).build(); ObjectMapper blackbird = mapperBuilder() .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT).build(); - for (String name : new String[] { "empty", "skip" }) { - assertTrue(_findProperty(blackbird, NullsBuilt.class, name) instanceof OptimizedSettableBeanProperty, name); - } + assertGeneratedReader(blackbird, NullsBuilt.class); // explicit nulls, and empty Strings the deserializer turns into nulls for (String json : new String[] { a2q("{'empty':null,'skip':null}"), a2q("{'empty':'','skip':''}") }) { String expected = vanilla.readValue(json, NullsBuilt.class).result; @@ -1153,7 +1110,6 @@ public void testSetAndReturnNullPrimitives() throws Exception for (String name : new String[] { "i", "l", "b" }) { SettableBeanProperty expectedProperty = _findProperty(vanilla, name); SettableBeanProperty actualProperty = _findProperty(MAPPER, name); - assertTrue(actualProperty instanceof OptimizedSettableBeanProperty, name); MutableBuilder expectedBuilder = new MutableBuilder(); MutableBuilder actualBuilder = new MutableBuilder(); Throwable expected = assertThrows(Throwable.class, @@ -1164,13 +1120,12 @@ public void testSetAndReturnNullPrimitives() throws Exception assertEquals(0, actualBuilder.reflectiveCalls, name); SettableBeanProperty skipping = actualProperty.withNullProvider(NullsConstantProvider.skipper()); - assertTrue(skipping instanceof OptimizedSettableBeanProperty, name); assertSame(actualBuilder, skipping.setAndReturn(CTXT, actualBuilder, null), name); assertEquals(0, actualBuilder.reflectiveCalls, name); } } - // The generated setter may only call what `MethodProperty`'s method handle could: + // The generated reader may only call what `MethodProperty`'s method handle could: // the protected setters only if Jackson made them accessible, and (with databind in // its own module, which doesn't read this one) the public one too @Test @@ -1188,18 +1143,10 @@ public void testAccessModifierOverridesMatchVanilla() throws Exception .configure(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS, canOverride) .configure(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS, overridePublic).build(); - // including a property buffered before the creator property, and a - // builder without a build method (which is still a builder) - for (Class type : new Class[] { ProtectedSetterBuilt.class, - ProtectedCreatorSetterBuilt.class, NoBuildMethodBuilder.class }) { - assertEquals(canOverride, _findProperty(blackbird, type, "value") - instanceof OptimizedSettableBeanProperty, desc + ", " + type.getSimpleName()); - } - assertEquals(canOverride && overridePublic, _findProperty(blackbird, PublicSetterBuilt.class, "value") - instanceof OptimizedSettableBeanProperty, desc); - if (!canOverride) { - // the protected setter isn't called (and fails as with reflection) + // the protected setter isn't called (and fails as with reflection), + // including for a property buffered before the creator property, + // and a builder without a build method (which is still a builder) for (String j : new String[] { json, bufferedJson }) { for (Class type : new Class[] { ProtectedSetterBuilt.class, ProtectedCreatorSetterBuilt.class, NoBuildMethodBuilder.class }) { @@ -1225,7 +1172,7 @@ public void testAccessModifierOverridesMatchVanilla() throws Exception @Test public void testValueOfAnotherTypeMatchesVanilla() throws Exception { - assertTrue(_findProperty(MAPPER, ChildBuilt.class, "child") instanceof OptimizedSettableBeanProperty); + assertGeneratedReader(MAPPER, ChildBuilt.class); String json = a2q("{'child':{}}"); Throwable expected = assertThrows(Throwable.class, () -> newVanillaJSONMapper().readValue(json, ChildBuilt.class)); Throwable actual = assertThrows(Throwable.class, () -> MAPPER.readValue(json, ChildBuilt.class)); @@ -1256,7 +1203,6 @@ public void testSetWithValueOfAnotherTypeMatchesVanilla() throws Exception String name = (String) c[0]; SettableBeanProperty expectedProp = _findProperty(vanilla, VoidSetterBuilt.class, name); SettableBeanProperty actualProp = _findProperty(MAPPER, VoidSetterBuilt.class, name); - assertTrue(actualProp instanceof OptimizedSettableBeanProperty, name); for (int i = 2; i < c.length; i++) { Object value = c[i]; String desc = name + " = " + value + " (" + value.getClass().getSimpleName() + ")"; @@ -1287,8 +1233,7 @@ public void testSetWithValueOfAnotherTypeMatchesVanilla() throws Exception @Test public void testPolymorphicNullMatchesVanilla() throws Exception { - assertTrue(_findProperty(MAPPER, ItemBuilt.class, "fluent") instanceof OptimizedSettableBeanProperty); - assertTrue(_findProperty(MAPPER, ItemBuilt.class, "void") instanceof OptimizedSettableBeanProperty); + assertGeneratedReader(MAPPER, ItemBuilt.class); for (String name : new String[] { "fluent", "void" }) { String json = a2q("{'" + name + "':{'@type':'Unknown'}}"); itemCalls = 0; @@ -1302,7 +1247,7 @@ public void testPolymorphicNullMatchesVanilla() throws Exception // `setAndReturn` can be given values its property's deserializer didn't produce, // such as an object id from a generator. Reflection widens some and rejects - // others, and the generated setters must not cast or truncate them instead. + // others, and Blackbird must not cast or truncate them instead. @Test public void testSetAndReturnWithOtherValueTypes() throws Exception { @@ -1315,17 +1260,9 @@ public void testSetAndReturnWithOtherValueTypes() throws Exception ObjectMapper vanilla = newVanillaJSONMapper(); for (Object[] c : cases) { String name = (String) c[0]; - assertTrue(_findProperty(MAPPER, name) instanceof OptimizedSettableBeanProperty, name); assertEquals(_setAndReturn(vanilla, name, c[1]), _setAndReturn(MAPPER, name, c[1]), name + "=" + c[1] + " (" + c[1].getClass().getSimpleName() + ")"); } - // while values of the setter's own type still use the generated setters - for (Object[] c : new Object[][] { { "i", 42 }, { "l", 42L }, { "b", true }, { "str", "x" }, - { "list", Arrays.asList("x") } }) { - MutableBuilder b = (MutableBuilder) _findProperty(MAPPER, (String) c[0]) - .setAndReturn(CTXT, new MutableBuilder(), c[1]); - assertEquals(0, b.reflectiveCalls, String.valueOf(c[0])); - } } private static String _setAndReturn(ObjectMapper mapper, String name, Object value) throws Exception { diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestInaccessibleSetterTypes.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestInaccessibleSetterTypes.java index 80a9e137a..543c28299 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestInaccessibleSetterTypes.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestInaccessibleSetterTypes.java @@ -2,28 +2,16 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.logging.Handler; -import java.util.logging.LogRecord; -import java.util.logging.Logger; import org.junit.jupiter.api.Test; import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.PropertyName; import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.deser.DeserializationContextExt; -import tools.jackson.databind.deser.bean.BeanDeserializerBase; -import tools.jackson.databind.deser.SettableBeanProperty; -import tools.jackson.databind.deser.impl.MethodProperty; import tools.jackson.module.blackbird.BlackbirdModule; import tools.jackson.module.blackbird.BlackbirdTestBase; import tools.jackson.module.blackbird.deser.isolated.Token; @@ -54,12 +42,14 @@ import static org.junit.jupiter.api.Assertions.*; -// Setters whose generated accessor would fail when called, even though reflection -// can call them, must be left to reflection +// Setters that generated code could easily get wrong, though reflection calls them +// fine: their types are inaccessible, shadowed by the bean's loader, or must not be +// loaded. Generated readers must handle them as stock does, without loading anything. +// (From #366, which fixed these for the earlier LambdaMetafactory engine.) public class TestInaccessibleSetterTypes extends BlackbirdTestBase { // `setMode` and `setModes` take a type that is package-private in another - // package, which reflection can pass but a generated accessor can't refer to + // package, which reflection can pass but generated code can't name static final class ModeBuilder extends ModeBuilderBase { public ModeBuilt build() { @@ -87,9 +77,8 @@ static final class ProtectedBean extends ProtectedSetterBeanBase { } static final class HiddenSetterBean extends HiddenSetterBeanBase { } - // Setters with JDK `java.*` signatures keep the generated setter, as do those with - // other types from other loaders (like JDK `javax.*` ones) when declared by a class - // from the lookup class's loader, which resolved them through that loader + // Common setter signatures, including fluent ones and types from other loaders + // (JDK `javax.*` ones) static class CommonSignatures { String a; @@ -113,40 +102,10 @@ public void testInaccessibleParameterType() throws Exception assertEquals("[SAFE]", result.modes()); assertEquals("foo", result.name()); assertEquals(2, ModeBeanBase.setterCalls); - // rather than failing (and falling back) on every call - assertEquals(MethodProperty.class, _findProperty(ModeBean.class, "mode").getClass()); - assertEquals(MethodProperty.class, _findProperty(ModeBean.class, "modes").getClass()); - SettableBeanProperty name = _findProperty(ModeBean.class, "name"); - assertTrue(name instanceof OptimizedSettableBeanProperty, name.getClass().getName()); + assertGeneratedReader(MAPPER, ModeBean.class); } - // so it's possible to find out why a property isn't optimized - @Test - public void testSkippedSetterIsLogged() throws Exception - { - Logger logger = Logger.getLogger(BBDeserializerModifier.class.getName()); - List records = Collections.synchronizedList(new java.util.ArrayList<>()); - Handler handler = new Handler() { - @Override public void publish(LogRecord r) { records.add(r); } - @Override public void flush() { } - @Override public void close() { } - }; - java.util.logging.Level oldLevel = logger.getLevel(); - logger.setLevel(java.util.logging.Level.FINE); - logger.addHandler(handler); - try { - _findProperty(newObjectMapper(), ModeBean.class, "mode"); - } finally { - logger.removeHandler(handler); - logger.setLevel(oldLevel); - } - assertTrue(records.stream().anyMatch(r -> r.getMessage().contains("setMode") - && r.getMessage().contains("couldn't link")), records.toString()); - assertFalse(records.stream().anyMatch(r -> r.getMessage().contains("setName"))); - } - - // `unreflect` refuses some inherited setters that reflection can call; these used - // to fail the whole deserializer + // Inherited setters that only reflection, or a lookup in the subclass, can call @Test public void testInheritedSettersFromAnotherPackage() throws Exception { @@ -154,8 +113,8 @@ public void testInheritedSettersFromAnotherPackage() throws Exception assertEquals(3, MAPPER.readValue(a2q("{'y':3}"), HiddenSetterBean.class).y()); } - // The bean's loader has its own copy of `Level`, so a generated accessor would - // resolve that one and fail to cast the inherited setter's `Level` to it + // The bean's loader has its own copy of `Level`, so code resolving it from there + // would fail to pass it to the inherited setter, which takes the parent's @Test public void testShadowedParameterType() throws Exception { @@ -169,12 +128,12 @@ public void testShadowedParameterType() throws Exception LevelBeanBase result = (LevelBeanBase) MAPPER.readValue(a2q("{'level':'HIGH'}"), bean); assertEquals("HIGH", result.level()); assertEquals(1, LevelBeanBase.setterCalls); - _assertOptimized(!lookupInBean(bean), _findProperty(bean, "level")); + assertGeneratedReader(MAPPER, bean); } } - // Same for a fluent setter's return type: nothing casts to it, but calling the - // setter names it, and the JVM requires both loaders to agree on it + // Same for a fluent setter's return type: nothing casts to it, but a call naming it + // requires both loaders to agree on it @Test public void testShadowedReturnType() throws Exception { @@ -189,12 +148,12 @@ public void testShadowedReturnType() throws Exception ChainBeanBase result = (ChainBeanBase) MAPPER.readValue(a2q("{'level':'HIGH'}"), bean); assertEquals("HIGH", result.level()); assertEquals(1, ChainBeanBase.setterCalls); - _assertOptimized(!lookupInBean(bean), _findProperty(bean, "level")); + assertGeneratedReader(MAPPER, bean); } } - // Nor may the check load the return type, which the generated setter never does: - // the loader would define its own copy, and the code using it might then fail + // Nor may Blackbird load the return type, which stock never does: the loader would + // define its own copy, and the code using it might then fail @Test public void testReturnTypeIsNotLoaded() throws Exception { @@ -211,52 +170,41 @@ public void testReturnTypeIsNotLoaded() throws Exception } } - // And on Java 8 for the declaring class of an inherited protected setter, which isn't - // in the handle's type (its receiver is narrowed to the bean), but still named by the - // call; newer JDKs don't name it, so keep the generated setter there + // Same for the declaring class of an inherited protected setter @Test public void testShadowedDeclaringClass() throws Exception { - ClassLoader loader = new ChildFirstLoader(getClass().getClassLoader(), - Arrays.asList(OwnerBase.class, OwnerBean.class)); - Class bean = Class.forName(OwnerBean.class.getName(), true, loader); - assertNotSame(OwnerBase.class, Class.forName(OwnerBase.class.getName(), false, loader)); - ObjectMapper mapper = _mapperWithLookupIn(bean); - - OwnerBase.setterCalls = 0; - Object result = mapper.readValue(a2q("{'value':7}"), bean); - assertEquals(7, bean.getMethod("value").invoke(result)); - assertEquals(1, OwnerBase.setterCalls); - SettableBeanProperty prop = _findProperty(mapper, bean, "value"); - int version = _javaVersion(); - if (version == 8) { - assertEquals(MethodProperty.class, prop.getClass()); - } else if (version >= 14) { - // (JDK 9 to 13 always use an access class, for which `unreflect` refuses the setter) - assertTrue(prop instanceof OptimizedSettableBeanProperty, prop.getClass().getName()); + for (boolean reads : READS) { + ClassLoader loader = new ChildFirstLoader(getClass().getClassLoader(), + Arrays.asList(OwnerBase.class, OwnerBean.class)); + Class bean = load(loader, OwnerBean.class, reads); + assertNotSame(OwnerBase.class, Class.forName(OwnerBase.class.getName(), false, loader)); + + OwnerBase.setterCalls = 0; + Object result = MAPPER.readValue(a2q("{'value':7}"), bean); + assertEquals(7, bean.getMethod("value").invoke(result)); + assertEquals(1, OwnerBase.setterCalls); + assertGeneratedReader(MAPPER, bean); } } - // Nor may checking the declaring class load it, when the generated setter wouldn't - // (or wouldn't exist at all, as for a `double` setter) + // Nor may Blackbird load the declaring class (including for a `double` setter) @Test public void testDeclaringClassIsNotLoaded() throws Exception { - for (boolean beanLookup : new boolean[] { false, true }) { + for (boolean reads : READS) { ChildFirstLoader loader = new ChildFirstLoader(getClass().getClassLoader(), Arrays.asList(OwnerBase.class, OwnerBean.class)); - Class bean = Class.forName(OwnerBean.class.getName(), true, loader); - ObjectMapper mapper = beanLookup ? _mapperWithLookupIn(bean) : MAPPER; - Object result = mapper.readValue(a2q("{'value':7,'ratio':0.5}"), bean); + Class bean = load(loader, OwnerBean.class, reads); + Object result = MAPPER.readValue(a2q("{'value':7,'ratio':0.5}"), bean); assertEquals(7, bean.getMethod("value").invoke(result)); assertEquals(0.5, bean.getMethod("ratio").invoke(result)); - assertFalse(loader.defined.contains(OwnerBase.class.getName()), "beanLookup=" + beanLookup); + assertFalse(loader.defined.contains(OwnerBase.class.getName()), "reads=" + reads); } } - // Nor may the parameter check load a type from another loader: the property falls - // back to reflection either way, but the copy it would define can't be undone, and - // the code relying on that loader then fails + // Nor a parameter type from another loader: the copy it would define can't be + // undone, and the code relying on that loader then fails @Test public void testParameterTypeIsNotLoaded() throws Exception { @@ -283,10 +231,7 @@ public void testParameterTypeIsNotLoaded() throws Exception @Test public void testCommonSignaturesStayOptimized() throws Exception { - for (String name : new String[] { "a", "b", "c", "d" }) { - SettableBeanProperty prop = _findProperty(CommonSignatures.class, name); - assertTrue(prop instanceof OptimizedSettableBeanProperty, name); - } + assertGeneratedReader(MAPPER, CommonSignatures.class); CommonSignatures result = MAPPER.readValue(a2q("{'a':'x','b':['y'],'c':3,'d':'z'}"), CommonSignatures.class); assertEquals("x", result.a); assertEquals(Arrays.asList("y"), result.b); @@ -294,7 +239,7 @@ public void testCommonSignaturesStayOptimized() throws Exception } // A setter declared by the bean itself, in a child loader, taking a type from the - // parent: the bean's loader resolved it, as the generated setter will + // parent @Test public void testParentLoaderParameterTypeStaysOptimized() throws Exception { @@ -305,14 +250,14 @@ public void testParentLoaderParameterTypeStaysOptimized() throws Exception assertNotSame(DirectLevelBean.class, bean); assertSame(Level.class, Class.forName(Level.class.getName(), false, loader)); - // (a lookup in the parent loader can't refer to the bean at all) - _assertOptimized(lookupInBean(bean), _findProperty(bean, "level")); + assertGeneratedReader(MAPPER, bean); Object result = MAPPER.readValue(a2q("{'level':'HIGH'}"), bean); assertEquals(Level.HIGH, bean.getMethod("level").invoke(result)); } } - // A loader that fails (other than not finding the class) only stops the optimization + // A loader that fails (other than not finding the class) for a type Blackbird + // needn't load @Test public void testFailingLoader() throws Exception { @@ -325,41 +270,13 @@ public void testFailingLoader() throws Exception LevelBeanBase result = (LevelBeanBase) MAPPER.readValue(a2q("{'level':'HIGH'}"), bean); assertEquals("HIGH", result.level()); assertEquals(1, LevelBeanBase.setterCalls); - _assertOptimized(!lookupInBean(bean), _findProperty(bean, "level")); - } - } - - // Blackbird only gets a lookup in a bean from another loader if its module reads the - // bean's. Being a named module (as here), it doesn't read a child loader's unnamed - // module unless told to, and keeps its own lookup, in the parent loader: shadowed - // types then resolve to the parent's classes, as for the inherited accessor, so a - // generated one works. Tests run both ways. - public static final boolean[] READS = { false, true }; - - public static Class load(ClassLoader loader, Class type, boolean reads) throws Exception { - Class loaded = Class.forName(type.getName(), true, loader); - if (reads) { - TestInaccessibleSetterTypes.class.getModule().addReads(loaded.getModule()); - assertTrue(lookupInBean(loaded)); - } - return loaded; - } - - public static boolean lookupInBean(Class bean) { - return BlackbirdModule.class.getModule().canRead(bean.getModule()); - } - - private static void _assertOptimized(boolean optimized, SettableBeanProperty prop) { - if (optimized) { - assertTrue(prop instanceof OptimizedSettableBeanProperty, prop.getClass().getName()); - } else { - assertEquals(MethodProperty.class, prop.getClass()); + assertGeneratedReader(MAPPER, bean); } } /* /********************************************************************** - /* Builders, whose setters used to always go through reflection + /* Builders, the same cases through builder setters /********************************************************************** */ @@ -373,10 +290,7 @@ public void testInaccessibleParameterTypeOnBuilder() throws Exception assertEquals("[SAFE]", result.modes); assertEquals("foo", result.name); assertEquals(2, ModeBuilderBase.setterCalls); - assertEquals(MethodProperty.class, _findProperty(ModeBuilt.class, "mode").getClass()); - assertEquals(MethodProperty.class, _findProperty(ModeBuilt.class, "modes").getClass()); - SettableBeanProperty name = _findProperty(ModeBuilt.class, "name"); - assertTrue(name instanceof OptimizedSettableBeanProperty, name.getClass().getName()); + assertGeneratedReader(MAPPER, ModeBuilt.class); } @Test @@ -392,7 +306,7 @@ public void testShadowedParameterTypeOnBuilder() throws Exception Object result = MAPPER.readValue(a2q("{'level':'HIGH'}"), built); assertEquals("HIGH", built.getField("level").get(result)); assertEquals(1, LevelBuilderBase.setterCalls); - _assertOptimized(!lookupInBean(built), _findProperty(built, "level")); + assertGeneratedReader(MAPPER, built); } } @@ -411,47 +325,42 @@ public void testShadowedReturnTypeOnBuilder() throws Exception Object result = MAPPER.readValue(a2q("{'level':'HIGH'}"), built); assertEquals("HIGH", built.getField("level").get(result)); assertEquals(1, ChainBuilderBase.setterCalls); - _assertOptimized(!lookupInBean(built), _findProperty(built, "level")); + assertGeneratedReader(MAPPER, built); } } @Test public void testReturnTypeIsNotLoadedOnBuilder() throws Exception { - ChildFirstLoader loader = new ChildFirstLoader(getClass().getClassLoader(), - Arrays.asList(Chain.class, ChainBuilder.class, ChainBuilt.class)); - Class built = Class.forName(ChainBuilt.class.getName(), true, loader); - - ChainBuilderBase.setterCalls = 0; - Object result = MAPPER.readValue(a2q("{'level':'HIGH'}"), built); - assertEquals("HIGH", built.getField("level").get(result)); - assertEquals(1, ChainBuilderBase.setterCalls); - assertFalse(loader.defined.contains(Chain.class.getName())); + for (boolean reads : READS) { + ChildFirstLoader loader = new ChildFirstLoader(getClass().getClassLoader(), + Arrays.asList(Chain.class, ChainBuilder.class, ChainBuilt.class)); + Class built = load(loader, ChainBuilt.class, reads); + + ChainBuilderBase.setterCalls = 0; + Object result = MAPPER.readValue(a2q("{'level':'HIGH'}"), built); + assertEquals("HIGH", built.getField("level").get(result)); + assertEquals(1, ChainBuilderBase.setterCalls); + assertFalse(loader.defined.contains(Chain.class.getName()), "reads=" + reads); + } } + // The builder is in another package than the protected setter's declaring class @Test public void testShadowedDeclaringClassOnBuilder() throws Exception { - ClassLoader loader = new ChildFirstLoader(getClass().getClassLoader(), - Arrays.asList(OwnerBase.class, OwnerBuilder.class, OwnerBuilt.class)); - Class built = Class.forName(OwnerBuilt.class.getName(), true, loader); - Class builder = Class.forName(OwnerBuilder.class.getName(), true, loader); - assertNotSame(OwnerBuilt.class, built); - assertNotSame(OwnerBase.class, Class.forName(OwnerBase.class.getName(), false, loader)); - // a lookup in the builder itself, which the protected setter needs - ObjectMapper mapper = _mapperWithLookupIn(builder); - - OwnerBase.setterCalls = 0; - Object result = mapper.readValue(a2q("{'value':7}"), built); - assertEquals(7, built.getField("value").get(result)); - assertEquals(1, OwnerBase.setterCalls); - // (as for beans, see `testShadowedDeclaringClass`) - SettableBeanProperty prop = _findProperty(mapper, built, "value"); - int version = _javaVersion(); - if (version == 8) { - assertEquals(MethodProperty.class, prop.getClass()); - } else if (version >= 14) { - assertTrue(prop instanceof OptimizedSettableBeanProperty, prop.getClass().getName()); + for (boolean reads : READS) { + ClassLoader loader = new ChildFirstLoader(getClass().getClassLoader(), + Arrays.asList(OwnerBase.class, OwnerBuilder.class, OwnerBuilt.class)); + Class built = load(loader, OwnerBuilt.class, reads); + assertNotSame(OwnerBuilt.class, built); + assertNotSame(OwnerBase.class, Class.forName(OwnerBase.class.getName(), false, loader)); + + OwnerBase.setterCalls = 0; + Object result = MAPPER.readValue(a2q("{'value':7}"), built); + assertEquals(7, built.getField("value").get(result)); + assertEquals(1, OwnerBase.setterCalls); + assertGeneratedReader(MAPPER, built); } } @@ -468,62 +377,26 @@ public void testFailingLoaderOnBuilder() throws Exception Object result = MAPPER.readValue(a2q("{'level':'HIGH'}"), built); assertEquals("HIGH", built.getField("level").get(result)); assertEquals(1, LevelBuilderBase.setterCalls); - _assertOptimized(!lookupInBean(built), _findProperty(built, "level")); + assertGeneratedReader(MAPPER, built); } } + // Blackbird, a named module here, doesn't read a child loader's unnamed module + // unless told to, which changes the lookups it can get in the bean. Tests run both + // ways. + public static final boolean[] READS = { false, true }; - // Setup skips this accessor, so build it the same way here: the access error the - // JVM really raises is reported like a setter failure, and the setter never runs - @Test - public void testRealAccessorFailureIsNotRetried() throws Throwable - { - Class mode = Class.forName(ModeBuilderBase.class.getPackage().getName() + ".Mode"); - MethodHandle setter = MethodHandles.lookup().unreflect( - ModeBuilderBase.class.getMethod("withMode", mode)); - BiFunction thunk = BBDeserializerModifier.createThunk( - MethodHandles.lookup(), BiFunction.class, Object.class, setter); - BiConsumer unused = (bean, value) -> fail("not a builder setter"); - - ObjectMapper vanilla = newVanillaJSONMapper(); - DeserializationContextExt ctxt = vanilla._deserializationContext(); - BeanDeserializerBase deser = (BeanDeserializerBase) ctxt.findRootValueDeserializer( - vanilla.constructType(ModeBuilt.class)); - SettableObjectProperty prop = new SettableObjectProperty(deser.findProperty(PropertyName.construct("mode")), unused, thunk, - setter.type().parameterType(0), mode, true); - - Object fast = mode.getEnumConstants()[0]; - ModeBuilderBase.setterCalls = 0; - ModeBuilder builder = new ModeBuilder(); - assertThrows(IllegalAccessError.class, () -> thunk.apply(builder, fast)); - // an `Error` is rethrown as is, as `MethodProperty` does - assertThrows(IllegalAccessError.class, () -> prop.setAndReturn(ctxt, builder, fast)); - assertEquals(0, ModeBuilderBase.setterCalls); - } - - // Uses a lookup in the bean itself (from its static `lookup()`), as protected - // setters inherited from another package need - private static ObjectMapper _mapperWithLookupIn(Class bean) throws Exception { - MethodHandles.Lookup beanLookup = (MethodHandles.Lookup) bean.getMethod("lookup").invoke(null); - return mapperBuilder(c -> (c == bean) ? beanLookup : MethodHandles.lookup()).build(); - } - - private static int _javaVersion() { - String version = System.getProperty("java.specification.version"); - return Integer.parseInt(version.startsWith("1.") ? version.substring(2) : version); - } - - private SettableBeanProperty _findProperty(Class type, String name) throws Exception - { - return _findProperty(MAPPER, type, name); + public static Class load(ClassLoader loader, Class type, boolean reads) throws Exception { + Class loaded = Class.forName(type.getName(), true, loader); + if (reads) { + TestInaccessibleSetterTypes.class.getModule().addReads(loaded.getModule()); + assertTrue(lookupInBean(loaded)); + } + return loaded; } - private static SettableBeanProperty _findProperty(ObjectMapper mapper, Class type, String name) - throws Exception - { - BeanDeserializerBase deser = (BeanDeserializerBase) mapper._deserializationContext() - .findRootValueDeserializer(mapper.constructType(type)); - return deser.findProperty(PropertyName.construct(name)); + public static boolean lookupInBean(Class bean) { + return BlackbirdModule.class.getModule().canRead(bean.getModule()); } // Defines its own copies of the given classes, delegating everything else, and diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestSetterFailures.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestSetterFailures.java index a6b836889..cc017b9b6 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestSetterFailures.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/TestSetterFailures.java @@ -2,15 +2,9 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.lang.invoke.LambdaMetafactory; -import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Supplier; import org.junit.jupiter.api.Test; @@ -32,7 +26,6 @@ import tools.jackson.databind.deser.bean.BeanDeserializerBase; import tools.jackson.databind.json.JsonMapper; import tools.jackson.module.blackbird.BlackbirdTestBase; -import tools.jackson.module.blackbird.deser.access.InaccessibleSetterValue; import static org.junit.jupiter.api.Assertions.*; @@ -210,9 +203,9 @@ public void testSetDoesNotRetrySetterFailures() throws Exception { List> beans = Arrays.asList(VoidBean::new, FluentBean::new); for (Supplier newBean : beans) { Class beanType = newBean.get().getClass(); + assertGeneratedReader(mapper, beanType); for (int i = 0; i < names.length; i++) { SettableBeanProperty property = property(mapper, beanType, names[i]); - assertTrue(property instanceof OptimizedSettableBeanProperty, names[i]); Object value = values[i]; for (Supplier nextFailure : failures()) { failure = nextFailure; @@ -259,7 +252,6 @@ public void testSetterFailuresAreReportedAsVanilla() throws Exception { public void testSetSkipsNulls() throws Exception { for (String name : new String[] { "str", "list", "i", "l", "b" }) { SettableBeanProperty property = property(newObjectMapper(), SkipNullsBean.class, name); - assertTrue(property instanceof OptimizedSettableBeanProperty, name); SkipNullsBean bean = new SkipNullsBean(1); calls = 0; property.set(CTXT, bean, null); @@ -287,7 +279,6 @@ public void testSetNullPrimitiveWithoutSkipping() throws Exception { for (String name : new String[] { "i", "l", "b" }) { SettableBeanProperty expectedProperty = property(vanilla, VoidBean.class, name); SettableBeanProperty actualProperty = property(blackbird, VoidBean.class, name); - assertTrue(actualProperty instanceof OptimizedSettableBeanProperty, name); calls = 0; Throwable expected = assertThrows(Throwable.class, () -> expectedProperty.set(CTXT, new VoidBean(), null), name); @@ -302,9 +293,9 @@ public void testSetNullPrimitiveWithoutSkipping() throws Exception { @Test public void testPolymorphicNullMatchesVanilla() throws Exception { ObjectMapper blackbird = newObjectMapper(); + assertGeneratedReader(blackbird, PolymorphicNullBean.class); for (String name : new String[] { "void", "fluent" }) { SettableBeanProperty prop = property(blackbird, PolymorphicNullBean.class, name); - assertTrue(prop instanceof OptimizedSettableBeanProperty, name); PolymorphicNullBean direct = new PolymorphicNullBean(); prop.set(CTXT, direct, null); @@ -331,7 +322,7 @@ public void testHiddenBeanSetterFailureIsNotRetried() throws Exception { hiddenVoid = defineHidden(HiddenVoidBean.class); hiddenFluent = defineHidden(HiddenFluentBean.class); for (Class type : new Class[] { HideableVoidBean.class, HideableFluentBean.class }) { - assertTrue(property(newObjectMapper(), type, "str") instanceof OptimizedSettableBeanProperty); + assertGeneratedReader(newObjectMapper(), type); for (ObjectMapper mapper : new ObjectMapper[] { newVanillaJSONMapper(), newObjectMapper() }) { calls = 0; assertThrows(DatabindException.class, () -> mapper.readValue(a2q("{'str':'foo'}"), type)); @@ -362,62 +353,9 @@ private void assertRunsOnce(Executable action, String description) { assertSame(lastFailure, thrown, description); } - @Test - public void testVoidAccessorAccessFailureIsNotRetried() throws Throwable { - InaccessibleSetterValue bean = new InaccessibleSetterValue(); - Object value = InaccessibleSetterValue.newValue(); - Method setter = InaccessibleSetterValue.class.getMethod("setVoidValue", value.getClass()); - BiConsumer accessor = voidAccessor(setter); - SettableObjectProperty property = new SettableObjectProperty( - property(newVanillaJSONMapper(), bean.getClass(), "voidValue"), accessor, null, - InaccessibleSetterValue.class, setter.getParameterTypes()[0], false); - - // The generated accessor cannot cast to the package-private parameter type; - // that is reported like the setter's failure (rethrown as is), and the setter - // never runs - assertThrows(IllegalAccessError.class, () -> accessor.accept(bean, value)); - assertThrows(IllegalAccessError.class, () -> property.set(CTXT, bean, value)); - assertEquals(0, bean.calls); - } - - @Test - public void testFluentAccessorAccessFailureIsNotRetried() throws Throwable { - InaccessibleSetterValue bean = new InaccessibleSetterValue(); - Object value = InaccessibleSetterValue.newValue(); - Method setter = InaccessibleSetterValue.class.getMethod("setFluentValue", value.getClass()); - BiFunction thunk = returningAccessor(setter); - BiConsumer accessor = (instance, v) -> thunk.apply(instance, v); - SettableObjectProperty property = new SettableObjectProperty( - property(newVanillaJSONMapper(), bean.getClass(), "fluentValue"), accessor, thunk, - InaccessibleSetterValue.class, setter.getParameterTypes()[0], false); - - // the same through the wrapper that fluent setters on regular beans use - assertThrows(IllegalAccessError.class, () -> accessor.accept(bean, value)); - assertThrows(IllegalAccessError.class, () -> property.set(CTXT, bean, value)); - assertEquals(0, bean.calls); - } - private static SettableBeanProperty property(ObjectMapper mapper, Class beanType, String name) throws Exception { BeanDeserializerBase deserializer = (BeanDeserializerBase) mapper._deserializationContext() .findRootValueDeserializer(mapper.constructType(beanType)); return deserializer.findProperty(PropertyName.construct(name)); } - - @SuppressWarnings("unchecked") - private BiConsumer voidAccessor(Method setter) throws Throwable { - MethodHandle handle = MethodHandles.lookup().unreflect(setter); - return (BiConsumer) LambdaMetafactory.metafactory( - MethodHandles.lookup(), "accept", MethodType.methodType(BiConsumer.class), - MethodType.methodType(void.class, Object.class, Object.class), handle, handle.type()) - .getTarget().invoke(); - } - - @SuppressWarnings("unchecked") - private BiFunction returningAccessor(Method setter) throws Throwable { - MethodHandle handle = MethodHandles.lookup().unreflect(setter); - return (BiFunction) LambdaMetafactory.metafactory( - MethodHandles.lookup(), "apply", MethodType.methodType(BiFunction.class), - MethodType.methodType(Object.class, Object.class, Object.class), handle, handle.type()) - .getTarget().invoke(); - } } diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/access/InaccessibleSetterValue.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/access/InaccessibleSetterValue.java deleted file mode 100644 index 9d3031c0c..000000000 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/access/InaccessibleSetterValue.java +++ /dev/null @@ -1,23 +0,0 @@ -package tools.jackson.module.blackbird.deser.access; - -public class InaccessibleSetterValue { - public int calls; - public Object value; - - public void setVoidValue(SetterValue value) { - calls++; - this.value = value; - } - - public InaccessibleSetterValue setFluentValue(SetterValue value) { - calls++; - this.value = value; - return this; - } - - public static Object newValue() { - return new SetterValue(); - } -} - -class SetterValue { } diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/shadowed/OwnerBean.java b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/shadowed/OwnerBean.java index 220a5e4a5..ccf72f846 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/deser/shadowed/OwnerBean.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/deser/shadowed/OwnerBean.java @@ -1,17 +1,10 @@ package tools.jackson.module.blackbird.deser.shadowed; -import java.lang.invoke.MethodHandles; - import tools.jackson.module.blackbird.deser.shadowed.owner.OwnerBridge; // in another package than the protected setter's declaring class, so its handle's // receiver is narrowed to this class public final class OwnerBean extends OwnerBridge { - // so Blackbird can be given a lookup in this class, as for protected setters it needs - public static MethodHandles.Lookup lookup() { - return MethodHandles.lookup(); - } - public int value() { return value; } } diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/internal/DiscoveredBlackbirdModuleTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/internal/DiscoveredBlackbirdModuleTest.java new file mode 100644 index 000000000..2eddbab63 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/internal/DiscoveredBlackbirdModuleTest.java @@ -0,0 +1,49 @@ +package tools.jackson.module.blackbird.internal; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.JacksonModule; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.module.blackbird.BlackbirdModule; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * On a runtime that generates codecs, the discovery provider must look like + * an explicit registration to databind: same registration id (so discovery + * plus an explicit registration installs Blackbird once) and the same module + * name. (On Java 17 to 24 the ids differ, so that discovery cannot replace + * an explicit registration, which must fail.) + */ +public class DiscoveredBlackbirdModuleTest +{ + @Test + public void sameIdentityAsExplicitModule() { + BlackbirdModule explicit = new BlackbirdModule(); + DiscoveredBlackbirdModule discovered = new DiscoveredBlackbirdModule(); + assertEquals(explicit.getRegistrationId(), discovered.getRegistrationId()); + assertEquals(explicit.getModuleName(), discovered.getModuleName()); + } + + @Test + public void discoveryPlusExplicitRegistersOnce() { + assertRegisteredOnce(JsonMapper.builder() + .addModule(new DiscoveredBlackbirdModule()) + .addModule(new BlackbirdModule()) + .build()); + assertRegisteredOnce(JsonMapper.builder() + .addModule(new BlackbirdModule()) + .addModule(new DiscoveredBlackbirdModule()) + .build()); + } + + private static void assertRegisteredOnce(ObjectMapper mapper) { + List blackbirds = mapper.registeredModules().stream() + .filter(m -> m instanceof BlackbirdModule) + .toList(); + assertEquals(1, blackbirds.size(), "registered: " + blackbirds); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/ser/BBCodecOmittedPropertyTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/BBCodecOmittedPropertyTest.java new file mode 100644 index 000000000..bd0eb83ed --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/BBCodecOmittedPropertyTest.java @@ -0,0 +1,93 @@ +package tools.jackson.module.blackbird.ser; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonView; + +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.util.JsonGeneratorDelegate; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +// A property hidden by the active view is written as an omitted property +// on generators that cannot omit properties (CSV, for one): stock +// serializers call writeOmittedProperty for it, so generated writers must +// too. +public class BBCodecOmittedPropertyTest extends BlackbirdTestBase +{ + static class Public { } + static class Internal { } + + @JsonPropertyOrder({ "id", "secret", "name", "note" }) + public static class ViewBean { + public int id = 1; + @JsonView(Internal.class) + public String secret = "s"; + @JsonView(Public.class) + public String name = "n"; + @JsonView(Internal.class) + public String note = "x"; + } + + // Records omitted properties, as a generator that cannot omit them + static class NoOmitGenerator extends JsonGeneratorDelegate { + final List omitted = new ArrayList<>(); + + NoOmitGenerator(JsonGenerator d) { + super(d); + } + + @Override + public boolean canOmitProperties() { + return false; + } + + @Override + public JsonGenerator writeOmittedProperty(String propName) { + omitted.add(propName); + return this; + } + } + + private static List omittedWith(ObjectMapper mapper, Class view) throws Exception { + try (NoOmitGenerator g = new NoOmitGenerator(mapper.createGenerator(new StringWriter()))) { + mapper.writerWithView(view).writeValue(g, new ViewBean()); + return g.omitted; + } + } + + @Test + public void testHiddenPropertiesWrittenAsOmitted() throws Exception { + ObjectMapper mapper = newObjectMapper(); + assertGeneratedWriter(mapper, ViewBean.class); + + List expected = omittedWith(newVanillaJSONMapper(), Public.class); + assertEquals(List.of("secret", "note"), expected); + assertEquals(expected, omittedWith(mapper, Public.class)); + assertEquals(List.of("name"), omittedWith(mapper, Internal.class)); + } + + // Without DEFAULT_VIEW_INCLUSION, a property with no view is left out + // altogether by stock (no omitted placeholder), unlike a view mismatch + @Test + public void testViewlessPropertiesNotWrittenAsOmitted() throws Exception { + ObjectMapper mapper = mapperBuilder() + .disable(MapperFeature.DEFAULT_VIEW_INCLUSION) + .build(); + assertGeneratedWriter(mapper, ViewBean.class); + + List expected = omittedWith(newVanillaJSONMapper().rebuild() + .disable(MapperFeature.DEFAULT_VIEW_INCLUSION) + .build(), Public.class); + assertEquals(List.of("secret", "note"), expected); + assertEquals(expected, omittedWith(mapper, Public.class)); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/ser/BBCodecSelfReferenceTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/BBCodecSelfReferenceTest.java new file mode 100644 index 000000000..9abcf7164 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/BBCodecSelfReferenceTest.java @@ -0,0 +1,74 @@ +package tools.jackson.module.blackbird.ser; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * A child property whose value is the bean itself goes to the stock + * self-reference handling, but its getter must still run once, as in stock. + */ +public class BBCodecSelfReferenceTest extends BlackbirdTestBase +{ + public static class Base { + public int getB() { return 1; } + } + + @JsonPropertyOrder({ "self", "calls", "b" }) + public static class Derived extends Base { + private int calls; + + @JsonSerialize(typing = JsonSerialize.Typing.STATIC) + public Base getSelf() { + calls++; + return this; + } + + public int getCalls() { return calls; } + } + + private static void assertMatchesVanilla(SerializationFeature disable, + SerializationFeature enable) throws Exception { + JsonMapper.Builder vb = JsonMapper.builder().disable(disable); + JsonMapper.Builder mb = mapperBuilder().disable(disable); + if (enable != null) { + vb.enable(enable); + mb.enable(enable); + } + ObjectMapper vanilla = vb.build(); + ObjectMapper module = mb.build(); + String expected = vanilla.writeValueAsString(new Derived()); + assertTrue(expected.contains("\"calls\":1"), expected); + assertEquals(expected, module.writeValueAsString(new Derived())); + assertTrue(assertGeneratedWriter(module, Derived.class).contains("self=CHILD")); + } + + @Test + public void testSelfReferenceReadsGetterOnce() throws Exception { + assertMatchesVanilla(SerializationFeature.FAIL_ON_SELF_REFERENCES, null); + } + + @Test + public void testSelfReferenceAsNullReadsGetterOnce() throws Exception { + assertMatchesVanilla(SerializationFeature.FAIL_ON_SELF_REFERENCES, + SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL); + } + + @Test + public void testSelfReferenceFailsLikeStock() throws Exception { + Derived bean = new Derived(); + Exception e = assertThrows(Exception.class, () -> newObjectMapper().writeValueAsString(bean)); + Exception expected = assertThrows(Exception.class, + () -> newVanillaJSONMapper().writeValueAsString(new Derived())); + assertEquals(expected.getClass(), e.getClass()); + assertEquals(1, bean.getCalls()); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/ser/BBWriterPolymorphicTest.java b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/BBWriterPolymorphicTest.java new file mode 100644 index 000000000..683e328e6 --- /dev/null +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/BBWriterPolymorphicTest.java @@ -0,0 +1,188 @@ +package tools.jackson.module.blackbird.ser; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeId; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.module.blackbird.BlackbirdTestBase; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Polymorphic serialization parity: databind calls serializeWithType on the + * subtype's generated serializer, whose native override replicates stock + * BeanSerializerBase's WritableTypeId flow (typeId, writeTypePrefix, + * assignCurrentValue, properties, writeTypeSuffix), so output must equal + * vanilla for every inclusion mechanism. Beans with a @JsonTypeId property + * keep the whole-call forwarding to stock. + */ +public class BBWriterPolymorphicTest extends BlackbirdTestBase +{ + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type") + @JsonSubTypes({ + @JsonSubTypes.Type(value = Cat.class, name = "cat"), + @JsonSubTypes.Type(value = Dog.class, name = "dog") + }) + public static abstract class Animal { + public String name; + } + + public static class Cat extends Animal { + public int lives; + public boolean indoor; + + static Cat of(String name, int lives, boolean indoor) { + Cat c = new Cat(); + c.name = name; + c.lives = lives; + c.indoor = indoor; + return c; + } + } + + public static class Dog extends Animal { + public long barks; + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) + @JsonSubTypes({ + @JsonSubTypes.Type(value = WrappedLeaf.class, name = "leaf") + }) + public static abstract class Wrapped { + } + + public static class WrappedLeaf extends Wrapped { + public String label; + public int size; + + static WrappedLeaf of(String label, int size) { + WrappedLeaf w = new WrappedLeaf(); + w.label = label; + w.size = size; + return w; + } + } + + public static class Zoo { + public String city; + public Animal star; + } + + private final ObjectMapper mapper = newObjectMapper(); + private final ObjectMapper vanilla = newVanillaJSONMapper(); + + @Test + public void testPropertyInclusionRootWrite() throws Exception { + Animal cat = Cat.of("mia", 9, true); + assertEquals(vanilla.writerFor(Animal.class).writeValueAsString(cat), + mapper.writerFor(Animal.class).writeValueAsString(cat)); + } + + @Test + public void testPropertyInclusionNestedWrite() throws Exception { + Zoo zoo = new Zoo(); + zoo.city = "sf"; + zoo.star = Cat.of("mia", 9, false); + assertEquals(vanilla.writeValueAsString(zoo), mapper.writeValueAsString(zoo)); + } + + @Test + public void testPropertyInclusionListWrite() throws Exception { + List animals = List.of(Cat.of("a", 1, true), new Dog()); + assertEquals(vanilla.writerFor(vanilla.getTypeFactory() + .constructCollectionType(List.class, Animal.class)) + .writeValueAsString(animals), + mapper.writerFor(mapper.getTypeFactory() + .constructCollectionType(List.class, Animal.class)) + .writeValueAsString(animals)); + } + + @Test + public void testWrapperObjectInclusionWrite() throws Exception { + Wrapped w = WrappedLeaf.of("x", 3); + assertEquals(vanilla.writerFor(Wrapped.class).writeValueAsString(w), + mapper.writerFor(Wrapped.class).writeValueAsString(w)); + } + + @Test + public void testPolymorphicRoundTrip() throws Exception { + Animal cat = Cat.of("mia", 9, true); + String doc = mapper.writerFor(Animal.class).writeValueAsString(cat); + Animal back = mapper.readValue(doc, Animal.class); + assertInstanceOf(Cat.class, back); + assertEquals("mia", back.name); + assertEquals(9, ((Cat) back).lives); + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_ARRAY) + @JsonSubTypes({ + @JsonSubTypes.Type(value = ArrayLeaf.class, name = "leaf") + }) + public static abstract class ArrayWrapped { + } + + public static class ArrayLeaf extends ArrayWrapped { + public String label; + public long count; + } + + @Test + public void testWrapperArrayInclusionWrite() throws Exception { + ArrayLeaf leaf = new ArrayLeaf(); + leaf.label = "x"; + leaf.count = 12; + assertEquals(vanilla.writerFor(ArrayWrapped.class).writeValueAsString(leaf), + mapper.writerFor(ArrayWrapped.class).writeValueAsString(leaf)); + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type") + @JsonSubTypes({ + @JsonSubTypes.Type(value = TypeIdLeaf.class, name = "unused") + }) + public static abstract class TypeIdBase { + } + + public static class TypeIdLeaf extends TypeIdBase { + @JsonTypeId + public String kind = "custom-id"; + public int n; + } + + @Test + public void testJsonTypeIdPropertyWrite() throws Exception { + // @JsonTypeId beans keep the base forwarding (the property value + // replaces the resolver's id); output still equals vanilla. + TypeIdLeaf leaf = new TypeIdLeaf(); + leaf.n = 5; + String v = vanilla.writerFor(TypeIdBase.class).writeValueAsString(leaf); + assertEquals(v, mapper.writerFor(TypeIdBase.class).writeValueAsString(leaf)); + assertTrue(v.contains("custom-id"), v); + } + + // The engaged writer must actually generate a codec: byte-equal output + // alone cannot distinguish native poly writes from forwarding. + @Test + public void testSubtypeWriterGeneratesCodec() throws Exception { + Map, ValueSerializer> seen = new ConcurrentHashMap<>(); + ObjectMapper capturing = capturingWriterMapper(seen); + Animal cat = Cat.of("mia", 9, true); + assertEquals(vanilla.writerFor(Animal.class).writeValueAsString(cat), + capturing.writerFor(Animal.class).writeValueAsString(cat)); + ValueSerializer captured = seen.get(Cat.class); + assertNotNull(captured, "no serializer captured for Cat"); + assertTrue(hasGeneratedCodec(captured), + "poly subtype did not engage a codec"); + Field codec = captured.getClass().getDeclaredField("_codec"); + codec.setAccessible(true); + assertNotNull(codec.get(captured), "poly subtype engaged but no writer generated"); + } +} diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/ser/TestBBClassloaders.java b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/ChildClassloaderTest.java similarity index 52% rename from blackbird/src/test/java/tools/jackson/module/blackbird/ser/TestBBClassloaders.java rename to blackbird/src/test/java/tools/jackson/module/blackbird/ser/ChildClassloaderTest.java index cf8de9df1..c0f762cf3 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/ser/TestBBClassloaders.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/ChildClassloaderTest.java @@ -1,41 +1,51 @@ package tools.jackson.module.blackbird.ser; -import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.junit.jupiter.api.Test; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ValueSerializer; import tools.jackson.module.blackbird.BlackbirdTestBase; import static org.junit.jupiter.api.Assertions.*; -public class TestBBClassloaders extends BlackbirdTestBase +// Cross-loader regression test (formerly tofix/TestBBClassloaders): a bean +// class redefined in a child classloader serializes correctly - and now +// ACCELERATES - with the module registered. Generated code never names the +// bean class (values load through unreflected constant handles bound to the +// child-loaded Class), so the foreign loader no longer gates codec +// generation; this widened pin holds that capability. +// +// Old Blackbird failed this on the module path only because the test read the +// class bytes through classloader getResource, which JPMS encapsulation nulls; +// Class#getResourceAsStream resolves inside this module and works in both +// modes. +public class ChildClassloaderTest extends BlackbirdTestBase { - protected final String resourceName = - (TestBBClassloaders.class.getName() + "$Data") - .replace('.', '/').concat(".class"); - - // Used to fail with Jackson 3.x: Blackbird, a named module, can't read the child - // loader's module, so its generated getter (in the parent loader) couldn't refer to - // the bean; it's now left to reflection @Test public void testLoadInChildClassloader() throws Exception { - // 19-Jan-2025, tatu: only fails on JDK 11 specifically, not on JDK 17 or later - // - // So just skip on that - if (System.getProperty("java.version").startsWith("11.")) { - System.out.println("Skipping `testLoadInChildClassloader()` on JDK 11"); - return; - } TestLoader loader = new TestLoader(getClass().getClassLoader()); Class clazz = Class.forName(Data.class.getName(), true, loader); - ObjectMapper mapper = newObjectMapper(); + assertNotSame(Data.class, clazz); + Map, ValueSerializer> seen = new ConcurrentHashMap<>(); + ObjectMapper mapper = capturingWriterMapper(seen); Constructor constructor = clazz.getConstructor(int.class); Object data = constructor.newInstance(42); assertEquals("{\"field\":42}", mapper.writeValueAsString(data)); + ValueSerializer captured = seen.get(clazz); + assertNotNull(captured, "no serializer captured for the child-loaded class"); + assertTrue(hasGeneratedCodec(captured), + "child-loaded bean did not engage a codec"); + Field codec = captured.getClass().getDeclaredField("_codec"); + codec.setAccessible(true); + assertNotNull(codec.get(captured), + "child-loaded bean engaged but no writer generated"); } public static class Data { @@ -65,13 +75,11 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE try { Class clazz; if (Data.class.getName().equals(name)) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - InputStream in = getResource(resourceName).openStream(); - int i; - while ((i = in.read()) != -1) { - baos.write(i); + byte[] bytes; + try (InputStream in = ChildClassloaderTest.class + .getResourceAsStream("ChildClassloaderTest$Data.class")) { + bytes = in.readAllBytes(); } - byte[] bytes = baos.toByteArray(); clazz = defineClass(name, bytes, 0, bytes.length); } else { clazz = super.loadClass(name, resolve); diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/ser/TestInaccessibleGetterTypes.java b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/TestInaccessibleGetterTypes.java index 3640b0a88..435e47223 100644 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/ser/TestInaccessibleGetterTypes.java +++ b/blackbird/src/test/java/tools/jackson/module/blackbird/ser/TestInaccessibleGetterTypes.java @@ -1,7 +1,6 @@ package tools.jackson.module.blackbird.ser; import java.util.Arrays; -import java.util.Iterator; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; @@ -9,9 +8,6 @@ import org.junit.jupiter.api.Test; import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.ser.BeanPropertyWriter; -import tools.jackson.databind.ser.PropertyWriter; -import tools.jackson.databind.ser.bean.BeanSerializerBase; import tools.jackson.module.blackbird.BlackbirdTestBase; import tools.jackson.module.blackbird.deser.TestInaccessibleSetterTypes.ChildFirstLoader; import tools.jackson.module.blackbird.deser.shadowed.Level; @@ -24,13 +20,12 @@ import static tools.jackson.module.blackbird.deser.TestInaccessibleSetterTypes.load; import static tools.jackson.module.blackbird.deser.TestInaccessibleSetterTypes.lookupInBean; -// Getters whose generated accessor would fail when called, even though reflection -// can call them, must be left to reflection (as with setters, see +// Getters that generated code could easily get wrong, though reflection calls them +// fine; generated writers must handle them as stock does (as with setters, see // `TestInaccessibleSetterTypes`) public class TestInaccessibleGetterTypes extends BlackbirdTestBase { - // a JDK `javax.*` return type comes from another loader, but this class's - // loader resolved it, as the generated getter will + // a JDK `javax.*` return type, from another loader public static class OtherLoaderReturnType { public XMLGregorianCalendar getWhen() throws Exception { @@ -42,17 +37,16 @@ public static final class HiddenGetterBean extends HiddenGetterBeanBase { } private final ObjectMapper MAPPER = newObjectMapper(); - // `unreflect` refuses some inherited getters that reflection can call; these used - // to fail the whole serializer + // An inherited getter that only reflection can call @Test public void testInheritedGetterFromAnotherPackage() throws Exception { assertEquals(a2q("{'y':3}"), MAPPER.writeValueAsString(new HiddenGetterBean())); - assertEquals(BeanPropertyWriter.class, _findWriter(HiddenGetterBean.class, "y").getClass()); + assertGeneratedWriter(MAPPER, HiddenGetterBean.class); } - // The bean's loader has its own copy of `Level`, so a generated getter would - // resolve that one and fail to link to the inherited getter returning `Level` + // The bean's loader has its own copy of `Level`, so code resolving it from there + // would fail to link to the inherited getter, which returns the parent's @Test public void testShadowedReturnType() throws Exception { @@ -65,30 +59,15 @@ public void testShadowedReturnType() throws Exception LevelBeanBase value = (LevelBeanBase) bean.getConstructor().newInstance(); value.setLevel(Level.HIGH); assertEquals(a2q("{'level':'HIGH'}"), MAPPER.writeValueAsString(value)); - // (see `TestInaccessibleSetterTypes.READS` for when it can stay optimized) - assertEquals(lookupInBean(bean), - BeanPropertyWriter.class == _findWriter(bean, "level").getClass(), "reads=" + reads); + assertGeneratedWriter(MAPPER, bean); } } @Test public void testOtherLoaderReturnTypeStaysOptimized() throws Exception { - assertNotEquals(BeanPropertyWriter.class, _findWriter(OtherLoaderReturnType.class, "when").getClass()); + assertGeneratedWriter(MAPPER, OtherLoaderReturnType.class); assertEquals(newVanillaJSONMapper().writeValueAsString(new OtherLoaderReturnType()), MAPPER.writeValueAsString(new OtherLoaderReturnType())); } - - private PropertyWriter _findWriter(Class type, String name) throws Exception - { - BeanSerializerBase ser = (BeanSerializerBase) MAPPER._serializationContext() - .findValueSerializer(type); - for (Iterator it = ser.properties(); it.hasNext(); ) { - PropertyWriter writer = it.next(); - if (writer.getName().equals(name)) { - return writer; - } - } - throw new AssertionError("No property '" + name + "' for " + type.getName()); - } } diff --git a/blackbird/src/test/java/tools/jackson/module/blackbird/util/TestReflectionHack.java b/blackbird/src/test/java/tools/jackson/module/blackbird/util/TestReflectionHack.java deleted file mode 100644 index a560a26f6..000000000 --- a/blackbird/src/test/java/tools/jackson/module/blackbird/util/TestReflectionHack.java +++ /dev/null @@ -1,45 +0,0 @@ -package tools.jackson.module.blackbird.util; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodHandles.Lookup; - -import org.junit.jupiter.api.Test; - -import tools.jackson.module.blackbird.deser.otherpackage.ModeBeanBase; - -import static org.junit.jupiter.api.Assertions.*; - -public class TestReflectionHack -{ - static class SamePackage { } - - private static final Lookup LOOKUP = MethodHandles.lookup(); - - @Test - public void testCanAccessClass() throws Exception - { - Class mode = _packagePrivateFromOtherPackage(); - assertTrue(ReflectionHack.canAccessClass(LOOKUP, int.class)); - assertTrue(ReflectionHack.canAccessClass(LOOKUP, int[].class)); - assertTrue(ReflectionHack.canAccessClass(LOOKUP, String.class)); - assertTrue(ReflectionHack.canAccessClass(LOOKUP, ModeBeanBase.class)); - assertTrue(ReflectionHack.canAccessClass(LOOKUP, SamePackage[].class)); - assertFalse(ReflectionHack.canAccessClass(LOOKUP, mode)); - assertFalse(ReflectionHack.canAccessClass(LOOKUP, java.lang.reflect.Array.newInstance(mode, 0).getClass())); - } - - // what Java 8 uses, which can't be picked on newer JDKs - @Test - public void testCanAccessClassWithoutModules() throws Exception - { - Class self = getClass(); - assertTrue(ReflectionHack.canAccessClassWithoutModules(self, String.class)); - assertTrue(ReflectionHack.canAccessClassWithoutModules(self, ModeBeanBase.class)); - assertTrue(ReflectionHack.canAccessClassWithoutModules(self, SamePackage.class)); - assertFalse(ReflectionHack.canAccessClassWithoutModules(self, _packagePrivateFromOtherPackage())); - } - - private static Class _packagePrivateFromOtherPackage() throws Exception { - return Class.forName("tools.jackson.module.blackbird.deser.otherpackage.Mode"); - } -} diff --git a/pom.xml b/pom.xml index ada677def..dd78dfd88 100644 --- a/pom.xml +++ b/pom.xml @@ -26,9 +26,7 @@ not datatype, data format, or JAX-RS provider modules. afterburner-tests android-record - blackbird - - blackbird-tests + guice guice7 jakarta-xmlbind @@ -46,6 +44,57 @@ not datatype, data format, or JAX-RS provider modules. spi-subtypes + + + + blackbird-jdk25 + + [25,) + + + blackbird + + blackbird-tests + + blackbird-jpms-tests + + + + + release + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-jdk25-for-release + validate + + enforce + + + + + [25,) + Release builds need JDK 25 or later: on older JDKs the reactor leaves out jackson-module-blackbird. + + + + + + + + + + + https://github.com/FasterXML/jackson-modules-base scm:git:git@github.com:FasterXML/jackson-modules-base.git @@ -58,6 +107,10 @@ not datatype, data format, or JAX-RS provider modules. + + 17 UTF-8 diff --git a/release-notes/VERSION b/release-notes/VERSION index 042e793cd..07209d008 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -24,6 +24,24 @@ Active maintainers: 3.3.0 (not yet released) +#359: (blackbird) New code generation engine: per-bean codec classes, generated with + the JDK ClassFile API, replace the LambdaMetafactory accessors. The codecs also + cover records (through the canonical constructor) and builder-based beans. + Blackbird needs Java 25 or later at run time: the generators use the ClassFile API + (final in Java 24) and compile at release 25, the current LTS release. The jar is + a multi-release jar whose base classes stay at Java 17: on Java 17-24, module + discovery (`findAndAddModules()`) skips Blackbird with one logged warning, and an + explicit `addModule(new BlackbirdModule())` throws `IllegalStateException`. The + build needs JDK 25 for Blackbird, and a release build on an older JDK fails. + Removed public types: `deser.CreatorOptimizer`, `deser.ObjBooleanConsumer`, the + `ObjIntBiFunction`, `ObjLongBiFunction` and `ObjBooleanBiFunction` interfaces of + `BBDeserializerModifier`, `ser.ToBooleanFunction`, and all of `util` + (`CheckedFunction`, `CheckedSupplier`, `ReflectionHack`, `Sneaky`, `Unchecked`). + `BBDeserializerModifier` and `BBSerializerModifier` take no arguments now. + Blackbird no longer uses the lookup that `BlackbirdModule` constructors and + `findLookup()` overrides supply, and module setup no longer calls `findLookup()`. The OSGi bundle exports only + `tools.jackson.module.blackbird`, as the JPMS module does. + (contributed by @stevenschlansker) #362: Bump `byte-buddy` dependency from 1.17.8 to 1.18.15 3.2.5 (not yet released)