From b1ae308d4901e09ca37431727934986d4f082ab0 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Sat, 3 Aug 2024 10:34:24 -0400 Subject: [PATCH 1/5] simplify generic custom adapters --- .../java/io/avaje/jsonb/generator/JsonbProcessor.java | 7 ++++++- jsonb/src/main/java/io/avaje/jsonb/CustomAdapter.java | 11 ++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java index d19af03e..9adbf7a1 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java @@ -15,6 +15,7 @@ import io.avaje.prism.GenerateAPContext; import io.avaje.prism.GenerateModuleInfoReader; +import io.avaje.prism.GenerateUtils; import static java.util.stream.Collectors.joining; @@ -25,6 +26,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; +@GenerateUtils @GenerateAPContext @GenerateModuleInfoReader @SupportedAnnotationTypes({ @@ -112,7 +114,10 @@ private Optional> getElements(RoundEnvironment private void registerCustomAdapters(Set elements) { for (final var typeElement : ElementFilter.typesIn(elements)) { final var type = typeElement.getQualifiedName().toString(); - if (CustomAdapterPrism.getInstanceOn(typeElement).isGeneric()) { + if (typeElement.getInterfaces().stream() + .map(UType::parse) + .filter(u -> u.full().contains("JsonAdapter")) + .anyMatch(u -> u.param0().isGeneric())) { ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream() .filter(isStaticFactory()) .findFirst() diff --git a/jsonb/src/main/java/io/avaje/jsonb/CustomAdapter.java b/jsonb/src/main/java/io/avaje/jsonb/CustomAdapter.java index 2ab72d35..9f9e3224 100644 --- a/jsonb/src/main/java/io/avaje/jsonb/CustomAdapter.java +++ b/jsonb/src/main/java/io/avaje/jsonb/CustomAdapter.java @@ -6,7 +6,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; - /** * Marks a type as a basic user-provided JsonAdapter to be registered automatically. * @@ -35,7 +34,7 @@ * *
{@code
  *
- * @CustomAdapter(isGeneric=true)
+ * @CustomAdapter
  * public class CustomJsonAdapter implements JsonAdapter> {
  *
  *   private final JsonAdapter genericTypeAdapter;
@@ -60,10 +59,4 @@
  */
 @Target(TYPE)
 @Retention(SOURCE)
-public @interface CustomAdapter {
-
-  /**
-   * Set to true when the adapter is for a type that uses generics.
-   */
-  boolean isGeneric() default false;
-}
+public @interface CustomAdapter {}
\ No newline at end of file

From 9d60d9b77b7a09d7a36ff15ecd003ab91ec410a3 Mon Sep 17 00:00:00 2001
From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
Date: Sat, 3 Aug 2024 10:37:24 -0400
Subject: [PATCH 2/5] Update CustomEntryJsonAdapter.java

---
 .../java/org/example/other/custom/CustomEntryJsonAdapter.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/blackbox-test/src/main/java/org/example/other/custom/CustomEntryJsonAdapter.java b/blackbox-test/src/main/java/org/example/other/custom/CustomEntryJsonAdapter.java
index 190bfe83..a02ec89f 100644
--- a/blackbox-test/src/main/java/org/example/other/custom/CustomEntryJsonAdapter.java
+++ b/blackbox-test/src/main/java/org/example/other/custom/CustomEntryJsonAdapter.java
@@ -13,7 +13,7 @@
 import io.avaje.jsonb.Types;
 import io.avaje.jsonb.spi.PropertyNames;
 
-@CustomAdapter(isGeneric = true)
+@CustomAdapter
 public class CustomEntryJsonAdapter implements JsonAdapter> {
 
   private final JsonAdapter generic1;

From c117e1a692053e8b0915cfe097186cbdc949c6fa Mon Sep 17 00:00:00 2001
From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
Date: Sat, 3 Aug 2024 10:39:23 -0400
Subject: [PATCH 3/5] fix tests

---
 .../java/org/example/other/custom/CustomEntryJsonAdapter.java   | 1 -
 .../jsonb/generator/models/valid/CustomEntryJsonAdapter.java    | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/blackbox-test/src/main/java/org/example/other/custom/CustomEntryJsonAdapter.java b/blackbox-test/src/main/java/org/example/other/custom/CustomEntryJsonAdapter.java
index a02ec89f..acce5d2b 100644
--- a/blackbox-test/src/main/java/org/example/other/custom/CustomEntryJsonAdapter.java
+++ b/blackbox-test/src/main/java/org/example/other/custom/CustomEntryJsonAdapter.java
@@ -1,6 +1,5 @@
 package org.example.other.custom;
 
-import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.Map.Entry;
diff --git a/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomEntryJsonAdapter.java b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomEntryJsonAdapter.java
index c24ebf55..07ec6d9f 100644
--- a/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomEntryJsonAdapter.java
+++ b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomEntryJsonAdapter.java
@@ -12,7 +12,7 @@
 import io.avaje.jsonb.Types;
 import io.avaje.jsonb.spi.PropertyNames;
 
-@CustomAdapter(isGeneric = true)
+@CustomAdapter
 public class CustomEntryJsonAdapter implements JsonAdapter> {
 
   private final JsonAdapter generic1;

From c3f217dc466b621d9228ea2524f6ef80224014a3 Mon Sep 17 00:00:00 2001
From: Josiah Noel <32279667+SentryMan@users.noreply.github.com>
Date: Sat, 3 Aug 2024 15:20:03 -0400
Subject: [PATCH 4/5] allow simple custom adapters

---
 .../io/avaje/jsonb/generator/JsonbProcessor.java     |  2 +-
 .../generator/models/valid/CustomJsonAdapter.java    |  3 +--
 jsonb/src/main/java/io/avaje/jsonb/Jsonb.java        |  6 ++++++
 jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java  | 12 ++++++++++++
 4 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java
index 9adbf7a1..025c73db 100644
--- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java
+++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java
@@ -136,7 +136,7 @@ private void registerCustomAdapters(Set elements) {
           .findAny()
           .ifPresentOrElse(
             x -> {},
-            () -> logError(typeElement, "Non-Generic adapters must have a public constructor with a single Jsonb parameter"));
+            () -> logNote(typeElement, "Non-Generic adapters should have a public constructor with a single Jsonb parameter"));
 
         metaData.add(type);
       }
diff --git a/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomJsonAdapter.java b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomJsonAdapter.java
index 14df6f56..136fee99 100644
--- a/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomJsonAdapter.java
+++ b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomJsonAdapter.java
@@ -4,13 +4,12 @@
 import io.avaje.jsonb.JsonAdapter;
 import io.avaje.jsonb.JsonReader;
 import io.avaje.jsonb.JsonWriter;
-import io.avaje.jsonb.Jsonb;
 import io.avaje.jsonb.generator.models.valid.Example3Packet.Example2Packet;
 
 @CustomAdapter
 public class CustomJsonAdapter implements JsonAdapter {
 
-  public CustomJsonAdapter(Jsonb jsonb) {}
+//  public CustomJsonAdapter(Jsonb jsonb) {}
 
   @Override
   public void toJson(JsonWriter writer, Example2Packet value) {}
diff --git a/jsonb/src/main/java/io/avaje/jsonb/Jsonb.java b/jsonb/src/main/java/io/avaje/jsonb/Jsonb.java
index 7dc81369..316bb100 100644
--- a/jsonb/src/main/java/io/avaje/jsonb/Jsonb.java
+++ b/jsonb/src/main/java/io/avaje/jsonb/Jsonb.java
@@ -13,6 +13,7 @@
 import java.io.Reader;
 import java.io.Writer;
 import java.lang.reflect.Type;
+import java.util.function.Supplier;
 
 /**
  * Provides access to json adapters by type.
@@ -386,6 +387,11 @@ interface Builder {
      */
      Builder add(Type type, JsonAdapter jsonAdapter);
 
+    /**
+     * Add a Supplier which provides a JsonAdapter to use for the given type.
+     */
+     Builder add(Type type, Supplier> builder);
+
     /**
      * Add a AdapterBuilder which provides a JsonAdapter to use for the given type.
      */
diff --git a/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java b/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java
index 1d8174ce..9b0a258c 100644
--- a/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java
+++ b/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java
@@ -13,6 +13,7 @@
 import java.lang.reflect.Type;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
 
 import static io.avaje.jsonb.core.Util.*;
 import static java.util.Objects.requireNonNull;
@@ -276,6 +277,11 @@ public  Builder add(Type type, JsonAdapter jsonAdapter) {
       return add(newAdapterFactory(type, jsonAdapter));
     }
 
+    @Override
+    public  Builder add(Type type, Supplier> jsonAdapter) {
+      return add(newAdapterFactory(type, jsonAdapter));
+    }
+
     @Override
     public Builder add(JsonbComponent component) {
       component.register(this);
@@ -317,6 +323,12 @@ static  JsonAdapter.Factory newAdapterFactory(Type type, JsonAdapter jsonA
       return (targetType, jsonb) -> simpleMatch(type, targetType) ? jsonAdapter : null;
     }
 
+    static  JsonAdapter.Factory newAdapterFactory(Type type, Supplier> jsonAdapter) {
+      requireNonNull(type);
+      requireNonNull(jsonAdapter);
+      return (targetType, jsonb) -> simpleMatch(type, targetType) ? jsonAdapter.get() : null;
+    }
+
     static JsonAdapter.Factory newAdapterFactory(Type type, AdapterBuilder builder) {
       requireNonNull(type);
       requireNonNull(builder);

From 778d220dd527e59561f4921100224317022c29f7 Mon Sep 17 00:00:00 2001
From: Rob Bygrave 
Date: Mon, 5 Aug 2024 21:42:46 +1200
Subject: [PATCH 5/5] Format, extract method, add missing test

---
 .../customtype/CustomScalarTypeTest.java       | 18 ++++++++++++++++++
 .../avaje/jsonb/generator/JsonbProcessor.java  | 12 ++++++++----
 .../models/valid/CustomJsonAdapter.java        |  2 --
 .../java/io/avaje/jsonb/CustomAdapter.java     |  2 +-
 .../main/java/io/avaje/jsonb/core/DJsonb.java  |  9 +--------
 5 files changed, 28 insertions(+), 15 deletions(-)

diff --git a/blackbox-test/src/test/java/org/example/customer/customtype/CustomScalarTypeTest.java b/blackbox-test/src/test/java/org/example/customer/customtype/CustomScalarTypeTest.java
index 4ff00988..61260488 100644
--- a/blackbox-test/src/test/java/org/example/customer/customtype/CustomScalarTypeTest.java
+++ b/blackbox-test/src/test/java/org/example/customer/customtype/CustomScalarTypeTest.java
@@ -50,6 +50,24 @@ void toJson_fromJson() {
     assertThat(wrapper1.custom()).isEqualTo(wrapper.custom());
   }
 
+  @Test
+  void toJson_fromJson_usingSupplier() {
+    Jsonb jsonb = Jsonb.builder()
+      // register a supplier
+      .add(MyCustomScalarType.class, () -> new CustomTypeAdapter().nullSafe())
+      .build();
+
+    MyWrapper wrapper = new MyWrapper(42, "hello", new MyCustomScalarType("hello".getBytes(StandardCharsets.UTF_8)));
+
+    String asJson = jsonb.toJson(wrapper);
+    assertThat(asJson).isEqualTo("{\"id\":42,\"base\":\"hello\",\"custom\":\"aGVsbG8=\"}");
+
+    MyWrapper wrapper1 = jsonb.type(MyWrapper.class).fromJson(asJson);
+
+    assertThat(wrapper1).isEqualTo(wrapper);
+    assertThat(wrapper1.custom()).isEqualTo(wrapper.custom());
+  }
+
   static class CustomTypeAdapter implements JsonAdapter {
 
     @Override
diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java
index 025c73db..a200e417 100644
--- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java
+++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/JsonbProcessor.java
@@ -114,10 +114,7 @@ private Optional> getElements(RoundEnvironment
   private void registerCustomAdapters(Set elements) {
     for (final var typeElement : ElementFilter.typesIn(elements)) {
       final var type = typeElement.getQualifiedName().toString();
-      if (typeElement.getInterfaces().stream()
-          .map(UType::parse)
-          .filter(u -> u.full().contains("JsonAdapter"))
-          .anyMatch(u -> u.param0().isGeneric())) {
+      if (isGenericJsonAdapter(typeElement)) {
         ElementFilter.fieldsIn(typeElement.getEnclosedElements()).stream()
           .filter(isStaticFactory())
           .findFirst()
@@ -143,6 +140,13 @@ private void registerCustomAdapters(Set elements) {
     }
   }
 
+  private static boolean isGenericJsonAdapter(TypeElement typeElement) {
+    return typeElement.getInterfaces().stream()
+      .map(UType::parse)
+      .filter(u -> u.full().contains("JsonAdapter"))
+      .anyMatch(u -> u.param0().isGeneric());
+  }
+
   private static Predicate isStaticFactory() {
     return v -> v.getModifiers().contains(Modifier.STATIC) && "FACTORY".equals(v.getSimpleName().toString());
   }
diff --git a/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomJsonAdapter.java b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomJsonAdapter.java
index 136fee99..f28beeae 100644
--- a/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomJsonAdapter.java
+++ b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/CustomJsonAdapter.java
@@ -9,8 +9,6 @@
 @CustomAdapter
 public class CustomJsonAdapter implements JsonAdapter {
 
-//  public CustomJsonAdapter(Jsonb jsonb) {}
-
   @Override
   public void toJson(JsonWriter writer, Example2Packet value) {}
 
diff --git a/jsonb/src/main/java/io/avaje/jsonb/CustomAdapter.java b/jsonb/src/main/java/io/avaje/jsonb/CustomAdapter.java
index 9f9e3224..4ac66767 100644
--- a/jsonb/src/main/java/io/avaje/jsonb/CustomAdapter.java
+++ b/jsonb/src/main/java/io/avaje/jsonb/CustomAdapter.java
@@ -59,4 +59,4 @@
  */
 @Target(TYPE)
 @Retention(SOURCE)
-public @interface CustomAdapter {}
\ No newline at end of file
+public @interface CustomAdapter {}
diff --git a/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java b/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java
index 9b0a258c..8970a572 100644
--- a/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java
+++ b/jsonb/src/main/java/io/avaje/jsonb/core/DJsonb.java
@@ -307,14 +307,7 @@ private void registerComponents() {
     @Override
     public DJsonb build() {
       registerComponents();
-      return new DJsonb(
-          adapter,
-          factories,
-          serializeNulls,
-          serializeEmpty,
-          failOnUnknown,
-          mathTypesAsString,
-          strategy);
+      return new DJsonb(adapter, factories, serializeNulls, serializeEmpty, failOnUnknown, mathTypesAsString, strategy);
     }
 
     static  JsonAdapter.Factory newAdapterFactory(Type type, JsonAdapter jsonAdapter) {