diff --git a/blackbox-test/pom.xml b/blackbox-test/pom.xml index 8ebdd667..cc7371c2 100644 --- a/blackbox-test/pom.xml +++ b/blackbox-test/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jsonb-parent - 1.10-RC1 + 1.10-RC2 blackbox-test diff --git a/jsonb-generator/pom.xml b/jsonb-generator/pom.xml index 20766ea3..540f2bf3 100644 --- a/jsonb-generator/pom.xml +++ b/jsonb-generator/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jsonb-parent - 1.10-RC1 + 1.10-RC2 avaje-jsonb-generator diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java index 2fa67b67..0b68429c 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java @@ -139,6 +139,16 @@ public boolean hasJsonAnnotation() { @Override public void read() { + + Optional.ofNullable(constructor) + .ifPresent( + c -> { + var enclosing = (TypeElement) c.element().getEnclosingElement(); + + importTypes.add(enclosing.getQualifiedName().toString()); + }); + + importTypes.add(Util.shortName(type)); for (final FieldReader field : allFields) { field.addImports(importTypes); if (field.isRaw()) { @@ -457,8 +467,9 @@ private void writeFromJsonImplementation(Append writer, String varName) { private void writeJsonBuildResult(Append writer, String varName) { writer.append(" // build and return %s", shortName).eol(); - writer.append(" %s _$%s = new %s(", shortName, varName, shortName); if (constructor != null) { + writer.append(" %s _$%s = " + constructor.creationString(), shortName, varName); + final List params = constructor.getParams(); for (int i = 0, size = params.size(); i < size; i++) { if (i > 0) { @@ -470,6 +481,8 @@ private void writeJsonBuildResult(Append writer, String varName) { // assuming name matches field here? writer.append(constructorParamName(name + (frequency == 0 ? "" : frequency.toString()))); } + } else { + writer.append(" %s _$%s = new %s(", shortName, varName, shortName); } writer.append(");").eol(); for (final FieldReader allField : allFields) { diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldReader.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldReader.java index 12551d87..caebd202 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldReader.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/FieldReader.java @@ -3,6 +3,8 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; + import java.util.*; final class FieldReader { @@ -12,43 +14,74 @@ final class FieldReader { private final FieldProperty property; private final String propertyName; private final boolean serialize; - private final boolean deserialize; + private boolean deserialize; private final boolean unmapped; private final boolean raw; - private final List aliases; + private final List aliases = new ArrayList<>(); private boolean isSubTypeField; private final String num; + private boolean isCreatorParam; + + FieldReader( + Element element, + NamingConvention namingConvention, + TypeSubTypeMeta subType, + List genericTypeParams, + Integer frequency) { - FieldReader(Element element, NamingConvention namingConvention, TypeSubTypeMeta subType, List genericTypeParams, Integer frequency) { + this(element, namingConvention, subType, genericTypeParams, frequency, false); + } + + FieldReader( + Element element, + NamingConvention namingConvention, + TypeSubTypeMeta subType, + List genericTypeParams, + Integer frequency, + boolean jsonCreatorPresent) { num = frequency == 0 ? "" : frequency.toString(); addSubType(subType); final PropertyIgnoreReader ignoreReader = new PropertyIgnoreReader(element); - this.unmapped = ignoreReader.unmapped(); var isMethod = element instanceof ExecutableElement; - this.raw = ignoreReader.raw(); - this.serialize = ignoreReader.serialize(); - this.deserialize = !isMethod && ignoreReader.deserialize(); + var isParam = element.getEnclosingElement() instanceof ExecutableElement; + this.unmapped = UnmappedPrism.isPresent(element); + this.raw = RawPrism.isPresent(element); + this.serialize = !isParam && ignoreReader.serialize(); + this.deserialize = isParam || !jsonCreatorPresent && !isMethod && ignoreReader.deserialize(); final var fieldName = element.getSimpleName().toString(); - final var publicField = !isMethod && element.getModifiers().contains(Modifier.PUBLIC); + final var publicField = + !isMethod && !isParam && element.getModifiers().contains(Modifier.PUBLIC); final var type = isMethod ? ((ExecutableElement) element).getReturnType() : element.asType(); - this.property = new FieldProperty(type, raw, unmapped, genericTypeParams, publicField, fieldName); - this.propertyName = PropertyPrism.getOptionalOn(element) - .map(PropertyPrism::value) - .map(Util::escapeQuotes) - .orElse(namingConvention.from(fieldName)); + this.property = + new FieldProperty(type, raw, unmapped, genericTypeParams, publicField, fieldName); + this.propertyName = + PropertyPrism.getOptionalOn(element) + .map(PropertyPrism::value) + .map(Util::escapeQuotes) + .orElse(namingConvention.from(fieldName)); + + initAliases(element); + } - this.aliases = initAliases(element); + public void readParam(VariableElement element) { + this.deserialize = true; + this.isCreatorParam = true; + initAliases(element); } - private static List initAliases(Element element) { - return AliasPrism.getOptionalOn(element) - .map(a -> Util.escapeQuotes(a.value())) - .orElse(JsonAliasPrism.getOptionalOn(element) - .map(a -> Util.escapeQuotes(a.value())) - .orElse(Collections.emptyList())); + private void initAliases(Element element) { + var alias = + AliasPrism.getOptionalOn(element) + .map(a -> Util.escapeQuotes(a.value())) + .orElse( + JsonAliasPrism.getOptionalOn(element) + .map(a -> Util.escapeQuotes(a.value())) + .orElse(Collections.emptyList())); + + aliases.addAll(alias); } void position(int pos) { @@ -203,6 +236,7 @@ void writeFromJsonSwitch(Append writer, boolean defaultConstructor, String varNa } void writeFromJsonSetter(Append writer, String varName, String prefix) { + if (isCreatorParam) return; property.writeFromJsonSetter(writer, varName, prefix, num); } @@ -243,6 +277,10 @@ public void setSetter(MethodReader setter) { property.setSetterMethod(setter); } + public void setDeserialize(boolean deserialize) { + this.deserialize = deserialize; + } + public boolean isDeserialize() { return deserialize; } @@ -254,4 +292,5 @@ public Map subTypes() { public List aliases() { return aliases; } + } diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/MethodReader.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/MethodReader.java index d94cde3e..db009462 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/MethodReader.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/MethodReader.java @@ -1,5 +1,6 @@ package io.avaje.jsonb.generator; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -14,7 +15,7 @@ final class MethodReader { private final String methodName; private final List params = new ArrayList<>(); - MethodReader(ExecutableElement element, TypeElement beanType) { + MethodReader(ExecutableElement element) { this.element = element; this.methodName = element.getSimpleName().toString(); } @@ -44,7 +45,6 @@ List getParams() { return params; } - public boolean isPublic() { return element.getModifiers().contains(Modifier.PUBLIC); } @@ -53,14 +53,31 @@ public boolean isProtected() { return element.getModifiers().contains(Modifier.PROTECTED); } + public String creationString() { + var shortName = + Util.shortName(((TypeElement) element.getEnclosingElement()).getQualifiedName().toString()); + + if (element.getKind() == ElementKind.CONSTRUCTOR) { + return String.format("new %s(", shortName); + } + + return String.format("%s.%s(", shortName, element.getSimpleName()); + } + + public ExecutableElement element() { + return element; + } + static class MethodParam { private final String simpleName; private final String type; + private final VariableElement element; MethodParam(VariableElement param) { this.simpleName = param.getSimpleName().toString(); this.type = param.asType().toString(); + element = param; } String name() { @@ -70,5 +87,13 @@ String name() { public String type() { return type; } + + public VariableElement element() { + return element; + } + + public VariableElement getElement() { + return element; + } } } diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/PropertyIgnoreReader.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/PropertyIgnoreReader.java index 95711a83..cc4f820f 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/PropertyIgnoreReader.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/PropertyIgnoreReader.java @@ -4,14 +4,10 @@ final class PropertyIgnoreReader { - private final boolean unmapped; - private final boolean raw; private boolean ignoreSerialize; private boolean ignoreDeserialize; PropertyIgnoreReader(Element element) { - unmapped = UnmappedPrism.isPresent(element) ; - raw = RawPrism.isPresent(element); final IgnorePrism ignored = IgnorePrism.getInstanceOn(element); if (ignored != null) { @@ -20,14 +16,6 @@ final class PropertyIgnoreReader { } } - boolean unmapped() { - return unmapped; - } - - boolean raw() { - return raw; - } - boolean serialize() { return !ignoreSerialize; } diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/TypeReader.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/TypeReader.java index b0c1d1a9..5314ffd6 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/TypeReader.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/TypeReader.java @@ -2,6 +2,10 @@ import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; + +import io.avaje.jsonb.generator.MethodReader.MethodParam; + import java.util.*; import java.util.stream.Collectors; @@ -9,8 +13,7 @@ import static io.avaje.jsonb.generator.ProcessingContext.importedJson; /** - * Read points for field injection and method injection - * on baseType plus inherited injection points. + * Read points for field injection and method injection on baseType plus inherited injection points. */ final class TypeReader { @@ -47,25 +50,63 @@ final class TypeReader { private final List methodProperties = new ArrayList<>(); private boolean optional; - /** - * Set when the type is known to extend Throwable - */ + + /** Set when the type is known to extend Throwable */ private boolean extendsThrowable; - TypeReader(TypeElement baseType, TypeElement mixInType, NamingConvention namingConvention, String typePropertyKey) { + private final boolean hasJsonCreator; + + TypeReader( + TypeElement baseType, + TypeElement mixInType, + NamingConvention namingConvention, + String typePropertyKey) { this.baseType = baseType; this.genericTypeParams = initTypeParams(baseType); + Optional jsonCreator = Optional.empty(); if (mixInType == null) { + this.mixInFields = new HashMap<>(); } else { - this.mixInFields = mixInType.getEnclosedElements().stream() - .filter(e -> e.getKind() == ElementKind.FIELD) - .collect(Collectors.toMap(e -> e.getSimpleName().toString(), e -> e)); + + jsonCreator = + ElementFilter.methodsIn(mixInType.getEnclosedElements()).stream() + .filter(CreatorPrism::isPresent) + .findFirst(); + this.mixInFields = + mixInType.getEnclosedElements().stream() + .filter(e -> e.getKind() == ElementKind.FIELD) + .collect(Collectors.toMap(e -> e.getSimpleName().toString(), e -> e)); } this.namingConvention = namingConvention; this.hasJsonAnnotation = JsonPrism.isPresent(baseType) || importedJson(baseType).isPresent(); this.subTypes = new TypeSubTypeReader(baseType); this.typePropertyKey = typePropertyKey; + + jsonCreator = + jsonCreator.or( + () -> + baseType.getEnclosedElements().stream() + .filter(CreatorPrism::isPresent) + .map(ExecutableElement.class::cast) + .findFirst()); + + constructor = + jsonCreator + .map( + ex -> { + var mods = ex.getModifiers(); + if (ex.getKind() != ElementKind.CONSTRUCTOR + && !mods.contains(Modifier.STATIC) + && !mods.contains(Modifier.PUBLIC)) + logError( + ex, + "@Json.Creator can only be placed on contructors and static factory methods"); + return new MethodReader(ex).read(); + }) + .orElse(null); + + this.hasJsonCreator = jsonCreator.isPresent(); } private List initTypeParams(TypeElement beanType) { @@ -84,6 +125,7 @@ int genericTypeParamsCount() { void read(TypeElement type) { final List localFields = new ArrayList<>(); + for (Element element : type.getEnclosedElements()) { switch (element.getKind()) { case CONSTRUCTOR: @@ -97,6 +139,20 @@ void read(TypeElement type) { break; } } + + if (hasJsonCreator) { + for (var param : constructor.getParams()) { + + var name = param.name(); + var element = param.element(); + var matchingField = + localFields.stream().filter(f -> f.propertyName().equals(name)).findFirst(); + + matchingField.ifPresentOrElse( + f -> f.readParam(element), () -> readField(element, localFields)); + } + } + if (currentSubType == null && type != baseType) { allFields.addAll(0, localFields); for (final FieldReader localField : localFields) { @@ -129,7 +185,14 @@ private void readField(Element element, List localFields) { } if (includeField(element)) { final var frequency = frequency(element.getSimpleName().toString()); - localFields.add(new FieldReader(element, namingConvention, currentSubType, genericTypeParams, frequency)); + localFields.add( + new FieldReader( + element, + namingConvention, + currentSubType, + genericTypeParams, + frequency, + hasJsonCreator)); } } @@ -152,6 +215,7 @@ private boolean includeField(Element element) { } private void readConstructor(Element element, TypeElement type) { + if (constructor != null) return; if (currentSubType != null) { if (currentSubType.element() != type) { // logError("subType " + currentSubType.element() + " ignore constructor " + element); @@ -162,7 +226,7 @@ private void readConstructor(Element element, TypeElement type) { return; } ExecutableElement ex = (ExecutableElement) element; - MethodReader methodReader = new MethodReader(ex, baseType).read(); + MethodReader methodReader = new MethodReader(ex).read(); if (methodReader.isPublic() || hasSubTypes() && methodReader.isProtected()) { if (currentSubType != null) { currentSubType.addConstructor(methodReader); @@ -180,7 +244,7 @@ private void readMethod(Element element, TypeElement type, List loc if (checkMethod2(methodElement)) { List parameters = methodElement.getParameters(); final String methodKey = methodElement.getSimpleName().toString(); - MethodReader methodReader = new MethodReader(methodElement, type).read(); + MethodReader methodReader = new MethodReader(methodElement).read(); if (parameters.size() == 1) { maybeSetterMethods.putIfAbsent(methodKey, methodReader); allSetterMethods.put(methodKey.toLowerCase(), methodReader); @@ -202,7 +266,7 @@ private void readMethod(Element element, TypeElement type, List loc // getter property as simulated read-only field with getter method final var frequency = frequency(propertyPrism.value()); final var reader = new FieldReader(element, namingConvention, currentSubType, genericTypeParams, frequency); - reader.getterMethod(new MethodReader(methodElement, type)); + reader.getterMethod(new MethodReader(methodElement)); localFields.add(reader); }); } @@ -364,6 +428,9 @@ MethodReader constructor() { } private MethodReader determineConstructor() { + if (constructor != null) { + return constructor; + } if (defaultPublicConstructor && !allSetterMethods.isEmpty()) { return null; } diff --git a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/package-info.java b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/package-info.java index fcce3acf..a594e26e 100644 --- a/jsonb-generator/src/main/java/io/avaje/jsonb/generator/package-info.java +++ b/jsonb-generator/src/main/java/io/avaje/jsonb/generator/package-info.java @@ -3,6 +3,7 @@ @GeneratePrism(io.avaje.jsonb.Json.Import.class) @GeneratePrism(value = io.avaje.jsonb.Json.Import.List.class, name = "ImportListPrism") @GeneratePrism(io.avaje.jsonb.Json.Alias.class) +@GeneratePrism(io.avaje.jsonb.Json.Creator.class) @GeneratePrism(io.avaje.jsonb.Json.JsonAlias.class) @GeneratePrism(io.avaje.jsonb.Json.Ignore.class) @GeneratePrism(io.avaje.jsonb.Json.Property.class) diff --git a/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/Kingfisher.java b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/Kingfisher.java new file mode 100644 index 00000000..e4808b41 --- /dev/null +++ b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/Kingfisher.java @@ -0,0 +1,18 @@ +package io.avaje.jsonb.generator.models.valid; + +public class Kingfisher { + private String name; + private int fishCaught; + + public String getName() { + return name; + } + + public int getFishCaught() { + return fishCaught; + } + + public void setFishCaught(int fishCaught) { + this.fishCaught = fishCaught; + } +} diff --git a/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/KingfisherMixin.java b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/KingfisherMixin.java new file mode 100644 index 00000000..de00fa9d --- /dev/null +++ b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/KingfisherMixin.java @@ -0,0 +1,13 @@ +package io.avaje.jsonb.generator.models.valid; + +import io.avaje.jsonb.Json; +import io.avaje.jsonb.Json.MixIn; + +@MixIn(Kingfisher.class) +public interface KingfisherMixin { + + @Json.Creator + static Kingfisher construct(String name) { + return null; + } +} diff --git a/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/Student.java b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/Student.java new file mode 100644 index 00000000..bf740a98 --- /dev/null +++ b/jsonb-generator/src/test/java/io/avaje/jsonb/generator/models/valid/Student.java @@ -0,0 +1,32 @@ +package io.avaje.jsonb.generator.models.valid; + +import io.avaje.jsonb.Json; + +@Json +public class Student { + private final String name; + private int rollNo; + + @Json.Creator + public Student(@Json.Alias("theName") String name, long rolling) { + this.name = name; + this.rollNo = name.length(); + } + + public Student(String name, int rollNo) { + this.name = name; + this.rollNo = rollNo; + } + + public String getName() { + return name; + } + + public int getRollNo() { + return rollNo; + } + + public void setRollNo(int rollNo) { + this.rollNo = rollNo; + } +} diff --git a/jsonb-inject-plugin/pom.xml b/jsonb-inject-plugin/pom.xml index 117ee560..60dcbbc7 100644 --- a/jsonb-inject-plugin/pom.xml +++ b/jsonb-inject-plugin/pom.xml @@ -6,7 +6,7 @@ avaje-jsonb-parent io.avaje - 1.10-RC1 + 1.10-RC2 avaje-jsonb-inject-plugin @@ -18,7 +18,7 @@ io.avaje avaje-jsonb - 1.7 + 1.10-RC1 provided true diff --git a/jsonb-inject-plugin/src/main/java/io/avaje/jsonb/inject/DefaultJsonbProvider.java b/jsonb-inject-plugin/src/main/java/io/avaje/jsonb/inject/DefaultJsonbProvider.java index 9563b27f..24abe3f9 100644 --- a/jsonb-inject-plugin/src/main/java/io/avaje/jsonb/inject/DefaultJsonbProvider.java +++ b/jsonb-inject-plugin/src/main/java/io/avaje/jsonb/inject/DefaultJsonbProvider.java @@ -2,6 +2,7 @@ import io.avaje.inject.BeanScopeBuilder; import io.avaje.jsonb.Jsonb; +import io.avaje.jsonb.stream.BufferRecycleStrategy; /** Plugin for avaje inject that provides a default Jsonb instance. */ public final class DefaultJsonbProvider implements io.avaje.inject.spi.Plugin { @@ -24,6 +25,11 @@ public void apply(BeanScopeBuilder builder) { .mathTypesAsString(props.equalTo("jsonb.serialize.mathTypesAsString", "true")) .serializeEmpty(props.notEqualTo("jsonb.serialize.empty", "false")) .serializeNulls(props.equalTo("jsonb.serialize.nulls", "true")) + .bufferRecycling( + props + .get("jsonb.bufferRecycling") + .map(BufferRecycleStrategy::valueOf) + .orElse(BufferRecycleStrategy.HYBRID_POOL)) .build(); }); } diff --git a/jsonb-jackson/pom.xml b/jsonb-jackson/pom.xml index 78b16584..1419a8fe 100644 --- a/jsonb-jackson/pom.xml +++ b/jsonb-jackson/pom.xml @@ -4,7 +4,7 @@ avaje-jsonb-parent io.avaje - 1.10-RC1 + 1.10-RC2 avaje-jsonb-jackson diff --git a/jsonb-spring-adapter/pom.xml b/jsonb-spring-adapter/pom.xml index 0cdbda44..3c963dbe 100644 --- a/jsonb-spring-adapter/pom.xml +++ b/jsonb-spring-adapter/pom.xml @@ -6,7 +6,7 @@ avaje-jsonb-parent io.avaje - 1.10-RC1 + 1.10-RC2 avaje-jsonb-spring-starter diff --git a/jsonb/pom.xml b/jsonb/pom.xml index c033347b..6296541e 100644 --- a/jsonb/pom.xml +++ b/jsonb/pom.xml @@ -4,7 +4,7 @@ io.avaje avaje-jsonb-parent - 1.10-RC1 + 1.10-RC2 avaje-jsonb diff --git a/jsonb/src/main/java/io/avaje/jsonb/Json.java b/jsonb/src/main/java/io/avaje/jsonb/Json.java index 89947d23..e89a0f6a 100644 --- a/jsonb/src/main/java/io/avaje/jsonb/Json.java +++ b/jsonb/src/main/java/io/avaje/jsonb/Json.java @@ -1,10 +1,12 @@ package io.avaje.jsonb; +import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.MODULE; import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.CLASS; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -130,7 +132,7 @@ * } */ @Retention(CLASS) - @Target(FIELD) + @Target({FIELD, PARAMETER}) @interface Alias { /** One or more secondary names to accept as aliases to the official name. */ @@ -309,6 +311,41 @@ Class value(); } + /** + * Marker annotation that can be used to define constructors or factory methods as one to use for instantiating new instances of the associated class. Can be used in Mixin classes to override an existing deserialization method + * + * The parameter names will be used as keys for deserialization instead of the field names. + * + *

Examples:

+ * + *
{@code
+   *
+   *   @Json
+   *   public class Kingfisher {
+   *
+   *     @Json.Creator
+   *     public Kingfisher(String name) {
+   *        ...
+   *     }
+   *   ...
+   *
+   * }
+ * + *
{@code
+   *
+   *   @Json
+   *   public record Product( ... ) {
+   *
+   *   public static Product factory(@Json.Alias("alias") String name){
+   *      ...
+   *   }
+   *
+   * }
+ */ + @Retention(CLASS) + @Target({CONSTRUCTOR, METHOD}) + public @interface Creator {} + /** * The naming convention that we can use for a given type. */ diff --git a/jsonb/src/main/java/io/avaje/jsonb/JsonReader.java b/jsonb/src/main/java/io/avaje/jsonb/JsonReader.java index 0d89be0b..7abb655f 100644 --- a/jsonb/src/main/java/io/avaje/jsonb/JsonReader.java +++ b/jsonb/src/main/java/io/avaje/jsonb/JsonReader.java @@ -214,30 +214,12 @@ enum Token { */ BEGIN_ARRAY, -// /** -// * The closing of a JSON array. Written using {@link JsonWriter#endArray} and read using {@link -// * JsonReader#endArray}. -// */ -// END_ARRAY, - /** * The opening of a JSON object. Written using {@link JsonWriter#beginObject} and read using * {@link JsonReader#beginObject}. */ BEGIN_OBJECT, -// /** -// * The closing of a JSON object. Written using {@link JsonWriter#endObject} and read using -// * {@link JsonReader#endObject}. -// */ -// END_OBJECT, -// -// /** -// * A JSON property name. Within objects, tokens alternate between names and their values. -// * Written using {@link JsonWriter#name} and read using {@link JsonReader#nextField()} -// */ -// NAME, - /** * A JSON string. */ @@ -258,10 +240,5 @@ enum Token { */ NULL, -// /** -// * The end of the JSON stream. This sentinel value is returned by {@link JsonReader#peek()} to -// * signal that the JSON-encoded value has no more tokens. -// */ -// END_DOCUMENT } } diff --git a/jsonb/src/main/java/io/avaje/jsonb/stream/HybridBufferRecycler.java b/jsonb/src/main/java/io/avaje/jsonb/stream/HybridBufferRecycler.java index 7a64e49c..9d2d4c0e 100644 --- a/jsonb/src/main/java/io/avaje/jsonb/stream/HybridBufferRecycler.java +++ b/jsonb/src/main/java/io/avaje/jsonb/stream/HybridBufferRecycler.java @@ -10,11 +10,11 @@ import java.util.function.Predicate; /** - * This is a custom implementation of the Jackson's RecyclerPool intended to work equally - * well with both platform and virtual threads. This pool works regardless of the version of the JVM - * in use and internally uses 2 distinct pools one for platform threads (which is exactly the same - * {@link ThreadLocal} based one provided by Jackson out of the box) and the other designed for - * being virtual threads friendly. It switches between the 2 only depending on the nature of thread + * This is a custom implementation of the Jackson's RecyclerPool intended to work equally well with + * both platform and virtual threads. This pool works regardless of the version of the JVM in use + * and internally uses 2 distinct pools one for platform threads (which is exactly the same {@link + * ThreadLocal} based one provided by Jackson out of the box) and the other designed for being + * virtual threads friendly. It switches between the 2 only depending on the nature of thread * (virtual or not) requiring the acquisition of a pooled resource, obtained via {@link * MethodHandle} to guarantee compatibility also with old JVM versions. The pool also guarantees * that the pooled resource is always released to the same internal pool from where it has been @@ -38,15 +38,10 @@ final class HybridBufferRecycler implements BufferRecycler { private static final Predicate isVirtual = VirtualPredicate.findIsVirtualPredicate(); - private final BufferRecycler nativePool = ThreadLocalPool.shared(); + private static final BufferRecycler NATIVE_RECYCLER = ThreadLocalPool.shared(); + private static final BufferRecycler VIRTUAL_RECYCLER = StripedLockFreePool.shared(); - private static class VirtualPoolHolder { - - private static final StripedLockFreePool virtualPool = new StripedLockFreePool(Runtime.getRuntime().availableProcessors()); - } - - private HybridBufferRecycler() { - } + private HybridBufferRecycler() {} static HybridBufferRecycler shared() { return INSTANCE; @@ -55,40 +50,41 @@ static HybridBufferRecycler shared() { @Override public JsonGenerator generator(JsonOutput target) { return isVirtual.test(Thread.currentThread()) - ? VirtualPoolHolder.virtualPool.generator(target) - : nativePool.generator(target); + ? VIRTUAL_RECYCLER.generator(target) + : NATIVE_RECYCLER.generator(target); } @Override public JsonParser parser(byte[] bytes) { return isVirtual.test(Thread.currentThread()) - ? VirtualPoolHolder.virtualPool.parser(bytes) - : nativePool.parser(bytes); + ? VIRTUAL_RECYCLER.parser(bytes) + : NATIVE_RECYCLER.parser(bytes); } @Override public JsonParser parser(InputStream in) { return isVirtual.test(Thread.currentThread()) - ? VirtualPoolHolder.virtualPool.parser(in) - : nativePool.parser(in); + ? VIRTUAL_RECYCLER.parser(in) + : NATIVE_RECYCLER.parser(in); } @Override public void recycle(JsonGenerator recycler) { if (recycler instanceof VThreadJGenerator) { - VirtualPoolHolder.virtualPool.recycle(recycler); + VIRTUAL_RECYCLER.recycle(recycler); } } @Override public void recycle(JsonParser recycler) { if (recycler instanceof VThreadJParser) { - VirtualPoolHolder.virtualPool.recycle(recycler); + VIRTUAL_RECYCLER.recycle(recycler); } } static final class StripedLockFreePool implements BufferRecycler { - private static final StripedLockFreePool INSTANCE = new StripedLockFreePool(Runtime.getRuntime().availableProcessors()); + private static final StripedLockFreePool INSTANCE = + new StripedLockFreePool(Runtime.getRuntime().availableProcessors()); private static final int CACHE_LINE_SHIFT = 4; @@ -244,7 +240,8 @@ private static final class VirtualPredicate { private static MethodHandle findVirtualMH() { try { - return MethodHandles.publicLookup().findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class)); + return MethodHandles.publicLookup() + .findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class)); } catch (Exception e) { return null; } @@ -252,15 +249,21 @@ private static MethodHandle findVirtualMH() { private static Predicate findIsVirtualPredicate() { if (virtualMh == null) { - return thread -> false; + return VirtualPredicate::notVirtual; } - return thread -> { - try { - return (boolean) virtualMh.invokeExact(thread); - } catch (Throwable e) { - throw new RuntimeException(e); - } - }; + return VirtualPredicate::isVirtual; + } + + private static boolean isVirtual(Thread thread) { + try { + return (boolean) virtualMh.invokeExact(thread); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + private static boolean notVirtual(Thread thread) { + return false; } } diff --git a/pom.xml b/pom.xml index 282c9434..ab007199 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ io.avaje avaje-jsonb-parent - 1.10-RC1 + 1.10-RC2 pom jsonb parent