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/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..96512a9 --- /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) { + for (TypeAdapter.Factory factory : factories) { + TypeAdapter adapter = factory.create(type); + + 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..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,47 +7,44 @@ 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); + TypeRef type(); + interface Factory { - static Factory predicate(Predicate> predicate, KeyAdapter adapter) { - return new PredicateKeyAdapterFactory(predicate, adapter); + static Factory where(Predicate> predicate, TypedFactory factory) { + return new PredicateKeyAdapterFactory(predicate, factory); } - static Factory polymorphic(Class type, KeyAdapter 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, KeyAdapter 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, KeyAdapter adapter) { - return predicate(subtype -> type.getType().equals(subtype.getType()), adapter); + static Factory exact(KeyAdapter adapter) { + return Factory.where(subtype -> adapter.type().getType().equals(subtype.getType()), ($1, $2) -> adapter); } - static Factory exactRaw(Class type, KeyAdapter adapter) { - return predicate(subtype -> type.equals(subtype.getRawType()), adapter); + static Factory exactRaw(Class type, TypedFactory factory) { + return where(subtype -> type.equals(subtype.getRawType()), factory); } - @Nullable KeyAdapter create(TypeRef type, TypeAdapters adapters); + static Factory composite(Factory... factories) { + return new CompositeKeyAdapterFactory(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 041fe8e..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 create(TypeRef type, TypeAdapters adapters) { + 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 57da131..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, TypeAdapters adapters) { + 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 c85e315..f811d09 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, 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..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,60 +11,54 @@ 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 exactRaw(Class type, TypedFactory factory) { + return where(subtype -> type.equals(subtype.getRawType()), factory); } - @Nullable TypeAdapter create(TypeRef type, TypeAdapters adapters); + static Factory composite(Factory... factories) { + return new CompositeTypeAdapterFactory(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/TypeAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java index 8472dfe..bb8623c 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/TypeAdapters.java @@ -1,22 +1,34 @@ 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.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; @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(TemporalAdapters.factory()) + .add(ArrayAdapter.factory()) .add(CollectionAdapter.factory()) .add(MapAdapter.factory()) .add(ObjectAdapter.annotatedFactory()) .addKey(ScalarKeyAdapters.factory()) .addKey(EnumKeyAdapter.factory()) + .addKey(TemporalKeyAdapters.factory()) .build(); static Builder builder() { @@ -61,6 +73,12 @@ default KeyAdapter getKeyOrThrow(TypeRef type) { throw new IllegalStateException("Could not find key adapter for " + type.getAnnotatedType()); } + @Override + @Nullable TypeAdapter create(TypeRef type); + + @Override + @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters); + @ApiStatus.NonExtendable interface Builder { Builder add(TypeAdapter.Factory factory); 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..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; @@ -17,8 +16,8 @@ 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.typeAdapters = new Registry<>(builder.typeAdapters, TypeAdapter.Factory::create); + this.keyAdapters = new Registry<>(builder.keyAdapters, (factory, type) -> factory.createKey(type, this)); } @Override @@ -35,6 +34,32 @@ private TypeAdaptersImpl(BuilderImpl builder) { return adapter; } + @Override + public @Nullable TypeAdapter create(TypeRef type) { + for (TypeAdapter.Factory factory : typeAdapters.factories) { + TypeAdapter adapter = factory.create(type); + + 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, adapters); + + if (adapter != null) { + return adapter; + } + } + + return null; + } + private static final class Registry { private final Map> cache = new ConcurrentHashMap<>(); private final Set factories; @@ -46,9 +71,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); @@ -80,11 +103,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/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 ec7e610..dd0e1c9 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, 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..d8d02be --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/array/ArrayAdapter.java @@ -0,0 +1,83 @@ +package net.roxymc.jserialize.adapter.array; + +import net.roxymc.jserialize.Reader; +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.*; +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.Array; +import java.util.ArrayList; +import java.util.List; + +public final class ArrayAdapter implements TypeAdapter { + 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 Factory factory() { + return FACTORY; + } + + @Override + public @Nullable Object read(Reader reader, ReadContext ctx) throws IOException { + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + TypeReader componentReader = ctx.typeAdapters().getOrThrow(componentType); + + reader.readArrayStart(); + + List<@Nullable Object> buffer = new ArrayList<>(); + + for (int index = 0; reader.peek() != TokenTypes.ARRAY_END; index++) { + buffer.add(componentReader.read(reader, ctx)); + } + + 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, @Nullable Object value, WriteContext ctx) throws IOException { + if (value == null) { + writer.writeNull(); + return; + } + + TypeWriter componentWriter = ctx.typeAdapters().getOrThrow(componentType); + + writer.writeArrayStart(); + + int length = Array.getLength(value); + + for (int i = 0; i < length; i++) { + componentWriter.write(writer, Array.get(value, i), ctx); + } + + writer.writeArrayEnd(); + } + + @Override + public TypeRef type() { + return arrayType; + } +} \ 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/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 new file mode 100644 index 0000000..c87da22 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicAdapters.java @@ -0,0 +1,19 @@ +package net.roxymc.jserialize.adapter.atomic; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +public final class AtomicAdapters { + private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( + AtomicBooleanAdapter.factory(), + AtomicIntegerAdapter.factory(), + AtomicLongAdapter.factory(), + AtomicReferenceAdapter.factory() + ); + + 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..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/AtomicReferenceAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java new file mode 100644 index 0000000..ee02ba1 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/atomic/AtomicReferenceAdapter.java @@ -0,0 +1,34 @@ +package net.roxymc.jserialize.adapter.atomic; + +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +public final class AtomicReferenceAdapter extends AbstractAtomicAdapter, V> { + @SuppressWarnings({"unchecked", "rawtypes"}) + private static final Factory FACTORY = Factory.exactRaw(AtomicReference.class, type -> new AtomicReferenceAdapter(type)); + + private AtomicReferenceAdapter(TypeRef> atomicType) { + super(atomicType); + } + + public static Factory factory() { + return FACTORY; + } + + @Override + protected AtomicReference<@Nullable V> createAtomic() { + return new AtomicReference<>(); + } + + @Override + protected @Nullable V getAtomic(AtomicReference<@Nullable V> atomic) { + return atomic.get(); + } + + @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/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..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 @@ -2,73 +2,61 @@ 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; 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; -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 { + public @Nullable Collection<@Nullable E> mutate(Reader reader, @Nullable Collection<@Nullable E> collection, ReadContext ctx) throws IOException { if (reader.peek() == TokenTypes.NULL) { reader.readNull(); + + if (collection != null) { + collection.clear(); + } + return collection; } - CollectionType collectionType = resolveCollectionType(type); - if (collection == null) { collection = collectionType.createCollection(providers); } - TypeAdapter<@NonNull E> elementAdapter = collectionType.elementAdapter(ctx.typeAdapters()); + TypeReader 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, ctx); collection.add(element); } @@ -79,30 +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 { + public void write(Writer writer, @Nullable Collection<@Nullable E> collection, WriteContext ctx) throws IOException { if (collection == null) { writer.writeNull(); return; } - CollectionType collectionType = resolveCollectionType(type); - - TypeAdapter<@NonNull E> elementAdapter = collectionType.elementAdapter(ctx.typeAdapters()); + TypeWriter elementWriter = ctx.typeAdapters().getOrThrow(collectionType.elementType); writer.writeArrayStart(); for (E element : collection) { - elementAdapter.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 13721fa..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 @@ -1,44 +1,30 @@ 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; 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); 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 "); - } + private @Nullable CollectionFactory collectionFactory; - 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]); } - 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..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 @@ -2,77 +2,65 @@ 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; 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; -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 { + 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(); + + if (map != null) { + map.clear(); + } + return map; } - MapType mapType = resolveMapType(type); - if (map == null) { map = mapType.createMap(providers); } - KeyAdapter<@NonNull K> keyAdapter = mapType.keyAdapter(ctx.typeAdapters()); - TypeAdapter<@NonNull V> valueAdapter = mapType.valueAdapter(ctx.typeAdapters()); + KeyDecoder keyDecoder = ctx.typeAdapters().getKeyOrThrow(mapType.keyType); + TypeReader 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, ctx.withKey(name)); map.put(key, value); } @@ -83,34 +71,29 @@ 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 { + 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); - - KeyAdapter<@NonNull K> keyAdapter = mapType.keyAdapter(ctx.typeAdapters()); - TypeAdapter<@NonNull V> valueAdapter = mapType.valueAdapter(ctx.typeAdapters()); + KeyEncoder keyEncoder = ctx.typeAdapters().getKeyOrThrow(mapType.keyType); + TypeWriter 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, 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 259da71..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 @@ -1,12 +1,7 @@ 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; @@ -17,18 +12,19 @@ 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); 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 parameterized"); + throw new IllegalStateException(mapType.getRawType() + " must be a parameterized Map"); } AnnotatedParameterizedType ptype = (AnnotatedParameterizedType) type; @@ -38,14 +34,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/ObjectAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/object/ObjectAdapter.java index d6b65f3..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 @@ -9,50 +9,56 @@ 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()); - 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(Predicate.not(TypeUtils::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( - type -> !TypeUtils.isPrimitive(type) && type.getAnnotatedType().isAnnotationPresent(JSerializable.class), - new ObjectAdapter<>(factory) + public static Factory annotatedFactory(ClassModel.Factory factory) { + return Factory.where( + type -> !type.getRawType().isPrimitive() && type.getAnnotatedType().isAnnotationPresent(JSerializable.class), + 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"); + } + reader.readNull(); - return value; + return null; } try { - ClassModel classModel = factory.create(type); @SuppressWarnings("NullableProblems") // IntelliJ has existential issues ObjectReader objectReader = new ObjectReader<>(classModel, type, value, ctx.formatUtils(), ctx); @@ -63,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); @@ -79,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 b2f2814..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 @@ -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; @@ -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; @@ -58,9 +57,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); @@ -150,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) ); } @@ -164,14 +163,14 @@ 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) { 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; @@ -186,7 +185,8 @@ 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 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 fb00b59..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 @@ -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; @@ -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 @@ -78,24 +77,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, 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..b4a3ade --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/AbstractNumberAdapter.java @@ -0,0 +1,98 @@ +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.TokenType; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; + +abstract class AbstractNumberAdapter implements TypeAdapter { + protected final TypeRef type; + protected final Class rawType; + + protected AbstractNumberAdapter(Class type) { + this.type = TypeRef.of(type); + this.rawType = type; + } + + @Override + public final @Nullable N read(Reader reader, ReadContext ctx) throws IOException { + TokenType tokenType = reader.peek(); + + if (tokenType == TokenTypes.NULL) { + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot read null into primitive " + rawType.getSimpleName()); + } + + 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 " + rawType.getSimpleName() + ": " + e.getMessage()); + } + } + + throw new IllegalStateException("Expected numeric token, but found: " + tokenType); + } + + protected N fromDouble(double value) { + if (!Double.isFinite(value)) { + throw new ArithmeticException(rawType.getSimpleName() + " must be finite: " + value); + } + + long longValue = (long) value; + + if (longValue != value) { + throw new ArithmeticException(rawType.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, @Nullable N value, WriteContext ctx) throws IOException { + if (value == null) { + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot write null for primitive " + rawType.getSimpleName()); + } + + writer.writeNull(); + return; + } + + write(writer, value); + } + + 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 new file mode 100644 index 0000000..78dfb96 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/BooleanAdapter.java @@ -0,0 +1,71 @@ +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; + +public final class BooleanAdapter implements TypeAdapter { + public static final TypeAdapter PRIMITIVE = new BooleanAdapter(boolean.class); + public static final TypeAdapter BOXED = new BooleanAdapter(Boolean.class); + + 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 (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, @Nullable Boolean value, WriteContext ctx) throws IOException { + if (value == null) { + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot write null for primitive " + rawType.getSimpleName()); + } + + writer.writeNull(); + return; + } + + 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 new file mode 100644 index 0000000..86d58c9 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ByteAdapter.java @@ -0,0 +1,45 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.io.IOException; + +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 + protected Byte fromLong(long value) { + byte byteValue = (byte) value; + + if (byteValue != value) { + throw new ArithmeticException(rawType.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/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 new file mode 100644 index 0000000..cfc47bb --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/CharacterAdapter.java @@ -0,0 +1,72 @@ +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; + +public final class CharacterAdapter implements TypeAdapter { + public static final TypeAdapter PRIMITIVE = new CharacterAdapter(char.class); + public static final TypeAdapter BOXED = new CharacterAdapter(Character.class); + + 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 (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot read null into primitive " + rawType.getSimpleName()); + } + + 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, @Nullable Character value, WriteContext ctx) throws IOException { + if (value == null) { + if (rawType.isPrimitive()) { + throw new IllegalStateException("Cannot write null for primitive " + rawType.getSimpleName()); + } + + writer.writeNull(); + return; + } + + 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 new file mode 100644 index 0000000..7e4b1e6 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/DoubleAdapter.java @@ -0,0 +1,50 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.io.IOException; + +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 + protected Double fromLong(long value) { + double doubleValue = (double) value; + + if ((long) doubleValue != value) { + throw new ArithmeticException(rawType.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/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 new file mode 100644 index 0000000..77336cf --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/EnumAdapter.java @@ -0,0 +1,58 @@ +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; + +public final class EnumAdapter> implements TypeAdapter { + @SuppressWarnings({"unchecked", "rawtypes"}) + private static final Factory FACTORY = Factory.where(TypeUtils::isEnum, EnumAdapter::new); + + private final TypeRef enumType; + private final Class enumClass; + + @SuppressWarnings("unchecked") + public EnumAdapter(TypeRef enumType) { + this.enumType = enumType; + this.enumClass = (Class) enumType.getRawType(); + } + + public static Factory factory() { + return FACTORY; + } + + @Override + public @Nullable E read(Reader reader, ReadContext ctx) throws IOException { + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + String name = reader.readString(); + + return Enum.valueOf(enumClass, name); + } + + @Override + public void write(Writer writer, @Nullable E value, WriteContext ctx) throws IOException { + if (value == null) { + writer.writeNull(); + return; + } + + 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 3871954..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 org.jspecify.annotations.Nullable; +import net.roxymc.jserialize.util.TypeUtils; -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 create(TypeRef type, TypeAdapters adapters) { - @SuppressWarnings("unchecked") - Class> enumType = (Class>) type.getRawType(); - if (!enumType.isEnum()) { - 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,18 +23,12 @@ 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; + 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 new file mode 100644 index 0000000..1c0add7 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/FloatAdapter.java @@ -0,0 +1,56 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.io.IOException; + +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 + protected Float fromLong(long value) { + float floatValue = (float) value; + + if ((long) floatValue != value) { + throw new ArithmeticException(rawType.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(rawType.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/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 new file mode 100644 index 0000000..22be758 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/IntegerAdapter.java @@ -0,0 +1,45 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; +import net.roxymc.jserialize.adapter.TypeAdapter; + +import java.io.IOException; + +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 + protected Integer fromLong(long value) { + int intValue = (int) value; + + if (intValue != value) { + throw new ArithmeticException(rawType.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/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 new file mode 100644 index 0000000..3c71415 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/LongAdapter.java @@ -0,0 +1,38 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; + +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 + 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/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 new file mode 100644 index 0000000..68bba0a --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/NumberAdapter.java @@ -0,0 +1,66 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; + +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); + } 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/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 new file mode 100644 index 0000000..a76bbdb --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ScalarAdapters.java @@ -0,0 +1,26 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +public final class ScalarAdapters { + private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( + CharacterAdapter.factory(), + BooleanAdapter.factory(), + ByteAdapter.factory(), + DoubleAdapter.factory(), + FloatAdapter.factory(), + IntegerAdapter.factory(), + LongAdapter.factory(), + ShortAdapter.factory(), + NumberAdapter.factory(), + StringAdapter.factory(), + UUIDAdapter.factory() + ); + + 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..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 @@ -1,46 +1,21 @@ 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.util.ObjectUtils.nonNull; public final class ScalarKeyAdapters { - private static final Map, KeyAdapter> ADAPTERS = new HashMap<>(); - - public static final KeyAdapter CHAR = keyAdapter(Character.class, 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; - } - }; + private static final KeyAdapter.Factory FACTORY = KeyAdapter.Factory.composite( + CharacterKeyAdapter.factory(), + BooleanKeyAdapter.factory(), + ByteKeyAdapter.factory(), + DoubleKeyAdapter.factory(), + FloatKeyAdapter.factory(), + IntegerKeyAdapter.factory(), + LongKeyAdapter.factory(), + ShortKeyAdapter.factory(), + NumberKeyAdapter.factory(), + StringKeyAdapter.factory(), + UUIDKeyAdapter.factory() + ); private ScalarKeyAdapters() { } @@ -48,16 +23,4 @@ private ScalarKeyAdapters() { 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; - } } 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..9bd2a6c --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/ShortAdapter.java @@ -0,0 +1,44 @@ +package net.roxymc.jserialize.adapter.scalar; + +import net.roxymc.jserialize.Writer; + +import java.io.IOException; + +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 + protected Short fromLong(long value) { + short shortValue = (short) value; + + if (shortValue != value) { + throw new ArithmeticException(rawType.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/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 new file mode 100644 index 0000000..fe722d9 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/StringAdapter.java @@ -0,0 +1,70 @@ +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.TokenType; +import net.roxymc.jserialize.token.TokenTypes; +import net.roxymc.jserialize.type.TypeRef; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; + +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; + } + + @Override + public @Nullable String read(Reader reader, ReadContext ctx) throws IOException { + TokenType tokenType = reader.peek(); + + if (tokenType == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + 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, @Nullable String value, WriteContext ctx) throws IOException { + if (value == null) { + writer.writeNull(); + return; + } + + 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 new file mode 100644 index 0000000..23693f6 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/scalar/UUIDAdapter.java @@ -0,0 +1,52 @@ +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; + +public final class UUIDAdapter implements TypeAdapter { + private static final TypeRef TYPE = TypeRef.of(UUID.class); + + 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; + } + + return UUID.fromString(reader.readString()); + } + + @Override + public void write(Writer writer, @Nullable UUID value, WriteContext ctx) throws IOException { + if (value == null) { + writer.writeNull(); + return; + } + + 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/AbstractTemporalAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java new file mode 100644 index 0000000..ae952ad --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalAdapter.java @@ -0,0 +1,52 @@ +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.TemporalQuery; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +abstract class AbstractTemporalAdapter implements TypeAdapter, TemporalQuery { + private final TypeRef type; + private final DateTimeFormatter formatter; + + protected AbstractTemporalAdapter(TypeRef type, DateTimeFormatter formatter) { + this.type = nonNull(type, "type"); + this.formatter = nonNull(formatter, "formatter"); + } + + @Override + public @Nullable T read(Reader reader, ReadContext ctx) throws IOException { + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + return formatter.parse(reader.readString(), this); + } + + @Override + public void write(Writer writer, @Nullable T value, WriteContext ctx) throws IOException { + if (value == null) { + writer.writeNull(); + return; + } + + writer.writeString(formatter.format(value)); + } + + @Override + public TypeRef type() { + return type; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalKeyAdapter.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalKeyAdapter.java new file mode 100644 index 0000000..83d0407 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/AbstractTemporalKeyAdapter.java @@ -0,0 +1,36 @@ +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; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalQuery; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +abstract class AbstractTemporalKeyAdapter implements KeyAdapter, TemporalQuery { + private final TypeRef type; + private final DateTimeFormatter formatter; + + protected AbstractTemporalKeyAdapter(TypeRef type, DateTimeFormatter formatter) { + this.type = nonNull(type, "type"); + this.formatter = nonNull(formatter, "formatter"); + } + + @Override + public @Nullable T decode(@Nullable String value) { + 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 new file mode 100644 index 0000000..0cd9152 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateAdapter.java @@ -0,0 +1,44 @@ +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; + +public final class DateAdapter implements TypeAdapter { + private static final TypeRef TYPE = TypeRef.of(Date.class); + + 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, @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 new file mode 100644 index 0000000..8683d5b --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/DateKeyAdapter.java @@ -0,0 +1,45 @@ +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.Instant; +import java.util.Date; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class DateKeyAdapter implements KeyAdapter { + private static final TypeRef TYPE = TypeRef.of(Date.class); + + private static final Factory FACTORY = Factory.exactRaw(Date.class, ($, adapters) -> + new DateKeyAdapter(adapters.getKeyOrThrow(Instant.class)) + ); + + private final KeyAdapter instantAdapter; + + 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); + 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); + } + + @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 new file mode 100644 index 0000000..0a2b9af --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalAdapters.java @@ -0,0 +1,22 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.TypeAdapter; + +public final class TemporalAdapters { + private static final TypeAdapter.Factory FACTORY = TypeAdapter.Factory.composite( + DateAdapter.factory(), + 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/TemporalKeyAdapters.java b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java new file mode 100644 index 0000000..5863806 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/adapter/temporal/TemporalKeyAdapters.java @@ -0,0 +1,22 @@ +package net.roxymc.jserialize.adapter.temporal; + +import net.roxymc.jserialize.adapter.KeyAdapter; + +public final class TemporalKeyAdapters { + private static final KeyAdapter.Factory FACTORY = KeyAdapter.Factory.composite( + DateKeyAdapter.factory(), + InstantKeyAdapter.factory(), + LocalDateKeyAdapter.factory(), + LocalTimeKeyAdapter.factory(), + LocalDateTimeKeyAdapter.factory(), + OffsetDateTimeKeyAdapter.factory(), + ZonedDateTimeKeyAdapter.factory() + ); + + private TemporalKeyAdapters() { + } + + public static KeyAdapter.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..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/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/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/type/TypeRef.java b/core/src/main/java/net/roxymc/jserialize/type/TypeRef.java index 21736a1..26ec303 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,29 @@ 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); +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; +public abstract class TypeRef { 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(nonNull(type, "type")); + this.rawType = (Class) GenericTypeReflector.erase(getType()); } public static TypeRef of(Class type) { @@ -46,29 +47,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 +87,6 @@ public final boolean equals(Object obj) { @Override public final String toString() { - return "TypeToken[" + getType() + "]"; + return "TypeRef[" + getType() + "]"; } } 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..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,31 +1,32 @@ package net.roxymc.jserialize.util; -import io.leangen.geantyref.GenericTypeReflector; import net.roxymc.jserialize.type.TypeRef; +import java.lang.reflect.AnnotatedParameterizedType; import java.lang.reflect.AnnotatedType; -import java.lang.reflect.Type; + +import static io.leangen.geantyref.GenericTypeReflector.capture; +import static io.leangen.geantyref.GenericTypeReflector.getExactSuperType; public final class TypeUtils { + private TypeUtils() { } - public static boolean isPrimitive(TypeRef type) { - return isPrimitive(type.getRawType()); + public static boolean isEnum(TypeRef type) { + return isEnum(type.getRawType()); } - public static boolean isPrimitive(Class type) { - return !Object.class.isAssignableFrom(type); + public static boolean isEnum(Class type) { + return type.isEnum() || (type.getSuperclass() != null && type.getSuperclass().isEnum()); } - public static AnnotatedType box(AnnotatedType type) { - Type rawType = type.getType(); - Type boxedType = GenericTypeReflector.box(rawType); - - if (rawType == boxedType) { - return type; + 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 GenericTypeReflector.annotate(boxedType, type.getAnnotations()); + 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 7cc8f88..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 @@ -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,36 +22,54 @@ 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; } - return new WrappedCodec<>(codec); + return new WrappedCodec<>(type, codec); } @Override public @Nullable KeyAdapter getKey(TypeRef type) { return adapters.getKey(type); } + + @Override + public @Nullable TypeAdapter create(TypeRef type) { + return get(type); + } + + @Override + public @Nullable KeyAdapter createKey(TypeRef type, TypeAdapters adapters) { + return adapters.createKey(type, adapters); + } } 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 new file mode 100644 index 0000000..8748c20 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/BsonTypeAdapters.java @@ -0,0 +1,16 @@ +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.temporal.BsonInstantAdapter; + +public final class BsonTypeAdapters { + public static final TypeAdapters DEFAULT = TypeAdapters.builder() + .add(BsonUUIDAdapter.factory()) + .add(BsonInstantAdapter.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..88ac49e --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/scalar/BsonUUIDAdapter.java @@ -0,0 +1,62 @@ +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; + +public final class BsonUUIDAdapter implements TypeAdapter { + private static final TypeRef TYPE = TypeRef.of(UUID.class); + private static final Factory FACTORY = factory(UuidRepresentation.STANDARD); + + private final UuidRepresentation uuidRepresentation; + + public BsonUUIDAdapter(UuidRepresentation uuidRepresentation) { + this.uuidRepresentation = nonNull(uuidRepresentation, "uuidRepresentation"); + } + + public static Factory factory() { + return FACTORY; + } + + public static Factory factory(UuidRepresentation uuidRepresentation) { + return Factory.exact(new BsonUUIDAdapter(uuidRepresentation)); + } + + @Override + public @Nullable UUID read(Reader reader, ReadContext context) throws IOException { + if (reader.peek() == TokenTypes.NULL) { + reader.readNull(); + return null; + } + + return reader.read(BsonTokenTypes.BINARY).asUuid(uuidRepresentation); + } + + @Override + public void write(Writer writer, @Nullable UUID value, WriteContext context) throws IOException { + if (value == null) { + writer.writeNull(); + return; + } + + 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/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 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..fa4edf4 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/adapter/temporal/BsonInstantAdapter.java @@ -0,0 +1,53 @@ +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; + +public final class BsonInstantAdapter implements TypeAdapter { + private static final TypeRef TYPE = TypeRef.of(Instant.class); + + 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; + } + + return Instant.ofEpochMilli(reader.read(BsonTokenTypes.DATE_TIME)); + } + + @Override + public void write(Writer writer, @Nullable Instant value, WriteContext ctx) throws IOException { + if (value == null) { + writer.writeNull(); + return; + } + + 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/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 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..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 @@ -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,20 +19,33 @@ 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); + return new WrappedTypeSerializer<>(type, serializer); } @Override public @Nullable KeyAdapter getKey(TypeRef type) { return adapters.getKey(type); } + + @Override + public @Nullable TypeAdapter create(TypeRef type) { + 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/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 bb7d30e..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 @@ -17,20 +17,36 @@ 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; } - return new WrappedGsonTypeAdapter<>(adapter); + return new WrappedGsonTypeAdapter<>(type, adapter); } @Override public @Nullable KeyAdapter getKey(TypeRef type) { return adapters.getKey(type); } + + @Override + public @Nullable TypeAdapter create(TypeRef type) { + 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/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); } }