From 0b2d706b75fc188914a3740e46363afb67fa9f49 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:30:02 +0200 Subject: [PATCH 01/13] Initial work on standard adapters --- .../adapter/CompositeKeyAdapterFactory.java | 27 ++++++ .../adapter/CompositeTypeAdapterFactory.java | 27 ++++++ .../roxymc/jserialize/adapter/KeyAdapter.java | 15 ++- .../adapter/PredicateKeyAdapterFactory.java | 2 +- .../jserialize/adapter/ReadContext.java | 12 +++ .../jserialize/adapter/TypeAdapter.java | 13 +++ .../jserialize/adapter/TypeAdapters.java | 10 +- .../jserialize/adapter/TypeAdaptersImpl.java | 35 ++++++- .../jserialize/adapter/WriteContext.java | 13 +++ .../adapter/array/ArrayAdapter.java | 94 +++++++++++++++++++ .../adapter/array/package-info.java | 4 + .../adapter/atomic/AtomicAdapters.java | 31 ++++++ .../adapter/atomic/AtomicBooleanAdapter.java | 50 ++++++++++ .../adapter/atomic/AtomicIntegerAdapter.java | 50 ++++++++++ .../adapter/atomic/AtomicLongAdapter.java | 50 ++++++++++ .../atomic/AtomicReferenceAdapter.java | 82 ++++++++++++++++ .../adapter/atomic/package-info.java | 4 + .../adapter/collection/CollectionAdapter.java | 17 ++-- .../adapter/collection/CollectionType.java | 12 +-- .../jserialize/adapter/map/MapAdapter.java | 26 ++--- .../jserialize/adapter/map/MapType.java | 17 +--- .../adapter/object/ObjectReader.java | 6 +- .../adapter/object/ObjectWriter.java | 15 ++- .../adapter/scalar/AbstractNumberAdapter.java | 90 ++++++++++++++++++ .../adapter/scalar/BooleanAdapter.java | 41 ++++++++ .../adapter/scalar/ByteAdapter.java | 32 +++++++ .../adapter/scalar/CharacterAdapter.java | 46 +++++++++ .../adapter/scalar/DoubleAdapter.java | 37 ++++++++ .../adapter/scalar/EnumAdapter.java | 50 ++++++++++ .../adapter/scalar/EnumKeyAdapter.java | 20 ++-- .../adapter/scalar/FloatAdapter.java | 43 +++++++++ .../adapter/scalar/IntegerAdapter.java | 32 +++++++ .../adapter/scalar/LongAdapter.java | 26 +++++ .../adapter/scalar/NumberAdapter.java | 59 ++++++++++++ .../adapter/scalar/ScalarAdapters.java | 41 ++++++++ .../adapter/scalar/ScalarKeyAdapters.java | 67 ++++++------- .../adapter/scalar/ShortAdapter.java | 32 +++++++ .../adapter/scalar/StringAdapter.java | 40 ++++++++ .../adapter/scalar/UUIDAdapter.java | 41 ++++++++ .../roxymc/jserialize/util/ObjectUtils.java | 11 +++ .../roxymc/jserialize/util/TypeChecks.java | 16 ++++ .../net/roxymc/jserialize/util/TypeUtils.java | 4 + .../format/bson/adapter/BsonTypeAdapters.java | 14 +++ .../format/bson/adapter/package-info.java | 4 + .../bson/adapter/scalar/BsonUUIDAdapter.java | 61 ++++++++++++ .../bson/adapter/scalar/package-info.java | 4 + 46 files changed, 1311 insertions(+), 112 deletions(-) create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/CompositeKeyAdapterFactory.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/CompositeTypeAdapterFactory.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/array/package-info.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/package-info.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarAdapters.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/util/TypeChecks.java create mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java create mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/package-info.java create mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonUUIDAdapter.java create mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/package-info.java diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/CompositeKeyAdapterFactory.java b/core/src/main/java/net/roxymc/jserialize/adapter/CompositeKeyAdapterFactory.java new file mode 100644 index 0000000..11545e3 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/CompositeKeyAdapterFactory.java @@ -0,0 +1,27 @@ +package net.roxymc.jserialize.adapter; + +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +final class CompositeKeyAdapterFactory implements KeyAdapter.Factory { + private final KeyAdapter.Factory[] factories; + + CompositeKeyAdapterFactory(KeyAdapter.Factory[] factories) { + this.factories = nonNull(factories, "factories").clone(); + } + + @Override + public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { + for (KeyAdapter.Factory factory : factories) { + KeyAdapter adapter = factory.createKey(type, adapters); + + if (adapter != null) { + return adapter; + } + } + + return null; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/CompositeTypeAdapterFactory.java b/core/src/main/java/net/roxymc/jserialize/adapter/CompositeTypeAdapterFactory.java new file mode 100644 index 0000000..c1b0eec --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/CompositeTypeAdapterFactory.java @@ -0,0 +1,27 @@ +package net.roxymc.jserialize.adapter; + +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +final class CompositeTypeAdapterFactory implements TypeAdapter.Factory { + private final TypeAdapter.Factory[] factories; + + CompositeTypeAdapterFactory(TypeAdapter.Factory[] factories) { + this.factories = nonNull(factories, "factories").clone(); + } + + @Override + public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + for (TypeAdapter.Factory factory : factories) { + TypeAdapter adapter = factory.create(type, adapters); + + if (adapter != null) { + return adapter; + } + } + + return null; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java index 81f859d..8ab3b01 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java @@ -2,6 +2,7 @@ import io.leangen.geantyref.GenericTypeReflector; import net.roxymc.jserialize.type.TypeRef; +import net.roxymc.jserialize.util.TypeUtils; import org.jspecify.annotations.Nullable; import java.util.function.Predicate; @@ -48,6 +49,18 @@ static Factory exactRaw(Class type, KeyAdapter adapter) { return predicate(subtype -> type.equals(subtype.getRawType()), adapter); } - @Nullable KeyAdapter create(TypeRef type, TypeAdapters adapters); + static Factory exactBoxed(Class type, KeyAdapter adapter) { + if (TypeUtils.isPrimitive(type)) { + throw new IllegalArgumentException("type cannot be primitive"); + } + + return predicate(subtype -> type.equals(GenericTypeReflector.box(subtype.getRawType())), adapter); + } + + static Factory composite(Factory... factories) { + return new CompositeKeyAdapterFactory(factories); + } + + @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/PredicateKeyAdapterFactory.java b/core/src/main/java/net/roxymc/jserialize/adapter/PredicateKeyAdapterFactory.java index 041fe8e..52b92ca 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/PredicateKeyAdapterFactory.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/PredicateKeyAdapterFactory.java @@ -17,7 +17,7 @@ final class PredicateKeyAdapterFactory implements KeyAdapter.Factory { } @Override - public @Nullable KeyAdapter create(TypeRef type, TypeAdapters adapters) { + public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { @SuppressWarnings("unchecked") KeyAdapter adapter = (KeyAdapter) this.adapter; return predicate.test(type) ? adapter : null; diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/ReadContext.java b/core/src/main/java/net/roxymc/jserialize/adapter/ReadContext.java index c85e315..60bdf67 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/ReadContext.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/ReadContext.java @@ -1,9 +1,13 @@ package net.roxymc.jserialize.adapter; +import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.adapter.object.FormatUtils; +import net.roxymc.jserialize.type.TypeRef; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.Nullable; +import java.io.IOException; + public interface ReadContext { @ApiStatus.Internal static ReadContext of(TypeAdapters typeAdapters, FormatUtils formatUtils) { @@ -20,6 +24,14 @@ static ReadContext of(TypeAdapters typeAdapters, FormatUtils formatUtils) { ReadContext withKey(@Nullable String key); + default @Nullable T read(Reader reader, Class type) throws IOException { + return read(reader, TypeRef.of(type)); + } + + default @Nullable T read(Reader reader, TypeRef type) throws IOException { + return typeAdapters().getOrThrow(type).read(reader, type, this); + } + @ApiStatus.Internal FormatUtils formatUtils(); } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java index 98ecfa4..ee65e11 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java @@ -4,6 +4,7 @@ import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.Writer; import net.roxymc.jserialize.type.TypeRef; +import net.roxymc.jserialize.util.TypeUtils; import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; @@ -65,6 +66,18 @@ static Factory exactRaw(Class type, TypeAdapter adapter) { return predicate(subtype -> type.equals(subtype.getRawType()), adapter); } + static Factory exactBoxed(Class type, TypeAdapter adapter) { + if (TypeUtils.isPrimitive(type)) { + throw new IllegalArgumentException("type cannot be primitive"); + } + + return predicate(subtype -> type.equals(GenericTypeReflector.box(subtype.getRawType())), adapter); + } + + static Factory composite(Factory... factories) { + return new CompositeTypeAdapterFactory(factories); + } + @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java index 8472dfe..007a4c9 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java @@ -1,17 +1,25 @@ package net.roxymc.jserialize.adapter; +import net.roxymc.jserialize.adapter.array.ArrayAdapter; +import net.roxymc.jserialize.adapter.atomic.AtomicAdapters; import net.roxymc.jserialize.adapter.collection.CollectionAdapter; import net.roxymc.jserialize.adapter.map.MapAdapter; import net.roxymc.jserialize.adapter.object.ObjectAdapter; +import net.roxymc.jserialize.adapter.scalar.EnumAdapter; import net.roxymc.jserialize.adapter.scalar.EnumKeyAdapter; +import net.roxymc.jserialize.adapter.scalar.ScalarAdapters; import net.roxymc.jserialize.adapter.scalar.ScalarKeyAdapters; import net.roxymc.jserialize.type.TypeRef; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.Nullable; @ApiStatus.NonExtendable -public interface TypeAdapters { +public interface TypeAdapters extends TypeAdapter.Factory, KeyAdapter.Factory { TypeAdapters DEFAULT = builder() + .add(ScalarAdapters.factory()) + .add(EnumAdapter.factory()) + .add(AtomicAdapters.factory()) + .add(ArrayAdapter.factory()) .add(CollectionAdapter.factory()) .add(MapAdapter.factory()) .add(ObjectAdapter.annotatedFactory()) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java index 37be55c..48ca6ad 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java @@ -18,7 +18,7 @@ final class TypeAdaptersImpl implements TypeAdapters { @SuppressWarnings({"NullableProblems", "DataFlowIssue"}) // IntelliJ has existential issues private TypeAdaptersImpl(BuilderImpl builder) { this.typeAdapters = new Registry<>(builder.typeAdapters, (factory, type) -> factory.create(type, this)); - this.keyAdapters = new Registry<>(builder.keyAdapters, (factory, type) -> factory.create(type, this)); + this.keyAdapters = new Registry<>(builder.keyAdapters, (factory, type) -> factory.createKey(type, this)); } @Override @@ -35,6 +35,32 @@ private TypeAdaptersImpl(BuilderImpl builder) { return adapter; } + @Override + public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + for (TypeAdapter.Factory factory : typeAdapters.factories) { + TypeAdapter adapter = factory.create(type, this); + + if (adapter != null) { + return adapter; + } + } + + return null; + } + + @Override + public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { + for (KeyAdapter.Factory factory : keyAdapters.factories) { + KeyAdapter adapter = factory.createKey(type, this); + + if (adapter != null) { + return adapter; + } + } + + return null; + } + private static final class Registry { private final Map> cache = new ConcurrentHashMap<>(); private final Set factories; @@ -80,11 +106,10 @@ public Builder addKey(KeyAdapter.Factory factory) { @Override public Builder addAll(TypeAdapters adapters) { - TypeAdaptersImpl adaptersImpl = (TypeAdaptersImpl) nonNull(adapters, "adapters"); - - typeAdapters.addAll(adaptersImpl.typeAdapters.factories); - keyAdapters.addAll(adaptersImpl.keyAdapters.factories); + nonNull(adapters, "adapters"); + typeAdapters.add(adapters); + keyAdapters.add(adapters); return this; } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/WriteContext.java b/core/src/main/java/net/roxymc/jserialize/adapter/WriteContext.java index ec7e610..e8d6d91 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/WriteContext.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/WriteContext.java @@ -1,7 +1,12 @@ package net.roxymc.jserialize.adapter; +import net.roxymc.jserialize.Writer; import net.roxymc.jserialize.adapter.object.FormatUtils; +import net.roxymc.jserialize.type.TypeRef; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; public interface WriteContext { @ApiStatus.Internal @@ -11,6 +16,14 @@ static WriteContext of(TypeAdapters typeAdapters, FormatUtils formatUtils) { TypeAdapters typeAdapters(); + default void write(Writer writer, Class type, @Nullable T value) throws IOException { + write(writer, TypeRef.of(type), value); + } + + default void write(Writer writer, TypeRef type, @Nullable T value) throws IOException { + typeAdapters().getOrThrow(type).write(writer, type, value, this); + } + @ApiStatus.Internal FormatUtils formatUtils(); } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java new file mode 100644 index 0000000..2c9effa --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java @@ -0,0 +1,94 @@ +package net.roxymc.jserialize.adapter.array; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.lang.reflect.AnnotatedArrayType; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.predicate; + +public final class ArrayAdapter implements TypeAdapter { + private static final TypeAdapter.Factory FACTORY = predicate(type -> type.getRawType().isArray(), new ArrayAdapter()); + + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + @Override + public @Nullable Object read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { + if (!type.getRawType().isArray()) { + throw new IllegalStateException(type.getRawType() + " is not an array"); + } + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + TypeRef componentType = resolveComponentType(type); + + reader.readArrayStart(); + + List<@Nullable Object> buffer = new ArrayList<>(); + + while (reader.peek() != TokenTypes.ARRAY_END) { + buffer.add(ctx.read(reader, componentType)); + } + + reader.readArrayEnd(); + + Object array = Array.newInstance(componentType.getRawType(), buffer.size()); + + for (int i = 0; i < buffer.size(); i++) { + Array.set(array, i, buffer.get(i)); + } + + return array; + } + + @Override + public void write(Writer writer, TypeRef type, @Nullable Object value, WriteContext context) throws IOException { + if (!type.getRawType().isArray()) { + throw new IllegalStateException(type.getRawType() + " is not an array"); + } + + if (value == null) { + writer.writeNull(); + return; + } + + TypeRef componentType = resolveComponentType(type); + + writer.writeArrayStart(); + + int length = Array.getLength(value); + + for (int i = 0; i < length; i++) { + context.write(writer, componentType, Array.get(value, i)); + } + + writer.writeArrayEnd(); + } + + private TypeRef resolveComponentType(TypeRef arrayType) { + AnnotatedType type = arrayType.getAnnotatedType(); + if (!(type instanceof AnnotatedArrayType)) { + throw new IllegalStateException(arrayType.getType() + " is not an array type"); + } + + AnnotatedArrayType atype = (AnnotatedArrayType) type; + + return TypeRef.of(atype.getAnnotatedGenericComponentType()); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/array/package-info.java b/core/src/main/java/net/roxymc/jserialize/adapter/array/package-info.java new file mode 100644 index 0000000..e79d280 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/array/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.roxymc.jserialize.adapter.array; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java new file mode 100644 index 0000000..f0b983f --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java @@ -0,0 +1,31 @@ +package net.roxymc.jserialize.adapter.atomic; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.polymorphic; + +public final class AtomicAdapters { + public static final TypeAdapter BOOLEAN = new AtomicBooleanAdapter(); + public static final TypeAdapter LONG = new AtomicLongAdapter(); + public static final TypeAdapter INTEGER = new AtomicIntegerAdapter(); + public static final TypeAdapter> REFERENCE = new AtomicReferenceAdapter(); + + private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( + polymorphic(AtomicBoolean.class, BOOLEAN), + polymorphic(AtomicLong.class, LONG), + polymorphic(AtomicInteger.class, INTEGER), + polymorphic(AtomicReference.class, REFERENCE) + ); + + private AtomicAdapters() { + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java new file mode 100644 index 0000000..cf97a0c --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java @@ -0,0 +1,50 @@ +package net.roxymc.jserialize.adapter.atomic; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class AtomicBooleanAdapter implements TypeAdapter.Mutable { + @Override + public @Nullable AtomicBoolean mutate( + Reader reader, TypeRef type, @Nullable AtomicBoolean value, ReadContext ctx + ) throws IOException { + checkAssignable(AtomicBoolean.class, type.getRawType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return value; + } + + if (value == null) { + value = new AtomicBoolean(); + } + + value.set(reader.readBoolean()); + return value; + } + + @Override + public void write( + Writer writer, TypeRef type, @Nullable AtomicBoolean value, WriteContext ctx + ) throws IOException { + checkAssignable(AtomicBoolean.class, type.getRawType()); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeBoolean(value.get()); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java new file mode 100644 index 0000000..7197077 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java @@ -0,0 +1,50 @@ +package net.roxymc.jserialize.adapter.atomic; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class AtomicIntegerAdapter implements TypeAdapter.Mutable { + @Override + public @Nullable AtomicInteger mutate( + Reader reader, TypeRef type, @Nullable AtomicInteger value, ReadContext ctx + ) throws IOException { + checkAssignable(AtomicInteger.class, type.getRawType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return value; + } + + if (value == null) { + value = new AtomicInteger(); + } + + value.set(reader.readInt()); + return value; + } + + @Override + public void write( + Writer writer, TypeRef type, @Nullable AtomicInteger value, WriteContext ctx + ) throws IOException { + checkAssignable(AtomicInteger.class, type.getRawType()); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeInt(value.get()); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java new file mode 100644 index 0000000..1fa4baf --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java @@ -0,0 +1,50 @@ +package net.roxymc.jserialize.adapter.atomic; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class AtomicLongAdapter implements TypeAdapter.Mutable { + @Override + public @Nullable AtomicLong mutate( + Reader reader, TypeRef type, @Nullable AtomicLong value, ReadContext ctx + ) throws IOException { + checkAssignable(AtomicLong.class, type.getRawType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return value; + } + + if (value == null) { + value = new AtomicLong(); + } + + value.set(reader.readLong()); + return value; + } + + @Override + public void write( + Writer writer, TypeRef type, @Nullable AtomicLong value, WriteContext ctx + ) throws IOException { + checkAssignable(AtomicLong.class, type.getRawType()); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeLong(value.get()); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java new file mode 100644 index 0000000..7c70a2b --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java @@ -0,0 +1,82 @@ +package net.roxymc.jserialize.adapter.atomic; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jetbrains.annotations.UnknownNullability; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; +import java.util.concurrent.atomic.AtomicReference; + +import static io.leangen.geantyref.GenericTypeReflector.capture; +import static io.leangen.geantyref.GenericTypeReflector.getExactSuperType; +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class AtomicReferenceAdapter implements TypeAdapter.Mutable> { + @Override + public @Nullable AtomicReference mutate( + Reader reader, TypeRef> type, @Nullable AtomicReference value, ReadContext ctx + ) throws IOException { + return mutate0(reader, type, value, ctx); + } + + private @Nullable AtomicReference mutate0( + Reader reader, TypeRef> type, @Nullable AtomicReference value, ReadContext ctx + ) throws IOException { + checkAssignable(AtomicReference.class, type.getRawType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return value; + } + + if (value == null) { + value = new AtomicReference<>(); + } + + TypeRef<@UnknownNullability V> valueType = resolveValueType(type); + + value.set(ctx.read(reader, valueType)); + return value; + } + + @Override + public void write( + Writer writer, TypeRef> type, @Nullable AtomicReference value, WriteContext ctx + ) throws IOException { + write0(writer, type, value, ctx); + } + + private void write0( + Writer writer, TypeRef> type, @Nullable AtomicReference value, WriteContext ctx + ) throws IOException { + checkAssignable(AtomicReference.class, type.getRawType()); + + if (value == null) { + writer.writeNull(); + return; + } + + TypeRef<@UnknownNullability V> valueType = resolveValueType(type); + + ctx.write(writer, valueType, value.get()); + } + + private TypeRef resolveValueType(TypeRef> referenceType) { + AnnotatedType type = getExactSuperType(capture(referenceType.getAnnotatedType()), AtomicReference.class); + if (!(type instanceof AnnotatedParameterizedType)) { + throw new IllegalStateException(referenceType.getType() + " must be a parameterized AtomicReference"); + } + + AnnotatedParameterizedType ptype = (AnnotatedParameterizedType) type; + + return TypeRef.of(ptype.getAnnotatedActualTypeArguments()[0]); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/package-info.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/package-info.java new file mode 100644 index 0000000..cd9d54e --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.roxymc.jserialize.adapter.atomic; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java index ac756c5..2ff7a5e 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java @@ -2,9 +2,7 @@ import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.adapter.ReadContext; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.adapter.*; import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.NonNull; @@ -17,6 +15,7 @@ import java.util.concurrent.ConcurrentHashMap; import static net.roxymc.jserialize.util.ObjectUtils.nonNull; +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; public final class CollectionAdapter implements TypeAdapter.Mutable> { private static final TypeAdapter.Factory FACTORY = factory(DefaultCollectionProvider.INSTANCE); @@ -52,6 +51,8 @@ public static TypeAdapter.Factory factory(CollectionProvider... providers) { private @Nullable Collection mutate0( Reader reader, TypeRef> type, @Nullable Collection collection, ReadContext ctx ) throws IOException { + checkAssignable(Collection.class, type.getRawType()); + if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return collection; @@ -63,12 +64,12 @@ public static TypeAdapter.Factory factory(CollectionProvider... providers) { collection = collectionType.createCollection(providers); } - TypeAdapter<@NonNull E> elementAdapter = collectionType.elementAdapter(ctx.typeAdapters()); + TypeReader<@NonNull E> elementReader = ctx.typeAdapters().getOrThrow(collectionType.elementType); reader.readArrayStart(); for (int index = 0; reader.peek() != TokenTypes.ARRAY_END; index++) { - E element = elementAdapter.read(reader, collectionType.elementType, ctx); + E element = elementReader.read(reader, collectionType.elementType, ctx); collection.add(element); } @@ -88,6 +89,8 @@ public void write( private void write0( Writer writer, TypeRef> type, @Nullable Collection collection, WriteContext ctx ) throws IOException { + checkAssignable(Collection.class, type.getRawType()); + if (collection == null) { writer.writeNull(); return; @@ -95,12 +98,12 @@ public void write( CollectionType collectionType = resolveCollectionType(type); - TypeAdapter<@NonNull E> elementAdapter = collectionType.elementAdapter(ctx.typeAdapters()); + TypeWriter<@NonNull E> elementWriter = ctx.typeAdapters().getOrThrow(collectionType.elementType); writer.writeArrayStart(); for (E element : collection) { - elementAdapter.write(writer, collectionType.elementType, element, ctx); + elementWriter.write(writer, collectionType.elementType, element, ctx); } writer.writeArrayEnd(); diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionType.java b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionType.java index 13721fa..05d81f0 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionType.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionType.java @@ -1,11 +1,8 @@ package net.roxymc.jserialize.adapter.collection; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.TypeAdapters; import net.roxymc.jserialize.type.TypeRef; import net.roxymc.jserialize.util.VarHandles; import org.jetbrains.annotations.UnknownNullability; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.lang.invoke.VarHandle; @@ -19,14 +16,15 @@ final class CollectionType { private static final VarHandle COLLECTION_FACTORY_HANDLE = VarHandles.find(CollectionType.class, "collectionFactory", CollectionFactory.class); - final TypeRef> collectionType; + private final TypeRef> collectionType; final TypeRef elementType; + private @Nullable CollectionFactory collectionFactory; CollectionType(TypeRef> collectionType) { AnnotatedType type = getExactSuperType(capture(collectionType.getAnnotatedType()), Collection.class); if (!(type instanceof AnnotatedParameterizedType)) { - throw new IllegalStateException(collectionType.getType() + " must be parameterized "); + throw new IllegalStateException(collectionType.getType() + " must be a parameterized Collection"); } AnnotatedParameterizedType ptype = (AnnotatedParameterizedType) type; @@ -35,10 +33,6 @@ final class CollectionType { this.elementType = TypeRef.of(ptype.getAnnotatedActualTypeArguments()[0]); } - TypeAdapter<@NonNull E> elementAdapter(TypeAdapters adapters) { - return adapters.getOrThrow(elementType); - } - @SuppressWarnings("unchecked") Collection createCollection(CollectionProvider[] providers) { CollectionFactory value = (CollectionFactory) COLLECTION_FACTORY_HANDLE.getAcquire(this); diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java index a70cf43..f8be972 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java @@ -2,10 +2,7 @@ import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.adapter.KeyAdapter; -import net.roxymc.jserialize.adapter.ReadContext; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.adapter.*; import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.NonNull; @@ -17,6 +14,7 @@ import java.util.concurrent.ConcurrentHashMap; import static net.roxymc.jserialize.util.ObjectUtils.nonNull; +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; public final class MapAdapter implements TypeAdapter.Mutable> { private static final TypeAdapter.Factory FACTORY = factory(DefaultMapProvider.INSTANCE); @@ -52,6 +50,8 @@ private MapType resolveMapType(TypeRef> type) { private @Nullable Map mutate0( Reader reader, TypeRef> type, @Nullable Map map, ReadContext ctx ) throws IOException { + checkAssignable(Map.class, type.getRawType()); + if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return map; @@ -63,16 +63,16 @@ private MapType resolveMapType(TypeRef> type) { map = mapType.createMap(providers); } - KeyAdapter<@NonNull K> keyAdapter = mapType.keyAdapter(ctx.typeAdapters()); - TypeAdapter<@NonNull V> valueAdapter = mapType.valueAdapter(ctx.typeAdapters()); + KeyDecoder<@NonNull K> keyDecoder = ctx.typeAdapters().getKeyOrThrow(mapType.keyType); + TypeReader<@NonNull V> valueReader = ctx.typeAdapters().getOrThrow(mapType.valueType); reader.readObjectStart(); while (reader.peek() == TokenTypes.NAME) { String name = reader.readName(); - K key = keyAdapter.decode(name); - V value = valueAdapter.read(reader, mapType.valueType, ctx.withKey(name)); + K key = keyDecoder.decode(name); + V value = valueReader.read(reader, mapType.valueType, ctx.withKey(name)); map.put(key, value); } @@ -92,6 +92,8 @@ public void write( private void write0( Writer writer, TypeRef> type, @Nullable Map map, WriteContext ctx ) throws IOException { + checkAssignable(Map.class, type.getRawType()); + if (map == null) { writer.writeNull(); return; @@ -99,16 +101,16 @@ public void write( MapType mapType = resolveMapType(type); - KeyAdapter<@NonNull K> keyAdapter = mapType.keyAdapter(ctx.typeAdapters()); - TypeAdapter<@NonNull V> valueAdapter = mapType.valueAdapter(ctx.typeAdapters()); + KeyEncoder<@NonNull K> keyEncoder = ctx.typeAdapters().getKeyOrThrow(mapType.keyType); + TypeWriter<@NonNull V> valueWriter = ctx.typeAdapters().getOrThrow(mapType.valueType); writer.writeObjectStart(); for (Map.Entry entry : map.entrySet()) { - String name = keyAdapter.encode(entry.getKey()); + String name = keyEncoder.encode(entry.getKey()); writer.writeName(name); - valueAdapter.write(writer, mapType.valueType, entry.getValue(), ctx); + valueWriter.write(writer, mapType.valueType, entry.getValue(), ctx); } writer.writeObjectEnd(); diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapType.java b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapType.java index 259da71..26a7c9b 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapType.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapType.java @@ -1,12 +1,8 @@ package net.roxymc.jserialize.adapter.map; -import net.roxymc.jserialize.adapter.KeyAdapter; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.TypeAdapters; import net.roxymc.jserialize.type.TypeRef; import net.roxymc.jserialize.util.VarHandles; import org.jetbrains.annotations.UnknownNullability; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.lang.invoke.VarHandle; @@ -20,15 +16,16 @@ final class MapType { private static final VarHandle MAP_FACTORY_HANDLE = VarHandles.find(MapType.class, "mapFactory", MapFactory.class); - final TypeRef> mapType; + private final TypeRef> mapType; final TypeRef keyType; final TypeRef valueType; + private @Nullable MapFactory mapFactory; MapType(TypeRef> mapType) { AnnotatedType type = getExactSuperType(capture(mapType.getAnnotatedType()), Map.class); if (!(type instanceof AnnotatedParameterizedType)) { - throw new IllegalStateException(mapType.getRawType() + " must be parameterized"); + throw new IllegalStateException(mapType.getRawType() + " must be a parameterized Map"); } AnnotatedParameterizedType ptype = (AnnotatedParameterizedType) type; @@ -38,14 +35,6 @@ final class MapType keyAdapter(TypeAdapters adapters) { - return adapters.getKeyOrThrow(keyType); - } - - TypeAdapter<@NonNull V> valueAdapter(TypeAdapters adapters) { - return adapters.getOrThrow(valueType); - } - @SuppressWarnings("unchecked") Map createMap(MapProvider[] providers) { MapFactory value = (MapFactory) MAP_FACTORY_HANDLE.getAcquire(this); diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java index b2f2814..b7e21f1 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java @@ -1,7 +1,7 @@ package net.roxymc.jserialize.adapter.object; import net.roxymc.jserialize.Reader; -import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.adapter.KeyDecoder; import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.creator.InstanceCreator; @@ -58,9 +58,9 @@ T read(Reader reader) throws Throwable { } TypeRef typeRef = TypeRef.of(type); - KeyAdapter adapter = context.typeAdapters().getKeyOrThrow(typeRef); + KeyDecoder decoder = context.typeAdapters().getKeyOrThrow(typeRef); - builder.property(property, adapter.decode(context.key())); + builder.property(property, decoder.decode(context.key())); }); PropertyModel extrasProperty = classModel.properties().get(PropertyKind.EXTRAS); diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java index fb00b59..d62b284 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java @@ -1,7 +1,7 @@ package net.roxymc.jserialize.adapter.object; import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.TypeWriter; import net.roxymc.jserialize.adapter.WriteContext; import net.roxymc.jserialize.model.ClassModel; import net.roxymc.jserialize.model.property.GetterRef; @@ -78,24 +78,23 @@ private void writeProperty(Writer writer, PropertyModel property) throws Throwab return; } - TypeRef typeRef = TypeRef.of(type); - TypeAdapter adapter = context.typeAdapters().getOrThrow(typeRef); - String name = resolveWriteName(property); writer.writeName(name); - adapter.write(writer, typeRef, value, context); + + context.write(writer, TypeRef.of(type), value); } private void writeExtrasProperty(Writer writer, AnnotatedType type, Object value) throws IOException { MapLike extrasMap = formatUtils.createMap(context.typeAdapters(), type); extrasMap.putAll((Map) value, context); - TypeRef typeRef = TypeRef.of(formatUtils.rawType()); - TypeAdapter adapter = context.typeAdapters().getOrThrow(typeRef); + TypeRef rawType = TypeRef.of(formatUtils.rawType()); + TypeWriter rawWriter = context.typeAdapters().getOrThrow(rawType); for (Map.Entry entry : extrasMap.asRawMap().entrySet()) { writer.writeName(entry.getKey()); - adapter.write(writer, typeRef, entry.getValue(), context); + + rawWriter.write(writer, rawType, entry.getValue(), context); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java new file mode 100644 index 0000000..bc75680 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java @@ -0,0 +1,90 @@ +package net.roxymc.jserialize.adapter.scalar; + +import io.leangen.geantyref.GenericTypeReflector; +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +abstract class AbstractNumberAdapter implements TypeAdapter { + protected final Class numberType; + + protected AbstractNumberAdapter(Class numberType) { + this.numberType = numberType; + } + + @Override + public final @Nullable N read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { + checkAssignable(numberType, GenericTypeReflector.box(type.getRawType())); + + TokenType tokenType = reader.peek(); + + if (tokenType == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + if (tokenType == TokenTypes.INT) { + return fromLong(reader.readInt()); + } + + if (tokenType == TokenTypes.LONG) { + return fromLong(reader.readLong()); + } + + if (tokenType == TokenTypes.DOUBLE) { + return fromDouble(reader.readDouble()); + } + + if (tokenType == TokenTypes.NUMERIC || tokenType == TokenTypes.STRING) { + try { + return parse(reader.readString()); + } catch (NumberFormatException e) { + throw new ArithmeticException("Invalid format for " + numberType.getSimpleName() + ": " + e.getMessage()); + } + } + + throw new IllegalStateException("Expected numeric token, but found: " + tokenType); + } + + protected N fromDouble(double value) { + if (!Double.isFinite(value)) { + throw new ArithmeticException(numberType.getSimpleName() + " must be finite: " + value); + } + + long longValue = (long) value; + + if (longValue != value) { + throw new ArithmeticException(numberType.getSimpleName() + " overflow or loss of precision: " + value); + } + + return fromLong(longValue); + } + + protected abstract N fromLong(long value); + + protected abstract N parse(String value) throws NumberFormatException; + + @Override + public final void write(Writer writer, TypeRef type, @Nullable N value, WriteContext ctx) throws IOException { + checkAssignable(numberType, GenericTypeReflector.box(type.getRawType())); + + if (value == null) { + writer.writeNull(); + return; + } + + write(writer, value); + } + + protected abstract void write(Writer writer, N value) throws IOException; +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java new file mode 100644 index 0000000..131ba6a --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java @@ -0,0 +1,41 @@ +package net.roxymc.jserialize.adapter.scalar; + +import io.leangen.geantyref.GenericTypeReflector; +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class BooleanAdapter implements TypeAdapter { + @Override + public @Nullable Boolean read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { + checkAssignable(Boolean.class, GenericTypeReflector.box(type.getRawType())); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + return reader.readBoolean(); + } + + @Override + public void write(Writer writer, TypeRef type, @Nullable Boolean value, WriteContext ctx) throws IOException { + checkAssignable(Boolean.class, GenericTypeReflector.box(type.getRawType())); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeBoolean(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteAdapter.java new file mode 100644 index 0000000..710d19a --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteAdapter.java @@ -0,0 +1,32 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; + +final class ByteAdapter extends AbstractNumberAdapter { + ByteAdapter() { + super(Byte.class); + } + + @Override + protected Byte fromLong(long value) { + byte byteValue = (byte) value; + + if (byteValue != value) { + throw new ArithmeticException(numberType.getSimpleName() + " overflow: " + value); + } + + return byteValue; + } + + @Override + protected Byte parse(String value) throws NumberFormatException { + return Byte.parseByte(value); + } + + @Override + protected void write(Writer writer, Byte value) throws IOException { + writer.writeInt(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java new file mode 100644 index 0000000..92d584f --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java @@ -0,0 +1,46 @@ +package net.roxymc.jserialize.adapter.scalar; + +import io.leangen.geantyref.GenericTypeReflector; +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class CharacterAdapter implements TypeAdapter { + @Override + public @Nullable Character read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { + checkAssignable(Character.class, GenericTypeReflector.box(type.getRawType())); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + String value = reader.readString(); + if (value.length() != 1) { + throw new IllegalStateException("Not a character: " + value); + } + + return value.charAt(0); + } + + @Override + public void write(Writer writer, TypeRef type, @Nullable Character value, WriteContext ctx) throws IOException { + checkAssignable(Character.class, GenericTypeReflector.box(type.getRawType())); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeString(value.toString()); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleAdapter.java new file mode 100644 index 0000000..381d1bb --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleAdapter.java @@ -0,0 +1,37 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; + +final class DoubleAdapter extends AbstractNumberAdapter { + DoubleAdapter() { + super(Double.class); + } + + @Override + protected Double fromLong(long value) { + double doubleValue = (double) value; + + if ((long) doubleValue != value) { + throw new ArithmeticException(numberType.getSimpleName() + " overflow or loss of precision: " + value); + } + + return doubleValue; + } + + @Override + protected Double fromDouble(double value) { + return value; + } + + @Override + protected Double parse(String value) throws NumberFormatException { + return Double.parseDouble(value); + } + + @Override + protected void write(Writer writer, Double value) throws IOException { + writer.writeDouble(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumAdapter.java new file mode 100644 index 0000000..18ac4b1 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumAdapter.java @@ -0,0 +1,50 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import net.roxymc.jserialize.util.TypeUtils; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; + +import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.predicate; +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +public final class EnumAdapter implements TypeAdapter> { + private static final TypeAdapter.Factory FACTORY = predicate(type -> TypeUtils.isEnum(type.getRawType()), new EnumAdapter()); + + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + @Override + public @Nullable Enum read(Reader reader, TypeRef> type, ReadContext ctx) throws IOException { + checkAssignable(Enum.class, type.getType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + String name = reader.readString(); + + return Enum.valueOf(type.getRawType().asSubclass(Enum.class), name); + } + + @Override + public void write(Writer writer, TypeRef> type, @Nullable Enum value, WriteContext ctx) throws IOException { + checkAssignable(Enum.class, type.getType()); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeString(value.name()); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumKeyAdapter.java index 3871954..4c8d1d4 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumKeyAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumKeyAdapter.java @@ -3,6 +3,7 @@ import net.roxymc.jserialize.adapter.KeyAdapter; import net.roxymc.jserialize.adapter.TypeAdapters; import net.roxymc.jserialize.type.TypeRef; +import net.roxymc.jserialize.util.TypeUtils; import org.jspecify.annotations.Nullable; import static net.roxymc.jserialize.util.ObjectUtils.nonNull; @@ -10,10 +11,9 @@ public final class EnumKeyAdapter implements KeyAdapter> { private static final KeyAdapter.Factory FACTORY = new KeyAdapter.Factory() { @Override - public @Nullable KeyAdapter create(TypeRef type, TypeAdapters adapters) { - @SuppressWarnings("unchecked") - Class> enumType = (Class>) type.getRawType(); - if (!enumType.isEnum()) { + public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { + Class enumType = type.getRawType(); + if (!TypeUtils.isEnum(enumType)) { return null; } @@ -23,9 +23,9 @@ public final class EnumKeyAdapter implements KeyAdapter> { } }; - private final Class> enumType; + private final Class enumType; - private EnumKeyAdapter(Class> enumType) { + private EnumKeyAdapter(Class enumType) { this.enumType = enumType; } @@ -35,13 +35,7 @@ public static KeyAdapter.Factory factory() { @Override public @Nullable Enum decode(@Nullable String value) { - if (value == null) { - return null; - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - Enum resolved = Enum.valueOf((Class) enumType, value); - return resolved; + return value != null ? Enum.valueOf(enumType.asSubclass(Enum.class), value) : null; } @Override diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatAdapter.java new file mode 100644 index 0000000..73cb8c1 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatAdapter.java @@ -0,0 +1,43 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; + +final class FloatAdapter extends AbstractNumberAdapter { + FloatAdapter() { + super(Float.class); + } + + @Override + protected Float fromLong(long value) { + float floatValue = (float) value; + + if ((long) floatValue != value) { + throw new ArithmeticException(numberType.getSimpleName() + " overflow or loss of precision: " + value); + } + + return floatValue; + } + + @Override + protected Float fromDouble(double value) { + float floatValue = (float) value; + + if (Double.isFinite(value) && (Float.isInfinite(floatValue) || (double) floatValue != value)) { + throw new ArithmeticException(numberType.getSimpleName() + " overflow or loss of precision: " + value); + } + + return floatValue; + } + + @Override + protected Float parse(String value) throws NumberFormatException { + return Float.parseFloat(value); + } + + @Override + protected void write(Writer writer, Float value) throws IOException { + writer.writeDouble(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerAdapter.java new file mode 100644 index 0000000..2b4ee23 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerAdapter.java @@ -0,0 +1,32 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; + +final class IntegerAdapter extends AbstractNumberAdapter { + IntegerAdapter() { + super(Integer.class); + } + + @Override + protected Integer fromLong(long value) { + int intValue = (int) value; + + if (intValue != value) { + throw new ArithmeticException(numberType.getSimpleName() + " overflow: " + value); + } + + return intValue; + } + + @Override + protected Integer parse(String value) throws NumberFormatException { + return Integer.parseInt(value); + } + + @Override + protected void write(Writer writer, Integer value) throws IOException { + writer.writeInt(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongAdapter.java new file mode 100644 index 0000000..696dd32 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongAdapter.java @@ -0,0 +1,26 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; + +final class LongAdapter extends AbstractNumberAdapter { + LongAdapter() { + super(Long.class); + } + + @Override + protected Long fromLong(long value) { + return value; + } + + @Override + protected Long parse(String value) throws NumberFormatException { + return Long.parseLong(value); + } + + @Override + protected void write(Writer writer, Long value) throws IOException { + writer.writeLong(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberAdapter.java new file mode 100644 index 0000000..a21395c --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberAdapter.java @@ -0,0 +1,59 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +final class NumberAdapter extends AbstractNumberAdapter { + NumberAdapter() { + super(Number.class); + } + + static Number parseNumber(String value) throws NumberFormatException { + try { + return Long.parseLong(value); + } catch (NumberFormatException ignored) { + } + + try { + return Double.parseDouble(value); + } catch (NumberFormatException ignored) { + } + + if (value.contains(".") || value.contains("e") || value.contains("E")) { + return new BigDecimal(value); + } else { + return new BigInteger(value); + } + } + + @Override + protected Number fromLong(long value) { + return value; + } + + @Override + protected Number fromDouble(double value) { + return value; + } + + @Override + protected Number parse(String value) throws NumberFormatException { + return parseNumber(value); + } + + @Override + protected void write(Writer writer, Number value) throws IOException { + if (value instanceof Integer || value instanceof Short || value instanceof Byte) { + writer.writeInt(value.intValue()); + } else if (value instanceof Long) { + writer.writeLong(value.longValue()); + } else if (value instanceof Double || value instanceof Float) { + writer.writeDouble(value.doubleValue()); + } else { + writer.writeString(value.toString()); + } + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarAdapters.java new file mode 100644 index 0000000..e57d918 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarAdapters.java @@ -0,0 +1,41 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactBoxed; +import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactRaw; + +public final class ScalarAdapters { + public static final TypeAdapter CHARACTER = new CharacterAdapter(); + public static final TypeAdapter BOOLEAN = new BooleanAdapter(); + public static final TypeAdapter BYTE = new ByteAdapter(); + public static final TypeAdapter DOUBLE = new DoubleAdapter(); + public static final TypeAdapter FLOAT = new FloatAdapter(); + public static final TypeAdapter INTEGER = new IntegerAdapter(); + public static final TypeAdapter LONG = new LongAdapter(); + public static final TypeAdapter SHORT = new ShortAdapter(); + public static final TypeAdapter NUMBER = new NumberAdapter(); + public static final TypeAdapter STRING = new StringAdapter(); + public static final TypeAdapter UUID = new UUIDAdapter(); + + private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( + exactBoxed(Character.class, CHARACTER), + exactBoxed(Boolean.class, BOOLEAN), + exactBoxed(Byte.class, BYTE), + exactBoxed(Double.class, DOUBLE), + exactBoxed(Float.class, FLOAT), + exactBoxed(Integer.class, INTEGER), + exactBoxed(Long.class, LONG), + exactBoxed(Short.class, SHORT), + exactRaw(Number.class, NUMBER), + exactRaw(String.class, STRING), + exactRaw(java.util.UUID.class, UUID) + ); + + private ScalarAdapters() { + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarKeyAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarKeyAdapters.java index 97d4b20..3c181a9 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarKeyAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarKeyAdapters.java @@ -1,46 +1,45 @@ package net.roxymc.jserialize.adapter.scalar; -import io.leangen.geantyref.GenericTypeReflector; import net.roxymc.jserialize.adapter.KeyAdapter; -import net.roxymc.jserialize.adapter.TypeAdapters; -import net.roxymc.jserialize.type.TypeRef; -import org.jspecify.annotations.Nullable; -import java.util.HashMap; -import java.util.Map; import java.util.function.Function; +import static net.roxymc.jserialize.adapter.KeyAdapter.Factory.exactBoxed; +import static net.roxymc.jserialize.adapter.KeyAdapter.Factory.exactRaw; import static net.roxymc.jserialize.util.ObjectUtils.nonNull; public final class ScalarKeyAdapters { - private static final Map, KeyAdapter> ADAPTERS = new HashMap<>(); - - public static final KeyAdapter CHAR = keyAdapter(Character.class, value -> { + public static final KeyAdapter CHARACTER = keyAdapter(value -> { if (value.length() != 1) { throw new IllegalStateException("value must be a single character"); } return value.charAt(0); }); - public static final KeyAdapter BOOLEAN = keyAdapter(Boolean.class, Boolean::parseBoolean); - public static final KeyAdapter BYTE = keyAdapter(Byte.class, Byte::parseByte); - public static final KeyAdapter DOUBLE = keyAdapter(Double.class, Double::parseDouble); - public static final KeyAdapter FLOAT = keyAdapter(Float.class, Float::parseFloat); - public static final KeyAdapter INTEGER = keyAdapter(Integer.class, Integer::parseInt); - public static final KeyAdapter LONG = keyAdapter(Long.class, Long::parseLong); - public static final KeyAdapter SHORT = keyAdapter(Short.class, Short::parseShort); - public static final KeyAdapter STRING = keyAdapter(String.class, Function.identity()); - - private static final KeyAdapter.Factory FACTORY = new KeyAdapter.Factory() { - @Override - public @Nullable KeyAdapter create(TypeRef type, TypeAdapters adapters) { - Class boxedType = (Class) GenericTypeReflector.box(type.getRawType()); - - @SuppressWarnings("unchecked") - KeyAdapter adapter = (KeyAdapter) ADAPTERS.get(boxedType); - return adapter; - } - }; + public static final KeyAdapter BOOLEAN = keyAdapter(Boolean::parseBoolean); + public static final KeyAdapter BYTE = keyAdapter(Byte::parseByte); + public static final KeyAdapter DOUBLE = keyAdapter(Double::parseDouble); + public static final KeyAdapter FLOAT = keyAdapter(Float::parseFloat); + public static final KeyAdapter INTEGER = keyAdapter(Integer::parseInt); + public static final KeyAdapter LONG = keyAdapter(Long::parseLong); + public static final KeyAdapter SHORT = keyAdapter(Short::parseShort); + public static final KeyAdapter NUMBER = keyAdapter(NumberAdapter::parseNumber); + public static final KeyAdapter STRING = keyAdapter(Function.identity()); + public static final KeyAdapter UUID = keyAdapter(java.util.UUID::fromString); + + private static final KeyAdapter.Factory FACTORY = KeyAdapter.Factory.composite( + exactBoxed(Character.class, CHARACTER), + exactBoxed(Boolean.class, BOOLEAN), + exactBoxed(Byte.class, BYTE), + exactBoxed(Double.class, DOUBLE), + exactBoxed(Float.class, FLOAT), + exactBoxed(Integer.class, INTEGER), + exactBoxed(Long.class, LONG), + exactBoxed(Short.class, SHORT), + exactRaw(Number.class, NUMBER), + exactRaw(String.class, STRING), + exactRaw(java.util.UUID.class, UUID) + ); private ScalarKeyAdapters() { } @@ -49,15 +48,7 @@ public static KeyAdapter.Factory factory() { return FACTORY; } - private static KeyAdapter keyAdapter(Class type, Function function) { - @SuppressWarnings("unchecked") - KeyAdapter keyAdapter = (KeyAdapter) ADAPTERS.compute(type, ($, adapter) -> { - if (adapter != null) { - throw new IllegalStateException("key adapter for " + type + " is already registered"); - } - - return KeyAdapter.of(value -> value != null ? function.apply(value) : null, value -> nonNull(value, "value").toString()); - }); - return keyAdapter; + private static KeyAdapter keyAdapter(Function function) { + return KeyAdapter.of(value -> value != null ? function.apply(value) : null, value -> nonNull(value, "value").toString()); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortAdapter.java new file mode 100644 index 0000000..7e5f2ef --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortAdapter.java @@ -0,0 +1,32 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; + +final class ShortAdapter extends AbstractNumberAdapter { + ShortAdapter() { + super(Short.class); + } + + @Override + protected Short fromLong(long value) { + short shortValue = (short) value; + + if (shortValue != value) { + throw new ArithmeticException(numberType.getSimpleName() + " overflow: " + value); + } + + return shortValue; + } + + @Override + protected Short parse(String value) throws NumberFormatException { + return Short.parseShort(value); + } + + @Override + protected void write(Writer writer, Short value) throws IOException { + writer.writeInt(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringAdapter.java new file mode 100644 index 0000000..7d26efb --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringAdapter.java @@ -0,0 +1,40 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class StringAdapter implements TypeAdapter { + @Override + public @Nullable String read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { + checkAssignable(String.class, type.getRawType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + return reader.readString(); + } + + @Override + public void write(Writer writer, TypeRef type, @Nullable String value, WriteContext ctx) throws IOException { + checkAssignable(String.class, type.getRawType()); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeString(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDAdapter.java new file mode 100644 index 0000000..6ca0d09 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDAdapter.java @@ -0,0 +1,41 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.util.UUID; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class UUIDAdapter implements TypeAdapter { + @Override + public @Nullable UUID read(Reader reader, TypeRef type, ReadContext context) throws IOException { + checkAssignable(UUID.class, type.getRawType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + return UUID.fromString(reader.readString()); + } + + @Override + public void write(Writer writer, TypeRef type, @Nullable UUID value, WriteContext context) throws IOException { + checkAssignable(UUID.class, type.getRawType()); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeString(value.toString()); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/util/ObjectUtils.java b/core/src/main/java/net/roxymc/jserialize/util/ObjectUtils.java index bbc8aaa..d67e46a 100644 --- a/core/src/main/java/net/roxymc/jserialize/util/ObjectUtils.java +++ b/core/src/main/java/net/roxymc/jserialize/util/ObjectUtils.java @@ -14,6 +14,17 @@ public static T nonNull(@Nullable T obj, String name) { return Objects.requireNonNull(obj, name + " == null"); } + @SuppressWarnings("NullableProblems") + public static T[] nonNull(@Nullable T @Nullable [] array, String name) { + nonNull((Object) array, name); + + for (int i = 0; i < array.length; i++) { + nonNull(array[i], name + "[" + i + "]"); + } + + return array; + } + @Contract("_, !null -> !null") public static @Nullable T nonNullOrElse(@Nullable T obj, @Nullable T defaultValue) { return obj != null ? obj : defaultValue; diff --git a/core/src/main/java/net/roxymc/jserialize/util/TypeChecks.java b/core/src/main/java/net/roxymc/jserialize/util/TypeChecks.java new file mode 100644 index 0000000..9968f69 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/util/TypeChecks.java @@ -0,0 +1,16 @@ +package net.roxymc.jserialize.util; + +import io.leangen.geantyref.GenericTypeReflector; + +import java.lang.reflect.Type; + +public final class TypeChecks { + private TypeChecks() { + } + + public static void checkAssignable(Type type, Type subtype) { + if (!GenericTypeReflector.isSuperType(type, subtype)) { + throw new IllegalStateException(subtype.getTypeName() + " is not a " + type.getTypeName()); + } + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java b/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java index 92d8390..f05532e 100644 --- a/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java +++ b/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java @@ -28,4 +28,8 @@ public static AnnotatedType box(AnnotatedType type) { return GenericTypeReflector.annotate(boxedType, type.getAnnotations()); } + + public static boolean isEnum(Class type) { + return type.isEnum() || (type.getSuperclass() != null && type.getSuperclass().isEnum()); + } } diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java new file mode 100644 index 0000000..014b4bd --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java @@ -0,0 +1,14 @@ +package net.roxymc.jserialize.format.bson.adapter; + +import net.roxymc.jserialize.adapter.TypeAdapters; +import net.roxymc.jserialize.format.bson.adapter.scalar.BsonUUIDAdapter; + +public final class BsonTypeAdapters { + public static final TypeAdapters DEFAULT = TypeAdapters.builder() + .add(BsonUUIDAdapter.factory()) + .addAll(TypeAdapters.DEFAULT) + .build(); + + private BsonTypeAdapters() { + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/package-info.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/package-info.java new file mode 100644 index 0000000..dab3f1f --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.roxymc.jserialize.format.bson.adapter; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonUUIDAdapter.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonUUIDAdapter.java new file mode 100644 index 0000000..30fb7ff --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonUUIDAdapter.java @@ -0,0 +1,61 @@ +package net.roxymc.jserialize.format.bson.adapter.scalar; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.format.bson.token.BsonTokenTypes; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.bson.BsonBinary; +import org.bson.UuidRepresentation; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.util.UUID; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +public final class BsonUUIDAdapter implements TypeAdapter { + private static final TypeAdapter.Factory FACTORY = factory(UuidRepresentation.STANDARD); + + private final UuidRepresentation uuidRepresentation; + + public BsonUUIDAdapter(UuidRepresentation uuidRepresentation) { + this.uuidRepresentation = nonNull(uuidRepresentation, "uuidRepresentation"); + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + public static TypeAdapter.Factory factory(UuidRepresentation uuidRepresentation) { + return TypeAdapter.Factory.exactRaw(UUID.class, new BsonUUIDAdapter(uuidRepresentation)); + } + + @Override + public @Nullable UUID read(Reader reader, TypeRef type, ReadContext context) throws IOException { + checkAssignable(UUID.class, type.getRawType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + return reader.read(BsonTokenTypes.BINARY).asUuid(uuidRepresentation); + } + + @Override + public void write(Writer writer, TypeRef type, @Nullable UUID value, WriteContext context) throws IOException { + checkAssignable(UUID.class, type.getRawType()); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.write(BsonTokenTypes.BINARY, new BsonBinary(value, uuidRepresentation)); + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/package-info.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/package-info.java new file mode 100644 index 0000000..92cdd40 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.roxymc.jserialize.format.bson.adapter.scalar; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file From 373b1355fc8ead9586781c08adbd4bcdf402fe2c Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:11:33 +0200 Subject: [PATCH 02/13] Add temporal adapters --- .../jserialize/adapter/TypeAdapters.java | 2 + .../temporal/AbstractTemporalAdapter.java | 54 +++++++++++++++++++ .../adapter/temporal/DateAdapter.java | 33 ++++++++++++ .../adapter/temporal/InstantAdapter.java | 28 ++++++++++ .../adapter/temporal/LocalDateAdapter.java | 28 ++++++++++ .../temporal/LocalDateTimeAdapter.java | 28 ++++++++++ .../adapter/temporal/LocalTimeAdapter.java | 28 ++++++++++ .../temporal/OffsetDateTimeAdapter.java | 28 ++++++++++ .../adapter/temporal/TemporalAdapters.java | 28 ++++++++++ .../temporal/ZonedDateTimeAdapter.java | 28 ++++++++++ .../adapter/temporal/package-info.java | 4 ++ .../format/bson/adapter/BsonTypeAdapters.java | 6 ++- .../adapter/scalar/BsonScalarAdapters.java | 16 ++++++ .../adapter/temporal/BsonInstantAdapter.java | 42 +++++++++++++++ .../temporal/BsonTemporalAdapters.java | 22 ++++++++ .../bson/adapter/temporal/package-info.java | 4 ++ 16 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/package-info.java create mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonScalarAdapters.java create mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonInstantAdapter.java create mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonTemporalAdapters.java create mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/package-info.java diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java index 007a4c9..eaa2581 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java @@ -9,6 +9,7 @@ import net.roxymc.jserialize.adapter.scalar.EnumKeyAdapter; import net.roxymc.jserialize.adapter.scalar.ScalarAdapters; import net.roxymc.jserialize.adapter.scalar.ScalarKeyAdapters; +import net.roxymc.jserialize.adapter.temporal.TemporalAdapters; import net.roxymc.jserialize.type.TypeRef; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.Nullable; @@ -19,6 +20,7 @@ public interface TypeAdapters extends TypeAdapter.Factory, KeyAdapter.Factory { .add(ScalarAdapters.factory()) .add(EnumAdapter.factory()) .add(AtomicAdapters.factory()) + .add(TemporalAdapters.factory()) .add(ArrayAdapter.factory()) .add(CollectionAdapter.factory()) .add(MapAdapter.factory()) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java new file mode 100644 index 0000000..d0ae437 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java @@ -0,0 +1,54 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.time.format.DateTimeFormatter; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +abstract class AbstractTemporalAdapter implements TypeAdapter { + private final Class type; + protected final DateTimeFormatter formatter; + + protected AbstractTemporalAdapter(Class type, DateTimeFormatter formatter) { + this.type = nonNull(type, "type"); + this.formatter = nonNull(formatter, "formatter"); + } + + @Override + public final @Nullable T read(Reader reader, TypeRef typeRef, ReadContext ctx) throws IOException { + checkAssignable(type, typeRef.getRawType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + return formatter.parse(reader.readString(), this::from); + } + + protected abstract T from(TemporalAccessor temporal); + + @Override + public final void write(Writer writer, TypeRef typeRef, @Nullable T value, WriteContext ctx) throws IOException { + checkAssignable(type, typeRef.getRawType()); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeString(formatter.format(value)); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateAdapter.java new file mode 100644 index 0000000..a0b8d63 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateAdapter.java @@ -0,0 +1,33 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.time.Instant; +import java.util.Date; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class DateAdapter implements TypeAdapter { + @Override + public @Nullable Date read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { + checkAssignable(Date.class, type.getRawType()); + + Instant instant = ctx.read(reader, Instant.class); + return instant != null ? Date.from(instant) : null; + } + + @Override + public void write(Writer writer, TypeRef type, @Nullable Date value, WriteContext ctx) throws IOException { + checkAssignable(Date.class, type.getRawType()); + + Instant instant = value != null ? value.toInstant() : null; + ctx.write(writer, Instant.class, instant); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java new file mode 100644 index 0000000..97308be --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java @@ -0,0 +1,28 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class InstantAdapter extends AbstractTemporalAdapter { + private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_INSTANT); + + public InstantAdapter(DateTimeFormatter formatter) { + super(Instant.class, formatter); + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { + return TypeAdapter.Factory.exactRaw(Instant.class, new InstantAdapter(formatter)); + } + + @Override + protected Instant from(TemporalAccessor temporal) { + return Instant.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java new file mode 100644 index 0000000..ccc7556 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java @@ -0,0 +1,28 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class LocalDateAdapter extends AbstractTemporalAdapter { + private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_DATE); + + public LocalDateAdapter(DateTimeFormatter formatter) { + super(LocalDate.class, formatter); + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { + return TypeAdapter.Factory.exactRaw(LocalDate.class, new LocalDateAdapter(formatter)); + } + + @Override + protected LocalDate from(TemporalAccessor accessor) { + return LocalDate.from(accessor); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java new file mode 100644 index 0000000..c41e5d0 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java @@ -0,0 +1,28 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class LocalDateTimeAdapter extends AbstractTemporalAdapter { + private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + + public LocalDateTimeAdapter(DateTimeFormatter formatter) { + super(LocalDateTime.class, formatter); + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { + return TypeAdapter.Factory.exactRaw(LocalDateTime.class, new LocalDateTimeAdapter(formatter)); + } + + @Override + protected LocalDateTime from(TemporalAccessor temporal) { + return LocalDateTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java new file mode 100644 index 0000000..4ad277b --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java @@ -0,0 +1,28 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class LocalTimeAdapter extends AbstractTemporalAdapter { + private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_TIME); + + public LocalTimeAdapter(DateTimeFormatter formatter) { + super(LocalTime.class, formatter); + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { + return TypeAdapter.Factory.exactRaw(LocalTime.class, new LocalTimeAdapter(formatter)); + } + + @Override + protected LocalTime from(TemporalAccessor temporal) { + return LocalTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java new file mode 100644 index 0000000..c977845 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java @@ -0,0 +1,28 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class OffsetDateTimeAdapter extends AbstractTemporalAdapter { + private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + + public OffsetDateTimeAdapter(DateTimeFormatter formatter) { + super(OffsetDateTime.class, formatter); + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { + return TypeAdapter.Factory.exactRaw(OffsetDateTime.class, new OffsetDateTimeAdapter(formatter)); + } + + @Override + protected OffsetDateTime from(TemporalAccessor temporal) { + return OffsetDateTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java new file mode 100644 index 0000000..13f111f --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java @@ -0,0 +1,28 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.util.Date; + +import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactRaw; + +public final class TemporalAdapters { + public static final TypeAdapter DATE = new DateAdapter(); + + private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( + exactRaw(Date.class, DATE), + InstantAdapter.factory(), + LocalDateAdapter.factory(), + LocalDateTimeAdapter.factory(), + LocalTimeAdapter.factory(), + OffsetDateTimeAdapter.factory(), + ZonedDateTimeAdapter.factory() + ); + + private TemporalAdapters() { + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java new file mode 100644 index 0000000..7b68796 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java @@ -0,0 +1,28 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class ZonedDateTimeAdapter extends AbstractTemporalAdapter { + private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_ZONED_DATE_TIME); + + public ZonedDateTimeAdapter(DateTimeFormatter formatter) { + super(ZonedDateTime.class, formatter); + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { + return TypeAdapter.Factory.exactRaw(ZonedDateTime.class, new ZonedDateTimeAdapter(formatter)); + } + + @Override + protected ZonedDateTime from(TemporalAccessor temporal) { + return ZonedDateTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/package-info.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/package-info.java new file mode 100644 index 0000000..4be940f --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.roxymc.jserialize.adapter.temporal; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java index 014b4bd..9c48037 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java @@ -1,11 +1,13 @@ package net.roxymc.jserialize.format.bson.adapter; import net.roxymc.jserialize.adapter.TypeAdapters; -import net.roxymc.jserialize.format.bson.adapter.scalar.BsonUUIDAdapter; +import net.roxymc.jserialize.format.bson.adapter.scalar.BsonScalarAdapters; +import net.roxymc.jserialize.format.bson.adapter.temporal.BsonTemporalAdapters; public final class BsonTypeAdapters { public static final TypeAdapters DEFAULT = TypeAdapters.builder() - .add(BsonUUIDAdapter.factory()) + .add(BsonScalarAdapters.factory()) + .add(BsonTemporalAdapters.factory()) .addAll(TypeAdapters.DEFAULT) .build(); diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonScalarAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonScalarAdapters.java new file mode 100644 index 0000000..514ea2a --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonScalarAdapters.java @@ -0,0 +1,16 @@ +package net.roxymc.jserialize.format.bson.adapter.scalar; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +public final class BsonScalarAdapters { + private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( + BsonUUIDAdapter.factory() + ); + + private BsonScalarAdapters() { + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonInstantAdapter.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonInstantAdapter.java new file mode 100644 index 0000000..91ed566 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonInstantAdapter.java @@ -0,0 +1,42 @@ +package net.roxymc.jserialize.format.bson.adapter.temporal; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.format.bson.token.BsonTokenTypes; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.time.Instant; + +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class BsonInstantAdapter implements TypeAdapter { + @Override + public @Nullable Instant read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { + checkAssignable(Instant.class, type.getRawType()); + + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + return Instant.ofEpochMilli(reader.read(BsonTokenTypes.DATE_TIME)); + } + + @Override + public void write(Writer writer, TypeRef type, @Nullable Instant value, WriteContext ctx) throws IOException { + checkAssignable(Instant.class, type.getRawType()); + + if (value == null) { + writer.writeNull(); + return; + } + + writer.write(BsonTokenTypes.DATE_TIME, value.toEpochMilli()); + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonTemporalAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonTemporalAdapters.java new file mode 100644 index 0000000..18174b4 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonTemporalAdapters.java @@ -0,0 +1,22 @@ +package net.roxymc.jserialize.format.bson.adapter.temporal; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.time.Instant; + +import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactRaw; + +public final class BsonTemporalAdapters { + public static final TypeAdapter INSTANT = new BsonInstantAdapter(); + + private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( + exactRaw(Instant.class, INSTANT) + ); + + private BsonTemporalAdapters() { + } + + public static TypeAdapter.Factory factory() { + return FACTORY; + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/package-info.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/package-info.java new file mode 100644 index 0000000..c4ad356 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package net.roxymc.jserialize.format.bson.adapter.temporal; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file From 0a963fda319c26afe09c61ae95620b34740d3e65 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:33:28 +0200 Subject: [PATCH 03/13] Fix compile issues --- .../jserialize/adapter/TypeAdapters.java | 6 +++ .../format/bson/BsonTypeAdapters.java | 39 ++++++++++++++----- .../configurate/ConfigurateTypeAdapters.java | 19 +++++++-- .../format/gson/GsonTypeAdapters.java | 20 +++++++++- 4 files changed, 68 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java index eaa2581..353bf45 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java @@ -71,6 +71,12 @@ default KeyAdapter getKeyOrThrow(TypeRef type) { throw new IllegalStateException("Could not find key adapter for " + type.getAnnotatedType()); } + @Override + @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters); + + @Override + @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters); + @ApiStatus.NonExtendable interface Builder { Builder add(TypeAdapter.Factory factory); diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java index 7cc8f88..59c7e4e 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java @@ -5,6 +5,7 @@ import net.roxymc.jserialize.adapter.TypeAdapters; import net.roxymc.jserialize.type.TypeRef; import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecConfigurationException; import org.bson.codecs.configuration.CodecRegistry; import org.jspecify.annotations.Nullable; @@ -21,27 +22,35 @@ final class BsonTypeAdapters implements TypeAdapters { this.adapters = adapters; } - private Codec getCodec(TypeRef typeRef) { + private @Nullable Codec getCodec(TypeRef typeRef) { Type type = typeRef.getType(); Class rawType = typeRef.getRawType(); - if (!(type instanceof ParameterizedType)) { + try { + if (!(type instanceof ParameterizedType)) { + @SuppressWarnings("unchecked") + Codec codec = (Codec) registry.get(rawType); + return codec; + } + + List typeArgs = List.of(((ParameterizedType) type).getActualTypeArguments()); + @SuppressWarnings("unchecked") - Codec codec = (Codec) registry.get(rawType); + Codec codec = (Codec) registry.get(rawType, typeArgs); return codec; + } catch (CodecConfigurationException e) { + return null; } - - List typeArgs = List.of(((ParameterizedType) type).getActualTypeArguments()); - - @SuppressWarnings("unchecked") - Codec codec = (Codec) registry.get(rawType, typeArgs); - return codec; } @Override - public TypeAdapter get(TypeRef type) { + public @Nullable TypeAdapter get(TypeRef type) { Codec codec = getCodec(type); + if (codec == null) { + return null; + } + if (codec instanceof WrappedTypeAdapter) { return ((WrappedTypeAdapter) codec).typeAdapter; } @@ -53,4 +62,14 @@ public TypeAdapter get(TypeRef type) { public @Nullable KeyAdapter getKey(TypeRef type) { return adapters.getKey(type); } + + @Override + public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + return get(type); + } + + @Override + public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { + return adapters.createKey(type, adapters); + } } diff --git a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java index 6fa21b6..16a1883 100644 --- a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java +++ b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java @@ -8,8 +8,6 @@ import org.spongepowered.configurate.ConfigurationOptions; import org.spongepowered.configurate.serialize.TypeSerializer; -import java.util.Objects; - final class ConfigurateTypeAdapters implements TypeAdapters { // TODO we should use TypeSerializers when MapLike is gone (see usages) final ConfigurationOptions options; @@ -21,15 +19,18 @@ final class ConfigurateTypeAdapters implements TypeAdapters { } @Override - public TypeAdapter get(TypeRef type) { + public @Nullable TypeAdapter get(TypeRef type) { @SuppressWarnings("unchecked") TypeSerializer serializer = (TypeSerializer) options.serializers().get(type.getAnnotatedType()); + if (serializer == null) { + return null; + } + if (serializer instanceof TypeAdapterProvider) { return ((TypeAdapterProvider) serializer).adapters.getOrThrow(type); } - Objects.requireNonNull(serializer); return new WrappedTypeSerializer<>(serializer); } @@ -37,4 +38,14 @@ public TypeAdapter get(TypeRef type) { public @Nullable KeyAdapter getKey(TypeRef type) { return adapters.getKey(type); } + + @Override + public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + return get(type); + } + + @Override + public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { + return adapters.createKey(type, adapters); + } } diff --git a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java index bb7d30e..5652e1f 100644 --- a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java +++ b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java @@ -17,10 +17,16 @@ final class GsonTypeAdapters implements TypeAdapters { } @Override - public TypeAdapter get(TypeRef type) { + public @Nullable TypeAdapter get(TypeRef type) { @SuppressWarnings("unchecked") com.google.gson.reflect.TypeToken typeToken = (com.google.gson.reflect.TypeToken) com.google.gson.reflect.TypeToken.get(type.getType()); - com.google.gson.TypeAdapter adapter = gson.getAdapter(typeToken); + com.google.gson.TypeAdapter adapter; + + try { + adapter = gson.getAdapter(typeToken); + } catch (IllegalArgumentException e) { + return null; + } if (adapter instanceof WrappedTypeAdapter) { return ((WrappedTypeAdapter) adapter).typeAdapter; @@ -33,4 +39,14 @@ public TypeAdapter get(TypeRef type) { public @Nullable KeyAdapter getKey(TypeRef type) { return adapters.getKey(type); } + + @Override + public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + return get(type); + } + + @Override + public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { + return adapters.createKey(type, adapters); + } } From 780b9d623e92cf88a3111e159e7118d4c8c04002 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:34:20 +0200 Subject: [PATCH 04/13] Make atomic adapters properly mutate --- .../adapter/atomic/AtomicBooleanAdapter.java | 27 +++++------ .../adapter/atomic/AtomicIntegerAdapter.java | 27 +++++------ .../adapter/atomic/AtomicLongAdapter.java | 27 +++++------ .../atomic/AtomicReferenceAdapter.java | 45 +++++++++++-------- 4 files changed, 62 insertions(+), 64 deletions(-) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java index cf97a0c..63d80a9 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java @@ -5,7 +5,6 @@ import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; @@ -17,34 +16,32 @@ final class AtomicBooleanAdapter implements TypeAdapter.Mutable { @Override public @Nullable AtomicBoolean mutate( - Reader reader, TypeRef type, @Nullable AtomicBoolean value, ReadContext ctx + Reader reader, TypeRef type, @Nullable AtomicBoolean ref, ReadContext ctx ) throws IOException { checkAssignable(AtomicBoolean.class, type.getRawType()); - if (reader.peek() == TokenTypes.NULL) { - reader.readNull(); - return value; - } + Boolean value = ctx.read(reader, boolean.class); if (value == null) { - value = new AtomicBoolean(); + return ref; + } + + if (ref == null) { + ref = new AtomicBoolean(); } - value.set(reader.readBoolean()); - return value; + ref.set(value); + return ref; } @Override public void write( - Writer writer, TypeRef type, @Nullable AtomicBoolean value, WriteContext ctx + Writer writer, TypeRef type, @Nullable AtomicBoolean ref, WriteContext ctx ) throws IOException { checkAssignable(AtomicBoolean.class, type.getRawType()); - if (value == null) { - writer.writeNull(); - return; - } + Boolean value = ref != null ? ref.get() : null; - writer.writeBoolean(value.get()); + ctx.write(writer, boolean.class, value); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java index 7197077..9b8f65f 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java @@ -5,7 +5,6 @@ import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; @@ -17,34 +16,32 @@ final class AtomicIntegerAdapter implements TypeAdapter.Mutable { @Override public @Nullable AtomicInteger mutate( - Reader reader, TypeRef type, @Nullable AtomicInteger value, ReadContext ctx + Reader reader, TypeRef type, @Nullable AtomicInteger ref, ReadContext ctx ) throws IOException { checkAssignable(AtomicInteger.class, type.getRawType()); - if (reader.peek() == TokenTypes.NULL) { - reader.readNull(); - return value; - } + Integer value = ctx.read(reader, int.class); if (value == null) { - value = new AtomicInteger(); + return ref; + } + + if (ref == null) { + ref = new AtomicInteger(); } - value.set(reader.readInt()); - return value; + ref.set(value); + return ref; } @Override public void write( - Writer writer, TypeRef type, @Nullable AtomicInteger value, WriteContext ctx + Writer writer, TypeRef type, @Nullable AtomicInteger ref, WriteContext ctx ) throws IOException { checkAssignable(AtomicInteger.class, type.getRawType()); - if (value == null) { - writer.writeNull(); - return; - } + Integer value = ref != null ? ref.get() : null; - writer.writeInt(value.get()); + ctx.write(writer, int.class, value); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java index 1fa4baf..4012819 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java @@ -5,7 +5,6 @@ import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; @@ -17,34 +16,32 @@ final class AtomicLongAdapter implements TypeAdapter.Mutable { @Override public @Nullable AtomicLong mutate( - Reader reader, TypeRef type, @Nullable AtomicLong value, ReadContext ctx + Reader reader, TypeRef type, @Nullable AtomicLong ref, ReadContext ctx ) throws IOException { checkAssignable(AtomicLong.class, type.getRawType()); - if (reader.peek() == TokenTypes.NULL) { - reader.readNull(); - return value; - } + Long value = ctx.read(reader, long.class); if (value == null) { - value = new AtomicLong(); + return ref; + } + + if (ref == null) { + ref = new AtomicLong(); } - value.set(reader.readLong()); - return value; + ref.set(value); + return ref; } @Override public void write( - Writer writer, TypeRef type, @Nullable AtomicLong value, WriteContext ctx + Writer writer, TypeRef type, @Nullable AtomicLong ref, WriteContext ctx ) throws IOException { checkAssignable(AtomicLong.class, type.getRawType()); - if (value == null) { - writer.writeNull(); - return; - } + Long value = ref != null ? ref.get() : null; - writer.writeLong(value.get()); + ctx.write(writer, long.class, value); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java index 7c70a2b..a85b91b 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java @@ -5,9 +5,9 @@ import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jetbrains.annotations.UnknownNullability; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.io.IOException; @@ -28,23 +28,33 @@ final class AtomicReferenceAdapter implements TypeAdapter.Mutable @Nullable AtomicReference mutate0( - Reader reader, TypeRef> type, @Nullable AtomicReference value, ReadContext ctx + Reader reader, TypeRef> type, @Nullable AtomicReference ref, ReadContext ctx ) throws IOException { checkAssignable(AtomicReference.class, type.getRawType()); - if (reader.peek() == TokenTypes.NULL) { - reader.readNull(); - return value; + TypeRef<@UnknownNullability V> valueType = resolveValueType(type); + TypeAdapter<@NonNull V> valueAdapter = ctx.typeAdapters().getOrThrow(valueType); + + V value; + + if (valueAdapter instanceof TypeAdapter.Mutable) { + V oldValue = ref != null ? ref.get() : null; + + value = ((TypeAdapter.Mutable<@NonNull V>) valueAdapter).mutate(reader, valueType, oldValue, ctx); + } else { + value = valueAdapter.read(reader, valueType, ctx); } if (value == null) { - value = new AtomicReference<>(); + return ref; } - TypeRef<@UnknownNullability V> valueType = resolveValueType(type); + if (ref == null) { + ref = new AtomicReference<>(); + } - value.set(ctx.read(reader, valueType)); - return value; + ref.set(value); + return ref; } @Override @@ -55,24 +65,21 @@ public void write( } private void write0( - Writer writer, TypeRef> type, @Nullable AtomicReference value, WriteContext ctx + Writer writer, TypeRef> type, @Nullable AtomicReference ref, WriteContext ctx ) throws IOException { checkAssignable(AtomicReference.class, type.getRawType()); - if (value == null) { - writer.writeNull(); - return; - } - TypeRef<@UnknownNullability V> valueType = resolveValueType(type); - ctx.write(writer, valueType, value.get()); + V value = ref != null ? ref.get() : null; + + ctx.write(writer, valueType, value); } - private TypeRef resolveValueType(TypeRef> referenceType) { - AnnotatedType type = getExactSuperType(capture(referenceType.getAnnotatedType()), AtomicReference.class); + private TypeRef resolveValueType(TypeRef> refType) { + AnnotatedType type = getExactSuperType(capture(refType.getAnnotatedType()), AtomicReference.class); if (!(type instanceof AnnotatedParameterizedType)) { - throw new IllegalStateException(referenceType.getType() + " must be a parameterized AtomicReference"); + throw new IllegalStateException(refType.getType() + " must be a parameterized AtomicReference"); } AnnotatedParameterizedType ptype = (AnnotatedParameterizedType) type; From edc78f320d0435aad33b401b8ccb5f02ca8b8f5a Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Mon, 29 Jun 2026 22:51:00 +0200 Subject: [PATCH 05/13] Handle NULL token better when mutating --- .../jserialize/adapter/array/ArrayAdapter.java | 14 +++++++------- .../adapter/atomic/AtomicReferenceAdapter.java | 12 ++++++------ .../adapter/collection/CollectionAdapter.java | 5 +++++ .../roxymc/jserialize/adapter/map/MapAdapter.java | 5 +++++ .../jserialize/adapter/object/ObjectAdapter.java | 6 +++++- .../adapter/scalar/AbstractNumberAdapter.java | 12 ++++++++++++ .../jserialize/adapter/scalar/BooleanAdapter.java | 8 ++++++++ .../adapter/scalar/CharacterAdapter.java | 8 ++++++++ 8 files changed, 56 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java index 2c9effa..da98fbb 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java @@ -2,9 +2,7 @@ import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.adapter.ReadContext; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.adapter.*; import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; @@ -37,13 +35,14 @@ public static TypeAdapter.Factory factory() { } TypeRef componentType = resolveComponentType(type); + TypeReader componentReader = ctx.typeAdapters().getOrThrow(componentType); reader.readArrayStart(); List<@Nullable Object> buffer = new ArrayList<>(); - while (reader.peek() != TokenTypes.ARRAY_END) { - buffer.add(ctx.read(reader, componentType)); + for (int index = 0; reader.peek() != TokenTypes.ARRAY_END; index++) { + buffer.add(componentReader.read(reader, componentType, ctx)); } reader.readArrayEnd(); @@ -58,7 +57,7 @@ public static TypeAdapter.Factory factory() { } @Override - public void write(Writer writer, TypeRef type, @Nullable Object value, WriteContext context) throws IOException { + public void write(Writer writer, TypeRef type, @Nullable Object value, WriteContext ctx) throws IOException { if (!type.getRawType().isArray()) { throw new IllegalStateException(type.getRawType() + " is not an array"); } @@ -69,13 +68,14 @@ public void write(Writer writer, TypeRef type, @Nullable Objec } TypeRef componentType = resolveComponentType(type); + TypeWriter componentWriter = ctx.typeAdapters().getOrThrow(componentType); writer.writeArrayStart(); int length = Array.getLength(value); for (int i = 0; i < length; i++) { - context.write(writer, componentType, Array.get(value, i)); + componentWriter.write(writer, componentType, Array.get(value, i), ctx); } writer.writeArrayEnd(); diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java index a85b91b..1a1080c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java @@ -5,6 +5,7 @@ import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jetbrains.annotations.UnknownNullability; import org.jspecify.annotations.NonNull; @@ -37,7 +38,7 @@ final class AtomicReferenceAdapter implements TypeAdapter.Mutable) valueAdapter).mutate(reader, valueType, oldValue, ctx); @@ -45,15 +46,14 @@ final class AtomicReferenceAdapter implements TypeAdapter.Mutable(); } - if (ref == null) { - ref = new AtomicReference<>(); + if (ref != null) { + ref.set(value); } - ref.set(value); return ref; } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java index 2ff7a5e..4e21c45 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java @@ -55,6 +55,11 @@ public static TypeAdapter.Factory factory(CollectionProvider... providers) { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); + + if (collection != null) { + collection.clear(); + } + return collection; } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java index f8be972..1693167 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java @@ -54,6 +54,11 @@ private MapType resolveMapType(TypeRef> type) { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); + + if (map != null) { + map.clear(); + } + return map; } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java index d6b65f3..8b3ce4a 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java @@ -47,8 +47,12 @@ public static TypeAdapter.Factory annotatedFactory(ClassModel.Factory factory) { @Override public @Nullable T mutate(Reader reader, TypeRef type, @Nullable T value, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { + if (value != null) { + throw new IllegalStateException("Cannot mutate value with null"); + } + reader.readNull(); - return value; + return null; } try { diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java index bc75680..161a328 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java @@ -19,6 +19,10 @@ abstract class AbstractNumberAdapter implements TypeAdapter protected final Class numberType; protected AbstractNumberAdapter(Class numberType) { + if (numberType.isPrimitive()) { + throw new IllegalArgumentException("numberType cannot be primitive"); + } + this.numberType = numberType; } @@ -29,6 +33,10 @@ protected AbstractNumberAdapter(Class numberType) { TokenType tokenType = reader.peek(); if (tokenType == TokenTypes.NULL) { + if (type.getRawType().isPrimitive()) { + throw new IllegalStateException("Cannot read null into primitive " + type.getRawType()); + } + reader.readNull(); return null; } @@ -79,6 +87,10 @@ public final void write(Writer writer, TypeRef type, @Nullable N va checkAssignable(numberType, GenericTypeReflector.box(type.getRawType())); if (value == null) { + if (type.getRawType().isPrimitive()) { + throw new IllegalStateException("Cannot write null for primitive " + type.getRawType()); + } + writer.writeNull(); return; } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java index 131ba6a..2668280 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java @@ -20,6 +20,10 @@ final class BooleanAdapter implements TypeAdapter { checkAssignable(Boolean.class, GenericTypeReflector.box(type.getRawType())); if (reader.peek() == TokenTypes.NULL) { + if (type.getRawType().isPrimitive()) { + throw new IllegalStateException("Cannot read null into primitive " + type.getRawType()); + } + reader.readNull(); return null; } @@ -32,6 +36,10 @@ public void write(Writer writer, TypeRef type, @Nullable Bool checkAssignable(Boolean.class, GenericTypeReflector.box(type.getRawType())); if (value == null) { + if (type.getRawType().isPrimitive()) { + throw new IllegalStateException("Cannot write null for primitive " + type.getRawType()); + } + writer.writeNull(); return; } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java index 92d584f..fdc5383 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java @@ -20,6 +20,10 @@ final class CharacterAdapter implements TypeAdapter { checkAssignable(Character.class, GenericTypeReflector.box(type.getRawType())); if (reader.peek() == TokenTypes.NULL) { + if (type.getRawType().isPrimitive()) { + throw new IllegalStateException("Cannot read null into primitive " + type.getRawType()); + } + reader.readNull(); return null; } @@ -37,6 +41,10 @@ public void write(Writer writer, TypeRef type, @Nullable Ch checkAssignable(Character.class, GenericTypeReflector.box(type.getRawType())); if (value == null) { + if (type.getRawType().isPrimitive()) { + throw new IllegalStateException("Cannot write null for primitive " + type.getRawType()); + } + writer.writeNull(); return; } From 76bf5bd7daa706b485dd252c5665cf89907e7433 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Mon, 29 Jun 2026 23:04:53 +0200 Subject: [PATCH 06/13] Remove unnecessary utils from TypeUtils and property type boxing from ObjectAdapter --- .../roxymc/jserialize/adapter/KeyAdapter.java | 3 +-- .../jserialize/adapter/TypeAdapter.java | 3 +-- .../adapter/object/ObjectAdapter.java | 6 ++--- .../adapter/object/ObjectReader.java | 5 ++-- .../adapter/object/ObjectWriter.java | 3 +-- .../model/ClassModelFactoryImpl.java | 3 +-- .../net/roxymc/jserialize/util/TypeUtils.java | 25 ------------------- 7 files changed, 8 insertions(+), 40 deletions(-) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java index 8ab3b01..1227de8 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java @@ -2,7 +2,6 @@ import io.leangen.geantyref.GenericTypeReflector; import net.roxymc.jserialize.type.TypeRef; -import net.roxymc.jserialize.util.TypeUtils; import org.jspecify.annotations.Nullable; import java.util.function.Predicate; @@ -50,7 +49,7 @@ static Factory exactRaw(Class type, KeyAdapter adapter) { } static Factory exactBoxed(Class type, KeyAdapter adapter) { - if (TypeUtils.isPrimitive(type)) { + if (type.isPrimitive()) { throw new IllegalArgumentException("type cannot be primitive"); } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java index ee65e11..3e58d54 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java @@ -4,7 +4,6 @@ import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.Writer; import net.roxymc.jserialize.type.TypeRef; -import net.roxymc.jserialize.util.TypeUtils; import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; @@ -67,7 +66,7 @@ static Factory exactRaw(Class type, TypeAdapter adapter) { } static Factory exactBoxed(Class type, TypeAdapter adapter) { - if (TypeUtils.isPrimitive(type)) { + if (type.isPrimitive()) { throw new IllegalArgumentException("type cannot be primitive"); } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java index 8b3ce4a..4090e11 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java @@ -9,11 +9,9 @@ import net.roxymc.jserialize.model.ClassModel; import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; -import net.roxymc.jserialize.util.TypeUtils; import org.jspecify.annotations.Nullable; import java.io.IOException; -import java.util.function.Predicate; public final class ObjectAdapter implements TypeAdapter.Mutable { private static final TypeAdapter.Factory FACTORY = factory(ClassModel.factory()); @@ -30,7 +28,7 @@ public static TypeAdapter.Factory factory() { } public static TypeAdapter.Factory factory(ClassModel.Factory factory) { - return TypeAdapter.Factory.predicate(Predicate.not(TypeUtils::isPrimitive), new ObjectAdapter<>(factory)); + return TypeAdapter.Factory.predicate(type -> !type.getRawType().isPrimitive(), new ObjectAdapter<>(factory)); } public static TypeAdapter.Factory annotatedFactory() { @@ -39,7 +37,7 @@ public static TypeAdapter.Factory annotatedFactory() { public static TypeAdapter.Factory annotatedFactory(ClassModel.Factory factory) { return TypeAdapter.Factory.predicate( - type -> !TypeUtils.isPrimitive(type) && type.getAnnotatedType().isAnnotationPresent(JSerializable.class), + type -> !type.getRawType().isPrimitive() && type.getAnnotatedType().isAnnotationPresent(JSerializable.class), new ObjectAdapter<>(factory) ); } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java index b7e21f1..460695c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java @@ -13,7 +13,6 @@ import net.roxymc.jserialize.model.property.meta.PropertyMeta; import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; -import net.roxymc.jserialize.util.TypeUtils; import org.jspecify.annotations.Nullable; import java.io.IOException; @@ -171,7 +170,7 @@ T read(Reader reader) throws Throwable { PropertyMeta meta = property.meta(); if (instance == null && property.parameterType() != null) { - return TypeUtils.box(resolveType(property.parameterType(), capture(type.getAnnotatedType()))); + return resolveType(property.parameterType(), capture(type.getAnnotatedType())); } MethodRef method = null; @@ -187,6 +186,6 @@ T read(Reader reader) throws Throwable { } AnnotatedType supertype = getExactSuperType(capture(type.getAnnotatedType()), method.declaringClass()); - return TypeUtils.box(resolveType(method.valueType(), supertype)); + return resolveType(method.valueType(), supertype); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java index d62b284..dcc7495 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java @@ -9,7 +9,6 @@ import net.roxymc.jserialize.model.property.meta.PropertyKind; import net.roxymc.jserialize.model.property.meta.PropertyMeta; import net.roxymc.jserialize.type.TypeRef; -import net.roxymc.jserialize.util.TypeUtils; import java.io.IOException; import java.lang.reflect.AnnotatedType; @@ -68,7 +67,7 @@ private void writeProperty(Writer writer, PropertyModel property) throws Throwab } AnnotatedType declaringType = getExactSuperType(capture(type.getAnnotatedType()), getter.declaringClass()); - AnnotatedType type = TypeUtils.box(resolveType(getter.valueType(), declaringType)); + AnnotatedType type = resolveType(getter.valueType(), declaringType); if (meta != null && meta.kind() == PropertyKind.EXTRAS) { // if it's an extras property, it never writes null diff --git a/core/src/main/java/net/roxymc/jserialize/model/ClassModelFactoryImpl.java b/core/src/main/java/net/roxymc/jserialize/model/ClassModelFactoryImpl.java index 541278c..0be725f 100644 --- a/core/src/main/java/net/roxymc/jserialize/model/ClassModelFactoryImpl.java +++ b/core/src/main/java/net/roxymc/jserialize/model/ClassModelFactoryImpl.java @@ -8,7 +8,6 @@ import net.roxymc.jserialize.model.resolver.SimpleConstructorResolver; import net.roxymc.jserialize.model.resolver.SimplePropertiesResolver; import net.roxymc.jserialize.type.TypeRef; -import net.roxymc.jserialize.util.TypeUtils; import java.lang.invoke.MethodHandles; import java.util.Map; @@ -44,7 +43,7 @@ public ClassModel create(Class type) { private ClassModel createUnchecked(Class type) { nonNull(type, "type"); - if (TypeUtils.isPrimitive(type)) { + if (type.isPrimitive()) { throw new IllegalArgumentException("type cannot be primitive"); } diff --git a/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java b/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java index f05532e..6f6b98a 100644 --- a/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java +++ b/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java @@ -1,34 +1,9 @@ package net.roxymc.jserialize.util; -import io.leangen.geantyref.GenericTypeReflector; -import net.roxymc.jserialize.type.TypeRef; - -import java.lang.reflect.AnnotatedType; -import java.lang.reflect.Type; - public final class TypeUtils { private TypeUtils() { } - public static boolean isPrimitive(TypeRef type) { - return isPrimitive(type.getRawType()); - } - - public static boolean isPrimitive(Class type) { - return !Object.class.isAssignableFrom(type); - } - - public static AnnotatedType box(AnnotatedType type) { - Type rawType = type.getType(); - Type boxedType = GenericTypeReflector.box(rawType); - - if (rawType == boxedType) { - return type; - } - - return GenericTypeReflector.annotate(boxedType, type.getAnnotations()); - } - public static boolean isEnum(Class type) { return type.isEnum() || (type.getSuperclass() != null && type.getSuperclass().isEnum()); } From 90885c745c7f634183fb0407042297b47a48b208 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:35:05 +0200 Subject: [PATCH 07/13] Throw when setting AtomicBoolean/Integer/Long to null --- .../jserialize/adapter/atomic/AtomicBooleanAdapter.java | 4 ++-- .../jserialize/adapter/atomic/AtomicIntegerAdapter.java | 5 +++-- .../roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java index 63d80a9..6faa958 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java @@ -15,7 +15,7 @@ final class AtomicBooleanAdapter implements TypeAdapter.Mutable { @Override - public @Nullable AtomicBoolean mutate( + public AtomicBoolean mutate( Reader reader, TypeRef type, @Nullable AtomicBoolean ref, ReadContext ctx ) throws IOException { checkAssignable(AtomicBoolean.class, type.getRawType()); @@ -23,7 +23,7 @@ final class AtomicBooleanAdapter implements TypeAdapter.Mutable { Boolean value = ctx.read(reader, boolean.class); if (value == null) { - return ref; + throw new IllegalStateException("Cannot cannot set AtomicBoolean to null"); } if (ref == null) { diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java index 9b8f65f..2e10507 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java @@ -6,6 +6,7 @@ import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.io.IOException; @@ -15,7 +16,7 @@ final class AtomicIntegerAdapter implements TypeAdapter.Mutable { @Override - public @Nullable AtomicInteger mutate( + public AtomicInteger mutate( Reader reader, TypeRef type, @Nullable AtomicInteger ref, ReadContext ctx ) throws IOException { checkAssignable(AtomicInteger.class, type.getRawType()); @@ -23,7 +24,7 @@ final class AtomicIntegerAdapter implements TypeAdapter.Mutable { Integer value = ctx.read(reader, int.class); if (value == null) { - return ref; + throw new IllegalStateException("Cannot cannot set AtomicInteger to null"); } if (ref == null) { diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java index 4012819..bfe2786 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java @@ -15,7 +15,7 @@ final class AtomicLongAdapter implements TypeAdapter.Mutable { @Override - public @Nullable AtomicLong mutate( + public AtomicLong mutate( Reader reader, TypeRef type, @Nullable AtomicLong ref, ReadContext ctx ) throws IOException { checkAssignable(AtomicLong.class, type.getRawType()); @@ -23,7 +23,7 @@ final class AtomicLongAdapter implements TypeAdapter.Mutable { Long value = ctx.read(reader, long.class); if (value == null) { - return ref; + throw new IllegalStateException("Cannot cannot set AtomicLong to null"); } if (ref == null) { From 05f52017a5574fcf1dded393ff4584f4ab236ad7 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:32:01 +0200 Subject: [PATCH 08/13] Add temporal key adapters --- .../jserialize/adapter/TypeAdapters.java | 2 + .../adapter/temporal/DateKeyAdapter.java | 44 +++++++++++++++++++ .../adapter/temporal/InstantAdapter.java | 28 ------------ .../adapter/temporal/LocalDateAdapter.java | 28 ------------ .../temporal/LocalDateTimeAdapter.java | 28 ------------ .../adapter/temporal/LocalTimeAdapter.java | 28 ------------ .../temporal/OffsetDateTimeAdapter.java | 28 ------------ ...poralAdapter.java => TemporalAdapter.java} | 18 ++++---- .../adapter/temporal/TemporalAdapters.java | 22 +++++++--- .../adapter/temporal/TemporalKeyAdapter.java | 30 +++++++++++++ .../adapter/temporal/TemporalKeyAdapters.java | 35 +++++++++++++++ .../temporal/ZonedDateTimeAdapter.java | 28 ------------ 12 files changed, 136 insertions(+), 183 deletions(-) create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateKeyAdapter.java delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java rename core/src/main/java/net/roxymc/jserialize/adapter/temporal/{AbstractTemporalAdapter.java => TemporalAdapter.java} (65%) create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java index 353bf45..b07ea0f 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java @@ -10,6 +10,7 @@ import net.roxymc.jserialize.adapter.scalar.ScalarAdapters; import net.roxymc.jserialize.adapter.scalar.ScalarKeyAdapters; import net.roxymc.jserialize.adapter.temporal.TemporalAdapters; +import net.roxymc.jserialize.adapter.temporal.TemporalKeyAdapters; import net.roxymc.jserialize.type.TypeRef; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.Nullable; @@ -27,6 +28,7 @@ public interface TypeAdapters extends TypeAdapter.Factory, KeyAdapter.Factory { .add(ObjectAdapter.annotatedFactory()) .addKey(ScalarKeyAdapters.factory()) .addKey(EnumKeyAdapter.factory()) + .addKey(TemporalKeyAdapters.factory()) .build(); static Builder builder() { diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateKeyAdapter.java new file mode 100644 index 0000000..073ed44 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateKeyAdapter.java @@ -0,0 +1,44 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.adapter.TypeAdapters; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.time.Instant; +import java.util.Date; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +final class DateKeyAdapter implements KeyAdapter { + static final KeyAdapter.Factory FACTORY = new Factory() { + @Override + public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { + if (!Date.class.isAssignableFrom(type.getRawType())) { + return null; + } + + @SuppressWarnings("unchecked") + KeyAdapter adapter = (KeyAdapter) new DateKeyAdapter(adapters.getKeyOrThrow(Instant.class)); + return adapter; + } + }; + + private final KeyAdapter instantAdapter; + + private DateKeyAdapter(KeyAdapter instantAdapter) { + this.instantAdapter = nonNull(instantAdapter, "instantAdapter"); + } + + @Override + public @Nullable Date decode(@Nullable String value) { + Instant instant = instantAdapter.decode(value); + return instant != null ? Date.from(instant) : null; + } + + @Override + public String encode(@Nullable Date value) { + Instant instant = value != null ? value.toInstant() : null; + return instantAdapter.encode(instant); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java deleted file mode 100644 index 97308be..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.roxymc.jserialize.adapter.temporal; - -import net.roxymc.jserialize.adapter.TypeAdapter; - -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; - -public final class InstantAdapter extends AbstractTemporalAdapter { - private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_INSTANT); - - public InstantAdapter(DateTimeFormatter formatter) { - super(Instant.class, formatter); - } - - public static TypeAdapter.Factory factory() { - return FACTORY; - } - - public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { - return TypeAdapter.Factory.exactRaw(Instant.class, new InstantAdapter(formatter)); - } - - @Override - protected Instant from(TemporalAccessor temporal) { - return Instant.from(temporal); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java deleted file mode 100644 index ccc7556..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.roxymc.jserialize.adapter.temporal; - -import net.roxymc.jserialize.adapter.TypeAdapter; - -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; - -public final class LocalDateAdapter extends AbstractTemporalAdapter { - private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_DATE); - - public LocalDateAdapter(DateTimeFormatter formatter) { - super(LocalDate.class, formatter); - } - - public static TypeAdapter.Factory factory() { - return FACTORY; - } - - public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { - return TypeAdapter.Factory.exactRaw(LocalDate.class, new LocalDateAdapter(formatter)); - } - - @Override - protected LocalDate from(TemporalAccessor accessor) { - return LocalDate.from(accessor); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java deleted file mode 100644 index c41e5d0..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.roxymc.jserialize.adapter.temporal; - -import net.roxymc.jserialize.adapter.TypeAdapter; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; - -public final class LocalDateTimeAdapter extends AbstractTemporalAdapter { - private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_DATE_TIME); - - public LocalDateTimeAdapter(DateTimeFormatter formatter) { - super(LocalDateTime.class, formatter); - } - - public static TypeAdapter.Factory factory() { - return FACTORY; - } - - public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { - return TypeAdapter.Factory.exactRaw(LocalDateTime.class, new LocalDateTimeAdapter(formatter)); - } - - @Override - protected LocalDateTime from(TemporalAccessor temporal) { - return LocalDateTime.from(temporal); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java deleted file mode 100644 index 4ad277b..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.roxymc.jserialize.adapter.temporal; - -import net.roxymc.jserialize.adapter.TypeAdapter; - -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; - -public final class LocalTimeAdapter extends AbstractTemporalAdapter { - private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_TIME); - - public LocalTimeAdapter(DateTimeFormatter formatter) { - super(LocalTime.class, formatter); - } - - public static TypeAdapter.Factory factory() { - return FACTORY; - } - - public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { - return TypeAdapter.Factory.exactRaw(LocalTime.class, new LocalTimeAdapter(formatter)); - } - - @Override - protected LocalTime from(TemporalAccessor temporal) { - return LocalTime.from(temporal); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java deleted file mode 100644 index c977845..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.roxymc.jserialize.adapter.temporal; - -import net.roxymc.jserialize.adapter.TypeAdapter; - -import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; - -public final class OffsetDateTimeAdapter extends AbstractTemporalAdapter { - private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_OFFSET_DATE_TIME); - - public OffsetDateTimeAdapter(DateTimeFormatter formatter) { - super(OffsetDateTime.class, formatter); - } - - public static TypeAdapter.Factory factory() { - return FACTORY; - } - - public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { - return TypeAdapter.Factory.exactRaw(OffsetDateTime.class, new OffsetDateTimeAdapter(formatter)); - } - - @Override - protected OffsetDateTime from(TemporalAccessor temporal) { - return OffsetDateTime.from(temporal); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapter.java similarity index 65% rename from core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java rename to core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapter.java index d0ae437..3d2f20d 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapter.java @@ -12,22 +12,24 @@ import java.io.IOException; import java.time.format.DateTimeFormatter; import java.time.temporal.Temporal; -import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQuery; import static net.roxymc.jserialize.util.ObjectUtils.nonNull; import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; -abstract class AbstractTemporalAdapter implements TypeAdapter { +final class TemporalAdapter implements TypeAdapter { private final Class type; - protected final DateTimeFormatter formatter; + private final DateTimeFormatter formatter; + private final TemporalQuery query; - protected AbstractTemporalAdapter(Class type, DateTimeFormatter formatter) { + TemporalAdapter(Class type, DateTimeFormatter formatter, TemporalQuery query) { this.type = nonNull(type, "type"); this.formatter = nonNull(formatter, "formatter"); + this.query = nonNull(query, "query"); } @Override - public final @Nullable T read(Reader reader, TypeRef typeRef, ReadContext ctx) throws IOException { + public @Nullable T read(Reader reader, TypeRef typeRef, ReadContext ctx) throws IOException { checkAssignable(type, typeRef.getRawType()); if (reader.peek() == TokenTypes.NULL) { @@ -35,13 +37,11 @@ protected AbstractTemporalAdapter(Class type, DateTimeFormatter formatter) { return null; } - return formatter.parse(reader.readString(), this::from); + return formatter.parse(reader.readString(), query); } - protected abstract T from(TemporalAccessor temporal); - @Override - public final void write(Writer writer, TypeRef typeRef, @Nullable T value, WriteContext ctx) throws IOException { + public void write(Writer writer, TypeRef typeRef, @Nullable T value, WriteContext ctx) throws IOException { checkAssignable(type, typeRef.getRawType()); if (value == null) { diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java index 13f111f..1b10616 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java @@ -2,6 +2,10 @@ import net.roxymc.jserialize.adapter.TypeAdapter; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalQuery; import java.util.Date; import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactRaw; @@ -11,12 +15,12 @@ public final class TemporalAdapters { private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( exactRaw(Date.class, DATE), - InstantAdapter.factory(), - LocalDateAdapter.factory(), - LocalDateTimeAdapter.factory(), - LocalTimeAdapter.factory(), - OffsetDateTimeAdapter.factory(), - ZonedDateTimeAdapter.factory() + factory(Instant.class, DateTimeFormatter.ISO_INSTANT, Instant::from), + factory(LocalDate.class, DateTimeFormatter.ISO_LOCAL_DATE, LocalDate::from), + factory(LocalTime.class, DateTimeFormatter.ISO_LOCAL_TIME, LocalTime::from), + factory(LocalDateTime.class, DateTimeFormatter.ISO_LOCAL_DATE_TIME, LocalDateTime::from), + factory(OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME, OffsetDateTime::from), + factory(ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME, ZonedDateTime::from) ); private TemporalAdapters() { @@ -25,4 +29,10 @@ private TemporalAdapters() { public static TypeAdapter.Factory factory() { return FACTORY; } + + public static TypeAdapter.Factory factory( + Class type, DateTimeFormatter formatter, TemporalQuery query + ) { + return exactRaw(type, new TemporalAdapter<>(type, formatter, query)); + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapter.java new file mode 100644 index 0000000..d35aa58 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapter.java @@ -0,0 +1,30 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.KeyAdapter; +import org.jspecify.annotations.Nullable; + +import java.time.format.DateTimeFormatter; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalQuery; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +final class TemporalKeyAdapter implements KeyAdapter { + private final DateTimeFormatter formatter; + private final TemporalQuery query; + + TemporalKeyAdapter(DateTimeFormatter formatter, TemporalQuery query) { + this.formatter = nonNull(formatter, "formatter"); + this.query = nonNull(query, "query"); + } + + @Override + public @Nullable T decode(@Nullable String value) { + return value != null ? formatter.parse(value, query) : null; + } + + @Override + public String encode(@Nullable T value) { + return formatter.format(nonNull(value, "value")); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java new file mode 100644 index 0000000..01015f9 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java @@ -0,0 +1,35 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.KeyAdapter; + +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalQuery; + +import static net.roxymc.jserialize.adapter.KeyAdapter.Factory.exactRaw; + +public final class TemporalKeyAdapters { + private static final KeyAdapter.Factory FACTORY = KeyAdapter.Factory.composite( + DateKeyAdapter.FACTORY, + factory(Instant.class, DateTimeFormatter.ISO_INSTANT, Instant::from), + factory(LocalDate.class, DateTimeFormatter.ISO_LOCAL_DATE, LocalDate::from), + factory(LocalTime.class, DateTimeFormatter.ISO_LOCAL_TIME, LocalTime::from), + factory(LocalDateTime.class, DateTimeFormatter.ISO_LOCAL_DATE_TIME, LocalDateTime::from), + factory(OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME, OffsetDateTime::from), + factory(ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME, ZonedDateTime::from) + ); + + private TemporalKeyAdapters() { + } + + public static KeyAdapter.Factory factory() { + return FACTORY; + } + + public static KeyAdapter.Factory factory( + Class type, DateTimeFormatter formatter, TemporalQuery query + ) { + return exactRaw(type, new TemporalKeyAdapter<>(formatter, query)); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java deleted file mode 100644 index 7b68796..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.roxymc.jserialize.adapter.temporal; - -import net.roxymc.jserialize.adapter.TypeAdapter; - -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; - -public final class ZonedDateTimeAdapter extends AbstractTemporalAdapter { - private static final TypeAdapter.Factory FACTORY = factory(DateTimeFormatter.ISO_ZONED_DATE_TIME); - - public ZonedDateTimeAdapter(DateTimeFormatter formatter) { - super(ZonedDateTime.class, formatter); - } - - public static TypeAdapter.Factory factory() { - return FACTORY; - } - - public static TypeAdapter.Factory factory(DateTimeFormatter formatter) { - return TypeAdapter.Factory.exactRaw(ZonedDateTime.class, new ZonedDateTimeAdapter(formatter)); - } - - @Override - protected ZonedDateTime from(TemporalAccessor temporal) { - return ZonedDateTime.from(temporal); - } -} From 004448add1a02f1e9639f7ad1090876cebd465f6 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:53:11 +0200 Subject: [PATCH 09/13] Simplify atomic adapters --- .../adapter/atomic/AtomicAdapters.java | 22 +++--- .../adapter/atomic/AtomicBooleanAdapter.java | 47 ------------ .../adapter/atomic/AtomicIntegerAdapter.java | 48 ------------- .../adapter/atomic/AtomicLongAdapter.java | 47 ------------ .../atomic/AtomicPrimitiveAdapter.java | 72 +++++++++++++++++++ 5 files changed, 86 insertions(+), 150 deletions(-) delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicPrimitiveAdapter.java diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java index f0b983f..cf2738c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java @@ -7,19 +7,25 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.polymorphic; +import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactRaw; public final class AtomicAdapters { - public static final TypeAdapter BOOLEAN = new AtomicBooleanAdapter(); - public static final TypeAdapter LONG = new AtomicLongAdapter(); - public static final TypeAdapter INTEGER = new AtomicIntegerAdapter(); + public static final TypeAdapter BOOLEAN = new AtomicPrimitiveAdapter<>( + AtomicBoolean.class, boolean.class, AtomicBoolean::new, AtomicBoolean::get, AtomicBoolean::set + ); + public static final TypeAdapter LONG = new AtomicPrimitiveAdapter<>( + AtomicLong.class, long.class, AtomicLong::new, AtomicLong::get, AtomicLong::set + ); + public static final TypeAdapter INTEGER = new AtomicPrimitiveAdapter<>( + AtomicInteger.class, int.class, AtomicInteger::new, AtomicInteger::get, AtomicInteger::set + ); public static final TypeAdapter> REFERENCE = new AtomicReferenceAdapter(); private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( - polymorphic(AtomicBoolean.class, BOOLEAN), - polymorphic(AtomicLong.class, LONG), - polymorphic(AtomicInteger.class, INTEGER), - polymorphic(AtomicReference.class, REFERENCE) + exactRaw(AtomicBoolean.class, BOOLEAN), + exactRaw(AtomicLong.class, LONG), + exactRaw(AtomicInteger.class, INTEGER), + exactRaw(AtomicReference.class, REFERENCE) ); private AtomicAdapters() { diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java deleted file mode 100644 index 6faa958..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.roxymc.jserialize.adapter.atomic; - -import net.roxymc.jserialize.Reader; -import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.adapter.ReadContext; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.type.TypeRef; -import org.jspecify.annotations.Nullable; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; - -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; - -final class AtomicBooleanAdapter implements TypeAdapter.Mutable { - @Override - public AtomicBoolean mutate( - Reader reader, TypeRef type, @Nullable AtomicBoolean ref, ReadContext ctx - ) throws IOException { - checkAssignable(AtomicBoolean.class, type.getRawType()); - - Boolean value = ctx.read(reader, boolean.class); - - if (value == null) { - throw new IllegalStateException("Cannot cannot set AtomicBoolean to null"); - } - - if (ref == null) { - ref = new AtomicBoolean(); - } - - ref.set(value); - return ref; - } - - @Override - public void write( - Writer writer, TypeRef type, @Nullable AtomicBoolean ref, WriteContext ctx - ) throws IOException { - checkAssignable(AtomicBoolean.class, type.getRawType()); - - Boolean value = ref != null ? ref.get() : null; - - ctx.write(writer, boolean.class, value); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java deleted file mode 100644 index 2e10507..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.roxymc.jserialize.adapter.atomic; - -import net.roxymc.jserialize.Reader; -import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.adapter.ReadContext; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.type.TypeRef; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; - -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; - -final class AtomicIntegerAdapter implements TypeAdapter.Mutable { - @Override - public AtomicInteger mutate( - Reader reader, TypeRef type, @Nullable AtomicInteger ref, ReadContext ctx - ) throws IOException { - checkAssignable(AtomicInteger.class, type.getRawType()); - - Integer value = ctx.read(reader, int.class); - - if (value == null) { - throw new IllegalStateException("Cannot cannot set AtomicInteger to null"); - } - - if (ref == null) { - ref = new AtomicInteger(); - } - - ref.set(value); - return ref; - } - - @Override - public void write( - Writer writer, TypeRef type, @Nullable AtomicInteger ref, WriteContext ctx - ) throws IOException { - checkAssignable(AtomicInteger.class, type.getRawType()); - - Integer value = ref != null ? ref.get() : null; - - ctx.write(writer, int.class, value); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java deleted file mode 100644 index bfe2786..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java +++ /dev/null @@ -1,47 +0,0 @@ -package net.roxymc.jserialize.adapter.atomic; - -import net.roxymc.jserialize.Reader; -import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.adapter.ReadContext; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.type.TypeRef; -import org.jspecify.annotations.Nullable; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicLong; - -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; - -final class AtomicLongAdapter implements TypeAdapter.Mutable { - @Override - public AtomicLong mutate( - Reader reader, TypeRef type, @Nullable AtomicLong ref, ReadContext ctx - ) throws IOException { - checkAssignable(AtomicLong.class, type.getRawType()); - - Long value = ctx.read(reader, long.class); - - if (value == null) { - throw new IllegalStateException("Cannot cannot set AtomicLong to null"); - } - - if (ref == null) { - ref = new AtomicLong(); - } - - ref.set(value); - return ref; - } - - @Override - public void write( - Writer writer, TypeRef type, @Nullable AtomicLong ref, WriteContext ctx - ) throws IOException { - checkAssignable(AtomicLong.class, type.getRawType()); - - Long value = ref != null ? ref.get() : null; - - ctx.write(writer, long.class, value); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicPrimitiveAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicPrimitiveAdapter.java new file mode 100644 index 0000000..98debc9 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicPrimitiveAdapter.java @@ -0,0 +1,72 @@ +package net.roxymc.jserialize.adapter.atomic; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; +import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; + +final class AtomicPrimitiveAdapter implements TypeAdapter.Mutable { + private final Class atomicType; + private final Class valueType; + + private final Supplier supplier; + private final Function getter; + private final BiConsumer setter; + + AtomicPrimitiveAdapter( + Class atomicType, + Class valueType, + Supplier supplier, + Function getter, + BiConsumer setter + ) { + if (!valueType.isPrimitive()) { + throw new IllegalArgumentException("valueType must be primitive"); + } + + this.atomicType = nonNull(atomicType, "atomicType"); + this.valueType = nonNull(valueType, "valueType"); + + this.supplier = nonNull(supplier, "supplier"); + this.getter = nonNull(getter, "getter"); + this.setter = nonNull(setter, "setter"); + } + + @Override + public A mutate(Reader reader, TypeRef type, @Nullable A ref, ReadContext ctx) throws IOException { + checkAssignable(atomicType, type.getRawType()); + + V value = ctx.read(reader, valueType); + + if (value == null) { + throw new IllegalStateException("Cannot cannot set atomic " + valueType + " to null"); + } + + if (ref == null) { + ref = supplier.get(); + } + + setter.accept(ref, value); + return ref; + } + + @Override + public void write(Writer writer, TypeRef type, @Nullable A ref, WriteContext ctx) throws IOException { + checkAssignable(atomicType, type.getRawType()); + + V value = ref != null ? getter.apply(ref) : null; + + ctx.write(writer, valueType, value); + } +} From 94c5d09034205fc6fd4642737241e4e1de40e9fc Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:53:33 +0200 Subject: [PATCH 10/13] Fix TypeAdaptersImpl#create and TypeAdaptersImpl#createKey --- .../java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java index 48ca6ad..f409aa8 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java @@ -38,7 +38,7 @@ private TypeAdaptersImpl(BuilderImpl builder) { @Override public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { for (TypeAdapter.Factory factory : typeAdapters.factories) { - TypeAdapter adapter = factory.create(type, this); + TypeAdapter adapter = factory.create(type, adapters); if (adapter != null) { return adapter; @@ -51,7 +51,7 @@ private TypeAdaptersImpl(BuilderImpl builder) { @Override public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { for (KeyAdapter.Factory factory : keyAdapters.factories) { - KeyAdapter adapter = factory.createKey(type, this); + KeyAdapter adapter = factory.createKey(type, adapters); if (adapter != null) { return adapter; From 27d5891e0ccfb5a57c70ea259a8f8d20056692a1 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Tue, 30 Jun 2026 15:31:51 +0200 Subject: [PATCH 11/13] Remove adapters from TypeAdapter.Factory --- .../adapter/CompositeTypeAdapterFactory.java | 4 ++-- .../adapter/PredicateTypeAdapterFactory.java | 2 +- .../net/roxymc/jserialize/adapter/TypeAdapter.java | 2 +- .../roxymc/jserialize/adapter/TypeAdapters.java | 2 +- .../jserialize/adapter/TypeAdaptersImpl.java | 14 +++----------- .../jserialize/format/bson/BsonTypeAdapters.java | 2 +- .../configurate/ConfigurateTypeAdapters.java | 2 +- .../jserialize/format/gson/GsonTypeAdapters.java | 2 +- 8 files changed, 11 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/CompositeTypeAdapterFactory.java b/core/src/main/java/net/roxymc/jserialize/adapter/CompositeTypeAdapterFactory.java index c1b0eec..96512a9 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/CompositeTypeAdapterFactory.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/CompositeTypeAdapterFactory.java @@ -13,9 +13,9 @@ final class CompositeTypeAdapterFactory implements TypeAdapter.Factory { } @Override - public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + public @Nullable TypeAdapter create(TypeRef type) { for (TypeAdapter.Factory factory : factories) { - TypeAdapter adapter = factory.create(type, adapters); + TypeAdapter adapter = factory.create(type); if (adapter != null) { return adapter; diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/PredicateTypeAdapterFactory.java b/core/src/main/java/net/roxymc/jserialize/adapter/PredicateTypeAdapterFactory.java index 57da131..db9fcc9 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/PredicateTypeAdapterFactory.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/PredicateTypeAdapterFactory.java @@ -17,7 +17,7 @@ final class PredicateTypeAdapterFactory implements TypeAdapter.Factory { } @Override - public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + public @Nullable TypeAdapter create(TypeRef type) { @SuppressWarnings("unchecked") TypeAdapter adapter = (TypeAdapter) this.adapter; return predicate.test(type) ? adapter : null; diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java index 3e58d54..a1bdd12 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java @@ -77,6 +77,6 @@ static Factory composite(Factory... factories) { return new CompositeTypeAdapterFactory(factories); } - @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters); + @Nullable TypeAdapter create(TypeRef type); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java index b07ea0f..bb8623c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java @@ -74,7 +74,7 @@ default KeyAdapter getKeyOrThrow(TypeRef type) { } @Override - @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters); + @Nullable TypeAdapter create(TypeRef type); @Override @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters); diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java index f409aa8..ee4e059 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java @@ -17,7 +17,7 @@ final class TypeAdaptersImpl implements TypeAdapters { @SuppressWarnings({"NullableProblems", "DataFlowIssue"}) // IntelliJ has existential issues private TypeAdaptersImpl(BuilderImpl builder) { - this.typeAdapters = new Registry<>(builder.typeAdapters, (factory, type) -> factory.create(type, this)); + this.typeAdapters = new Registry<>(builder.typeAdapters, TypeAdapter.Factory::create); this.keyAdapters = new Registry<>(builder.keyAdapters, (factory, type) -> factory.createKey(type, this)); } @@ -36,16 +36,8 @@ private TypeAdaptersImpl(BuilderImpl builder) { } @Override - public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { - for (TypeAdapter.Factory factory : typeAdapters.factories) { - TypeAdapter adapter = factory.create(type, adapters); - - if (adapter != null) { - return adapter; - } - } - - return null; + public @Nullable TypeAdapter create(TypeRef type) { + return get(type); } @Override diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java index 59c7e4e..719aa9a 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java @@ -64,7 +64,7 @@ final class BsonTypeAdapters implements TypeAdapters { } @Override - public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + public @Nullable TypeAdapter create(TypeRef type) { return get(type); } diff --git a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java index 16a1883..9be1a0c 100644 --- a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java +++ b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java @@ -40,7 +40,7 @@ final class ConfigurateTypeAdapters implements TypeAdapters { } @Override - public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + public @Nullable TypeAdapter create(TypeRef type) { return get(type); } diff --git a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java index 5652e1f..cc3b66b 100644 --- a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java +++ b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java @@ -41,7 +41,7 @@ final class GsonTypeAdapters implements TypeAdapters { } @Override - public @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters) { + public @Nullable TypeAdapter create(TypeRef type) { return get(type); } From 809eeb11c219f82eabe83cb3935dab3d65021b44 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:22:42 +0200 Subject: [PATCH 12/13] Pre-resolve canonical and raw type in TypeRef --- .../jserialize/adapter/TypeAdaptersImpl.java | 4 +-- .../net/roxymc/jserialize/type/TypeRef.java | 32 +++++++------------ 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java index ee4e059..a701c15 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java @@ -64,9 +64,7 @@ private Registry(Set factories, BiFunction, @Nullable A> creato } private Optional get(TypeRef type) { - AnnotatedType targetType = GenericTypeReflector.toCanonicalBoxed(type.getAnnotatedType()); - - return cache.computeIfAbsent(targetType, $ -> { + return cache.computeIfAbsent(type.getAnnotatedType(), $ -> { for (F factory : factories) { A adapter = creator.apply(factory, type); diff --git a/core/src/main/java/net/roxymc/jserialize/type/TypeRef.java b/core/src/main/java/net/roxymc/jserialize/type/TypeRef.java index 21736a1..c18bbd3 100644 --- a/core/src/main/java/net/roxymc/jserialize/type/TypeRef.java +++ b/core/src/main/java/net/roxymc/jserialize/type/TypeRef.java @@ -1,28 +1,27 @@ package net.roxymc.jserialize.type; import io.leangen.geantyref.GenericTypeReflector; -import net.roxymc.jserialize.util.VarHandles; import org.jetbrains.annotations.UnknownNullability; -import org.jspecify.annotations.Nullable; -import java.lang.invoke.VarHandle; import java.lang.reflect.AnnotatedParameterizedType; import java.lang.reflect.AnnotatedType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; public abstract class TypeRef { - private static final VarHandle RAW_TYPE_HANDLE = VarHandles.find(TypeRef.class, "rawType", Class.class); - private final AnnotatedType annotatedType; - private @Nullable Class rawType; + private final Class rawType; + @SuppressWarnings("unchecked") protected TypeRef() { - this.annotatedType = extractType(); + this.annotatedType = GenericTypeReflector.toCanonical(extractType()); + this.rawType = (Class) GenericTypeReflector.erase(getType()); } + @SuppressWarnings("unchecked") private TypeRef(AnnotatedType type) { - this.annotatedType = type; + this.annotatedType = GenericTypeReflector.toCanonical(type); + this.rawType = (Class) GenericTypeReflector.erase(getType()); } public static TypeRef of(Class type) { @@ -46,29 +45,20 @@ public final AnnotatedType getAnnotatedType() { return annotatedType; } - @SuppressWarnings("unchecked") public final Class getRawType() { - Class value = (Class) RAW_TYPE_HANDLE.getAcquire(this); - - if (value == null) { - Class erased = (Class) GenericTypeReflector.erase(getType()); - Class witness = (Class) RAW_TYPE_HANDLE.compareAndExchangeRelease(this, null, erased); - - value = witness != null ? witness : erased; - } - - return value; + return rawType; } private AnnotatedType extractType() { AnnotatedType type = getClass().getAnnotatedSuperclass(); + if (!(type instanceof AnnotatedParameterizedType)) { throw new IllegalStateException("missing type parameters"); } AnnotatedParameterizedType ptype = (AnnotatedParameterizedType) type; if (((ParameterizedType) ptype.getType()).getRawType() != TypeRef.class) { - throw new IllegalStateException("does not directly extend TypeToken"); + throw new IllegalStateException("does not directly extend TypeRef"); } return ptype.getAnnotatedActualTypeArguments()[0]; @@ -95,6 +85,6 @@ public final boolean equals(Object obj) { @Override public final String toString() { - return "TypeToken[" + getType() + "]"; + return "TypeRef[" + getType() + "]"; } } From baa2e05cbe2f3c6c1d56fcdea46417db9a82abd2 Mon Sep 17 00:00:00 2001 From: GliczDev <67753196+GliczDev@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:18:01 +0200 Subject: [PATCH 13/13] Make TypeAdapters and KeyAdapters instances strictly bound to specific type --- .../adapter/AbstractKeyAdapter.java | 22 +++++ .../roxymc/jserialize/adapter/KeyAdapter.java | 49 ++++------- .../adapter/PredicateKeyAdapterFactory.java | 14 +-- .../adapter/PredicateTypeAdapterFactory.java | 14 +-- .../jserialize/adapter/ReadContext.java | 2 +- .../jserialize/adapter/TypeAdapter.java | 64 +++++--------- .../jserialize/adapter/TypeAdaptersImpl.java | 11 ++- .../roxymc/jserialize/adapter/TypeReader.java | 3 +- .../roxymc/jserialize/adapter/TypeWriter.java | 3 +- .../jserialize/adapter/WriteContext.java | 2 +- .../adapter/array/ArrayAdapter.java | 45 ++++------ .../adapter/atomic/AbstractAtomicAdapter.java | 75 ++++++++++++++++ .../adapter/atomic/AtomicAdapters.java | 26 +----- .../adapter/atomic/AtomicBooleanAdapter.java | 37 ++++++++ .../adapter/atomic/AtomicIntegerAdapter.java | 37 ++++++++ .../adapter/atomic/AtomicLongAdapter.java | 37 ++++++++ .../atomic/AtomicPrimitiveAdapter.java | 72 ---------------- .../atomic/AtomicReferenceAdapter.java | 85 ++++--------------- .../adapter/collection/CollectionAdapter.java | 71 +++++----------- .../adapter/collection/CollectionType.java | 20 ++--- .../jserialize/adapter/map/MapAdapter.java | 74 ++++++---------- .../jserialize/adapter/map/MapType.java | 7 +- .../adapter/object/ObjectAdapter.java | 40 +++++---- .../adapter/object/ObjectReader.java | 7 +- .../adapter/object/ObjectWriter.java | 2 +- .../adapter/scalar/AbstractNumberAdapter.java | 42 +++++---- .../adapter/scalar/BooleanAdapter.java | 48 ++++++++--- .../adapter/scalar/BooleanKeyAdapter.java | 29 +++++++ .../adapter/scalar/ByteAdapter.java | 21 ++++- .../adapter/scalar/ByteKeyAdapter.java | 29 +++++++ .../adapter/scalar/CharacterAdapter.java | 44 +++++++--- .../adapter/scalar/CharacterKeyAdapter.java | 33 +++++++ .../adapter/scalar/DoubleAdapter.java | 21 ++++- .../adapter/scalar/DoubleKeyAdapter.java | 29 +++++++ .../adapter/scalar/EnumAdapter.java | 32 ++++--- .../adapter/scalar/EnumKeyAdapter.java | 37 +++----- .../adapter/scalar/FloatAdapter.java | 23 +++-- .../adapter/scalar/FloatKeyAdapter.java | 29 +++++++ .../adapter/scalar/IntegerAdapter.java | 21 ++++- .../adapter/scalar/IntegerKeyAdapter.java | 29 +++++++ .../adapter/scalar/LongAdapter.java | 18 +++- .../adapter/scalar/LongKeyAdapter.java | 29 +++++++ .../adapter/scalar/NumberAdapter.java | 11 ++- .../adapter/scalar/NumberKeyAdapter.java | 29 +++++++ .../adapter/scalar/ScalarAdapters.java | 37 +++----- .../adapter/scalar/ScalarKeyAdapters.java | 50 +++-------- .../adapter/scalar/ShortAdapter.java | 20 ++++- .../adapter/scalar/ShortKeyAdapter.java | 29 +++++++ .../adapter/scalar/StringAdapter.java | 48 +++++++++-- .../adapter/scalar/StringKeyAdapter.java | 29 +++++++ .../adapter/scalar/UUIDAdapter.java | 27 ++++-- .../adapter/scalar/UUIDKeyAdapter.java | 31 +++++++ ...pter.java => AbstractTemporalAdapter.java} | 24 +++--- ...r.java => AbstractTemporalKeyAdapter.java} | 16 ++-- .../adapter/temporal/DateAdapter.java | 27 ++++-- .../adapter/temporal/DateKeyAdapter.java | 27 +++--- .../adapter/temporal/InstantAdapter.java | 29 +++++++ .../adapter/temporal/InstantKeyAdapter.java | 29 +++++++ .../adapter/temporal/LocalDateAdapter.java | 29 +++++++ .../adapter/temporal/LocalDateKeyAdapter.java | 29 +++++++ .../temporal/LocalDateTimeAdapter.java | 29 +++++++ .../temporal/LocalDateTimeKeyAdapter.java | 29 +++++++ .../adapter/temporal/LocalTimeAdapter.java | 29 +++++++ .../adapter/temporal/LocalTimeKeyAdapter.java | 29 +++++++ .../temporal/OffsetDateTimeAdapter.java | 29 +++++++ .../temporal/OffsetDateTimeKeyAdapter.java | 29 +++++++ .../adapter/temporal/TemporalAdapters.java | 30 ++----- .../adapter/temporal/TemporalKeyAdapters.java | 27 ++---- .../temporal/ZonedDateTimeAdapter.java | 29 +++++++ .../temporal/ZonedDateTimeKeyAdapter.java | 29 +++++++ .../net/roxymc/jserialize/type/TypeRef.java | 4 +- .../net/roxymc/jserialize/util/TypeUtils.java | 22 +++++ .../format/bson/BsonTypeAdapters.java | 2 +- .../jserialize/format/bson/BsonUtils.java | 6 +- .../jserialize/format/bson/WrappedCodec.java | 13 ++- .../format/bson/WrappedTypeAdapter.java | 4 +- .../format/bson/adapter/BsonTypeAdapters.java | 8 +- .../adapter/scalar/BsonScalarAdapters.java | 16 ---- .../bson/adapter/scalar/BsonUUIDAdapter.java | 23 ++--- .../adapter/temporal/BsonInstantAdapter.java | 27 ++++-- .../temporal/BsonTemporalAdapters.java | 22 ----- .../configurate/ConfigurateTypeAdapters.java | 2 +- .../format/configurate/ConfigurateUtils.java | 6 +- .../configurate/TypeAdapterProvider.java | 4 +- .../configurate/WrappedTypeSerializer.java | 13 ++- .../format/gson/GsonTypeAdapters.java | 2 +- .../jserialize/format/gson/GsonUtils.java | 6 +- .../format/gson/TypeAdapterProvider.java | 2 +- .../format/gson/WrappedGsonTypeAdapter.java | 13 ++- .../format/gson/WrappedTypeAdapter.java | 9 +- 90 files changed, 1560 insertions(+), 787 deletions(-) create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/AbstractKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AbstractAtomicAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java delete mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicPrimitiveAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDKeyAdapter.java rename core/src/main/java/net/roxymc/jserialize/adapter/temporal/{TemporalAdapter.java => AbstractTemporalAdapter.java} (58%) rename core/src/main/java/net/roxymc/jserialize/adapter/temporal/{TemporalKeyAdapter.java => AbstractTemporalKeyAdapter.java} (57%) create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeKeyAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java create mode 100644 core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeKeyAdapter.java delete mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonScalarAdapters.java delete mode 100644 format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonTemporalAdapters.java diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/AbstractKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/AbstractKeyAdapter.java new file mode 100644 index 0000000..bd42433 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/AbstractKeyAdapter.java @@ -0,0 +1,22 @@ +package net.roxymc.jserialize.adapter; + +import org.jspecify.annotations.Nullable; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public abstract class AbstractKeyAdapter implements KeyAdapter { + protected AbstractKeyAdapter() { + } + + @Override + public final @Nullable T decode(@Nullable String value) { + return value != null ? parse(value) : null; + } + + protected abstract T parse(String value); + + @Override + public final String encode(@Nullable T value) { + return nonNull(value, "value").toString(); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java index 1227de8..4373d8f 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/KeyAdapter.java @@ -7,53 +7,33 @@ import java.util.function.Predicate; public interface KeyAdapter extends KeyDecoder, KeyEncoder { - static KeyAdapter of(KeyDecoder decoder, KeyEncoder encoder) { - return new KeyAdapter<>() { - @Override - public @Nullable T decode(@Nullable String value) { - return decoder.decode(value); - } - - @Override - public String encode(@Nullable T value) { - return encoder.encode(value); - } - }; - } - @Override @Nullable T decode(@Nullable String value); @Override String encode(@Nullable T value); - interface Factory { - static Factory predicate(Predicate> predicate, KeyAdapter adapter) { - return new PredicateKeyAdapterFactory(predicate, adapter); - } + TypeRef type(); - static Factory polymorphic(Class type, KeyAdapter adapter) { - return predicate(subtype -> type.isAssignableFrom(subtype.getRawType()), adapter); + interface Factory { + static Factory where(Predicate> predicate, TypedFactory factory) { + return new PredicateKeyAdapterFactory(predicate, factory); } - static Factory polymorphic(TypeRef type, KeyAdapter adapter) { - return predicate(subtype -> GenericTypeReflector.isSuperType(type.getType(), subtype.getType()), adapter); + static Factory polymorphic(Class type, TypedFactory factory) { + return where(subtype -> type.isAssignableFrom(subtype.getRawType()), factory); } - static Factory exact(TypeRef type, KeyAdapter adapter) { - return predicate(subtype -> type.getType().equals(subtype.getType()), adapter); + static Factory polymorphic(TypeRef type, TypedFactory factory) { + return where(subtype -> GenericTypeReflector.isSuperType(type.getType(), subtype.getType()), factory); } - static Factory exactRaw(Class type, KeyAdapter adapter) { - return predicate(subtype -> type.equals(subtype.getRawType()), adapter); + static Factory exact(KeyAdapter adapter) { + return Factory.where(subtype -> adapter.type().getType().equals(subtype.getType()), ($1, $2) -> adapter); } - static Factory exactBoxed(Class type, KeyAdapter adapter) { - if (type.isPrimitive()) { - throw new IllegalArgumentException("type cannot be primitive"); - } - - return predicate(subtype -> type.equals(GenericTypeReflector.box(subtype.getRawType())), adapter); + static Factory exactRaw(Class type, TypedFactory factory) { + return where(subtype -> type.equals(subtype.getRawType()), factory); } static Factory composite(Factory... factories) { @@ -62,4 +42,9 @@ static Factory composite(Factory... factories) { @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters); } + + @FunctionalInterface + interface TypedFactory { + @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters); + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/PredicateKeyAdapterFactory.java b/core/src/main/java/net/roxymc/jserialize/adapter/PredicateKeyAdapterFactory.java index 52b92ca..58f1fec 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/PredicateKeyAdapterFactory.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/PredicateKeyAdapterFactory.java @@ -8,18 +8,22 @@ import static net.roxymc.jserialize.util.ObjectUtils.nonNull; final class PredicateKeyAdapterFactory implements KeyAdapter.Factory { - private final KeyAdapter adapter; private final Predicate> predicate; + private final KeyAdapter.TypedFactory factory; - PredicateKeyAdapterFactory(Predicate> predicate, KeyAdapter adapter) { - this.adapter = nonNull(adapter, "adapter"); + PredicateKeyAdapterFactory(Predicate> predicate, KeyAdapter.TypedFactory factory) { this.predicate = nonNull(predicate, "predicate"); + this.factory = nonNull(factory, "factory"); } @Override public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { + if (!predicate.test(type)) { + return null; + } + @SuppressWarnings("unchecked") - KeyAdapter adapter = (KeyAdapter) this.adapter; - return predicate.test(type) ? adapter : null; + KeyAdapter adapter = ((KeyAdapter.TypedFactory) this.factory).createKey(type, adapters); + return adapter; } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/PredicateTypeAdapterFactory.java b/core/src/main/java/net/roxymc/jserialize/adapter/PredicateTypeAdapterFactory.java index db9fcc9..4a21b81 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/PredicateTypeAdapterFactory.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/PredicateTypeAdapterFactory.java @@ -8,18 +8,22 @@ import static net.roxymc.jserialize.util.ObjectUtils.nonNull; final class PredicateTypeAdapterFactory implements TypeAdapter.Factory { - private final TypeAdapter adapter; private final Predicate> predicate; + private final TypeAdapter.TypedFactory factory; - PredicateTypeAdapterFactory(Predicate> predicate, TypeAdapter adapter) { - this.adapter = nonNull(adapter, "adapter"); + PredicateTypeAdapterFactory(Predicate> predicate, TypeAdapter.TypedFactory factory) { this.predicate = nonNull(predicate, "predicate"); + this.factory = nonNull(factory, "factory"); } @Override public @Nullable TypeAdapter create(TypeRef type) { + if (!predicate.test(type)) { + return null; + } + @SuppressWarnings("unchecked") - TypeAdapter adapter = (TypeAdapter) this.adapter; - return predicate.test(type) ? adapter : null; + TypeAdapter adapter = ((TypeAdapter.TypedFactory) this.factory).create(type); + return adapter; } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/ReadContext.java b/core/src/main/java/net/roxymc/jserialize/adapter/ReadContext.java index 60bdf67..f811d09 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/ReadContext.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/ReadContext.java @@ -29,7 +29,7 @@ static ReadContext of(TypeAdapters typeAdapters, FormatUtils formatUtils) { } default @Nullable T read(Reader reader, TypeRef type) throws IOException { - return typeAdapters().getOrThrow(type).read(reader, type, this); + return typeAdapters().getOrThrow(type).read(reader, this); } @ApiStatus.Internal diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java index a1bdd12..e3bdc79 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapter.java @@ -11,66 +11,43 @@ import java.util.function.Predicate; public interface TypeAdapter extends TypeReader, TypeWriter { - static TypeAdapter of(TypeReader reader, TypeWriter writer) { - return new TypeAdapter<>() { - private final TypeReader reader0 = reader; - private final TypeWriter writer0 = writer; - - @Override - public @Nullable T read(Reader reader, TypeRef type, ReadContext context) throws IOException { - return reader0.read(reader, type, context); - } - - @Override - public void write(Writer writer, TypeRef type, @Nullable T value, WriteContext context) throws IOException { - writer0.write(writer, type, value, context); - } - }; - } - @Override - @Nullable T read(Reader reader, TypeRef type, ReadContext context) throws IOException; + @Nullable T read(Reader reader, ReadContext context) throws IOException; @Override - void write(Writer writer, TypeRef type, @Nullable T value, WriteContext context) throws IOException; + void write(Writer writer, @Nullable T value, WriteContext context) throws IOException; + + TypeRef type(); interface Mutable extends TypeAdapter { @Override - default @Nullable T read(Reader reader, TypeRef type, ReadContext context) throws IOException { - return mutate(reader, type, null, context); + default @Nullable T read(Reader reader, ReadContext context) throws IOException { + return mutate(reader, null, context); } - @Contract("_, _, !null, _ -> param3") - @Nullable T mutate(Reader reader, TypeRef type, @Nullable T value, ReadContext context) throws IOException; + @Contract("_, !null, _ -> param2") + @Nullable T mutate(Reader reader, @Nullable T value, ReadContext context) throws IOException; } interface Factory { - static Factory predicate(Predicate> predicate, TypeAdapter adapter) { - return new PredicateTypeAdapterFactory(predicate, adapter); + static Factory where(Predicate> predicate, TypedFactory factory) { + return new PredicateTypeAdapterFactory(predicate, factory); } - static Factory polymorphic(Class type, TypeAdapter adapter) { - return predicate(subtype -> type.isAssignableFrom(subtype.getRawType()), adapter); + static Factory polymorphic(Class type, TypedFactory factory) { + return where(subtype -> type.isAssignableFrom(subtype.getRawType()), factory); } - static Factory polymorphic(TypeRef type, TypeAdapter adapter) { - return predicate(subtype -> GenericTypeReflector.isSuperType(type.getType(), subtype.getType()), adapter); + static Factory polymorphic(TypeRef type, TypedFactory factory) { + return where(subtype -> GenericTypeReflector.isSuperType(type.getType(), subtype.getType()), factory); } - static Factory exact(TypeRef type, TypeAdapter adapter) { - return predicate(subtype -> type.getType().equals(subtype.getType()), adapter); + static Factory exact(TypeAdapter adapter) { + return Factory.where(subtype -> adapter.type().getType().equals(subtype.getType()), $ -> adapter); } - static Factory exactRaw(Class type, TypeAdapter adapter) { - return predicate(subtype -> type.equals(subtype.getRawType()), adapter); - } - - static Factory exactBoxed(Class type, TypeAdapter adapter) { - if (type.isPrimitive()) { - throw new IllegalArgumentException("type cannot be primitive"); - } - - return predicate(subtype -> type.equals(GenericTypeReflector.box(subtype.getRawType())), adapter); + static Factory exactRaw(Class type, TypedFactory factory) { + return where(subtype -> type.equals(subtype.getRawType()), factory); } static Factory composite(Factory... factories) { @@ -79,4 +56,9 @@ static Factory composite(Factory... factories) { @Nullable TypeAdapter create(TypeRef type); } + + @FunctionalInterface + interface TypedFactory { + @Nullable TypeAdapter create(TypeRef type); + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java index a701c15..50e3b9c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdaptersImpl.java @@ -1,6 +1,5 @@ package net.roxymc.jserialize.adapter; -import io.leangen.geantyref.GenericTypeReflector; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; @@ -37,7 +36,15 @@ private TypeAdaptersImpl(BuilderImpl builder) { @Override public @Nullable TypeAdapter create(TypeRef type) { - return get(type); + for (TypeAdapter.Factory factory : typeAdapters.factories) { + TypeAdapter adapter = factory.create(type); + + if (adapter != null) { + return adapter; + } + } + + return null; } @Override diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeReader.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeReader.java index 7d1e1f1..51fc39c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeReader.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeReader.java @@ -1,11 +1,10 @@ package net.roxymc.jserialize.adapter; import net.roxymc.jserialize.Reader; -import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; import java.io.IOException; public interface TypeReader { - @Nullable T read(Reader reader, TypeRef type, ReadContext context) throws IOException; + @Nullable T read(Reader reader, ReadContext context) throws IOException; } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/TypeWriter.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeWriter.java index 3cc4e1c..271cb50 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeWriter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeWriter.java @@ -1,11 +1,10 @@ package net.roxymc.jserialize.adapter; import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; import java.io.IOException; public interface TypeWriter { - void write(Writer writer, TypeRef type, @Nullable T value, WriteContext context) throws IOException; + void write(Writer writer, @Nullable T value, WriteContext context) throws IOException; } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/WriteContext.java b/core/src/main/java/net/roxymc/jserialize/adapter/WriteContext.java index e8d6d91..dd0e1c9 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/WriteContext.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/WriteContext.java @@ -21,7 +21,7 @@ default void write(Writer writer, Class type, @Nullable T value) throws I } default void write(Writer writer, TypeRef type, @Nullable T value) throws IOException { - typeAdapters().getOrThrow(type).write(writer, type, value, this); + typeAdapters().getOrThrow(type).write(writer, value, this); } @ApiStatus.Internal diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java index da98fbb..d8d02be 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java @@ -9,32 +9,32 @@ import java.io.IOException; import java.lang.reflect.AnnotatedArrayType; -import java.lang.reflect.AnnotatedType; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; -import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.predicate; - public final class ArrayAdapter implements TypeAdapter { - private static final TypeAdapter.Factory FACTORY = predicate(type -> type.getRawType().isArray(), new ArrayAdapter()); + private static final Factory FACTORY = Factory.where(type -> type.getRawType().isArray(), ArrayAdapter::new); + + private final TypeRef arrayType; + private final TypeRef componentType; + + private ArrayAdapter(TypeRef arrayType) { + this.arrayType = arrayType; + this.componentType = TypeRef.of(((AnnotatedArrayType) arrayType).getAnnotatedGenericComponentType()); + } - public static TypeAdapter.Factory factory() { + public static Factory factory() { return FACTORY; } @Override - public @Nullable Object read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { - if (!type.getRawType().isArray()) { - throw new IllegalStateException(type.getRawType() + " is not an array"); - } - + public @Nullable Object read(Reader reader, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return null; } - TypeRef componentType = resolveComponentType(type); TypeReader componentReader = ctx.typeAdapters().getOrThrow(componentType); reader.readArrayStart(); @@ -42,7 +42,7 @@ public static TypeAdapter.Factory factory() { List<@Nullable Object> buffer = new ArrayList<>(); for (int index = 0; reader.peek() != TokenTypes.ARRAY_END; index++) { - buffer.add(componentReader.read(reader, componentType, ctx)); + buffer.add(componentReader.read(reader, ctx)); } reader.readArrayEnd(); @@ -57,17 +57,12 @@ public static TypeAdapter.Factory factory() { } @Override - public void write(Writer writer, TypeRef type, @Nullable Object value, WriteContext ctx) throws IOException { - if (!type.getRawType().isArray()) { - throw new IllegalStateException(type.getRawType() + " is not an array"); - } - + public void write(Writer writer, @Nullable Object value, WriteContext ctx) throws IOException { if (value == null) { writer.writeNull(); return; } - TypeRef componentType = resolveComponentType(type); TypeWriter componentWriter = ctx.typeAdapters().getOrThrow(componentType); writer.writeArrayStart(); @@ -75,20 +70,14 @@ public void write(Writer writer, TypeRef type, @Nullable Objec int length = Array.getLength(value); for (int i = 0; i < length; i++) { - componentWriter.write(writer, componentType, Array.get(value, i), ctx); + componentWriter.write(writer, Array.get(value, i), ctx); } writer.writeArrayEnd(); } - private TypeRef resolveComponentType(TypeRef arrayType) { - AnnotatedType type = arrayType.getAnnotatedType(); - if (!(type instanceof AnnotatedArrayType)) { - throw new IllegalStateException(arrayType.getType() + " is not an array type"); - } - - AnnotatedArrayType atype = (AnnotatedArrayType) type; - - return TypeRef.of(atype.getAnnotatedGenericComponentType()); + @Override + public TypeRef type() { + return arrayType; } } \ No newline at end of file diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AbstractAtomicAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AbstractAtomicAdapter.java new file mode 100644 index 0000000..3cdf5d3 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AbstractAtomicAdapter.java @@ -0,0 +1,75 @@ +package net.roxymc.jserialize.adapter.atomic; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.ReadContext; +import net.roxymc.jserialize.adapter.TypeAdapter; +import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import net.roxymc.jserialize.util.TypeUtils; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.lang.reflect.AnnotatedParameterizedType; +import java.util.concurrent.atomic.AtomicReference; + +abstract class AbstractAtomicAdapter implements TypeAdapter.Mutable { + private final TypeRef atomicType; + private final TypeRef valueType; + + protected AbstractAtomicAdapter(Class atomicType, Class valueType) { + this.atomicType = TypeRef.of(atomicType); + this.valueType = TypeRef.of(valueType); + } + + protected AbstractAtomicAdapter(TypeRef atomicType) { + AnnotatedParameterizedType ptype = TypeUtils.resolveTypeParameters(atomicType.getAnnotatedType(), AtomicReference.class); + + this.atomicType = atomicType; + this.valueType = TypeRef.of(ptype.getAnnotatedActualTypeArguments()[0]); + } + + protected abstract A createAtomic(); + + protected abstract @Nullable V getAtomic(A atomic); + + protected abstract void setAtomic(A atomic, @Nullable V value); + + @Override + public @Nullable A mutate(Reader reader, @Nullable A atomic, ReadContext ctx) throws IOException { + TypeAdapter valueAdapter = ctx.typeAdapters().getOrThrow(valueType); + + V value; + + if (valueAdapter instanceof TypeAdapter.Mutable && reader.peek() != TokenTypes.NULL) { + V oldValue = atomic != null ? getAtomic(atomic) : null; + + value = ((TypeAdapter.Mutable) valueAdapter).mutate(reader, oldValue, ctx); + } else { + value = valueAdapter.read(reader, ctx); + } + + if (atomic == null && value != null) { + atomic = createAtomic(); + } + + if (atomic != null) { + setAtomic(atomic, value); + } + + return atomic; + } + + @Override + public void write(Writer writer, @Nullable A atomic, WriteContext ctx) throws IOException { + V value = atomic != null ? getAtomic(atomic) : null; + + ctx.write(writer, valueType, value); + } + + @Override + public TypeRef type() { + return atomicType; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java index cf2738c..c87da22 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java @@ -2,30 +2,12 @@ import net.roxymc.jserialize.adapter.TypeAdapter; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - -import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactRaw; - public final class AtomicAdapters { - public static final TypeAdapter BOOLEAN = new AtomicPrimitiveAdapter<>( - AtomicBoolean.class, boolean.class, AtomicBoolean::new, AtomicBoolean::get, AtomicBoolean::set - ); - public static final TypeAdapter LONG = new AtomicPrimitiveAdapter<>( - AtomicLong.class, long.class, AtomicLong::new, AtomicLong::get, AtomicLong::set - ); - public static final TypeAdapter INTEGER = new AtomicPrimitiveAdapter<>( - AtomicInteger.class, int.class, AtomicInteger::new, AtomicInteger::get, AtomicInteger::set - ); - public static final TypeAdapter> REFERENCE = new AtomicReferenceAdapter(); - private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( - exactRaw(AtomicBoolean.class, BOOLEAN), - exactRaw(AtomicLong.class, LONG), - exactRaw(AtomicInteger.class, INTEGER), - exactRaw(AtomicReference.class, REFERENCE) + AtomicBooleanAdapter.factory(), + AtomicIntegerAdapter.factory(), + AtomicLongAdapter.factory(), + AtomicReferenceAdapter.factory() ); private AtomicAdapters() { diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java new file mode 100644 index 0000000..6e92c67 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicBooleanAdapter.java @@ -0,0 +1,37 @@ +package net.roxymc.jserialize.adapter.atomic; + +import org.jspecify.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicBoolean; + +public final class AtomicBooleanAdapter extends AbstractAtomicAdapter { + public static final AtomicBooleanAdapter INSTANCE = new AtomicBooleanAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private AtomicBooleanAdapter() { + super(AtomicBoolean.class, boolean.class); + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected AtomicBoolean createAtomic() { + return new AtomicBoolean(); + } + + @Override + protected Boolean getAtomic(AtomicBoolean atomic) { + return atomic.get(); + } + + @Override + protected void setAtomic(AtomicBoolean atomic, @Nullable Boolean value) { + if (value == null) { + throw new IllegalStateException("value cannot be null for AtomicBoolean"); + } + + atomic.set(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java new file mode 100644 index 0000000..d3d8a90 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicIntegerAdapter.java @@ -0,0 +1,37 @@ +package net.roxymc.jserialize.adapter.atomic; + +import org.jspecify.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicInteger; + +public final class AtomicIntegerAdapter extends AbstractAtomicAdapter { + public static final AtomicIntegerAdapter INSTANCE = new AtomicIntegerAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private AtomicIntegerAdapter() { + super(AtomicInteger.class, int.class); + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected AtomicInteger createAtomic() { + return new AtomicInteger(); + } + + @Override + protected Integer getAtomic(AtomicInteger atomic) { + return atomic.get(); + } + + @Override + protected void setAtomic(AtomicInteger atomic, @Nullable Integer value) { + if (value == null) { + throw new IllegalStateException("value cannot be null for AtomicInteger"); + } + + atomic.set(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java new file mode 100644 index 0000000..1d9ff0f --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicLongAdapter.java @@ -0,0 +1,37 @@ +package net.roxymc.jserialize.adapter.atomic; + +import org.jspecify.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicLong; + +public final class AtomicLongAdapter extends AbstractAtomicAdapter { + public static final AtomicLongAdapter INSTANCE = new AtomicLongAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private AtomicLongAdapter() { + super(AtomicLong.class, long.class); + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected AtomicLong createAtomic() { + return new AtomicLong(); + } + + @Override + protected Long getAtomic(AtomicLong atomic) { + return atomic.get(); + } + + @Override + protected void setAtomic(AtomicLong atomic, @Nullable Long value) { + if (value == null) { + throw new IllegalStateException("value cannot be null for AtomicLong"); + } + + atomic.set(value); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicPrimitiveAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicPrimitiveAdapter.java deleted file mode 100644 index 98debc9..0000000 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicPrimitiveAdapter.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.roxymc.jserialize.adapter.atomic; - -import net.roxymc.jserialize.Reader; -import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.adapter.ReadContext; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.type.TypeRef; -import org.jspecify.annotations.Nullable; - -import java.io.IOException; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; - -final class AtomicPrimitiveAdapter implements TypeAdapter.Mutable { - private final Class atomicType; - private final Class valueType; - - private final Supplier supplier; - private final Function getter; - private final BiConsumer setter; - - AtomicPrimitiveAdapter( - Class atomicType, - Class valueType, - Supplier supplier, - Function getter, - BiConsumer setter - ) { - if (!valueType.isPrimitive()) { - throw new IllegalArgumentException("valueType must be primitive"); - } - - this.atomicType = nonNull(atomicType, "atomicType"); - this.valueType = nonNull(valueType, "valueType"); - - this.supplier = nonNull(supplier, "supplier"); - this.getter = nonNull(getter, "getter"); - this.setter = nonNull(setter, "setter"); - } - - @Override - public A mutate(Reader reader, TypeRef type, @Nullable A ref, ReadContext ctx) throws IOException { - checkAssignable(atomicType, type.getRawType()); - - V value = ctx.read(reader, valueType); - - if (value == null) { - throw new IllegalStateException("Cannot cannot set atomic " + valueType + " to null"); - } - - if (ref == null) { - ref = supplier.get(); - } - - setter.accept(ref, value); - return ref; - } - - @Override - public void write(Writer writer, TypeRef type, @Nullable A ref, WriteContext ctx) throws IOException { - checkAssignable(atomicType, type.getRawType()); - - V value = ref != null ? getter.apply(ref) : null; - - ctx.write(writer, valueType, value); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java index 1a1080c..ee02ba1 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java @@ -1,89 +1,34 @@ package net.roxymc.jserialize.adapter.atomic; -import net.roxymc.jserialize.Reader; -import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.adapter.ReadContext; -import net.roxymc.jserialize.adapter.TypeAdapter; -import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; -import org.jetbrains.annotations.UnknownNullability; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; -import java.io.IOException; -import java.lang.reflect.AnnotatedParameterizedType; -import java.lang.reflect.AnnotatedType; import java.util.concurrent.atomic.AtomicReference; -import static io.leangen.geantyref.GenericTypeReflector.capture; -import static io.leangen.geantyref.GenericTypeReflector.getExactSuperType; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; +public final class AtomicReferenceAdapter extends AbstractAtomicAdapter, V> { + @SuppressWarnings({"unchecked", "rawtypes"}) + private static final Factory FACTORY = Factory.exactRaw(AtomicReference.class, type -> new AtomicReferenceAdapter(type)); -final class AtomicReferenceAdapter implements TypeAdapter.Mutable> { - @Override - public @Nullable AtomicReference mutate( - Reader reader, TypeRef> type, @Nullable AtomicReference value, ReadContext ctx - ) throws IOException { - return mutate0(reader, type, value, ctx); + private AtomicReferenceAdapter(TypeRef> atomicType) { + super(atomicType); } - private @Nullable AtomicReference mutate0( - Reader reader, TypeRef> type, @Nullable AtomicReference ref, ReadContext ctx - ) throws IOException { - checkAssignable(AtomicReference.class, type.getRawType()); - - TypeRef<@UnknownNullability V> valueType = resolveValueType(type); - TypeAdapter<@NonNull V> valueAdapter = ctx.typeAdapters().getOrThrow(valueType); - - V value; - - if (valueAdapter instanceof TypeAdapter.Mutable && reader.peek() != TokenTypes.NULL) { - V oldValue = ref != null ? ref.get() : null; - - value = ((TypeAdapter.Mutable<@NonNull V>) valueAdapter).mutate(reader, valueType, oldValue, ctx); - } else { - value = valueAdapter.read(reader, valueType, ctx); - } - - if (ref == null && value != null) { - ref = new AtomicReference<>(); - } - - if (ref != null) { - ref.set(value); - } - - return ref; + public static Factory factory() { + return FACTORY; } @Override - public void write( - Writer writer, TypeRef> type, @Nullable AtomicReference value, WriteContext ctx - ) throws IOException { - write0(writer, type, value, ctx); + protected AtomicReference<@Nullable V> createAtomic() { + return new AtomicReference<>(); } - private void write0( - Writer writer, TypeRef> type, @Nullable AtomicReference ref, WriteContext ctx - ) throws IOException { - checkAssignable(AtomicReference.class, type.getRawType()); - - TypeRef<@UnknownNullability V> valueType = resolveValueType(type); - - V value = ref != null ? ref.get() : null; - - ctx.write(writer, valueType, value); + @Override + protected @Nullable V getAtomic(AtomicReference<@Nullable V> atomic) { + return atomic.get(); } - private TypeRef resolveValueType(TypeRef> refType) { - AnnotatedType type = getExactSuperType(capture(refType.getAnnotatedType()), AtomicReference.class); - if (!(type instanceof AnnotatedParameterizedType)) { - throw new IllegalStateException(refType.getType() + " must be a parameterized AtomicReference"); - } - - AnnotatedParameterizedType ptype = (AnnotatedParameterizedType) type; - - return TypeRef.of(ptype.getAnnotatedActualTypeArguments()[0]); + @Override + protected void setAtomic(AtomicReference<@Nullable V> atomic, @Nullable V value) { + atomic.set(value); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java index 4e21c45..c2f8167 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java @@ -5,54 +5,38 @@ import net.roxymc.jserialize.adapter.*; import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.io.IOException; -import java.lang.reflect.Type; import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; -public final class CollectionAdapter implements TypeAdapter.Mutable> { - private static final TypeAdapter.Factory FACTORY = factory(DefaultCollectionProvider.INSTANCE); +public final class CollectionAdapter implements TypeAdapter.Mutable> { + private static final Factory FACTORY = factory(DefaultCollectionProvider.INSTANCE); - private final Map> cache = new ConcurrentHashMap<>(); + private final CollectionType collectionType; private final CollectionProvider[] providers; - public CollectionAdapter(CollectionProvider... providers) { - this.providers = nonNull(providers, "providers").clone(); + private CollectionAdapter(TypeRef> collectionType, CollectionProvider[] providers) { + this.collectionType = new CollectionType<>(collectionType); + this.providers = providers; } - public static TypeAdapter.Factory factory() { + public static Factory factory() { return FACTORY; } - public static TypeAdapter.Factory factory(CollectionProvider... providers) { - return TypeAdapter.Factory.polymorphic(Collection.class, new CollectionAdapter(providers)); - } + public static Factory factory(CollectionProvider... providers) { + CollectionProvider[] providers0 = nonNull(providers, "providers").clone(); - private CollectionType resolveCollectionType(TypeRef> type) { - @SuppressWarnings("unchecked") - CollectionType collectionType = (CollectionType) cache.computeIfAbsent(type.getType(), $ -> new CollectionType<>(type)); - return collectionType; + @SuppressWarnings({"unchecked", "rawtypes"}) + Factory factory = Factory.polymorphic(Collection.class, type -> new CollectionAdapter(type, providers0)); + return factory; } @Override - public @Nullable Collection mutate( - Reader reader, TypeRef> type, @Nullable Collection value, ReadContext ctx - ) throws IOException { - return mutate0(reader, type, value, ctx); - } - - private @Nullable Collection mutate0( - Reader reader, TypeRef> type, @Nullable Collection collection, ReadContext ctx - ) throws IOException { - checkAssignable(Collection.class, type.getRawType()); - + public @Nullable Collection<@Nullable E> mutate(Reader reader, @Nullable Collection<@Nullable E> collection, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); @@ -63,18 +47,16 @@ public static TypeAdapter.Factory factory(CollectionProvider... providers) { return collection; } - CollectionType collectionType = resolveCollectionType(type); - if (collection == null) { collection = collectionType.createCollection(providers); } - TypeReader<@NonNull E> elementReader = ctx.typeAdapters().getOrThrow(collectionType.elementType); + TypeReader elementReader = ctx.typeAdapters().getOrThrow(collectionType.elementType); reader.readArrayStart(); for (int index = 0; reader.peek() != TokenTypes.ARRAY_END; index++) { - E element = elementReader.read(reader, collectionType.elementType, ctx); + E element = elementReader.read(reader, ctx); collection.add(element); } @@ -85,32 +67,25 @@ public static TypeAdapter.Factory factory(CollectionProvider... providers) { } @Override - public void write( - Writer writer, TypeRef> type, @Nullable Collection value, WriteContext ctx - ) throws IOException { - write0(writer, type, value, ctx); - } - - private void write0( - Writer writer, TypeRef> type, @Nullable Collection collection, WriteContext ctx - ) throws IOException { - checkAssignable(Collection.class, type.getRawType()); - + public void write(Writer writer, @Nullable Collection<@Nullable E> collection, WriteContext ctx) throws IOException { if (collection == null) { writer.writeNull(); return; } - CollectionType collectionType = resolveCollectionType(type); - - TypeWriter<@NonNull E> elementWriter = ctx.typeAdapters().getOrThrow(collectionType.elementType); + TypeWriter elementWriter = ctx.typeAdapters().getOrThrow(collectionType.elementType); writer.writeArrayStart(); for (E element : collection) { - elementWriter.write(writer, collectionType.elementType, element, ctx); + elementWriter.write(writer, element, ctx); } writer.writeArrayEnd(); } + + @Override + public TypeRef> type() { + return collectionType.collectionType; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionType.java b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionType.java index 05d81f0..bedd31c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionType.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionType.java @@ -2,34 +2,26 @@ import net.roxymc.jserialize.type.TypeRef; import net.roxymc.jserialize.util.VarHandles; -import org.jetbrains.annotations.UnknownNullability; import org.jspecify.annotations.Nullable; import java.lang.invoke.VarHandle; import java.lang.reflect.AnnotatedParameterizedType; -import java.lang.reflect.AnnotatedType; import java.util.Collection; -import static io.leangen.geantyref.GenericTypeReflector.capture; -import static io.leangen.geantyref.GenericTypeReflector.getExactSuperType; +import static net.roxymc.jserialize.util.TypeUtils.resolveTypeParameters; -final class CollectionType { +final class CollectionType { private static final VarHandle COLLECTION_FACTORY_HANDLE = VarHandles.find(CollectionType.class, "collectionFactory", CollectionFactory.class); - private final TypeRef> collectionType; + final TypeRef> collectionType; final TypeRef elementType; private @Nullable CollectionFactory collectionFactory; - CollectionType(TypeRef> collectionType) { - AnnotatedType type = getExactSuperType(capture(collectionType.getAnnotatedType()), Collection.class); - if (!(type instanceof AnnotatedParameterizedType)) { - throw new IllegalStateException(collectionType.getType() + " must be a parameterized Collection"); - } - - AnnotatedParameterizedType ptype = (AnnotatedParameterizedType) type; + CollectionType(TypeRef> collectionType) { + AnnotatedParameterizedType ptype = resolveTypeParameters(collectionType.getAnnotatedType(), Collection.class); - this.collectionType = TypeRef.of(ptype); + this.collectionType = collectionType; this.elementType = TypeRef.of(ptype.getAnnotatedActualTypeArguments()[0]); } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java index 1693167..8d52222 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapAdapter.java @@ -5,53 +5,38 @@ import net.roxymc.jserialize.adapter.*; import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.io.IOException; -import java.lang.reflect.Type; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; -public final class MapAdapter implements TypeAdapter.Mutable> { - private static final TypeAdapter.Factory FACTORY = factory(DefaultMapProvider.INSTANCE); +public final class MapAdapter implements TypeAdapter.Mutable> { + private static final Factory FACTORY = factory(DefaultMapProvider.INSTANCE); - private final Map> cache = new ConcurrentHashMap<>(); + private final MapType mapType; private final MapProvider[] providers; - public MapAdapter(MapProvider... providers) { - this.providers = nonNull(providers, "providers").clone(); + public MapAdapter(TypeRef> mapType, MapProvider[] providers) { + this.mapType = new MapType<>(mapType); + this.providers = providers; } - public static TypeAdapter.Factory factory() { + public static Factory factory() { return FACTORY; } - public static TypeAdapter.Factory factory(MapProvider... providers) { - return TypeAdapter.Factory.polymorphic(Map.class, new MapAdapter(providers)); - } + public static Factory factory(MapProvider... providers) { + MapProvider[] providers0 = nonNull(providers, "providers").clone(); - private MapType resolveMapType(TypeRef> type) { - @SuppressWarnings("unchecked") - MapType mapType = (MapType) cache.computeIfAbsent(type.getType(), $ -> new MapType<>(type)); - return mapType; + @SuppressWarnings({"unchecked", "rawtypes"}) + Factory factory = Factory.polymorphic(Map.class, type -> new MapAdapter(type, providers0)); + return factory; } @Override - public @Nullable Map mutate( - Reader reader, TypeRef> type, @Nullable Map value, ReadContext ctx - ) throws IOException { - return mutate0(reader, type, value, ctx); - } - - private @Nullable Map mutate0( - Reader reader, TypeRef> type, @Nullable Map map, ReadContext ctx - ) throws IOException { - checkAssignable(Map.class, type.getRawType()); - + public @Nullable Map<@Nullable K, @Nullable V> mutate(Reader reader, @Nullable Map<@Nullable K, @Nullable V> map, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); @@ -62,14 +47,12 @@ private MapType resolveMapType(TypeRef> type) { return map; } - MapType mapType = resolveMapType(type); - if (map == null) { map = mapType.createMap(providers); } - KeyDecoder<@NonNull K> keyDecoder = ctx.typeAdapters().getKeyOrThrow(mapType.keyType); - TypeReader<@NonNull V> valueReader = ctx.typeAdapters().getOrThrow(mapType.valueType); + KeyDecoder keyDecoder = ctx.typeAdapters().getKeyOrThrow(mapType.keyType); + TypeReader valueReader = ctx.typeAdapters().getOrThrow(mapType.valueType); reader.readObjectStart(); @@ -77,7 +60,7 @@ private MapType resolveMapType(TypeRef> type) { String name = reader.readName(); K key = keyDecoder.decode(name); - V value = valueReader.read(reader, mapType.valueType, ctx.withKey(name)); + V value = valueReader.read(reader, ctx.withKey(name)); map.put(key, value); } @@ -88,26 +71,14 @@ private MapType resolveMapType(TypeRef> type) { } @Override - public void write( - Writer writer, TypeRef> type, @Nullable Map value, WriteContext ctx - ) throws IOException { - write0(writer, type, value, ctx); - } - - private void write0( - Writer writer, TypeRef> type, @Nullable Map map, WriteContext ctx - ) throws IOException { - checkAssignable(Map.class, type.getRawType()); - + public void write(Writer writer, @Nullable Map<@Nullable K, @Nullable V> map, WriteContext ctx) throws IOException { if (map == null) { writer.writeNull(); return; } - MapType mapType = resolveMapType(type); - - KeyEncoder<@NonNull K> keyEncoder = ctx.typeAdapters().getKeyOrThrow(mapType.keyType); - TypeWriter<@NonNull V> valueWriter = ctx.typeAdapters().getOrThrow(mapType.valueType); + KeyEncoder keyEncoder = ctx.typeAdapters().getKeyOrThrow(mapType.keyType); + TypeWriter valueWriter = ctx.typeAdapters().getOrThrow(mapType.valueType); writer.writeObjectStart(); @@ -115,9 +86,14 @@ public void write( String name = keyEncoder.encode(entry.getKey()); writer.writeName(name); - valueWriter.write(writer, mapType.valueType, entry.getValue(), ctx); + valueWriter.write(writer, entry.getValue(), ctx); } writer.writeObjectEnd(); } + + @Override + public TypeRef> type() { + return mapType.mapType; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapType.java b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapType.java index 26a7c9b..d78021c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/map/MapType.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/map/MapType.java @@ -2,7 +2,6 @@ import net.roxymc.jserialize.type.TypeRef; import net.roxymc.jserialize.util.VarHandles; -import org.jetbrains.annotations.UnknownNullability; import org.jspecify.annotations.Nullable; import java.lang.invoke.VarHandle; @@ -13,16 +12,16 @@ import static io.leangen.geantyref.GenericTypeReflector.capture; import static io.leangen.geantyref.GenericTypeReflector.getExactSuperType; -final class MapType { +final class MapType { private static final VarHandle MAP_FACTORY_HANDLE = VarHandles.find(MapType.class, "mapFactory", MapFactory.class); - private final TypeRef> mapType; + final TypeRef> mapType; final TypeRef keyType; final TypeRef valueType; private @Nullable MapFactory mapFactory; - MapType(TypeRef> mapType) { + MapType(TypeRef> mapType) { AnnotatedType type = getExactSuperType(capture(mapType.getAnnotatedType()), Map.class); if (!(type instanceof AnnotatedParameterizedType)) { throw new IllegalStateException(mapType.getRawType() + " must be a parameterized Map"); diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java index 4090e11..fc8beac 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java @@ -14,36 +14,41 @@ import java.io.IOException; public final class ObjectAdapter implements TypeAdapter.Mutable { - private static final TypeAdapter.Factory FACTORY = factory(ClassModel.factory()); - private static final TypeAdapter.Factory ANNOTATED_FACTORY = annotatedFactory(ClassModel.factory()); + private static final Factory FACTORY = factory(ClassModel.factory()); + private static final Factory ANNOTATED_FACTORY = annotatedFactory(ClassModel.factory()); - private final ClassModel.Factory factory; + private final TypeRef type; + private final ClassModel classModel; - public ObjectAdapter(ClassModel.Factory factory) { - this.factory = factory; + private ObjectAdapter(TypeRef type, ClassModel classModel) { + this.type = type; + this.classModel = classModel; } - public static TypeAdapter.Factory factory() { + public static Factory factory() { return FACTORY; } - public static TypeAdapter.Factory factory(ClassModel.Factory factory) { - return TypeAdapter.Factory.predicate(type -> !type.getRawType().isPrimitive(), new ObjectAdapter<>(factory)); + public static Factory factory(ClassModel.Factory factory) { + return Factory.where( + type -> !type.getRawType().isPrimitive(), + type -> new ObjectAdapter<>(type, factory.create(type)) + ); } - public static TypeAdapter.Factory annotatedFactory() { + public static Factory annotatedFactory() { return ANNOTATED_FACTORY; } - public static TypeAdapter.Factory annotatedFactory(ClassModel.Factory factory) { - return TypeAdapter.Factory.predicate( + public static Factory annotatedFactory(ClassModel.Factory factory) { + return Factory.where( type -> !type.getRawType().isPrimitive() && type.getAnnotatedType().isAnnotationPresent(JSerializable.class), - new ObjectAdapter<>(factory) + type -> new ObjectAdapter<>(type, factory.create(type)) ); } @Override - public @Nullable T mutate(Reader reader, TypeRef type, @Nullable T value, ReadContext ctx) throws IOException { + public @Nullable T mutate(Reader reader, @Nullable T value, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { if (value != null) { throw new IllegalStateException("Cannot mutate value with null"); @@ -54,7 +59,6 @@ public static TypeAdapter.Factory annotatedFactory(ClassModel.Factory factory) { } try { - ClassModel classModel = factory.create(type); @SuppressWarnings("NullableProblems") // IntelliJ has existential issues ObjectReader objectReader = new ObjectReader<>(classModel, type, value, ctx.formatUtils(), ctx); @@ -65,14 +69,13 @@ public static TypeAdapter.Factory annotatedFactory(ClassModel.Factory factory) { } @Override - public void write(Writer writer, TypeRef type, @Nullable T value, WriteContext ctx) throws IOException { + public void write(Writer writer, @Nullable T value, WriteContext ctx) throws IOException { if (value == null) { writer.writeNull(); return; } try { - ClassModel classModel = factory.create(type); @SuppressWarnings("NullableProblems") // IntelliJ has existential issues ObjectWriter objectWriter = new ObjectWriter<>(classModel, type, value, ctx.formatUtils(), ctx); @@ -81,4 +84,9 @@ public void write(Writer writer, TypeRef type, @Nullable T value, W throw new RuntimeException("Failed to write object of type: " + type.getType(), e); } } + + @Override + public TypeRef type() { + return type; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java index 460695c..acd302b 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectReader.java @@ -149,13 +149,13 @@ T read(Reader reader) throws Throwable { if (!(adapter instanceof TypeAdapter.Mutable)) { return parent -> adapter.read( - valueReader, typeRef, context.withParent(parent) + valueReader, context.withParent(parent) ); } TypeAdapter.Mutable mutableAdapter = (TypeAdapter.Mutable) adapter; return (PropertyValue.Mutable) (parent, instance) -> mutableAdapter.mutate( - valueReader, typeRef, instance, context.withParent(parent) + valueReader, instance, context.withParent(parent) ); } @@ -163,7 +163,7 @@ T read(Reader reader) throws Throwable { TypeRef typeRef = TypeRef.of(formatUtils.rawType()); TypeAdapter adapter = context.typeAdapters().getOrThrow(typeRef); - return adapter.read(reader, typeRef, context.withParent(null).withKey(null)); + return adapter.read(reader, context.withParent(null).withKey(null)); } private @Nullable AnnotatedType resolveReadType(PropertyModel property) { @@ -185,6 +185,7 @@ T read(Reader reader) throws Throwable { return null; } + // TODO looking at the resolveType/getTypeParameter impl we might not need getExactSuperType AnnotatedType supertype = getExactSuperType(capture(type.getAnnotatedType()), method.declaringClass()); return resolveType(method.valueType(), supertype); } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java index dcc7495..4daa524 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectWriter.java @@ -93,7 +93,7 @@ private void writeExtrasProperty(Writer writer, AnnotatedType type, Object value for (Map.Entry entry : extrasMap.asRawMap().entrySet()) { writer.writeName(entry.getKey()); - rawWriter.write(writer, rawType, entry.getValue(), context); + rawWriter.write(writer, entry.getValue(), context); } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java index 161a328..b4a3ade 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java @@ -1,6 +1,5 @@ package net.roxymc.jserialize.adapter.scalar; -import io.leangen.geantyref.GenericTypeReflector; import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.Writer; import net.roxymc.jserialize.adapter.ReadContext; @@ -13,28 +12,22 @@ import java.io.IOException; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; - abstract class AbstractNumberAdapter implements TypeAdapter { - protected final Class numberType; - - protected AbstractNumberAdapter(Class numberType) { - if (numberType.isPrimitive()) { - throw new IllegalArgumentException("numberType cannot be primitive"); - } + protected final TypeRef type; + protected final Class rawType; - this.numberType = numberType; + protected AbstractNumberAdapter(Class type) { + this.type = TypeRef.of(type); + this.rawType = type; } @Override - public final @Nullable N read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { - checkAssignable(numberType, GenericTypeReflector.box(type.getRawType())); - + public final @Nullable N read(Reader reader, ReadContext ctx) throws IOException { TokenType tokenType = reader.peek(); if (tokenType == TokenTypes.NULL) { - if (type.getRawType().isPrimitive()) { - throw new IllegalStateException("Cannot read null into primitive " + type.getRawType()); + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot read null into primitive " + rawType.getSimpleName()); } reader.readNull(); @@ -57,7 +50,7 @@ protected AbstractNumberAdapter(Class numberType) { try { return parse(reader.readString()); } catch (NumberFormatException e) { - throw new ArithmeticException("Invalid format for " + numberType.getSimpleName() + ": " + e.getMessage()); + throw new ArithmeticException("Invalid format for " + rawType.getSimpleName() + ": " + e.getMessage()); } } @@ -66,13 +59,13 @@ protected AbstractNumberAdapter(Class numberType) { protected N fromDouble(double value) { if (!Double.isFinite(value)) { - throw new ArithmeticException(numberType.getSimpleName() + " must be finite: " + value); + throw new ArithmeticException(rawType.getSimpleName() + " must be finite: " + value); } long longValue = (long) value; if (longValue != value) { - throw new ArithmeticException(numberType.getSimpleName() + " overflow or loss of precision: " + value); + throw new ArithmeticException(rawType.getSimpleName() + " overflow or loss of precision: " + value); } return fromLong(longValue); @@ -83,12 +76,10 @@ protected N fromDouble(double value) { protected abstract N parse(String value) throws NumberFormatException; @Override - public final void write(Writer writer, TypeRef type, @Nullable N value, WriteContext ctx) throws IOException { - checkAssignable(numberType, GenericTypeReflector.box(type.getRawType())); - + public final void write(Writer writer, @Nullable N value, WriteContext ctx) throws IOException { if (value == null) { - if (type.getRawType().isPrimitive()) { - throw new IllegalStateException("Cannot write null for primitive " + type.getRawType()); + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot write null for primitive " + rawType.getSimpleName()); } writer.writeNull(); @@ -99,4 +90,9 @@ public final void write(Writer writer, TypeRef type, @Nullable N va } protected abstract void write(Writer writer, N value) throws IOException; + + @Override + public TypeRef type() { + return type; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java index 2668280..78dfb96 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java @@ -1,6 +1,5 @@ package net.roxymc.jserialize.adapter.scalar; -import io.leangen.geantyref.GenericTypeReflector; import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.Writer; import net.roxymc.jserialize.adapter.ReadContext; @@ -12,32 +11,50 @@ import java.io.IOException; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; +public final class BooleanAdapter implements TypeAdapter { + public static final TypeAdapter PRIMITIVE = new BooleanAdapter(boolean.class); + public static final TypeAdapter BOXED = new BooleanAdapter(Boolean.class); -final class BooleanAdapter implements TypeAdapter { - @Override - public @Nullable Boolean read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { - checkAssignable(Boolean.class, GenericTypeReflector.box(type.getRawType())); + private static final Factory FACTORY = Factory.composite( + Factory.exact(PRIMITIVE), + Factory.exact(BOXED) + ); + + private final TypeRef type; + private final Class rawType; + + private BooleanAdapter(Class type) { + this.type = TypeRef.of(type); + this.rawType = type; + } + public static TypeAdapter.Factory factory() { + return FACTORY; + } + + @Override + public @Nullable Boolean read(Reader reader, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { - if (type.getRawType().isPrimitive()) { - throw new IllegalStateException("Cannot read null into primitive " + type.getRawType()); + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot read null into primitive " + rawType.getSimpleName()); } reader.readNull(); return null; } + if (reader.peek() == TokenTypes.STRING) { + return Boolean.parseBoolean(reader.readString()); + } + return reader.readBoolean(); } @Override - public void write(Writer writer, TypeRef type, @Nullable Boolean value, WriteContext ctx) throws IOException { - checkAssignable(Boolean.class, GenericTypeReflector.box(type.getRawType())); - + public void write(Writer writer, @Nullable Boolean value, WriteContext ctx) throws IOException { if (value == null) { - if (type.getRawType().isPrimitive()) { - throw new IllegalStateException("Cannot write null for primitive " + type.getRawType()); + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot write null for primitive " + rawType.getSimpleName()); } writer.writeNull(); @@ -46,4 +63,9 @@ public void write(Writer writer, TypeRef type, @Nullable Bool writer.writeBoolean(value); } + + @Override + public TypeRef type() { + return type; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanKeyAdapter.java new file mode 100644 index 0000000..ef8c78b --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class BooleanKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Boolean.class); + + public static final KeyAdapter INSTANCE = new BooleanKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private BooleanKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected Boolean parse(String value) { + return Boolean.parseBoolean(value); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteAdapter.java index 710d19a..86d58c9 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteAdapter.java @@ -1,12 +1,25 @@ package net.roxymc.jserialize.adapter.scalar; import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.TypeAdapter; import java.io.IOException; -final class ByteAdapter extends AbstractNumberAdapter { - ByteAdapter() { - super(Byte.class); +public final class ByteAdapter extends AbstractNumberAdapter { + public static final TypeAdapter PRIMITIVE = new ByteAdapter(byte.class); + public static final TypeAdapter BOXED = new ByteAdapter(Byte.class); + + private static final Factory FACTORY = Factory.composite( + Factory.exact(PRIMITIVE), + Factory.exact(BOXED) + ); + + private ByteAdapter(Class type) { + super(type); + } + + public static TypeAdapter.Factory factory() { + return FACTORY; } @Override @@ -14,7 +27,7 @@ protected Byte fromLong(long value) { byte byteValue = (byte) value; if (byteValue != value) { - throw new ArithmeticException(numberType.getSimpleName() + " overflow: " + value); + throw new ArithmeticException(rawType.getSimpleName() + " overflow: " + value); } return byteValue; diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteKeyAdapter.java new file mode 100644 index 0000000..34d8f70 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class ByteKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Byte.class); + + public static final KeyAdapter INSTANCE = new ByteKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private ByteKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected Byte parse(String value) { + return Byte.parseByte(value); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java index fdc5383..cfc47bb 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java @@ -1,6 +1,5 @@ package net.roxymc.jserialize.adapter.scalar; -import io.leangen.geantyref.GenericTypeReflector; import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.Writer; import net.roxymc.jserialize.adapter.ReadContext; @@ -12,16 +11,32 @@ import java.io.IOException; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; +public final class CharacterAdapter implements TypeAdapter { + public static final TypeAdapter PRIMITIVE = new CharacterAdapter(char.class); + public static final TypeAdapter BOXED = new CharacterAdapter(Character.class); -final class CharacterAdapter implements TypeAdapter { - @Override - public @Nullable Character read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { - checkAssignable(Character.class, GenericTypeReflector.box(type.getRawType())); + private static final Factory FACTORY = Factory.composite( + Factory.exact(PRIMITIVE), + Factory.exact(BOXED) + ); + + private final TypeRef type; + private final Class rawType; + + private CharacterAdapter(Class type) { + this.type = TypeRef.of(type); + this.rawType = type; + } + + public static Factory factory() { + return FACTORY; + } + @Override + public @Nullable Character read(Reader reader, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { - if (type.getRawType().isPrimitive()) { - throw new IllegalStateException("Cannot read null into primitive " + type.getRawType()); + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot read null into primitive " + rawType.getSimpleName()); } reader.readNull(); @@ -37,12 +52,10 @@ final class CharacterAdapter implements TypeAdapter { } @Override - public void write(Writer writer, TypeRef type, @Nullable Character value, WriteContext ctx) throws IOException { - checkAssignable(Character.class, GenericTypeReflector.box(type.getRawType())); - + public void write(Writer writer, @Nullable Character value, WriteContext ctx) throws IOException { if (value == null) { - if (type.getRawType().isPrimitive()) { - throw new IllegalStateException("Cannot write null for primitive " + type.getRawType()); + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot write null for primitive " + rawType.getSimpleName()); } writer.writeNull(); @@ -51,4 +64,9 @@ public void write(Writer writer, TypeRef type, @Nullable Ch writer.writeString(value.toString()); } + + @Override + public TypeRef type() { + return type; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterKeyAdapter.java new file mode 100644 index 0000000..b0c7186 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterKeyAdapter.java @@ -0,0 +1,33 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class CharacterKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Character.class); + + public static final KeyAdapter INSTANCE = new CharacterKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private CharacterKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected Character parse(String value) { + if (value.length() != 1) { + throw new IllegalStateException("value must be a single character"); + } + + return value.charAt(0); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleAdapter.java index 381d1bb..7e4b1e6 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleAdapter.java @@ -1,12 +1,25 @@ package net.roxymc.jserialize.adapter.scalar; import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.TypeAdapter; import java.io.IOException; -final class DoubleAdapter extends AbstractNumberAdapter { - DoubleAdapter() { - super(Double.class); +public final class DoubleAdapter extends AbstractNumberAdapter { + public static final TypeAdapter PRIMITIVE = new DoubleAdapter(double.class); + public static final TypeAdapter BOXED = new DoubleAdapter(Double.class); + + private static final Factory FACTORY = Factory.composite( + Factory.exact(PRIMITIVE), + Factory.exact(BOXED) + ); + + private DoubleAdapter(Class type) { + super(type); + } + + public static Factory factory() { + return FACTORY; } @Override @@ -14,7 +27,7 @@ protected Double fromLong(long value) { double doubleValue = (double) value; if ((long) doubleValue != value) { - throw new ArithmeticException(numberType.getSimpleName() + " overflow or loss of precision: " + value); + throw new ArithmeticException(rawType.getSimpleName() + " overflow or loss of precision: " + value); } return doubleValue; diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleKeyAdapter.java new file mode 100644 index 0000000..9f4f6df --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class DoubleKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Double.class); + + public static final KeyAdapter INSTANCE = new DoubleKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private DoubleKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected Double parse(String value) { + return Double.parseDouble(value); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumAdapter.java index 18ac4b1..77336cf 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumAdapter.java @@ -12,20 +12,25 @@ import java.io.IOException; -import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.predicate; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; +public final class EnumAdapter> implements TypeAdapter { + @SuppressWarnings({"unchecked", "rawtypes"}) + private static final Factory FACTORY = Factory.where(TypeUtils::isEnum, EnumAdapter::new); -public final class EnumAdapter implements TypeAdapter> { - private static final TypeAdapter.Factory FACTORY = predicate(type -> TypeUtils.isEnum(type.getRawType()), new EnumAdapter()); + private final TypeRef enumType; + private final Class enumClass; - public static TypeAdapter.Factory factory() { + @SuppressWarnings("unchecked") + public EnumAdapter(TypeRef enumType) { + this.enumType = enumType; + this.enumClass = (Class) enumType.getRawType(); + } + + public static Factory factory() { return FACTORY; } @Override - public @Nullable Enum read(Reader reader, TypeRef> type, ReadContext ctx) throws IOException { - checkAssignable(Enum.class, type.getType()); - + public @Nullable E read(Reader reader, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return null; @@ -33,13 +38,11 @@ public static TypeAdapter.Factory factory() { String name = reader.readString(); - return Enum.valueOf(type.getRawType().asSubclass(Enum.class), name); + return Enum.valueOf(enumClass, name); } @Override - public void write(Writer writer, TypeRef> type, @Nullable Enum value, WriteContext ctx) throws IOException { - checkAssignable(Enum.class, type.getType()); - + public void write(Writer writer, @Nullable E value, WriteContext ctx) throws IOException { if (value == null) { writer.writeNull(); return; @@ -47,4 +50,9 @@ public void write(Writer writer, TypeRef> type, @Nullable Enum writer.writeString(value.name()); } + + @Override + public TypeRef type() { + return enumType; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumKeyAdapter.java index 4c8d1d4..a64f7dc 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumKeyAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumKeyAdapter.java @@ -1,32 +1,21 @@ package net.roxymc.jserialize.adapter.scalar; +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; import net.roxymc.jserialize.adapter.KeyAdapter; -import net.roxymc.jserialize.adapter.TypeAdapters; import net.roxymc.jserialize.type.TypeRef; import net.roxymc.jserialize.util.TypeUtils; -import org.jspecify.annotations.Nullable; -import static net.roxymc.jserialize.util.ObjectUtils.nonNull; +public final class EnumKeyAdapter> extends AbstractKeyAdapter { + @SuppressWarnings({"unchecked", "rawtypes"}) + private static final Factory FACTORY = Factory.where(TypeUtils::isEnum, (type, $) -> new EnumKeyAdapter(type)); -public final class EnumKeyAdapter implements KeyAdapter> { - private static final KeyAdapter.Factory FACTORY = new KeyAdapter.Factory() { - @Override - public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { - Class enumType = type.getRawType(); - if (!TypeUtils.isEnum(enumType)) { - return null; - } + private final TypeRef enumType; + private final Class enumClass; - @SuppressWarnings("unchecked") - KeyAdapter adapter = (KeyAdapter) new EnumKeyAdapter(enumType); - return adapter; - } - }; - - private final Class enumType; - - private EnumKeyAdapter(Class enumType) { + @SuppressWarnings("unchecked") + private EnumKeyAdapter(TypeRef enumType) { this.enumType = enumType; + this.enumClass = (Class) enumType.getRawType(); } public static KeyAdapter.Factory factory() { @@ -34,12 +23,12 @@ public static KeyAdapter.Factory factory() { } @Override - public @Nullable Enum decode(@Nullable String value) { - return value != null ? Enum.valueOf(enumType.asSubclass(Enum.class), value) : null; + protected E parse(String value) { + return Enum.valueOf(enumClass, value); } @Override - public String encode(@Nullable Enum value) { - return nonNull(value, "value").name(); + public TypeRef type() { + return enumType; } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatAdapter.java index 73cb8c1..1c0add7 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatAdapter.java @@ -1,12 +1,25 @@ package net.roxymc.jserialize.adapter.scalar; import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.TypeAdapter; import java.io.IOException; -final class FloatAdapter extends AbstractNumberAdapter { - FloatAdapter() { - super(Float.class); +public final class FloatAdapter extends AbstractNumberAdapter { + public static final TypeAdapter PRIMITIVE = new FloatAdapter(float.class); + public static final TypeAdapter BOXED = new FloatAdapter(Float.class); + + private static final Factory FACTORY = Factory.composite( + Factory.exact(PRIMITIVE), + Factory.exact(BOXED) + ); + + private FloatAdapter(Class type) { + super(type); + } + + public static Factory factory() { + return FACTORY; } @Override @@ -14,7 +27,7 @@ protected Float fromLong(long value) { float floatValue = (float) value; if ((long) floatValue != value) { - throw new ArithmeticException(numberType.getSimpleName() + " overflow or loss of precision: " + value); + throw new ArithmeticException(rawType.getSimpleName() + " overflow or loss of precision: " + value); } return floatValue; @@ -25,7 +38,7 @@ protected Float fromDouble(double value) { float floatValue = (float) value; if (Double.isFinite(value) && (Float.isInfinite(floatValue) || (double) floatValue != value)) { - throw new ArithmeticException(numberType.getSimpleName() + " overflow or loss of precision: " + value); + throw new ArithmeticException(rawType.getSimpleName() + " overflow or loss of precision: " + value); } return floatValue; diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatKeyAdapter.java new file mode 100644 index 0000000..8bfae71 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class FloatKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Float.class); + + public static final KeyAdapter INSTANCE = new FloatKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private FloatKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected Float parse(String value) { + return Float.parseFloat(value); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerAdapter.java index 2b4ee23..22be758 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerAdapter.java @@ -1,12 +1,25 @@ package net.roxymc.jserialize.adapter.scalar; import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.TypeAdapter; import java.io.IOException; -final class IntegerAdapter extends AbstractNumberAdapter { - IntegerAdapter() { - super(Integer.class); +public final class IntegerAdapter extends AbstractNumberAdapter { + public static final TypeAdapter PRIMITIVE = new IntegerAdapter(int.class); + public static final TypeAdapter BOXED = new IntegerAdapter(Integer.class); + + private static final Factory FACTORY = Factory.composite( + Factory.exact(PRIMITIVE), + Factory.exact(BOXED) + ); + + private IntegerAdapter(Class type) { + super(type); + } + + public static Factory factory() { + return FACTORY; } @Override @@ -14,7 +27,7 @@ protected Integer fromLong(long value) { int intValue = (int) value; if (intValue != value) { - throw new ArithmeticException(numberType.getSimpleName() + " overflow: " + value); + throw new ArithmeticException(rawType.getSimpleName() + " overflow: " + value); } return intValue; diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerKeyAdapter.java new file mode 100644 index 0000000..f25ca5f --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class IntegerKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Integer.class); + + public static final KeyAdapter INSTANCE = new IntegerKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private IntegerKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected Integer parse(String value) { + return Integer.parseInt(value); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongAdapter.java index 696dd32..3c71415 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongAdapter.java @@ -4,9 +4,21 @@ import java.io.IOException; -final class LongAdapter extends AbstractNumberAdapter { - LongAdapter() { - super(Long.class); +public final class LongAdapter extends AbstractNumberAdapter { + public static final LongAdapter PRIMITIVE = new LongAdapter(long.class); + public static final LongAdapter BOXED = new LongAdapter(Long.class); + + private static final Factory FACTORY = Factory.composite( + Factory.exact(PRIMITIVE), + Factory.exact(BOXED) + ); + + private LongAdapter(Class type) { + super(type); + } + + public static Factory factory() { + return FACTORY; } @Override diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongKeyAdapter.java new file mode 100644 index 0000000..760a610 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class LongKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Long.class); + + public static final KeyAdapter INSTANCE = new LongKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private LongKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected Long parse(String value) { + return Long.parseLong(value); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberAdapter.java index a21395c..68bba0a 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberAdapter.java @@ -6,11 +6,18 @@ import java.math.BigDecimal; import java.math.BigInteger; -final class NumberAdapter extends AbstractNumberAdapter { - NumberAdapter() { +public final class NumberAdapter extends AbstractNumberAdapter { + public static final NumberAdapter INSTANCE = new NumberAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private NumberAdapter() { super(Number.class); } + public static Factory factory() { + return FACTORY; + } + static Number parseNumber(String value) throws NumberFormatException { try { return Long.parseLong(value); diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberKeyAdapter.java new file mode 100644 index 0000000..388ab51 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class NumberKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Number.class); + + public static final KeyAdapter INSTANCE = new NumberKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private NumberKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected Number parse(String value) { + return NumberAdapter.parseNumber(value); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarAdapters.java index e57d918..a76bbdb 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarAdapters.java @@ -2,34 +2,19 @@ import net.roxymc.jserialize.adapter.TypeAdapter; -import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactBoxed; -import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactRaw; - public final class ScalarAdapters { - public static final TypeAdapter CHARACTER = new CharacterAdapter(); - public static final TypeAdapter BOOLEAN = new BooleanAdapter(); - public static final TypeAdapter BYTE = new ByteAdapter(); - public static final TypeAdapter DOUBLE = new DoubleAdapter(); - public static final TypeAdapter FLOAT = new FloatAdapter(); - public static final TypeAdapter INTEGER = new IntegerAdapter(); - public static final TypeAdapter LONG = new LongAdapter(); - public static final TypeAdapter SHORT = new ShortAdapter(); - public static final TypeAdapter NUMBER = new NumberAdapter(); - public static final TypeAdapter STRING = new StringAdapter(); - public static final TypeAdapter UUID = new UUIDAdapter(); - private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( - exactBoxed(Character.class, CHARACTER), - exactBoxed(Boolean.class, BOOLEAN), - exactBoxed(Byte.class, BYTE), - exactBoxed(Double.class, DOUBLE), - exactBoxed(Float.class, FLOAT), - exactBoxed(Integer.class, INTEGER), - exactBoxed(Long.class, LONG), - exactBoxed(Short.class, SHORT), - exactRaw(Number.class, NUMBER), - exactRaw(String.class, STRING), - exactRaw(java.util.UUID.class, UUID) + CharacterAdapter.factory(), + BooleanAdapter.factory(), + ByteAdapter.factory(), + DoubleAdapter.factory(), + FloatAdapter.factory(), + IntegerAdapter.factory(), + LongAdapter.factory(), + ShortAdapter.factory(), + NumberAdapter.factory(), + StringAdapter.factory(), + UUIDAdapter.factory() ); private ScalarAdapters() { diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarKeyAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarKeyAdapters.java index 3c181a9..1b489f6 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarKeyAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarKeyAdapters.java @@ -2,43 +2,19 @@ import net.roxymc.jserialize.adapter.KeyAdapter; -import java.util.function.Function; - -import static net.roxymc.jserialize.adapter.KeyAdapter.Factory.exactBoxed; -import static net.roxymc.jserialize.adapter.KeyAdapter.Factory.exactRaw; -import static net.roxymc.jserialize.util.ObjectUtils.nonNull; - public final class ScalarKeyAdapters { - public static final KeyAdapter CHARACTER = keyAdapter(value -> { - if (value.length() != 1) { - throw new IllegalStateException("value must be a single character"); - } - - return value.charAt(0); - }); - public static final KeyAdapter BOOLEAN = keyAdapter(Boolean::parseBoolean); - public static final KeyAdapter BYTE = keyAdapter(Byte::parseByte); - public static final KeyAdapter DOUBLE = keyAdapter(Double::parseDouble); - public static final KeyAdapter FLOAT = keyAdapter(Float::parseFloat); - public static final KeyAdapter INTEGER = keyAdapter(Integer::parseInt); - public static final KeyAdapter LONG = keyAdapter(Long::parseLong); - public static final KeyAdapter SHORT = keyAdapter(Short::parseShort); - public static final KeyAdapter NUMBER = keyAdapter(NumberAdapter::parseNumber); - public static final KeyAdapter STRING = keyAdapter(Function.identity()); - public static final KeyAdapter UUID = keyAdapter(java.util.UUID::fromString); - private static final KeyAdapter.Factory FACTORY = KeyAdapter.Factory.composite( - exactBoxed(Character.class, CHARACTER), - exactBoxed(Boolean.class, BOOLEAN), - exactBoxed(Byte.class, BYTE), - exactBoxed(Double.class, DOUBLE), - exactBoxed(Float.class, FLOAT), - exactBoxed(Integer.class, INTEGER), - exactBoxed(Long.class, LONG), - exactBoxed(Short.class, SHORT), - exactRaw(Number.class, NUMBER), - exactRaw(String.class, STRING), - exactRaw(java.util.UUID.class, UUID) + CharacterKeyAdapter.factory(), + BooleanKeyAdapter.factory(), + ByteKeyAdapter.factory(), + DoubleKeyAdapter.factory(), + FloatKeyAdapter.factory(), + IntegerKeyAdapter.factory(), + LongKeyAdapter.factory(), + ShortKeyAdapter.factory(), + NumberKeyAdapter.factory(), + StringKeyAdapter.factory(), + UUIDKeyAdapter.factory() ); private ScalarKeyAdapters() { @@ -47,8 +23,4 @@ private ScalarKeyAdapters() { public static KeyAdapter.Factory factory() { return FACTORY; } - - private static KeyAdapter keyAdapter(Function function) { - return KeyAdapter.of(value -> value != null ? function.apply(value) : null, value -> nonNull(value, "value").toString()); - } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortAdapter.java index 7e5f2ef..9bd2a6c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortAdapter.java @@ -4,9 +4,21 @@ import java.io.IOException; -final class ShortAdapter extends AbstractNumberAdapter { - ShortAdapter() { - super(Short.class); +public final class ShortAdapter extends AbstractNumberAdapter { + public static final ShortAdapter PRIMITIVE = new ShortAdapter(short.class); + public static final ShortAdapter BOXED = new ShortAdapter(Short.class); + + private static final Factory FACTORY = Factory.composite( + Factory.exact(PRIMITIVE), + Factory.exact(BOXED) + ); + + private ShortAdapter(Class type) { + super(type); + } + + public static Factory factory() { + return FACTORY; } @Override @@ -14,7 +26,7 @@ protected Short fromLong(long value) { short shortValue = (short) value; if (shortValue != value) { - throw new ArithmeticException(numberType.getSimpleName() + " overflow: " + value); + throw new ArithmeticException(rawType.getSimpleName() + " overflow: " + value); } return shortValue; diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortKeyAdapter.java new file mode 100644 index 0000000..4935421 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class ShortKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Short.class); + + public static final KeyAdapter INSTANCE = new ShortKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private ShortKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected Short parse(String value) { + return Short.parseShort(value); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringAdapter.java index 7d26efb..fe722d9 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringAdapter.java @@ -5,31 +5,56 @@ import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; +import net.roxymc.jserialize.token.TokenType; import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; import java.io.IOException; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; +public final class StringAdapter implements TypeAdapter { + private static final TypeRef TYPE = TypeRef.of(String.class); + + public static final TypeAdapter INSTANCE = new StringAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private StringAdapter() { + } + + public static Factory factory() { + return FACTORY; + } -final class StringAdapter implements TypeAdapter { @Override - public @Nullable String read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { - checkAssignable(String.class, type.getRawType()); + public @Nullable String read(Reader reader, ReadContext ctx) throws IOException { + TokenType tokenType = reader.peek(); - if (reader.peek() == TokenTypes.NULL) { + if (tokenType == TokenTypes.NULL) { reader.readNull(); return null; } - return reader.readString(); + if (tokenType == TokenTypes.STRING || tokenType == TokenTypes.NUMERIC) { + return reader.readString(); + } + + if (tokenType == TokenTypes.INT) { + return String.valueOf(reader.readInt()); + } + + if (tokenType == TokenTypes.LONG) { + return String.valueOf(reader.readLong()); + } + + if (tokenType == TokenTypes.DOUBLE) { + return String.valueOf(reader.readDouble()); + } + + throw new IllegalStateException("Expected string token, but found: " + tokenType); } @Override - public void write(Writer writer, TypeRef type, @Nullable String value, WriteContext ctx) throws IOException { - checkAssignable(String.class, type.getRawType()); - + public void write(Writer writer, @Nullable String value, WriteContext ctx) throws IOException { if (value == null) { writer.writeNull(); return; @@ -37,4 +62,9 @@ public void write(Writer writer, TypeRef type, @Nullable Strin writer.writeString(value); } + + @Override + public TypeRef type() { + return TYPE; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringKeyAdapter.java new file mode 100644 index 0000000..be84a66 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +public final class StringKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(String.class); + + public static final KeyAdapter INSTANCE = new StringKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private StringKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected String parse(String value) { + return value; + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDAdapter.java index 6ca0d09..23693f6 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDAdapter.java @@ -12,13 +12,21 @@ import java.io.IOException; import java.util.UUID; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; +public final class UUIDAdapter implements TypeAdapter { + private static final TypeRef TYPE = TypeRef.of(UUID.class); -final class UUIDAdapter implements TypeAdapter { - @Override - public @Nullable UUID read(Reader reader, TypeRef type, ReadContext context) throws IOException { - checkAssignable(UUID.class, type.getRawType()); + public static final TypeAdapter INSTANCE = new UUIDAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private UUIDAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + @Override + public @Nullable UUID read(Reader reader, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return null; @@ -28,9 +36,7 @@ final class UUIDAdapter implements TypeAdapter { } @Override - public void write(Writer writer, TypeRef type, @Nullable UUID value, WriteContext context) throws IOException { - checkAssignable(UUID.class, type.getRawType()); - + public void write(Writer writer, @Nullable UUID value, WriteContext ctx) throws IOException { if (value == null) { writer.writeNull(); return; @@ -38,4 +44,9 @@ public void write(Writer writer, TypeRef type, @Nullable UUID va writer.writeString(value.toString()); } + + @Override + public TypeRef type() { + return TYPE; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDKeyAdapter.java new file mode 100644 index 0000000..bd595d6 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDKeyAdapter.java @@ -0,0 +1,31 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.AbstractKeyAdapter; +import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; + +import java.util.UUID; + +public final class UUIDKeyAdapter extends AbstractKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(UUID.class); + + public static final KeyAdapter INSTANCE = new UUIDKeyAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private UUIDKeyAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected UUID parse(String value) { + return UUID.fromString(value); + } + + @Override + public TypeRef type() { + return TYPE; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java similarity index 58% rename from core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapter.java rename to core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java index 3d2f20d..ae952ad 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java @@ -15,35 +15,28 @@ import java.time.temporal.TemporalQuery; import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; -final class TemporalAdapter implements TypeAdapter { - private final Class type; +abstract class AbstractTemporalAdapter implements TypeAdapter, TemporalQuery { + private final TypeRef type; private final DateTimeFormatter formatter; - private final TemporalQuery query; - TemporalAdapter(Class type, DateTimeFormatter formatter, TemporalQuery query) { + protected AbstractTemporalAdapter(TypeRef type, DateTimeFormatter formatter) { this.type = nonNull(type, "type"); this.formatter = nonNull(formatter, "formatter"); - this.query = nonNull(query, "query"); } @Override - public @Nullable T read(Reader reader, TypeRef typeRef, ReadContext ctx) throws IOException { - checkAssignable(type, typeRef.getRawType()); - + public @Nullable T read(Reader reader, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return null; } - return formatter.parse(reader.readString(), query); + return formatter.parse(reader.readString(), this); } @Override - public void write(Writer writer, TypeRef typeRef, @Nullable T value, WriteContext ctx) throws IOException { - checkAssignable(type, typeRef.getRawType()); - + public void write(Writer writer, @Nullable T value, WriteContext ctx) throws IOException { if (value == null) { writer.writeNull(); return; @@ -51,4 +44,9 @@ public void write(Writer writer, TypeRef typeRef, @Nullable T value writer.writeString(formatter.format(value)); } + + @Override + public TypeRef type() { + return type; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalKeyAdapter.java similarity index 57% rename from core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapter.java rename to core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalKeyAdapter.java index d35aa58..83d0407 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalKeyAdapter.java @@ -1,6 +1,7 @@ package net.roxymc.jserialize.adapter.temporal; import net.roxymc.jserialize.adapter.KeyAdapter; +import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; import java.time.format.DateTimeFormatter; @@ -9,22 +10,27 @@ import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -final class TemporalKeyAdapter implements KeyAdapter { +abstract class AbstractTemporalKeyAdapter implements KeyAdapter, TemporalQuery { + private final TypeRef type; private final DateTimeFormatter formatter; - private final TemporalQuery query; - TemporalKeyAdapter(DateTimeFormatter formatter, TemporalQuery query) { + protected AbstractTemporalKeyAdapter(TypeRef type, DateTimeFormatter formatter) { + this.type = nonNull(type, "type"); this.formatter = nonNull(formatter, "formatter"); - this.query = nonNull(query, "query"); } @Override public @Nullable T decode(@Nullable String value) { - return value != null ? formatter.parse(value, query) : null; + return value != null ? formatter.parse(value, this) : null; } @Override public String encode(@Nullable T value) { return formatter.format(nonNull(value, "value")); } + + @Override + public TypeRef type() { + return type; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateAdapter.java index a0b8d63..0cd9152 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateAdapter.java @@ -12,22 +12,33 @@ import java.time.Instant; import java.util.Date; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; +public final class DateAdapter implements TypeAdapter { + private static final TypeRef TYPE = TypeRef.of(Date.class); -final class DateAdapter implements TypeAdapter { - @Override - public @Nullable Date read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { - checkAssignable(Date.class, type.getRawType()); + public static final TypeAdapter INSTANCE = new DateAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private DateAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + @Override + public @Nullable Date read(Reader reader, ReadContext ctx) throws IOException { Instant instant = ctx.read(reader, Instant.class); return instant != null ? Date.from(instant) : null; } @Override - public void write(Writer writer, TypeRef type, @Nullable Date value, WriteContext ctx) throws IOException { - checkAssignable(Date.class, type.getRawType()); - + public void write(Writer writer, @Nullable Date value, WriteContext ctx) throws IOException { Instant instant = value != null ? value.toInstant() : null; ctx.write(writer, Instant.class, instant); } + + @Override + public TypeRef type() { + return TYPE; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateKeyAdapter.java index 073ed44..8683d5b 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateKeyAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateKeyAdapter.java @@ -1,7 +1,6 @@ package net.roxymc.jserialize.adapter.temporal; import net.roxymc.jserialize.adapter.KeyAdapter; -import net.roxymc.jserialize.adapter.TypeAdapters; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; @@ -10,19 +9,12 @@ import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -final class DateKeyAdapter implements KeyAdapter { - static final KeyAdapter.Factory FACTORY = new Factory() { - @Override - public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { - if (!Date.class.isAssignableFrom(type.getRawType())) { - return null; - } +public final class DateKeyAdapter implements KeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Date.class); - @SuppressWarnings("unchecked") - KeyAdapter adapter = (KeyAdapter) new DateKeyAdapter(adapters.getKeyOrThrow(Instant.class)); - return adapter; - } - }; + private static final Factory FACTORY = Factory.exactRaw(Date.class, ($, adapters) -> + new DateKeyAdapter(adapters.getKeyOrThrow(Instant.class)) + ); private final KeyAdapter instantAdapter; @@ -30,6 +22,10 @@ private DateKeyAdapter(KeyAdapter instantAdapter) { this.instantAdapter = nonNull(instantAdapter, "instantAdapter"); } + public static Factory factory() { + return FACTORY; + } + @Override public @Nullable Date decode(@Nullable String value) { Instant instant = instantAdapter.decode(value); @@ -41,4 +37,9 @@ public String encode(@Nullable Date value) { Instant instant = value != null ? value.toInstant() : null; return instantAdapter.encode(instant); } + + @Override + public TypeRef type() { + return TYPE; + } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java new file mode 100644 index 0000000..395c8ae --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class InstantAdapter extends AbstractTemporalAdapter { + private static final TypeRef TYPE = TypeRef.of(Instant.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_INSTANT); + + private InstantAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new InstantAdapter(formatter)); + } + + @Override + public Instant queryFrom(TemporalAccessor temporal) { + return Instant.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantKeyAdapter.java new file mode 100644 index 0000000..b578f57 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/InstantKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class InstantKeyAdapter extends AbstractTemporalKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Instant.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_INSTANT); + + private InstantKeyAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new InstantKeyAdapter(formatter)); + } + + @Override + public Instant queryFrom(TemporalAccessor temporal) { + return Instant.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java new file mode 100644 index 0000000..48045a4 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class LocalDateAdapter extends AbstractTemporalAdapter { + private static final TypeRef TYPE = TypeRef.of(LocalDate.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_DATE); + + private LocalDateAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new LocalDateAdapter(formatter)); + } + + @Override + public LocalDate queryFrom(TemporalAccessor temporal) { + return LocalDate.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateKeyAdapter.java new file mode 100644 index 0000000..eee4344 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class LocalDateKeyAdapter extends AbstractTemporalKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(LocalDate.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_DATE); + + private LocalDateKeyAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new LocalDateKeyAdapter(formatter)); + } + + @Override + public LocalDate queryFrom(TemporalAccessor temporal) { + return LocalDate.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java new file mode 100644 index 0000000..e70bd29 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class LocalDateTimeAdapter extends AbstractTemporalAdapter { + private static final TypeRef TYPE = TypeRef.of(LocalDateTime.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + + private LocalDateTimeAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new LocalDateTimeAdapter(formatter)); + } + + @Override + public LocalDateTime queryFrom(TemporalAccessor temporal) { + return LocalDateTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeKeyAdapter.java new file mode 100644 index 0000000..19300cd --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalDateTimeKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class LocalDateTimeKeyAdapter extends AbstractTemporalKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(LocalDateTime.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + + private LocalDateTimeKeyAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new LocalDateTimeKeyAdapter(formatter)); + } + + @Override + public LocalDateTime queryFrom(TemporalAccessor temporal) { + return LocalDateTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java new file mode 100644 index 0000000..bd9e6db --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class LocalTimeAdapter extends AbstractTemporalAdapter { + private static final TypeRef TYPE = TypeRef.of(LocalTime.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_TIME); + + private LocalTimeAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new LocalTimeAdapter(formatter)); + } + + @Override + public LocalTime queryFrom(TemporalAccessor temporal) { + return LocalTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeKeyAdapter.java new file mode 100644 index 0000000..d2a742c --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/LocalTimeKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class LocalTimeKeyAdapter extends AbstractTemporalKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(LocalTime.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_LOCAL_TIME); + + private LocalTimeKeyAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new LocalTimeKeyAdapter(formatter)); + } + + @Override + public LocalTime queryFrom(TemporalAccessor temporal) { + return LocalTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java new file mode 100644 index 0000000..9e046cc --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class OffsetDateTimeAdapter extends AbstractTemporalAdapter { + private static final TypeRef TYPE = TypeRef.of(OffsetDateTime.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + + private OffsetDateTimeAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new OffsetDateTimeAdapter(formatter)); + } + + @Override + public OffsetDateTime queryFrom(TemporalAccessor temporal) { + return OffsetDateTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeKeyAdapter.java new file mode 100644 index 0000000..c0aaf18 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/OffsetDateTimeKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class OffsetDateTimeKeyAdapter extends AbstractTemporalKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(OffsetDateTime.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + + private OffsetDateTimeKeyAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new OffsetDateTimeKeyAdapter(formatter)); + } + + @Override + public OffsetDateTime queryFrom(TemporalAccessor temporal) { + return OffsetDateTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java index 1b10616..0a2b9af 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java @@ -2,25 +2,15 @@ import net.roxymc.jserialize.adapter.TypeAdapter; -import java.time.*; -import java.time.format.DateTimeFormatter; -import java.time.temporal.Temporal; -import java.time.temporal.TemporalQuery; -import java.util.Date; - -import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactRaw; - public final class TemporalAdapters { - public static final TypeAdapter DATE = new DateAdapter(); - private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( - exactRaw(Date.class, DATE), - factory(Instant.class, DateTimeFormatter.ISO_INSTANT, Instant::from), - factory(LocalDate.class, DateTimeFormatter.ISO_LOCAL_DATE, LocalDate::from), - factory(LocalTime.class, DateTimeFormatter.ISO_LOCAL_TIME, LocalTime::from), - factory(LocalDateTime.class, DateTimeFormatter.ISO_LOCAL_DATE_TIME, LocalDateTime::from), - factory(OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME, OffsetDateTime::from), - factory(ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME, ZonedDateTime::from) + DateAdapter.factory(), + InstantAdapter.factory(), + LocalDateAdapter.factory(), + LocalDateTimeAdapter.factory(), + LocalTimeAdapter.factory(), + OffsetDateTimeAdapter.factory(), + ZonedDateTimeAdapter.factory() ); private TemporalAdapters() { @@ -29,10 +19,4 @@ private TemporalAdapters() { public static TypeAdapter.Factory factory() { return FACTORY; } - - public static TypeAdapter.Factory factory( - Class type, DateTimeFormatter formatter, TemporalQuery query - ) { - return exactRaw(type, new TemporalAdapter<>(type, formatter, query)); - } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java index 01015f9..5863806 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java @@ -2,22 +2,15 @@ import net.roxymc.jserialize.adapter.KeyAdapter; -import java.time.*; -import java.time.format.DateTimeFormatter; -import java.time.temporal.Temporal; -import java.time.temporal.TemporalQuery; - -import static net.roxymc.jserialize.adapter.KeyAdapter.Factory.exactRaw; - public final class TemporalKeyAdapters { private static final KeyAdapter.Factory FACTORY = KeyAdapter.Factory.composite( - DateKeyAdapter.FACTORY, - factory(Instant.class, DateTimeFormatter.ISO_INSTANT, Instant::from), - factory(LocalDate.class, DateTimeFormatter.ISO_LOCAL_DATE, LocalDate::from), - factory(LocalTime.class, DateTimeFormatter.ISO_LOCAL_TIME, LocalTime::from), - factory(LocalDateTime.class, DateTimeFormatter.ISO_LOCAL_DATE_TIME, LocalDateTime::from), - factory(OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME, OffsetDateTime::from), - factory(ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME, ZonedDateTime::from) + DateKeyAdapter.factory(), + InstantKeyAdapter.factory(), + LocalDateKeyAdapter.factory(), + LocalTimeKeyAdapter.factory(), + LocalDateTimeKeyAdapter.factory(), + OffsetDateTimeKeyAdapter.factory(), + ZonedDateTimeKeyAdapter.factory() ); private TemporalKeyAdapters() { @@ -26,10 +19,4 @@ private TemporalKeyAdapters() { public static KeyAdapter.Factory factory() { return FACTORY; } - - public static KeyAdapter.Factory factory( - Class type, DateTimeFormatter formatter, TemporalQuery query - ) { - return exactRaw(type, new TemporalKeyAdapter<>(formatter, query)); - } } diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java new file mode 100644 index 0000000..aaf7c4c --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class ZonedDateTimeAdapter extends AbstractTemporalAdapter { + private static final TypeRef TYPE = TypeRef.of(ZonedDateTime.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_ZONED_DATE_TIME); + + private ZonedDateTimeAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new ZonedDateTimeAdapter(formatter)); + } + + @Override + public ZonedDateTime queryFrom(TemporalAccessor temporal) { + return ZonedDateTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeKeyAdapter.java new file mode 100644 index 0000000..2c2554f --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/ZonedDateTimeKeyAdapter.java @@ -0,0 +1,29 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.type.TypeRef; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public final class ZonedDateTimeKeyAdapter extends AbstractTemporalKeyAdapter { + private static final TypeRef TYPE = TypeRef.of(ZonedDateTime.class); + private static final Factory FACTORY = factory(DateTimeFormatter.ISO_ZONED_DATE_TIME); + + private ZonedDateTimeKeyAdapter(DateTimeFormatter formatter) { + super(TYPE, formatter); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(DateTimeFormatter formatter) { + return Factory.exact(new ZonedDateTimeKeyAdapter(formatter)); + } + + @Override + public ZonedDateTime queryFrom(TemporalAccessor temporal) { + return ZonedDateTime.from(temporal); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/type/TypeRef.java b/core/src/main/java/net/roxymc/jserialize/type/TypeRef.java index c18bbd3..26ec303 100644 --- a/core/src/main/java/net/roxymc/jserialize/type/TypeRef.java +++ b/core/src/main/java/net/roxymc/jserialize/type/TypeRef.java @@ -8,6 +8,8 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + public abstract class TypeRef { private final AnnotatedType annotatedType; private final Class rawType; @@ -20,7 +22,7 @@ protected TypeRef() { @SuppressWarnings("unchecked") private TypeRef(AnnotatedType type) { - this.annotatedType = GenericTypeReflector.toCanonical(type); + this.annotatedType = GenericTypeReflector.toCanonical(nonNull(type, "type")); this.rawType = (Class) GenericTypeReflector.erase(getType()); } diff --git a/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java b/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java index 6f6b98a..ed10186 100644 --- a/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java +++ b/core/src/main/java/net/roxymc/jserialize/util/TypeUtils.java @@ -1,10 +1,32 @@ package net.roxymc.jserialize.util; +import net.roxymc.jserialize.type.TypeRef; + +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; + +import static io.leangen.geantyref.GenericTypeReflector.capture; +import static io.leangen.geantyref.GenericTypeReflector.getExactSuperType; + public final class TypeUtils { + private TypeUtils() { } + public static boolean isEnum(TypeRef type) { + return isEnum(type.getRawType()); + } + public static boolean isEnum(Class type) { return type.isEnum() || (type.getSuperclass() != null && type.getSuperclass().isEnum()); } + + public static AnnotatedParameterizedType resolveTypeParameters(AnnotatedType subtype, Class supertype) { + AnnotatedType type = getExactSuperType(capture(subtype), supertype); + if (!(type instanceof AnnotatedParameterizedType)) { + throw new IllegalStateException(subtype.getType().getTypeName() + " must be a parameterized " + supertype.getTypeName()); + } + + return (AnnotatedParameterizedType) type; + } } diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java index 719aa9a..6bd1c76 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonTypeAdapters.java @@ -55,7 +55,7 @@ final class BsonTypeAdapters implements TypeAdapters { return ((WrappedTypeAdapter) codec).typeAdapter; } - return new WrappedCodec<>(codec); + return new WrappedCodec<>(type, codec); } @Override diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonUtils.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonUtils.java index c800b55..c58ea6e 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonUtils.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonUtils.java @@ -98,7 +98,7 @@ public void putAll(Map map, WriteContext ctx) throws IOException { BsonDocument result = new BsonDocument(); try (BsonDocumentWriter writer = new BsonDocumentWriter(result)) { - mapAdapter.write(new BsonWriterAdapter(writer), typeRef, map, ctx); + mapAdapter.write(new BsonWriterAdapter(writer), map, ctx); } document.putAll(result); @@ -110,10 +110,10 @@ public void putAll(Map map, WriteContext ctx) throws IOException { Reader readerAdapter = new StandardBsonReaderAdapter(reader); if (!(mapAdapter instanceof TypeAdapter.Mutable)) { - return mapAdapter.read(readerAdapter, typeRef, ctx); + return mapAdapter.read(readerAdapter, ctx); } - return ((TypeAdapter.Mutable>) mapAdapter).mutate(readerAdapter, typeRef, instance, ctx); + return ((TypeAdapter.Mutable>) mapAdapter).mutate(readerAdapter, instance, ctx); } } diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/WrappedCodec.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/WrappedCodec.java index 71e961a..522742f 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/WrappedCodec.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/WrappedCodec.java @@ -15,14 +15,16 @@ import java.io.IOException; final class WrappedCodec implements TypeAdapter { + private final TypeRef type; private final Codec codec; - WrappedCodec(Codec codec) { + WrappedCodec(TypeRef type, Codec codec) { + this.type = type; this.codec = codec; } @Override - public @Nullable T read(Reader reader, TypeRef type, ReadContext context) throws IOException { + public @Nullable T read(Reader reader, ReadContext context) throws IOException { if (reader.peek() == TokenTypes.NULL) { return null; } @@ -37,7 +39,7 @@ final class WrappedCodec implements TypeAdapter { } @Override - public void write(Writer writer, TypeRef type, @Nullable T value, WriteContext context) throws IOException { + public void write(Writer writer, @Nullable T value, WriteContext context) throws IOException { if (value == null) { writer.writeNull(); return; @@ -45,4 +47,9 @@ public void write(Writer writer, TypeRef type, @Nullable T value, W codec.encode(((BsonWriterAdapter) writer).writer, value, EncoderContext.builder().build()); } + + @Override + public TypeRef type() { + return type; + } } diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/WrappedTypeAdapter.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/WrappedTypeAdapter.java index 0d390b1..c3f859f 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/WrappedTypeAdapter.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/WrappedTypeAdapter.java @@ -44,7 +44,7 @@ final class WrappedTypeAdapter implements Codec { } try { - return typeAdapter.read(reader, type, readCtx); + return typeAdapter.read(reader, readCtx); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -55,7 +55,7 @@ public void encode(BsonWriter bsonWriter, T value, EncoderContext ctx) { Writer writer = new BsonWriterAdapter(bsonWriter); try { - typeAdapter.write(writer, type, value, writeCtx); + typeAdapter.write(writer, value, writeCtx); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java index 9c48037..8748c20 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java @@ -1,13 +1,13 @@ package net.roxymc.jserialize.format.bson.adapter; import net.roxymc.jserialize.adapter.TypeAdapters; -import net.roxymc.jserialize.format.bson.adapter.scalar.BsonScalarAdapters; -import net.roxymc.jserialize.format.bson.adapter.temporal.BsonTemporalAdapters; +import net.roxymc.jserialize.format.bson.adapter.scalar.BsonUUIDAdapter; +import net.roxymc.jserialize.format.bson.adapter.temporal.BsonInstantAdapter; public final class BsonTypeAdapters { public static final TypeAdapters DEFAULT = TypeAdapters.builder() - .add(BsonScalarAdapters.factory()) - .add(BsonTemporalAdapters.factory()) + .add(BsonUUIDAdapter.factory()) + .add(BsonInstantAdapter.factory()) .addAll(TypeAdapters.DEFAULT) .build(); diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonScalarAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonScalarAdapters.java deleted file mode 100644 index 514ea2a..0000000 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonScalarAdapters.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.roxymc.jserialize.format.bson.adapter.scalar; - -import net.roxymc.jserialize.adapter.TypeAdapter; - -public final class BsonScalarAdapters { - private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( - BsonUUIDAdapter.factory() - ); - - private BsonScalarAdapters() { - } - - public static TypeAdapter.Factory factory() { - return FACTORY; - } -} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonUUIDAdapter.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonUUIDAdapter.java index 30fb7ff..88ac49e 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonUUIDAdapter.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonUUIDAdapter.java @@ -16,10 +16,10 @@ import java.util.UUID; import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; public final class BsonUUIDAdapter implements TypeAdapter { - private static final TypeAdapter.Factory FACTORY = factory(UuidRepresentation.STANDARD); + private static final TypeRef TYPE = TypeRef.of(UUID.class); + private static final Factory FACTORY = factory(UuidRepresentation.STANDARD); private final UuidRepresentation uuidRepresentation; @@ -27,18 +27,16 @@ public BsonUUIDAdapter(UuidRepresentation uuidRepresentation) { this.uuidRepresentation = nonNull(uuidRepresentation, "uuidRepresentation"); } - public static TypeAdapter.Factory factory() { + public static Factory factory() { return FACTORY; } - public static TypeAdapter.Factory factory(UuidRepresentation uuidRepresentation) { - return TypeAdapter.Factory.exactRaw(UUID.class, new BsonUUIDAdapter(uuidRepresentation)); + public static Factory factory(UuidRepresentation uuidRepresentation) { + return Factory.exact(new BsonUUIDAdapter(uuidRepresentation)); } @Override - public @Nullable UUID read(Reader reader, TypeRef type, ReadContext context) throws IOException { - checkAssignable(UUID.class, type.getRawType()); - + public @Nullable UUID read(Reader reader, ReadContext context) throws IOException { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return null; @@ -48,9 +46,7 @@ public static TypeAdapter.Factory factory(UuidRepresentation uuidRepresentation) } @Override - public void write(Writer writer, TypeRef type, @Nullable UUID value, WriteContext context) throws IOException { - checkAssignable(UUID.class, type.getRawType()); - + public void write(Writer writer, @Nullable UUID value, WriteContext context) throws IOException { if (value == null) { writer.writeNull(); return; @@ -58,4 +54,9 @@ public void write(Writer writer, TypeRef type, @Nullable UUID va writer.write(BsonTokenTypes.BINARY, new BsonBinary(value, uuidRepresentation)); } + + @Override + public TypeRef type() { + return TYPE; + } } diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonInstantAdapter.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonInstantAdapter.java index 91ed566..fa4edf4 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonInstantAdapter.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonInstantAdapter.java @@ -13,13 +13,21 @@ import java.io.IOException; import java.time.Instant; -import static net.roxymc.jserialize.util.TypeChecks.checkAssignable; +public final class BsonInstantAdapter implements TypeAdapter { + private static final TypeRef TYPE = TypeRef.of(Instant.class); -final class BsonInstantAdapter implements TypeAdapter { - @Override - public @Nullable Instant read(Reader reader, TypeRef type, ReadContext ctx) throws IOException { - checkAssignable(Instant.class, type.getRawType()); + public static final TypeAdapter INSTANCE = new BsonInstantAdapter(); + private static final Factory FACTORY = Factory.exact(INSTANCE); + + private BsonInstantAdapter() { + } + + public static Factory factory() { + return FACTORY; + } + @Override + public @Nullable Instant read(Reader reader, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return null; @@ -29,9 +37,7 @@ final class BsonInstantAdapter implements TypeAdapter { } @Override - public void write(Writer writer, TypeRef type, @Nullable Instant value, WriteContext ctx) throws IOException { - checkAssignable(Instant.class, type.getRawType()); - + public void write(Writer writer, @Nullable Instant value, WriteContext ctx) throws IOException { if (value == null) { writer.writeNull(); return; @@ -39,4 +45,9 @@ public void write(Writer writer, TypeRef type, @Nullable Inst writer.write(BsonTokenTypes.DATE_TIME, value.toEpochMilli()); } + + @Override + public TypeRef type() { + return TYPE; + } } diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonTemporalAdapters.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonTemporalAdapters.java deleted file mode 100644 index 18174b4..0000000 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonTemporalAdapters.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.roxymc.jserialize.format.bson.adapter.temporal; - -import net.roxymc.jserialize.adapter.TypeAdapter; - -import java.time.Instant; - -import static net.roxymc.jserialize.adapter.TypeAdapter.Factory.exactRaw; - -public final class BsonTemporalAdapters { - public static final TypeAdapter INSTANT = new BsonInstantAdapter(); - - private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( - exactRaw(Instant.class, INSTANT) - ); - - private BsonTemporalAdapters() { - } - - public static TypeAdapter.Factory factory() { - return FACTORY; - } -} diff --git a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java index 9be1a0c..3111d3d 100644 --- a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java +++ b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateTypeAdapters.java @@ -31,7 +31,7 @@ final class ConfigurateTypeAdapters implements TypeAdapters { return ((TypeAdapterProvider) serializer).adapters.getOrThrow(type); } - return new WrappedTypeSerializer<>(serializer); + return new WrappedTypeSerializer<>(type, serializer); } @Override diff --git a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateUtils.java b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateUtils.java index 81466c0..1f53ac6 100644 --- a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateUtils.java +++ b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurateUtils.java @@ -65,7 +65,7 @@ public void put(String key, @Nullable ConfigurationNode value) { public void putAll(Map map, WriteContext ctx) throws IOException { CommentedConfigurationNode result = CommentedConfigurationNode.root(node.options()); - mapAdapter.write(newWriter0(result), typeRef, map, ctx); + mapAdapter.write(newWriter0(result), map, ctx); node.mergeFrom(result); } @@ -75,10 +75,10 @@ public void putAll(Map map, WriteContext ctx) throws IOException { Reader readerAdapter = newReader0(node); if (!(mapAdapter instanceof TypeAdapter.Mutable)) { - return mapAdapter.read(readerAdapter, typeRef, ctx); + return mapAdapter.read(readerAdapter, ctx); } - return ((TypeAdapter.Mutable>) mapAdapter).mutate(readerAdapter, typeRef, instance, ctx); + return ((TypeAdapter.Mutable>) mapAdapter).mutate(readerAdapter, instance, ctx); } @Override diff --git a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/TypeAdapterProvider.java b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/TypeAdapterProvider.java index 53a14a5..9e84d3d 100644 --- a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/TypeAdapterProvider.java +++ b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/TypeAdapterProvider.java @@ -39,7 +39,7 @@ public boolean hasTypeAdapter(AnnotatedType type) { try { ReadContext context = ReadContext.of(new ConfigurateTypeAdapters(node.options(), adapters), ConfigurateUtils.INSTANCE); - return typeAdapter.read(reader, typeRef, context); + return typeAdapter.read(reader, context); } catch (IOException e) { throw new SerializationException(e); } @@ -55,7 +55,7 @@ public void serialize(AnnotatedType type, @Nullable Object obj, ConfigurationNod try { WriteContext context = WriteContext.of(new ConfigurateTypeAdapters(node.options(), adapters), ConfigurateUtils.INSTANCE); - typeAdapter.write(writer, typeRef, obj, context); + typeAdapter.write(writer, obj, context); } catch (IOException e) { throw new SerializationException(e); } diff --git a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/WrappedTypeSerializer.java b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/WrappedTypeSerializer.java index eafe6e8..4a4832c 100644 --- a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/WrappedTypeSerializer.java +++ b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/WrappedTypeSerializer.java @@ -15,23 +15,30 @@ import java.io.IOException; final class WrappedTypeSerializer implements TypeAdapter { + private final TypeRef type; private final TypeSerializer serializer; - WrappedTypeSerializer(TypeSerializer serializer) { + WrappedTypeSerializer(TypeRef type, TypeSerializer serializer) { + this.type = type; this.serializer = serializer; } @Override - public T read(Reader reader, TypeRef type, ReadContext context) throws IOException { + public T read(Reader reader, ReadContext context) throws IOException { @SuppressWarnings("unchecked") TreeReader tokenReader = (TreeReader) reader; return serializer.deserialize(type.getAnnotatedType(), tokenReader.currentValue()); } @Override - public void write(Writer writer, TypeRef type, @Nullable T value, WriteContext context) throws IOException { + public void write(Writer writer, @Nullable T value, WriteContext context) throws IOException { @SuppressWarnings("unchecked") TreeWriter tokenWriter = (TreeWriter) writer; serializer.serialize(type.getAnnotatedType(), value, tokenWriter.currentValue()); } + + @Override + public TypeRef type() { + return type; + } } diff --git a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java index cc3b66b..1462b32 100644 --- a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java +++ b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonTypeAdapters.java @@ -32,7 +32,7 @@ final class GsonTypeAdapters implements TypeAdapters { return ((WrappedTypeAdapter) adapter).typeAdapter; } - return new WrappedGsonTypeAdapter<>(adapter); + return new WrappedGsonTypeAdapter<>(type, adapter); } @Override diff --git a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonUtils.java b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonUtils.java index 9315c0e..7af89bd 100644 --- a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonUtils.java +++ b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonUtils.java @@ -73,7 +73,7 @@ public void putAll(Map map, WriteContext ctx) throws IOException { JsonObject result; try (JsonTreeWriter writer = new JsonTreeWriter()) { - mapAdapter.write(new GsonWriterAdapter(writer), typeRef, map, ctx); + mapAdapter.write(new GsonWriterAdapter(writer), map, ctx); result = writer.get().getAsJsonObject(); } @@ -87,10 +87,10 @@ public void putAll(Map map, WriteContext ctx) throws IOException { Reader readerAdapter = new GsonReaderAdapter(reader); if (!(mapAdapter instanceof TypeAdapter.Mutable)) { - return mapAdapter.read(readerAdapter, typeRef, ctx); + return mapAdapter.read(readerAdapter, ctx); } - return ((TypeAdapter.Mutable>) mapAdapter).mutate(readerAdapter, typeRef, instance, ctx); + return ((TypeAdapter.Mutable>) mapAdapter).mutate(readerAdapter, instance, ctx); } } diff --git a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/TypeAdapterProvider.java b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/TypeAdapterProvider.java index ba821aa..ee8472e 100644 --- a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/TypeAdapterProvider.java +++ b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/TypeAdapterProvider.java @@ -26,6 +26,6 @@ public TypeAdapterProvider(TypeAdapters adapters) { } GsonTypeAdapters adapters = new GsonTypeAdapters(gson, this.adapters); - return new WrappedTypeAdapter<>(type, adapter, adapters); + return new WrappedTypeAdapter<>(adapter, adapters); } } diff --git a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/WrappedGsonTypeAdapter.java b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/WrappedGsonTypeAdapter.java index 02782c5..e6f4574 100644 --- a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/WrappedGsonTypeAdapter.java +++ b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/WrappedGsonTypeAdapter.java @@ -11,20 +11,27 @@ import java.io.IOException; final class WrappedGsonTypeAdapter implements TypeAdapter { + private final TypeRef type; private final com.google.gson.TypeAdapter adapter; - WrappedGsonTypeAdapter(com.google.gson.TypeAdapter adapter) { + WrappedGsonTypeAdapter(TypeRef type, com.google.gson.TypeAdapter adapter) { + this.type = type; this.adapter = adapter; } @Override - public T read(Reader reader, TypeRef type, ReadContext context) throws IOException { + public T read(Reader reader, ReadContext context) throws IOException { return adapter.read(((GsonReaderAdapter) reader).reader); } @Override - public void write(Writer writer, TypeRef type, @Nullable T value, WriteContext context) throws IOException { + public void write(Writer writer, @Nullable T value, WriteContext context) throws IOException { //noinspection DataFlowIssue - Gson allows null value adapter.write(((GsonWriterAdapter) writer).writer, value); } + + @Override + public TypeRef type() { + return type; + } } diff --git a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/WrappedTypeAdapter.java b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/WrappedTypeAdapter.java index a534012..1d1ee0b 100644 --- a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/WrappedTypeAdapter.java +++ b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/WrappedTypeAdapter.java @@ -8,20 +8,17 @@ import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.TypeAdapters; import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; import java.io.IOException; final class WrappedTypeAdapter extends com.google.gson.TypeAdapter { - private final TypeRef type; final TypeAdapter typeAdapter; private final ReadContext readCtx; private final WriteContext writeCtx; - WrappedTypeAdapter(TypeRef type, TypeAdapter typeAdapter, TypeAdapters typeAdapters) { - this.type = type; + WrappedTypeAdapter(TypeAdapter typeAdapter, TypeAdapters typeAdapters) { this.typeAdapter = typeAdapter; this.readCtx = ReadContext.of(typeAdapters, GsonUtils.INSTANCE); @@ -32,13 +29,13 @@ final class WrappedTypeAdapter extends com.google.gson.TypeAdapter { public @Nullable T read(JsonReader jsonReader) throws IOException { Reader reader = new GsonReaderAdapter(jsonReader); - return typeAdapter.read(reader, type, readCtx); + return typeAdapter.read(reader, readCtx); } @Override public void write(JsonWriter jsonWriter, @Nullable T value) throws IOException { Writer writer = new GsonWriterAdapter(jsonWriter); - typeAdapter.write(writer, type, value, writeCtx); + typeAdapter.write(writer, value, writeCtx); } }