diff --git a/core/src/main/java/net/roxymc/jserialize/AbstractReader.java b/core/src/main/java/net/roxymc/jserialize/AbstractReader.java index e80e75b..d7983f3 100644 --- a/core/src/main/java/net/roxymc/jserialize/AbstractReader.java +++ b/core/src/main/java/net/roxymc/jserialize/AbstractReader.java @@ -1,11 +1,13 @@ package net.roxymc.jserialize; import net.roxymc.jserialize.token.TokenType; +import org.jetbrains.annotations.ApiStatus; import java.util.function.Predicate; import static java.lang.String.format; +@ApiStatus.NonExtendable public abstract class AbstractReader implements Reader { protected final void checkToken(TokenType current, TokenType expected) { if (current != expected) { diff --git a/core/src/main/java/net/roxymc/jserialize/AbstractWriter.java b/core/src/main/java/net/roxymc/jserialize/AbstractWriter.java index b968748..1c745fc 100644 --- a/core/src/main/java/net/roxymc/jserialize/AbstractWriter.java +++ b/core/src/main/java/net/roxymc/jserialize/AbstractWriter.java @@ -1,7 +1,9 @@ package net.roxymc.jserialize; import net.roxymc.jserialize.token.TokenType; +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.NonExtendable public abstract class AbstractWriter implements Writer { protected final UnsupportedOperationException notSupported(TokenType tokenType) { return new UnsupportedOperationException(tokenType + " is not supported"); diff --git a/core/src/main/java/net/roxymc/jserialize/Reader.java b/core/src/main/java/net/roxymc/jserialize/Reader.java index e54413f..311f05d 100644 --- a/core/src/main/java/net/roxymc/jserialize/Reader.java +++ b/core/src/main/java/net/roxymc/jserialize/Reader.java @@ -1,35 +1,66 @@ package net.roxymc.jserialize; import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; +import org.jetbrains.annotations.ApiStatus; import java.io.IOException; +@ApiStatus.NonExtendable public interface Reader { TokenType peek() throws IOException; - String readName() throws IOException; + void read(TokenType.NonValued tokenType) throws IOException; - void readObjectStart() throws IOException; + T read(TokenType.Valued tokenType) throws IOException; - void readObjectEnd() throws IOException; + default String readName() throws IOException { + return read(TokenTypes.NAME); + } - void readArrayStart() throws IOException; + default void readObjectStart() throws IOException { + read(TokenTypes.OBJECT_START); + } - void readArrayEnd() throws IOException; + default void readObjectEnd() throws IOException { + read(TokenTypes.OBJECT_END); + } - String readString() throws IOException; + default void readArrayStart() throws IOException { + read(TokenTypes.ARRAY_START); + } - boolean readBoolean() throws IOException; + default void readArrayEnd() throws IOException { + read(TokenTypes.ARRAY_END); + } - int readInt() throws IOException; + default String readString() throws IOException { + return read(TokenTypes.STRING); + } - long readLong() throws IOException; + default boolean readBoolean() throws IOException { + return read(TokenTypes.BOOLEAN); + } - double readDouble() throws IOException; + default int readInt() throws IOException { + return read(TokenTypes.INT); + } - byte[] readBinary() throws IOException; + default long readLong() throws IOException { + return read(TokenTypes.LONG); + } - void readNull() throws IOException; + default double readDouble() throws IOException { + return read(TokenTypes.DOUBLE); + } + + default byte[] readBinary() throws IOException { + return read(TokenTypes.BINARY); + } + + default void readNull() throws IOException { + read(TokenTypes.NULL); + } void skipValue() throws IOException; } diff --git a/core/src/main/java/net/roxymc/jserialize/Writer.java b/core/src/main/java/net/roxymc/jserialize/Writer.java index 9200096..0940826 100644 --- a/core/src/main/java/net/roxymc/jserialize/Writer.java +++ b/core/src/main/java/net/roxymc/jserialize/Writer.java @@ -1,29 +1,62 @@ package net.roxymc.jserialize; +import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; +import org.jetbrains.annotations.ApiStatus; + import java.io.IOException; +@ApiStatus.NonExtendable public interface Writer { - void writeName(String name) throws IOException; + void write(TokenType.NonValued tokenType) throws IOException; + + void write(TokenType.Valued tokenType, T value) throws IOException; + + default void writeName(String name) throws IOException { + write(TokenTypes.NAME, name); + } - void writeObjectStart() throws IOException; + default void writeObjectStart() throws IOException { + write(TokenTypes.OBJECT_START); + } - void writeObjectEnd() throws IOException; + default void writeObjectEnd() throws IOException { + write(TokenTypes.OBJECT_END); + } - void writeArrayStart() throws IOException; + default void writeArrayStart() throws IOException { + write(TokenTypes.ARRAY_START); + } - void writeArrayEnd() throws IOException; + default void writeArrayEnd() throws IOException { + write(TokenTypes.ARRAY_END); + } - void writeString(String value) throws IOException; + default void writeString(String value) throws IOException { + write(TokenTypes.STRING, value); + } - void writeBoolean(boolean value) throws IOException; + default void writeBoolean(boolean value) throws IOException { + write(TokenTypes.BOOLEAN, value); + } - void writeInt(int value) throws IOException; + default void writeInt(int value) throws IOException { + write(TokenTypes.INT, value); + } - void writeLong(long value) throws IOException; + default void writeLong(long value) throws IOException { + write(TokenTypes.LONG, value); + } - void writeDouble(double value) throws IOException; + default void writeDouble(double value) throws IOException { + write(TokenTypes.DOUBLE, value); + } - void writeBinary(byte[] value) throws IOException; + default void writeBinary(byte[] value) throws IOException { + write(TokenTypes.BINARY, value); + } - void writeNull() throws IOException; + default void writeNull() throws IOException { + write(TokenTypes.NULL); + } } 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 762018b..ac756c5 100644 --- a/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java +++ b/core/src/main/java/net/roxymc/jserialize/adapter/collection/CollectionAdapter.java @@ -5,7 +5,7 @@ import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -52,7 +52,7 @@ public static TypeAdapter.Factory factory(CollectionProvider... providers) { private @Nullable Collection mutate0( Reader reader, TypeRef> type, @Nullable Collection collection, ReadContext ctx ) throws IOException { - if (reader.peek() == TokenType.NULL) { + if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return collection; } @@ -67,7 +67,7 @@ public static TypeAdapter.Factory factory(CollectionProvider... providers) { reader.readArrayStart(); - for (int index = 0; reader.peek() != TokenType.ARRAY_END; index++) { + for (int index = 0; reader.peek() != TokenTypes.ARRAY_END; index++) { E element = elementAdapter.read(reader, collectionType.elementType, ctx); collection.add(element); 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 3f82063..a70cf43 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 @@ -6,7 +6,7 @@ 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.NonNull; import org.jspecify.annotations.Nullable; @@ -52,7 +52,7 @@ private MapType resolveMapType(TypeRef> type) { private @Nullable Map mutate0( Reader reader, TypeRef> type, @Nullable Map map, ReadContext ctx ) throws IOException { - if (reader.peek() == TokenType.NULL) { + if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return map; } @@ -68,7 +68,7 @@ private MapType resolveMapType(TypeRef> type) { reader.readObjectStart(); - while (reader.peek() == TokenType.NAME) { + while (reader.peek() == TokenTypes.NAME) { String name = reader.readName(); K key = keyAdapter.decode(name); 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 42fa995..d6b65f3 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 @@ -7,7 +7,7 @@ import net.roxymc.jserialize.adapter.WriteContext; import net.roxymc.jserialize.annotation.JSerializable; import net.roxymc.jserialize.model.ClassModel; -import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import net.roxymc.jserialize.util.TypeUtils; import org.jspecify.annotations.Nullable; @@ -46,7 +46,7 @@ public static TypeAdapter.Factory annotatedFactory(ClassModel.Factory factory) { @Override public @Nullable T mutate(Reader reader, TypeRef type, @Nullable T value, ReadContext ctx) throws IOException { - if (reader.peek() == TokenType.NULL) { + if (reader.peek() == TokenTypes.NULL) { reader.readNull(); return value; } 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 e04c583..b2f2814 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 @@ -11,7 +11,7 @@ import net.roxymc.jserialize.model.property.PropertyModel; import net.roxymc.jserialize.model.property.meta.PropertyKind; import net.roxymc.jserialize.model.property.meta.PropertyMeta; -import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import net.roxymc.jserialize.util.TypeUtils; import org.jspecify.annotations.Nullable; @@ -74,7 +74,7 @@ T read(Reader reader) throws Throwable { extrasMap = null; } - while (reader.peek() == TokenType.NAME) { + while (reader.peek() == TokenTypes.NAME) { String name = reader.readName(); try { diff --git a/core/src/main/java/net/roxymc/jserialize/creator/InstanceCreator.java b/core/src/main/java/net/roxymc/jserialize/creator/InstanceCreator.java index 70ee187..29382db 100644 --- a/core/src/main/java/net/roxymc/jserialize/creator/InstanceCreator.java +++ b/core/src/main/java/net/roxymc/jserialize/creator/InstanceCreator.java @@ -122,10 +122,12 @@ private void resolveProperty(T instance, PropertyModel property) throws Throwabl } if (value instanceof PropertyValue.Mutable) { + @SuppressWarnings("unchecked") + PropertyValue.Mutable mutableValue = (PropertyValue.Mutable) value; + Object currentValue = property.getter().get(instance); - //noinspection unchecked,rawtypes - ((PropertyValue.Mutable) value).mutate(instance, currentValue); + mutableValue.mutate(instance, currentValue); return; } diff --git a/core/src/main/java/net/roxymc/jserialize/format/TokenTypeInfo.java b/core/src/main/java/net/roxymc/jserialize/format/TokenTypeInfo.java new file mode 100644 index 0000000..86b7afc --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/format/TokenTypeInfo.java @@ -0,0 +1,46 @@ +package net.roxymc.jserialize.format; + +import net.roxymc.jserialize.util.IOBiConsumer; +import net.roxymc.jserialize.util.IOConsumer; +import net.roxymc.jserialize.util.IOFunction; + +import java.io.IOException; + +public interface TokenTypeInfo { + final class NonValued implements TokenTypeInfo { + private final IOConsumer reader; + private final IOConsumer writer; + + NonValued(IOConsumer reader, IOConsumer writer) { + this.reader = reader; + this.writer = writer; + } + + public void read(R reader) throws IOException { + this.reader.accept(reader); + } + + public void write(W writer) throws IOException { + this.writer.accept(writer); + } + } + + final class Valued implements TokenTypeInfo { + private final IOFunction reader; + private final IOBiConsumer writer; + + Valued(IOFunction reader, IOBiConsumer writer) { + this.reader = reader; + this.writer = writer; + } + + @SuppressWarnings("DataFlowIssue") // IntelliJ has existential issues + public T read(R reader) throws IOException { + return this.reader.apply(reader); + } + + public void write(W writer, T value) throws IOException { + this.writer.accept(writer, value); + } + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/format/TokenTypeRegistry.java b/core/src/main/java/net/roxymc/jserialize/format/TokenTypeRegistry.java new file mode 100644 index 0000000..0b87099 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/format/TokenTypeRegistry.java @@ -0,0 +1,76 @@ +package net.roxymc.jserialize.format; + +import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.util.IOBiConsumer; +import net.roxymc.jserialize.util.IOConsumer; +import net.roxymc.jserialize.util.IOFunction; +import org.jspecify.annotations.Nullable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +public final class TokenTypeRegistry { + private final Map> registry; + private final Map fromNative; + + private TokenTypeRegistry(Builder builder) { + this.registry = Collections.unmodifiableMap(new IdentityHashMap<>(builder.registry)); + this.fromNative = Map.copyOf(builder.fromNative); + } + + public static TokenTypeRegistry create(UnaryOperator> builder) { + return new TokenTypeRegistry<>(builder.apply(new Builder<>())); + } + + public TokenTypeInfo.@Nullable NonValued get(TokenType.NonValued tokenType) { + return (TokenTypeInfo.NonValued) registry.get(tokenType); + } + + public TokenTypeInfo.@Nullable Valued get(TokenType.Valued tokenType) { + @SuppressWarnings("unchecked") + TokenTypeInfo.Valued info = (TokenTypeInfo.Valued) registry.get(tokenType); + return info; + } + + public TokenType fromNative(N nativeType) { + TokenType tokenType = fromNative.get(nativeType); + if (tokenType == null) { + throw new IllegalStateException("Missing token type for " + nativeType); + } + + return tokenType; + } + + public static final class Builder { + private final Map> registry = new IdentityHashMap<>(); + private final Map fromNative = new HashMap<>(); + + public Builder bind(TokenType.NonValued tokenType, @Nullable N nativeType, IOConsumer reader, IOConsumer writer) { + registry.put(tokenType, new TokenTypeInfo.NonValued<>(reader, writer)); + + if (nativeType != null) { + fromNative.put(nativeType, tokenType); + } + + return this; + } + + public Builder bind(TokenType.Valued tokenType, @Nullable N nativeType, IOFunction reader, IOBiConsumer writer) { + registry.put(tokenType, new TokenTypeInfo.Valued<>(reader, writer)); + + if (nativeType != null) { + fromNative.put(nativeType, tokenType); + } + + return this; + } + + public Builder bind(TokenType.Virtual tokenType, N nativeType) { + fromNative.put(nativeType, tokenType); + return this; + } + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/format/package-info.java b/core/src/main/java/net/roxymc/jserialize/format/package-info.java new file mode 100644 index 0000000..2322972 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/format/package-info.java @@ -0,0 +1,6 @@ +@ApiStatus.Internal +@NullMarked +package net.roxymc.jserialize.format; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/core/src/main/java/net/roxymc/jserialize/format/tree/TreeAccessor.java b/core/src/main/java/net/roxymc/jserialize/format/tree/TreeAccessor.java new file mode 100644 index 0000000..b233050 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/format/tree/TreeAccessor.java @@ -0,0 +1,22 @@ +package net.roxymc.jserialize.format.tree; + +import net.roxymc.jserialize.token.ScalarToken; +import org.jspecify.annotations.Nullable; + +public interface TreeAccessor { + TreeNode nodeOf(V value); + + boolean isObject(V value); + + void initObject(V value); + + boolean isArray(V value); + + void initArray(V value); + + void setScalar(V value, @Nullable ScalarToken token); + + V appendEntry(V object, String name); + + V appendElement(V array); +} diff --git a/core/src/main/java/net/roxymc/jserialize/format/tree/TreeNode.java b/core/src/main/java/net/roxymc/jserialize/format/tree/TreeNode.java new file mode 100644 index 0000000..f5f8d73 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/format/tree/TreeNode.java @@ -0,0 +1,62 @@ +package net.roxymc.jserialize.format.tree; + +import net.roxymc.jserialize.token.ScalarToken; +import org.jspecify.annotations.Nullable; + +import java.util.Map; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public interface TreeNode { + static TreeNode object(Iterable> entries) { + return new ObjectNode<>(entries); + } + + static TreeNode array(Iterable elements) { + return new ArrayNode<>(elements); + } + + static TreeNode scalar(@Nullable ScalarToken token) { + @SuppressWarnings("unchecked") + TreeNode node = (TreeNode) (token != null ? new ScalarNode<>(token) : ScalarNode.NULL); + return node; + } + + final class ObjectNode implements TreeNode { + private final Iterable> entries; + + private ObjectNode(Iterable> entries) { + this.entries = nonNull(entries, "entries"); + } + + public Iterable> entries() { + return entries; + } + } + + final class ArrayNode implements TreeNode { + private final Iterable elements; + + private ArrayNode(Iterable elements) { + this.elements = nonNull(elements, "elements"); + } + + public Iterable elements() { + return elements; + } + } + + final class ScalarNode implements TreeNode { + private static final ScalarNode NULL = new ScalarNode<>(null); + + private final @Nullable ScalarToken token; + + private ScalarNode(@Nullable ScalarToken token) { + this.token = token; + } + + public @Nullable ScalarToken token() { + return token; + } + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/format/tree/TreeReader.java b/core/src/main/java/net/roxymc/jserialize/format/tree/TreeReader.java new file mode 100644 index 0000000..afce977 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/format/tree/TreeReader.java @@ -0,0 +1,177 @@ +package net.roxymc.jserialize.format.tree; + +import net.roxymc.jserialize.AbstractReader; +import net.roxymc.jserialize.token.*; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.util.*; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class TreeReader extends AbstractReader { + private final TreeAccessor accessor; + + private final Deque frames = new ArrayDeque<>(); + private final Queue buffer = new ArrayDeque<>(2); + + public TreeReader(TreeAccessor accessor, V root) { + this.accessor = nonNull(accessor, "accessor"); + + prepareValue(nonNull(root, "root")); + } + + @Override + public TokenType peek() { + fillBufferIfNeeded(); + + TokenHolder token = buffer.peek(); + return token != null ? token.token.type() : TokenTypes.END; + } + + @Override + public void read(TokenType.NonValued tokenType) throws IOException { + checkToken(peek(), tokenType); + + buffer.remove(); + } + + @Override + public T read(TokenType.Valued tokenType) throws IOException { + checkToken(peek(), tokenType); + + @SuppressWarnings("unchecked") + ValuedToken token = (ValuedToken) buffer.remove().token; + return token.value(); + } + + @Override + public void skipValue() { + TokenType type = peek(); + + if (!type.kind().marksValue()) { + throw new IllegalStateException(type + " does not support this operation"); + } + + Token token = buffer.remove().token; + + if (token.type().kind() == TokenType.Kind.STRUCTURE_START) { + frames.pop(); + } + } + + private void fillBufferIfNeeded() { + if (!buffer.isEmpty() || frames.isEmpty()) { + return; + } + + if (!frames.element().flushNext()) { + frames.pop(); + } + } + + private void prepareValue(V value) { + TreeNode node = accessor.nodeOf(value); + + if (node instanceof TreeNode.ObjectNode) { + TreeNode.ObjectNode objectNode = (TreeNode.ObjectNode) node; + + buffer.add(new TokenHolder(Tokens.OBJECT_START, value)); + frames.push(new ObjectFrame(objectNode.entries())); + } else if (node instanceof TreeNode.ArrayNode) { + TreeNode.ArrayNode arrayNode = (TreeNode.ArrayNode) node; + + buffer.add(new TokenHolder(Tokens.ARRAY_START, value)); + frames.push(new ArrayFrame(arrayNode.elements())); + } else if (node instanceof TreeNode.ScalarNode) { + TreeNode.ScalarNode scalarNode = (TreeNode.ScalarNode) node; + + ScalarToken token = scalarNode.token(); + buffer.add(new TokenHolder(token != null ? token : Tokens.NULL, value)); + } + } + + private interface Frame { + boolean flushNext(); + } + + public V currentValue() { // TODO temporary bridge for Configurate + fillBufferIfNeeded(); + + TokenHolder token = buffer.poll(); + V value = token != null ? token.value : null; + + if (value == null) { + throw new IllegalStateException("Cannot retrieve value"); + } + + if (token.token.type().kind() == TokenType.Kind.STRUCTURE_START) { + frames.pop(); + } + + return value; + } + + private final class ObjectFrame implements Frame { + private final Iterator> iterator; + private boolean exhausted; + + ObjectFrame(Iterable> iterable) { + this.iterator = iterable.iterator(); + } + + @Override + public boolean flushNext() { + if (exhausted) { + throw new IllegalStateException("exhausted"); + } + + if (!iterator.hasNext()) { + buffer.add(new TokenHolder(Tokens.OBJECT_END, null)); + exhausted = true; + return false; + } + + Map.Entry entry = iterator.next(); + + buffer.add(new TokenHolder(new NameToken(entry.getKey()), null)); + prepareValue(entry.getValue()); + return true; + } + } + + private final class ArrayFrame implements Frame { + private final Iterator iterator; + private boolean exhausted; + + ArrayFrame(Iterable iterable) { + this.iterator = iterable.iterator(); + } + + @Override + public boolean flushNext() { + if (exhausted) { + throw new IllegalStateException("exhausted"); + } + + if (!iterator.hasNext()) { + buffer.add(new TokenHolder(Tokens.ARRAY_END, null)); + exhausted = true; + return false; + } + + prepareValue(iterator.next()); + return true; + } + } + + private final class TokenHolder { + private final Token token; + private final @Nullable V value; + + private TokenHolder(Token token, @Nullable V value) { + this.token = token; + this.value = value; + } + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/format/tree/TreeWriter.java b/core/src/main/java/net/roxymc/jserialize/format/tree/TreeWriter.java new file mode 100644 index 0000000..9523127 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/format/tree/TreeWriter.java @@ -0,0 +1,107 @@ +package net.roxymc.jserialize.format.tree; + +import net.roxymc.jserialize.AbstractWriter; +import net.roxymc.jserialize.token.ScalarToken; +import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenType.Kind; +import net.roxymc.jserialize.token.TokenTypes; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Deque; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class TreeWriter extends AbstractWriter { + private final TreeAccessor accessor; + + private final V root; + private final Deque containers = new ArrayDeque<>(); + + private @Nullable String pendingName; + + public TreeWriter(TreeAccessor accessor, V root) { + this.accessor = nonNull(accessor, "accessor"); + this.root = nonNull(root, "root"); + } + + private void checkNoPendingName() { + if (pendingName != null) { + throw new IllegalStateException("Previous name was not consumed"); + } + } + + @Override + public void write(TokenType.NonValued tokenType) throws IOException { + if (tokenType == TokenTypes.OBJECT_START) { + V container = currentValue(); + accessor.initObject(container); + + containers.push(container); + } else if (tokenType == TokenTypes.OBJECT_END) { + checkNoPendingName(); + + V container = containers.peek(); + if (container == null || !accessor.isObject(container)) { + throw new IllegalStateException("No object to end"); + } + + containers.pop(); + } else if (tokenType == TokenTypes.ARRAY_START) { + V container = currentValue(); + accessor.initArray(container); + + containers.push(container); + } else if (tokenType == TokenTypes.ARRAY_END) { + checkNoPendingName(); + + V container = containers.peek(); + if (container == null || !accessor.isArray(container)) { + throw new IllegalStateException("No array to end"); + } + + containers.pop(); + } else if (tokenType.kind() == Kind.NULL) { + accessor.setScalar(currentValue(), null); + } else { + throw notSupported(tokenType); + } + } + + @Override + public void write(TokenType.Valued tokenType, T value) throws IOException { + if (tokenType == TokenTypes.NAME) { + checkNoPendingName(); + pendingName = (String) value; + return; + } + + accessor.setScalar(currentValue(), (ScalarToken) tokenType.create(value)); + } + + public V currentValue() { // TODO temporary bridge for Configurate + if (containers.isEmpty()) { + return root; + } + + V container = nonNull(containers.peek(), "container"); + + if (pendingName != null) { + if (!accessor.isObject(container)) { + throw new IllegalStateException("Cannot write named value to a non-object"); + } + + String name = pendingName; + pendingName = null; + + return accessor.appendEntry(container, name); + } + + if (!accessor.isArray(container)) { + throw new IllegalStateException("Cannot append to a non-array"); + } + + return accessor.appendElement(container); + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/format/tree/package-info.java b/core/src/main/java/net/roxymc/jserialize/format/tree/package-info.java new file mode 100644 index 0000000..2da4706 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/format/tree/package-info.java @@ -0,0 +1,6 @@ +@ApiStatus.Internal +@NullMarked +package net.roxymc.jserialize.format.tree; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/core/src/main/java/net/roxymc/jserialize/token/reader/package-info.java b/core/src/main/java/net/roxymc/jserialize/package-info.java similarity index 56% rename from core/src/main/java/net/roxymc/jserialize/token/reader/package-info.java rename to core/src/main/java/net/roxymc/jserialize/package-info.java index db9c8b7..cce85bb 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/reader/package-info.java +++ b/core/src/main/java/net/roxymc/jserialize/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package net.roxymc.jserialize.token.reader; +package net.roxymc.jserialize; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/core/src/main/java/net/roxymc/jserialize/token/AbstractValueAccessor.java b/core/src/main/java/net/roxymc/jserialize/token/AbstractValueAccessor.java deleted file mode 100644 index a78e263..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/AbstractValueAccessor.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.roxymc.jserialize.token; - -import org.jspecify.annotations.Nullable; - -public abstract class AbstractValueAccessor implements ValueAccessor { - protected TokenType getRawTokenType(@Nullable Object raw) { - if (raw == null) { - return TokenType.NULL; - } else if (raw instanceof String) { - return TokenType.STRING; - } else if (raw instanceof Boolean) { - return TokenType.BOOLEAN; - } else if (raw instanceof Float || raw instanceof Double) { - return TokenType.DOUBLE; - } else if (raw instanceof Long) { - return TokenType.LONG; - } else if (raw instanceof Number) { - return TokenType.INT; - } else if (raw instanceof byte[]) { - return TokenType.BINARY; - } else { - return TokenType.UNKNOWN; - } - } - - protected ScalarToken toRawToken(@Nullable Object raw) { - if (raw == null) { - return ScalarToken.NULL; - } else if (raw instanceof String) { - return new StringToken((String) raw); - } else if (raw instanceof Boolean) { - return (boolean) raw ? BooleanToken.TRUE : BooleanToken.FALSE; - } else if (raw instanceof Float || raw instanceof Double) { - return new DoubleToken(((Number) raw).doubleValue()); - } else if (raw instanceof Long) { - return new LongToken((long) raw); - } else if (raw instanceof Number) { - return new IntToken(((Number) raw).intValue()); - } else if (raw instanceof byte[]) { - return new BinaryToken((byte[]) raw); - } else { - return new UnknownToken(raw); - } - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/AbstractScalarToken.java b/core/src/main/java/net/roxymc/jserialize/token/AbstractValuedToken.java similarity index 72% rename from core/src/main/java/net/roxymc/jserialize/token/AbstractScalarToken.java rename to core/src/main/java/net/roxymc/jserialize/token/AbstractValuedToken.java index c99da83..7b742b0 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/AbstractScalarToken.java +++ b/core/src/main/java/net/roxymc/jserialize/token/AbstractValuedToken.java @@ -1,10 +1,12 @@ package net.roxymc.jserialize.token; +import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.Nullable; import java.util.Objects; -abstract class AbstractScalarToken implements ScalarToken { +@ApiStatus.Internal +public abstract class AbstractValuedToken implements ValuedToken { @Override public int hashCode() { return Objects.hashCode(value()); @@ -20,7 +22,7 @@ public boolean equals(@Nullable Object obj) { return false; } - ScalarToken that = (ScalarToken) obj; + ValuedToken that = (ValuedToken) obj; return Objects.equals(this.value(), that.value()); } } diff --git a/core/src/main/java/net/roxymc/jserialize/token/BinaryToken.java b/core/src/main/java/net/roxymc/jserialize/token/BinaryToken.java index 043aca6..a103625 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/BinaryToken.java +++ b/core/src/main/java/net/roxymc/jserialize/token/BinaryToken.java @@ -1,12 +1,8 @@ package net.roxymc.jserialize.token; -import net.roxymc.jserialize.Writer; - -import java.io.IOException; - import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -public final class BinaryToken extends AbstractScalarToken { +public final class BinaryToken extends AbstractValuedToken implements ScalarToken { private final byte[] value; public BinaryToken(byte[] value) { @@ -19,12 +15,7 @@ public byte[] value() { } @Override - public void write(Writer writer) throws IOException { - writer.writeBinary(value()); - } - - @Override - public TokenType type() { - return TokenType.BINARY; + public TokenType.Valued type() { + return TokenTypes.BINARY; } } diff --git a/core/src/main/java/net/roxymc/jserialize/token/BooleanToken.java b/core/src/main/java/net/roxymc/jserialize/token/BooleanToken.java index 85b918e..bc4cb85 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/BooleanToken.java +++ b/core/src/main/java/net/roxymc/jserialize/token/BooleanToken.java @@ -1,9 +1,5 @@ package net.roxymc.jserialize.token; -import net.roxymc.jserialize.Writer; - -import java.io.IOException; - public final class BooleanToken implements ScalarToken { public static final BooleanToken TRUE = new BooleanToken(true); public static final BooleanToken FALSE = new BooleanToken(false); @@ -14,6 +10,10 @@ private BooleanToken(boolean value) { this.value = value; } + public static BooleanToken of(boolean value) { + return value ? TRUE : FALSE; + } + public boolean booleanValue() { return value; } @@ -24,13 +24,8 @@ public Boolean value() { } @Override - public void write(Writer writer) throws IOException { - writer.writeBoolean(booleanValue()); - } - - @Override - public TokenType type() { - return TokenType.BOOLEAN; + public TokenType.Valued type() { + return TokenTypes.BOOLEAN; } @Override diff --git a/core/src/main/java/net/roxymc/jserialize/token/DoubleToken.java b/core/src/main/java/net/roxymc/jserialize/token/DoubleToken.java index 06a6baf..5fe7019 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/DoubleToken.java +++ b/core/src/main/java/net/roxymc/jserialize/token/DoubleToken.java @@ -1,9 +1,5 @@ package net.roxymc.jserialize.token; -import net.roxymc.jserialize.Writer; - -import java.io.IOException; - public final class DoubleToken implements NumberToken { private final double value; @@ -21,13 +17,8 @@ public Double value() { } @Override - public void write(Writer writer) throws IOException { - writer.writeDouble(doubleValue()); - } - - @Override - public TokenType type() { - return TokenType.DOUBLE; + public TokenType.Valued type() { + return TokenTypes.DOUBLE; } @Override diff --git a/core/src/main/java/net/roxymc/jserialize/token/IntToken.java b/core/src/main/java/net/roxymc/jserialize/token/IntToken.java index d6cc4d2..20d2a9f 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/IntToken.java +++ b/core/src/main/java/net/roxymc/jserialize/token/IntToken.java @@ -1,9 +1,5 @@ package net.roxymc.jserialize.token; -import net.roxymc.jserialize.Writer; - -import java.io.IOException; - public final class IntToken implements NumberToken { private final int value; @@ -21,13 +17,8 @@ public Integer value() { } @Override - public void write(Writer writer) throws IOException { - writer.writeInt(intValue()); - } - - @Override - public TokenType type() { - return TokenType.INT; + public TokenType.Valued type() { + return TokenTypes.INT; } @Override diff --git a/core/src/main/java/net/roxymc/jserialize/token/LongToken.java b/core/src/main/java/net/roxymc/jserialize/token/LongToken.java index 3aae708..3ca8e05 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/LongToken.java +++ b/core/src/main/java/net/roxymc/jserialize/token/LongToken.java @@ -1,9 +1,5 @@ package net.roxymc.jserialize.token; -import net.roxymc.jserialize.Writer; - -import java.io.IOException; - public final class LongToken implements NumberToken { private final long value; @@ -21,13 +17,8 @@ public Long value() { } @Override - public void write(Writer writer) throws IOException { - writer.writeLong(longValue()); - } - - @Override - public TokenType type() { - return TokenType.LONG; + public TokenType.Valued type() { + return TokenTypes.LONG; } @Override diff --git a/core/src/main/java/net/roxymc/jserialize/token/NameToken.java b/core/src/main/java/net/roxymc/jserialize/token/NameToken.java index f3a7817..39e6e06 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/NameToken.java +++ b/core/src/main/java/net/roxymc/jserialize/token/NameToken.java @@ -1,12 +1,8 @@ package net.roxymc.jserialize.token; -import net.roxymc.jserialize.Writer; - -import java.io.IOException; - import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -public final class NameToken implements Token { +public final class NameToken extends AbstractValuedToken { private final String value; public NameToken(String value) { @@ -18,31 +14,7 @@ public String value() { } @Override - public void write(Writer writer) throws IOException { - writer.writeString(value()); - } - - @Override - public TokenType type() { - return TokenType.NAME; - } - - @Override - public int hashCode() { - return value.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (!(obj instanceof NameToken)) { - return false; - } - - NameToken that = (NameToken) obj; - return this.value.equals(that.value); + public TokenType.Valued type() { + return TokenTypes.NAME; } } diff --git a/core/src/main/java/net/roxymc/jserialize/token/NullToken.java b/core/src/main/java/net/roxymc/jserialize/token/NullToken.java deleted file mode 100644 index e6e07e5..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/NullToken.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.roxymc.jserialize.token; - -import net.roxymc.jserialize.Writer; -import org.jspecify.annotations.Nullable; - -import java.io.IOException; - -final class NullToken extends AbstractScalarToken<@Nullable Object> { - static final NullToken INSTANCE = new NullToken(); - - private NullToken() { - } - - @Override - public @Nullable Object value() { - return null; - } - - @Override - public void write(Writer writer) throws IOException { - writer.writeNull(); - } - - @Override - public TokenType type() { - return TokenType.NULL; - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/ScalarToken.java b/core/src/main/java/net/roxymc/jserialize/token/ScalarToken.java index 1bb6155..ff625ca 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/ScalarToken.java +++ b/core/src/main/java/net/roxymc/jserialize/token/ScalarToken.java @@ -1,11 +1,7 @@ package net.roxymc.jserialize.token; import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.Nullable; @ApiStatus.NonExtendable -public interface ScalarToken extends Token { - ScalarToken<@Nullable Object> NULL = NullToken.INSTANCE; - - T value(); +public interface ScalarToken extends ValuedToken { } diff --git a/core/src/main/java/net/roxymc/jserialize/token/StringToken.java b/core/src/main/java/net/roxymc/jserialize/token/StringToken.java index f07a3eb..56170df 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/StringToken.java +++ b/core/src/main/java/net/roxymc/jserialize/token/StringToken.java @@ -1,12 +1,8 @@ package net.roxymc.jserialize.token; -import net.roxymc.jserialize.Writer; - -import java.io.IOException; - import static net.roxymc.jserialize.util.ObjectUtils.nonNull; -public final class StringToken extends AbstractScalarToken { +public final class StringToken extends AbstractValuedToken implements ScalarToken { private final String value; public StringToken(String value) { @@ -19,12 +15,7 @@ public String value() { } @Override - public void write(Writer writer) throws IOException { - writer.writeString(value()); - } - - @Override - public TokenType type() { - return TokenType.STRING; + public TokenType.Valued type() { + return TokenTypes.STRING; } } diff --git a/core/src/main/java/net/roxymc/jserialize/token/Token.java b/core/src/main/java/net/roxymc/jserialize/token/Token.java index f831d8c..020b7ea 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/Token.java +++ b/core/src/main/java/net/roxymc/jserialize/token/Token.java @@ -1,20 +1,8 @@ package net.roxymc.jserialize.token; -import net.roxymc.jserialize.Writer; import org.jetbrains.annotations.ApiStatus; -import java.io.IOException; - @ApiStatus.NonExtendable public interface Token { - Token OBJECT_START = new TokenImpl(TokenType.OBJECT_START, Writer::writeObjectStart); - Token OBJECT_END = new TokenImpl(TokenType.OBJECT_END, Writer::writeObjectEnd); - Token ARRAY_START = new TokenImpl(TokenType.ARRAY_START, Writer::writeArrayStart); - Token ARRAY_END = new TokenImpl(TokenType.ARRAY_END, Writer::writeArrayEnd); - - default void write(Writer writer) throws IOException { - throw new UnsupportedOperationException(type() + " does not support this operation"); - } - TokenType type(); } diff --git a/core/src/main/java/net/roxymc/jserialize/token/TokenImpl.java b/core/src/main/java/net/roxymc/jserialize/token/TokenImpl.java deleted file mode 100644 index 9eb38c3..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/TokenImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.roxymc.jserialize.token; - -import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.util.IOConsumer; - -import java.io.IOException; - -final class TokenImpl implements Token { - private final TokenType type; - private final IOConsumer write; - - TokenImpl(TokenType type, IOConsumer write) { - this.type = type; - this.write = write; - } - - @Override - public void write(Writer writer) throws IOException { - write.accept(writer); - } - - @Override - public TokenType type() { - return type; - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/TokenType.java b/core/src/main/java/net/roxymc/jserialize/token/TokenType.java index c3227a3..8ea76e3 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/TokenType.java +++ b/core/src/main/java/net/roxymc/jserialize/token/TokenType.java @@ -1,82 +1,89 @@ package net.roxymc.jserialize.token; -import net.roxymc.jserialize.Reader; -import net.roxymc.jserialize.util.IOFunction; -import org.jspecify.annotations.Nullable; - -import java.io.IOException; - -public enum TokenType { - NAME(Kind.MARKER, reader -> new NameToken(reader.readName())), - OBJECT_START(Kind.VALUE, reader -> { - reader.readObjectStart(); - return Token.OBJECT_START; - }), - OBJECT_END(Kind.MARKER, reader -> { - reader.readObjectEnd(); - return Token.OBJECT_END; - }), - ARRAY_START(Kind.VALUE, reader -> { - reader.readArrayStart(); - return Token.ARRAY_START; - }), - ARRAY_END(Kind.MARKER, reader -> { - reader.readArrayEnd(); - return Token.ARRAY_END; - }), - STRING(Kind.VALUE_SCALAR, reader -> new StringToken(reader.readString())), - BOOLEAN(Kind.VALUE_SCALAR, reader -> reader.readBoolean() ? BooleanToken.TRUE : BooleanToken.FALSE), - INT(Kind.VALUE_SCALAR, reader -> new IntToken(reader.readInt())), - LONG(Kind.VALUE_SCALAR, reader -> new LongToken(reader.readLong())), - DOUBLE(Kind.VALUE_SCALAR, reader -> new DoubleToken(reader.readDouble())), - NUMBER(Kind.VALUE_SCALAR, reader -> { - TokenType peek = reader.peek(); - - switch (peek) { - case INT: - case LONG: - return peek.read(reader); - default: - return DOUBLE.read(reader); - } - }), - BINARY(Kind.VALUE_SCALAR, reader -> new BinaryToken(reader.readBinary())), - NULL(Kind.VALUE_SCALAR, reader -> { - reader.readNull(); - return ScalarToken.NULL; - }), - UNKNOWN(Kind.VALUE_SCALAR, null), - END(Kind.MARKER, null), - ; +import org.jetbrains.annotations.ApiStatus; + +import java.util.function.Function; +import java.util.function.Supplier; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; +@ApiStatus.NonExtendable +public abstract class TokenType { private final Kind kind; - private final @Nullable IOFunction read; - TokenType(Kind kind, @Nullable IOFunction read) { + private TokenType(Kind kind) { this.kind = kind; - this.read = read; + } + + @ApiStatus.Internal + public static NonValued nonValued(Kind kind, Supplier token) { + return new NonValued(kind, token); + } + + @ApiStatus.Internal + public static Valued valued(Kind kind, Function> tokenFactory) { + return new Valued<>(kind, tokenFactory); + } + + @ApiStatus.Internal + public static Virtual virtual(Kind kind) { + return new Virtual(kind); } public Kind kind() { return kind; } - public Token read(Reader reader) throws IOException { - if (read == null) { - throw new UnsupportedOperationException(this + " does not support this operation"); + public static final class NonValued extends TokenType { + private final Supplier token; + + @ApiStatus.Internal + private NonValued(Kind kind, Supplier token) { + super(kind); + this.token = token; } - return read.apply(reader); + public Token create() { + return token.get(); + } + } + + public static final class Valued extends TokenType { + private final Function> tokenFactory; + + private Valued(Kind kind, Function> tokenFactory) { + super(kind); + this.tokenFactory = tokenFactory; + } + + public ValuedToken create(T value) { + return tokenFactory.apply(nonNull(value, "value")); + } + } + + public static final class Virtual extends TokenType { + private Virtual(Kind kind) { + super(kind); + } } public enum Kind { - MARKER, - VALUE, - VALUE_SCALAR, + NAME(false), + STRUCTURE_START(true), + STRUCTURE_END(false), + SCALAR(true), + NULL(true), + END(false), ; - public boolean isValue() { - return this == VALUE || this == VALUE_SCALAR; + private final boolean marksValue; + + Kind(boolean marksValue) { + this.marksValue = marksValue; + } + + public boolean marksValue() { + return marksValue; } } } diff --git a/core/src/main/java/net/roxymc/jserialize/token/TokenTypes.java b/core/src/main/java/net/roxymc/jserialize/token/TokenTypes.java new file mode 100644 index 0000000..215beef --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/token/TokenTypes.java @@ -0,0 +1,23 @@ +package net.roxymc.jserialize.token; + +import static net.roxymc.jserialize.token.TokenType.*; + +public final class TokenTypes { + public static final TokenType.Valued NAME = valued(Kind.NAME, NameToken::new); + public static final TokenType.NonValued OBJECT_START = nonValued(Kind.STRUCTURE_START, () -> Tokens.OBJECT_START); + public static final TokenType.NonValued OBJECT_END = nonValued(Kind.STRUCTURE_END, () -> Tokens.OBJECT_END); + public static final TokenType.NonValued ARRAY_START = nonValued(Kind.STRUCTURE_START, () -> Tokens.ARRAY_START); + public static final TokenType.NonValued ARRAY_END = nonValued(Kind.STRUCTURE_END, () -> Tokens.ARRAY_END); + public static final TokenType.Valued STRING = valued(Kind.SCALAR, StringToken::new); + public static final TokenType.Valued BOOLEAN = valued(Kind.SCALAR, BooleanToken::of); + public static final TokenType.Valued INT = valued(Kind.SCALAR, IntToken::new); + public static final TokenType.Valued LONG = valued(Kind.SCALAR, LongToken::new); + public static final TokenType.Valued DOUBLE = valued(Kind.SCALAR, DoubleToken::new); + public static final TokenType.Virtual NUMERIC = virtual(Kind.SCALAR); + public static final TokenType.Valued BINARY = valued(Kind.SCALAR, BinaryToken::new); + public static final TokenType.NonValued NULL = nonValued(Kind.NULL, () -> Tokens.NULL); + public static final TokenType.Virtual END = virtual(Kind.END); + + private TokenTypes() { + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/token/Tokens.java b/core/src/main/java/net/roxymc/jserialize/token/Tokens.java new file mode 100644 index 0000000..04d867b --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/token/Tokens.java @@ -0,0 +1,12 @@ +package net.roxymc.jserialize.token; + +public final class Tokens { + public static final Token OBJECT_START = () -> TokenTypes.OBJECT_START; + public static final Token OBJECT_END = () -> TokenTypes.OBJECT_END; + public static final Token ARRAY_START = () -> TokenTypes.ARRAY_START; + public static final Token ARRAY_END = () -> TokenTypes.ARRAY_END; + public static final Token NULL = () -> TokenTypes.NULL; + + private Tokens() { + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/token/UnknownToken.java b/core/src/main/java/net/roxymc/jserialize/token/UnknownToken.java deleted file mode 100644 index 7de6f97..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/UnknownToken.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.roxymc.jserialize.token; - -import static net.roxymc.jserialize.util.ObjectUtils.nonNull; - -public final class UnknownToken extends AbstractScalarToken { - private final Object value; - - public UnknownToken(Object value) { - this.value = nonNull(value, "value"); - } - - @Override - public Object value() { - return value; - } - - @Override - public TokenType type() { - return TokenType.UNKNOWN; - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/ValueAccessor.java b/core/src/main/java/net/roxymc/jserialize/token/ValueAccessor.java deleted file mode 100644 index f28f2f3..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/ValueAccessor.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.roxymc.jserialize.token; - -import org.jspecify.annotations.Nullable; - -public interface ValueAccessor { - String getName(V value); - - boolean isObject(V value); - - boolean isArray(V value); - - Iterable getValues(V value); - - V objectAppend(V container, String name); - - V listAppend(V container); - - void setScalar(V value, @Nullable Object raw); - - TokenType getTokenType(V value); - - ScalarToken toToken(V value); -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/ValuedToken.java b/core/src/main/java/net/roxymc/jserialize/token/ValuedToken.java new file mode 100644 index 0000000..dc67436 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/token/ValuedToken.java @@ -0,0 +1,11 @@ +package net.roxymc.jserialize.token; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface ValuedToken extends Token { + T value(); + + @Override + TokenType.Valued type(); +} diff --git a/core/src/main/java/net/roxymc/jserialize/token/reader/SimpleTokenizer.java b/core/src/main/java/net/roxymc/jserialize/token/reader/SimpleTokenizer.java deleted file mode 100644 index 12281a7..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/reader/SimpleTokenizer.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.roxymc.jserialize.token.reader; - -import net.roxymc.jserialize.token.Token; -import net.roxymc.jserialize.token.TokenType; -import org.jspecify.annotations.Nullable; - -import java.util.Iterator; - -import static net.roxymc.jserialize.util.ObjectUtils.nonNull; - -public final class SimpleTokenizer implements Tokenizer { - private final Iterator iterator; - private @Nullable Token token; - - public SimpleTokenizer(Iterator iterator) { - this.iterator = nonNull(iterator, "iterator"); - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public TokenType peek() { - return peek0().type(); - } - - private Token peek0() { - if (token == null) { - token = iterator.next(); - } - - return token; - } - - @Override - public Token next() { - Token token = peek0(); - this.token = null; - return token; - } - - @Override - public Token nextValue() { - return next(); - } - - @Override - public void skipValue() { - TokenType type = peek(); - if (type.kind() != TokenType.Kind.VALUE) { - throw new IllegalStateException(type + "does not support this operation"); - } - - next(); // skip the value - - if (type == TokenType.OBJECT_START) { - while (peek() != TokenType.OBJECT_END) { - next(); // skip name - skipValue(); - } - - next(); // skip object end - } else if (type == TokenType.ARRAY_START) { - while (peek() != TokenType.ARRAY_END) { - skipValue(); - } - - next(); // skip array end - } - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/reader/TokenReader.java b/core/src/main/java/net/roxymc/jserialize/token/reader/TokenReader.java deleted file mode 100644 index 85aa2dd..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/reader/TokenReader.java +++ /dev/null @@ -1,114 +0,0 @@ -package net.roxymc.jserialize.token.reader; - -import net.roxymc.jserialize.AbstractReader; -import net.roxymc.jserialize.token.*; - -import static net.roxymc.jserialize.util.ObjectUtils.nonNull; - -public final class TokenReader extends AbstractReader { - private final Tokenizer tokenizer; - - public TokenReader(Tokenizer tokenizer) { - this.tokenizer = nonNull(tokenizer, "tokenizer"); - } - - public Tokenizer tokenizer() { - return tokenizer; - } - - @Override - public TokenType peek() { - if (!tokenizer.hasNext()) { - return TokenType.END; - } - - return tokenizer.peek(); - } - - private T pop(Class type) { - return type.cast(tokenizer.next()); - } - - private void pop(Token expected) { - if (tokenizer.next() != expected) { - throw new IllegalStateException("token mismatch"); - } - } - - @Override - public String readName() { - checkToken(peek(), TokenType.NAME); - return pop(NameToken.class).value(); - } - - @Override - public void readObjectStart() { - checkToken(peek(), TokenType.OBJECT_START); - pop(Token.OBJECT_START); - } - - @Override - public void readObjectEnd() { - checkToken(peek(), TokenType.OBJECT_END); - pop(Token.OBJECT_END); - } - - @Override - public void readArrayStart() { - checkToken(peek(), TokenType.ARRAY_START); - pop(Token.ARRAY_START); - } - - @Override - public void readArrayEnd() { - checkToken(peek(), TokenType.ARRAY_END); - pop(Token.ARRAY_END); - } - - @Override - public String readString() { - checkToken(peek(), TokenType.STRING); - return pop(StringToken.class).value(); - } - - @Override - public boolean readBoolean() { - checkToken(peek(), TokenType.BOOLEAN); - return pop(BooleanToken.class).booleanValue(); - } - - @Override - public int readInt() { - checkToken(peek(), TokenType.INT); - return pop(IntToken.class).intValue(); - } - - @Override - public long readLong() { - checkToken(peek(), TokenType.LONG); - return pop(LongToken.class).longValue(); - } - - @Override - public double readDouble() { - checkToken(peek(), TokenType.DOUBLE); - return pop(DoubleToken.class).doubleValue(); - } - - @Override - public byte[] readBinary() { - checkToken(peek(), TokenType.BINARY); - return pop(BinaryToken.class).value(); - } - - @Override - public void readNull() { - checkToken(peek(), TokenType.NULL); - pop(ScalarToken.NULL); - } - - @Override - public void skipValue() { - tokenizer.skipValue(); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/reader/Tokenizer.java b/core/src/main/java/net/roxymc/jserialize/token/reader/Tokenizer.java deleted file mode 100644 index 937be07..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/reader/Tokenizer.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.roxymc.jserialize.token.reader; - -import net.roxymc.jserialize.token.Token; -import net.roxymc.jserialize.token.TokenType; - -import java.util.Iterator; - -public interface Tokenizer extends Iterator { - @Override - boolean hasNext(); - - TokenType peek(); - - @Override - Token next(); - - // returns raw value without tokenization - V nextValue(); - - void skipValue(); -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/reader/ValueTokenizer.java b/core/src/main/java/net/roxymc/jserialize/token/reader/ValueTokenizer.java deleted file mode 100644 index 390e6e9..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/reader/ValueTokenizer.java +++ /dev/null @@ -1,202 +0,0 @@ -package net.roxymc.jserialize.token.reader; - -import net.roxymc.jserialize.token.NameToken; -import net.roxymc.jserialize.token.Token; -import net.roxymc.jserialize.token.TokenType; -import net.roxymc.jserialize.token.ValueAccessor; -import org.jspecify.annotations.Nullable; - -import java.util.*; - -import static net.roxymc.jserialize.util.ObjectUtils.nonNull; - -public final class ValueTokenizer implements Tokenizer { - private final ValueAccessor accessor; - private final Deque> stack = new ArrayDeque<>(); - - public ValueTokenizer(V value, ValueAccessor accessor) { - this.accessor = nonNull(accessor, "accessor"); - push(value); - } - - private void push(V value) { - nonNull(value, "value"); - - boolean isObject = accessor.isObject(value); - if (isObject || accessor.isArray(value)) { - stack.push(new ContainerEntry<>(isObject, value, accessor.getValues(value).iterator())); - } else { - stack.push(new ScalarEntry<>(value)); - } - } - - @Override - public boolean hasNext() { - return !stack.isEmpty(); - } - - public TokenType peek() { - if (stack.isEmpty()) { - throw new NoSuchElementException(); - } - - Entry entry = stack.element(); - V value = entry.peek(); - - if (entry instanceof ContainerEntry) { - ContainerEntry container = (ContainerEntry) entry; - - if (!container.started) { - return container.isObject ? TokenType.OBJECT_START : TokenType.ARRAY_START; - } - - if (value == null && !container.hasNext()) { - return container.isObject ? TokenType.OBJECT_END : TokenType.ARRAY_END; - } - } - - if (value != null) { - if (entry instanceof ContainerEntry && ((ContainerEntry) entry).isObject) { - return TokenType.NAME; - } - - if (accessor.isObject(value)) { - return TokenType.OBJECT_START; - } - - if (accessor.isArray(value)) { - return TokenType.ARRAY_START; - } - - return accessor.getTokenType(value); - } - - // scalar is exhausted, pop it - stack.pop(); - - return peek(); - } - - @Override - public Token next() { - TokenType type = peek(); - Entry entry = stack.element(); - - if (type == TokenType.OBJECT_START || type == TokenType.ARRAY_START) { - V current = entry.pop(); - - if (current == null) { - return type == TokenType.OBJECT_START ? Token.OBJECT_START : Token.ARRAY_START; - } - - push(current); - return next(); - } - - if (type == TokenType.OBJECT_END || type == TokenType.ARRAY_END) { - stack.pop(); - return type == TokenType.OBJECT_END ? Token.OBJECT_END : Token.ARRAY_END; - } - - V value = Objects.requireNonNull(entry.pop()); - - if (type == TokenType.NAME) { - push(value); - return new NameToken(accessor.getName(value)); - } - - return accessor.toToken(value); - } - - @Override - public V nextValue() { - TokenType type = peek(); - Entry entry = stack.element(); - - if (type.kind() == TokenType.Kind.MARKER) { - throw new IllegalStateException(type + " does not support this operation"); - } - - if (type == TokenType.OBJECT_START || type == TokenType.ARRAY_START) { - ContainerEntry container = ((ContainerEntry) entry); - - if (!container.started) { - stack.pop(); - return container.container; - } - } - - return Objects.requireNonNull(entry.pop()); - } - - @Override - public void skipValue() { - nextValue(); - } - - private interface Entry { - boolean hasNext(); - - @Nullable V peek(); - - @Nullable V pop(); - } - - private static final class ScalarEntry implements Entry { - private @Nullable V value; - - private ScalarEntry(V value) { - this.value = nonNull(value, "value"); - } - - @Override - public boolean hasNext() { - return value != null; - } - - @Override - public @Nullable V peek() { - return value; - } - - @Override - public @Nullable V pop() { - V value = this.value; - this.value = null; - return value; - } - } - - private static final class ContainerEntry implements Entry { - private final boolean isObject; - private final V container; - private final Iterator iterator; - - private boolean started = false; - private @Nullable V value; - - private ContainerEntry(boolean isObject, V container, Iterator iterator) { - this.isObject = isObject; - this.container = nonNull(container, "container"); - this.iterator = nonNull(iterator, "iterator"); - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public @Nullable V peek() { - return value; - } - - @Override - public @Nullable V pop() { - started = true; - V value = this.value; - this.value = iterator.hasNext() ? iterator.next() : null; - return value; - } - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/writer/Detokenizer.java b/core/src/main/java/net/roxymc/jserialize/token/writer/Detokenizer.java deleted file mode 100644 index 2933a4f..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/writer/Detokenizer.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.roxymc.jserialize.token.writer; - -import net.roxymc.jserialize.token.Token; - -public interface Detokenizer { - void accept(Token token); - - V value(); -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/writer/TokenWriter.java b/core/src/main/java/net/roxymc/jserialize/token/writer/TokenWriter.java deleted file mode 100644 index c3a07d8..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/writer/TokenWriter.java +++ /dev/null @@ -1,76 +0,0 @@ -package net.roxymc.jserialize.token.writer; - -import net.roxymc.jserialize.Writer; -import net.roxymc.jserialize.token.*; - -public final class TokenWriter implements Writer { - private final Detokenizer detokenizer; - - public TokenWriter(Detokenizer detokenizer) { - this.detokenizer = detokenizer; - } - - public Detokenizer detokenizer() { - return detokenizer; - } - - @Override - public void writeName(String name) { - detokenizer.accept(new NameToken(name)); - } - - @Override - public void writeObjectStart() { - detokenizer.accept(Token.OBJECT_START); - } - - @Override - public void writeObjectEnd() { - detokenizer.accept(Token.OBJECT_END); - } - - @Override - public void writeArrayStart() { - detokenizer.accept(Token.ARRAY_START); - } - - @Override - public void writeArrayEnd() { - detokenizer.accept(Token.ARRAY_END); - } - - @Override - public void writeString(String value) { - detokenizer.accept(new StringToken(value)); - } - - @Override - public void writeBoolean(boolean value) { - detokenizer.accept(value ? BooleanToken.TRUE : BooleanToken.FALSE); - } - - @Override - public void writeInt(int value) { - detokenizer.accept(new IntToken(value)); - } - - @Override - public void writeLong(long value) { - detokenizer.accept(new LongToken(value)); - } - - @Override - public void writeDouble(double value) { - detokenizer.accept(new DoubleToken(value)); - } - - @Override - public void writeBinary(byte[] value) { - detokenizer.accept(new BinaryToken(value)); - } - - @Override - public void writeNull() { - detokenizer.accept(ScalarToken.NULL); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/token/writer/ValueDetokenizer.java b/core/src/main/java/net/roxymc/jserialize/token/writer/ValueDetokenizer.java deleted file mode 100644 index bb333fc..0000000 --- a/core/src/main/java/net/roxymc/jserialize/token/writer/ValueDetokenizer.java +++ /dev/null @@ -1,107 +0,0 @@ -package net.roxymc.jserialize.token.writer; - -import net.roxymc.jserialize.token.NameToken; -import net.roxymc.jserialize.token.ScalarToken; -import net.roxymc.jserialize.token.Token; -import net.roxymc.jserialize.token.ValueAccessor; -import org.jspecify.annotations.Nullable; - -import java.util.ArrayDeque; -import java.util.Collections; -import java.util.Deque; - -public final class ValueDetokenizer implements Detokenizer { - private final V root; - private final Deque stack = new ArrayDeque<>(); - private final ValueAccessor accessor; - - private @Nullable String pendingName; - - public ValueDetokenizer(V value, ValueAccessor accessor) { - this.root = value; - this.accessor = accessor; - } - - private void checkNoPendingName() { - if (pendingName != null) { - throw new IllegalStateException("previous name was not consumed"); - } - } - - @Override - public void accept(Token token) { - if (token instanceof NameToken) { - checkNoPendingName(); - this.pendingName = ((NameToken) token).value(); - return; - } - - if (token == Token.OBJECT_END) { - checkNoPendingName(); - - V container = stack.peek(); - if (container == null || !accessor.isObject(container)) { - throw new IllegalStateException("no object to end"); - } - - stack.pop(); - return; - } - - if (token == Token.ARRAY_END) { - V container = stack.peek(); - if (container == null || !accessor.isArray(container)) { - throw new IllegalStateException("no array to end"); - } - - stack.pop(); - return; - } - - if (token instanceof ScalarToken) { - accessor.setScalar(value(), ((ScalarToken) token).value()); - return; - } - - if (token == Token.OBJECT_START) { - V container = value(); - accessor.setScalar(container, Collections.emptyMap()); - stack.push(container); - return; - } - - if (token == Token.ARRAY_START) { - V container = value(); - accessor.setScalar(container, Collections.emptyList()); - stack.push(container); - return; - } - - throw new IllegalArgumentException("Unsupported token: " + token); - } - - @Override - public V value() { - if (stack.isEmpty()) { - return root; - } - - V container = stack.peek(); - - if (pendingName != null) { - if (!accessor.isObject(container)) { - throw new IllegalStateException("cannot write named value to a non-object"); - } - - String name = pendingName; - pendingName = null; - return accessor.objectAppend(container, name); - } - - if (!accessor.isArray(container)) { - throw new IllegalStateException("cannot append to a non-array"); - } - - return accessor.listAppend(container); - } -} diff --git a/core/src/main/java/net/roxymc/jserialize/util/IOBiConsumer.java b/core/src/main/java/net/roxymc/jserialize/util/IOBiConsumer.java new file mode 100644 index 0000000..a044820 --- /dev/null +++ b/core/src/main/java/net/roxymc/jserialize/util/IOBiConsumer.java @@ -0,0 +1,10 @@ +package net.roxymc.jserialize.util; + +import org.jspecify.annotations.Nullable; + +import java.io.IOException; + +@FunctionalInterface +public interface IOBiConsumer { + void accept(T t, U u) throws IOException; +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonReaderAdapter.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonReaderAdapter.java index 52d20b8..8108b6d 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonReaderAdapter.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonReaderAdapter.java @@ -4,5 +4,6 @@ import org.bson.BsonReader; interface BsonReaderAdapter extends Reader { + BsonReader getBsonReader(); } 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 d4607e1..6c5f4da 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 @@ -7,6 +7,9 @@ import net.roxymc.jserialize.adapter.WriteContext; import net.roxymc.jserialize.adapter.object.FormatUtils; import net.roxymc.jserialize.adapter.object.MapLike; +import net.roxymc.jserialize.format.TokenTypeRegistry; +import net.roxymc.jserialize.format.bson.token.BsonTokenTypes; +import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.bson.*; import org.jspecify.annotations.Nullable; @@ -17,6 +20,35 @@ import java.util.Map; final class BsonUtils implements FormatUtils { + static final TokenTypeRegistry TOKEN_TYPES = TokenTypeRegistry.create(builder -> builder + .bind(TokenTypes.NAME, null, BsonReader::readName, BsonWriter::writeName) + .bind(TokenTypes.OBJECT_START, BsonType.DOCUMENT, BsonReader::readStartDocument, BsonWriter::writeStartDocument) + .bind(TokenTypes.OBJECT_END, null, BsonReader::readEndDocument, BsonWriter::writeEndDocument) + .bind(TokenTypes.ARRAY_START, BsonType.ARRAY, BsonReader::readStartArray, BsonWriter::writeStartArray) + .bind(TokenTypes.ARRAY_END, null, BsonReader::readEndArray, BsonWriter::writeEndArray) + .bind(TokenTypes.STRING, BsonType.STRING, BsonReader::readString, BsonWriter::writeString) + .bind(TokenTypes.BOOLEAN, BsonType.BOOLEAN, BsonReader::readBoolean, BsonWriter::writeBoolean) + .bind(TokenTypes.INT, BsonType.INT32, BsonReader::readInt32, BsonWriter::writeInt32) + .bind(TokenTypes.LONG, BsonType.INT64, BsonReader::readInt64, BsonWriter::writeInt64) + .bind(TokenTypes.DOUBLE, BsonType.DOUBLE, BsonReader::readDouble, BsonWriter::writeDouble) + .bind(TokenTypes.BINARY, BsonType.BINARY, + reader -> reader.readBinaryData().getData(), + (writer, value) -> writer.writeBinaryData(new BsonBinary(value)) + ) + .bind(TokenTypes.NULL, BsonType.NULL, BsonReader::readNull, BsonWriter::writeNull) + .bind(BsonTokenTypes.UNDEFINED, BsonType.UNDEFINED, BsonReader::readUndefined, BsonWriter::writeUndefined) + .bind(BsonTokenTypes.OBJECT_ID, BsonType.OBJECT_ID, BsonReader::readObjectId, BsonWriter::writeObjectId) + .bind(BsonTokenTypes.DATE_TIME, BsonType.DATE_TIME, BsonReader::readDateTime, BsonWriter::writeDateTime) + .bind(BsonTokenTypes.REGULAR_EXPRESSION, BsonType.REGULAR_EXPRESSION, BsonReader::readRegularExpression, BsonWriter::writeRegularExpression) + .bind(BsonTokenTypes.DB_POINTER, BsonType.DB_POINTER, BsonReader::readDBPointer, BsonWriter::writeDBPointer) + .bind(BsonTokenTypes.JAVA_SCRIPT, BsonType.JAVASCRIPT, BsonReader::readJavaScript, BsonWriter::writeJavaScript) + .bind(BsonTokenTypes.SYMBOL, BsonType.SYMBOL, BsonReader::readSymbol, BsonWriter::writeSymbol) + .bind(BsonTokenTypes.JAVA_SCRIPT_WITH_SCOPE, BsonType.JAVASCRIPT_WITH_SCOPE, BsonReader::readJavaScriptWithScope, BsonWriter::writeJavaScriptWithScope) + .bind(BsonTokenTypes.DECIMAL_128, BsonType.DECIMAL128, BsonReader::readDecimal128, BsonWriter::writeDecimal128) + .bind(BsonTokenTypes.MIN_KEY, BsonType.MIN_KEY, BsonReader::readMinKey, BsonWriter::writeMinKey) + .bind(BsonTokenTypes.MAX_KEY, BsonType.MAX_KEY, BsonReader::readMaxKey, BsonWriter::writeMaxKey) + ); + static final BsonUtils INSTANCE = new BsonUtils(); private BsonUtils() { diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonWriterAdapter.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonWriterAdapter.java index caec540..d051b7d 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonWriterAdapter.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/BsonWriterAdapter.java @@ -1,9 +1,13 @@ package net.roxymc.jserialize.format.bson; import net.roxymc.jserialize.AbstractWriter; -import org.bson.BsonBinary; +import net.roxymc.jserialize.format.TokenTypeInfo; +import net.roxymc.jserialize.token.TokenType; +import org.bson.BsonReader; import org.bson.BsonWriter; +import java.io.IOException; + final class BsonWriterAdapter extends AbstractWriter { final BsonWriter writer; @@ -12,33 +16,23 @@ final class BsonWriterAdapter extends AbstractWriter { } @Override - public void writeName(String name) { - writer.writeName(name); - } + public void write(TokenType.NonValued tokenType) throws IOException { + TokenTypeInfo.NonValued info = BsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } - @Override - public void writeObjectStart() { - writer.writeStartDocument(); + info.write(writer); } @Override - public void writeObjectEnd() { - writer.writeEndDocument(); - } + public void write(TokenType.Valued tokenType, T value) throws IOException { + TokenTypeInfo.Valued info = BsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } - @Override - public void writeArrayStart() { - writer.writeStartArray(); - } - - @Override - public void writeArrayEnd() { - writer.writeEndArray(); - } - - @Override - public void writeString(String value) { - writer.writeString(value); + info.write(writer, value); } @Override @@ -60,14 +54,4 @@ public void writeLong(long value) { public void writeDouble(double value) { writer.writeDouble(value); } - - @Override - public void writeBinary(byte[] value) { - writer.writeBinaryData(new BsonBinary(value)); - } - - @Override - public void writeNull() { - writer.writeNull(); - } } diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/FallbackBsonReaderAdapter.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/FallbackBsonReaderAdapter.java index 4a7cbeb..7dcda7a 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/FallbackBsonReaderAdapter.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/FallbackBsonReaderAdapter.java @@ -1,11 +1,15 @@ package net.roxymc.jserialize.format.bson; import net.roxymc.jserialize.AbstractReader; +import net.roxymc.jserialize.format.TokenTypeInfo; import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; import org.bson.BsonReader; import org.bson.BsonType; +import org.bson.BsonWriter; import org.jspecify.annotations.Nullable; +import java.io.IOException; import java.util.ArrayDeque; import java.util.Deque; @@ -44,43 +48,63 @@ public TokenType peek() { if (container == null) { // that's the best way we can handle this case, // unfortunately BsonReader interface doesn't expose much info - return tokenType = TokenType.END; + return tokenType = TokenTypes.END; } - return tokenType = (container == Container.OBJECT ? TokenType.OBJECT_END : TokenType.ARRAY_END); + return tokenType = (container == Container.OBJECT ? TokenTypes.OBJECT_END : TokenTypes.ARRAY_END); } - if (containerStack.peek() == Container.OBJECT && lastTokenType != TokenType.NAME) { - return tokenType = TokenType.NAME; + if (containerStack.peek() == Container.OBJECT && lastTokenType != TokenTypes.NAME) { + return tokenType = TokenTypes.NAME; } - switch (bsonType) { - case DOCUMENT: - return tokenType = TokenType.OBJECT_START; - case ARRAY: - return tokenType = TokenType.ARRAY_START; - case BOOLEAN: - return tokenType = TokenType.BOOLEAN; - case STRING: - return tokenType = TokenType.STRING; - case INT32: - return tokenType = TokenType.INT; - case INT64: - return tokenType = TokenType.LONG; - case DOUBLE: - return tokenType = TokenType.DOUBLE; - case BINARY: - return tokenType = TokenType.BINARY; - case NULL: - return tokenType = TokenType.NULL; - default: - return tokenType = TokenType.UNKNOWN; - } + return tokenType = BsonUtils.TOKEN_TYPES.fromNative(bsonType); } finally { lastTokenType = tokenType; } } + @Override + public void read(TokenType.NonValued tokenType) throws IOException { + checkToken(peek(), tokenType); + + if (tokenType == TokenTypes.OBJECT_START) { + readObjectStart(); + return; + } else if (tokenType == TokenTypes.OBJECT_END) { + readObjectEnd(); + return; + } else if (tokenType == TokenTypes.ARRAY_START) { + readArrayStart(); + return; + } else if (tokenType == TokenTypes.ARRAY_END) { + readArrayEnd(); + return; + } + + TokenTypeInfo.NonValued info = BsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } + + info.read(reader); + resetToken(); + } + + @Override + public T read(TokenType.Valued tokenType) throws IOException { + checkToken(peek(), tokenType); + + TokenTypeInfo.Valued info = BsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } + + T value = info.read(reader); + resetToken(); + return value; + } + void resetToken() { tokenType = null; @@ -92,18 +116,9 @@ void resetToken() { } } - @Override - public String readName() { - checkToken(peek(), TokenType.NAME); - - String value = reader.readName(); - resetToken(); - return value; - } - @Override public void readObjectStart() { - checkToken(peek(), TokenType.OBJECT_START); + checkToken(peek(), TokenTypes.OBJECT_START); reader.readStartDocument(); containerStack.push(Container.OBJECT); @@ -112,7 +127,7 @@ public void readObjectStart() { @Override public void readObjectEnd() { - checkToken(peek(), TokenType.OBJECT_END); + checkToken(peek(), TokenTypes.OBJECT_END); reader.readEndDocument(); containerStack.pop(); @@ -121,7 +136,7 @@ public void readObjectEnd() { @Override public void readArrayStart() { - checkToken(peek(), TokenType.ARRAY_START); + checkToken(peek(), TokenTypes.ARRAY_START); reader.readStartArray(); containerStack.push(Container.ARRAY); @@ -130,25 +145,16 @@ public void readArrayStart() { @Override public void readArrayEnd() { - checkToken(peek(), TokenType.ARRAY_END); + checkToken(peek(), TokenTypes.ARRAY_END); reader.readEndArray(); containerStack.pop(); resetToken(); } - @Override - public String readString() { - checkToken(peek(), TokenType.STRING); - - String value = reader.readString(); - resetToken(); - return value; - } - @Override public boolean readBoolean() { - checkToken(peek(), TokenType.BOOLEAN); + checkToken(peek(), TokenTypes.BOOLEAN); boolean value = reader.readBoolean(); resetToken(); @@ -157,7 +163,7 @@ public boolean readBoolean() { @Override public int readInt() { - checkToken(peek(), TokenType.INT); + checkToken(peek(), TokenTypes.INT); int value = reader.readInt32(); resetToken(); @@ -166,7 +172,7 @@ public int readInt() { @Override public long readLong() { - checkToken(peek(), TokenType.LONG); + checkToken(peek(), TokenTypes.LONG); long value = reader.readInt64(); resetToken(); @@ -175,33 +181,16 @@ public long readLong() { @Override public double readDouble() { - checkToken(peek(), TokenType.DOUBLE); + checkToken(peek(), TokenTypes.DOUBLE); double value = reader.readDouble(); resetToken(); return value; } - @Override - public byte[] readBinary() { - checkToken(peek(), TokenType.BINARY); - - byte[] value = reader.readBinaryData().getData(); - resetToken(); - return value; - } - - @Override - public void readNull() { - checkToken(peek(), TokenType.NULL); - - reader.readNull(); - resetToken(); - } - @Override public void skipValue() { - checkToken(peek(), type -> type.kind().isValue()); + checkToken(peek(), type -> type.kind().marksValue()); reader.skipValue(); resetToken(); diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/StandardBsonReaderAdapter.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/StandardBsonReaderAdapter.java index b239783..59b771b 100644 --- a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/StandardBsonReaderAdapter.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/StandardBsonReaderAdapter.java @@ -1,11 +1,15 @@ package net.roxymc.jserialize.format.bson; import net.roxymc.jserialize.AbstractReader; +import net.roxymc.jserialize.format.TokenTypeInfo; import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; import org.bson.AbstractBsonReader; import org.bson.BsonReader; import org.bson.BsonType; +import org.bson.BsonWriter; +import java.io.IOException; import java.util.Objects; final class StandardBsonReaderAdapter extends AbstractReader implements BsonReaderAdapter { @@ -27,7 +31,7 @@ public TokenType peek() { } if (reader.getState() == AbstractBsonReader.State.NAME) { - return TokenType.NAME; + return TokenTypes.NAME; } // TODO actually, that's not necessarily true. After State.DONE, reader usually comes back to State.VALUE with BsonType.DOCUMENT @@ -42,113 +46,65 @@ public TokenType peek() { BsonType bsonType = Objects.requireNonNullElseGet(reader.getCurrentBsonType(), reader::readBsonType); if (reader.getState() == AbstractBsonReader.State.END_OF_DOCUMENT) { - return TokenType.OBJECT_END; + return TokenTypes.OBJECT_END; } else if (reader.getState() == AbstractBsonReader.State.END_OF_ARRAY) { - return TokenType.ARRAY_END; + return TokenTypes.ARRAY_END; } - switch (bsonType) { - case DOCUMENT: - return TokenType.OBJECT_START; - case ARRAY: - return TokenType.ARRAY_START; - case END_OF_DOCUMENT: - // ARRAY_END is handled earlier - return TokenType.OBJECT_END; - case BOOLEAN: - return TokenType.BOOLEAN; - case STRING: - return TokenType.STRING; - case INT32: - return TokenType.INT; - case INT64: - return TokenType.LONG; - case DOUBLE: - return TokenType.DOUBLE; - case BINARY: - return TokenType.BINARY; - case NULL: - return TokenType.NULL; - default: - return TokenType.UNKNOWN; - } + return BsonUtils.TOKEN_TYPES.fromNative(bsonType); } @Override - public String readName() { - checkToken(peek(), TokenType.NAME); - return reader.readName(); - } + public void read(TokenType.NonValued tokenType) throws IOException { + checkToken(peek(), tokenType); - @Override - public void readObjectStart() { - checkToken(peek(), TokenType.OBJECT_START); - reader.readStartDocument(); - } + TokenTypeInfo.NonValued info = BsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } - @Override - public void readObjectEnd() { - checkToken(peek(), TokenType.OBJECT_END); - reader.readEndDocument(); + info.read(reader); } @Override - public void readArrayStart() { - checkToken(peek(), TokenType.ARRAY_START); - reader.readStartArray(); - } + public T read(TokenType.Valued tokenType) throws IOException { + checkToken(peek(), tokenType); - @Override - public void readArrayEnd() { - checkToken(peek(), TokenType.ARRAY_END); - reader.readEndArray(); - } + TokenTypeInfo.Valued info = BsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } - @Override - public String readString() { - checkToken(peek(), TokenType.STRING); - return reader.readString(); + return info.read(reader); } @Override public boolean readBoolean() { - checkToken(peek(), TokenType.BOOLEAN); + checkToken(peek(), TokenTypes.BOOLEAN); return reader.readBoolean(); } @Override public int readInt() { - checkToken(peek(), TokenType.INT); + checkToken(peek(), TokenTypes.INT); return reader.readInt32(); } @Override public long readLong() { - checkToken(peek(), TokenType.LONG); + checkToken(peek(), TokenTypes.LONG); return reader.readInt64(); } @Override public double readDouble() { - checkToken(peek(), TokenType.DOUBLE); + checkToken(peek(), TokenTypes.DOUBLE); return reader.readDouble(); } - @Override - public byte[] readBinary() { - checkToken(peek(), TokenType.BINARY); - return reader.readBinaryData().getData(); - } - - @Override - public void readNull() { - checkToken(peek(), TokenType.NULL); - reader.readNull(); - } - @Override public void skipValue() { - checkToken(peek(), type -> type.kind().isValue()); + checkToken(peek(), type -> type.kind().marksValue()); reader.skipValue(); } } 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 264d5dd..71e961a 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 @@ -5,7 +5,7 @@ import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.bson.codecs.Codec; import org.bson.codecs.DecoderContext; @@ -23,7 +23,7 @@ final class WrappedCodec implements TypeAdapter { @Override public @Nullable T read(Reader reader, TypeRef type, ReadContext context) throws IOException { - if (reader.peek() == TokenType.NULL) { + if (reader.peek() == TokenTypes.NULL) { return null; } diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/BsonTokenTypes.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/BsonTokenTypes.java new file mode 100644 index 0000000..09a651a --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/BsonTokenTypes.java @@ -0,0 +1,28 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenType.Kind; +import org.bson.BsonDbPointer; +import org.bson.BsonRegularExpression; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; + +import static net.roxymc.jserialize.token.TokenType.nonValued; +import static net.roxymc.jserialize.token.TokenType.valued; + +public final class BsonTokenTypes { + public static final TokenType.NonValued UNDEFINED = nonValued(Kind.NULL, () -> BsonTokens.UNDEFINED); + public static final TokenType.Valued OBJECT_ID = valued(Kind.SCALAR, ObjectIdToken::new); + public static final TokenType.Valued DATE_TIME = valued(Kind.SCALAR, DateTimeToken::new); + public static final TokenType.Valued REGULAR_EXPRESSION = valued(Kind.SCALAR, RegularExpressionToken::new); + public static final TokenType.Valued DB_POINTER = valued(Kind.SCALAR, DbPointerToken::new); + public static final TokenType.Valued JAVA_SCRIPT = valued(Kind.SCALAR, JavaScriptToken::new); + public static final TokenType.Valued SYMBOL = valued(Kind.SCALAR, SymbolToken::new); + public static final TokenType.Valued JAVA_SCRIPT_WITH_SCOPE = valued(Kind.SCALAR, JavaScriptWithScopeToken::new); + public static final TokenType.Valued DECIMAL_128 = valued(Kind.SCALAR, Decimal128Token::new); + public static final TokenType.NonValued MIN_KEY = nonValued(Kind.NULL, () -> BsonTokens.MIN_KEY); + public static final TokenType.NonValued MAX_KEY = nonValued(Kind.NULL, () -> BsonTokens.MAX_KEY); + + private BsonTokenTypes() { + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/BsonTokens.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/BsonTokens.java new file mode 100644 index 0000000..78cd879 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/BsonTokens.java @@ -0,0 +1,12 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.Token; + +public final class BsonTokens { + public static final Token UNDEFINED = () -> BsonTokenTypes.UNDEFINED; + public static final Token MIN_KEY = () -> BsonTokenTypes.MIN_KEY; + public static final Token MAX_KEY = () -> BsonTokenTypes.MAX_KEY; + + private BsonTokens() { + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/DateTimeToken.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/DateTimeToken.java new file mode 100644 index 0000000..aa9cb38 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/DateTimeToken.java @@ -0,0 +1,26 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.AbstractValuedToken; +import net.roxymc.jserialize.token.TokenType; + +public final class DateTimeToken extends AbstractValuedToken { + private final long value; + + public DateTimeToken(long value) { + this.value = value; + } + + public long longValue() { + return value; + } + + @Override + public Long value() { + return value; + } + + @Override + public TokenType.Valued type() { + return BsonTokenTypes.DATE_TIME; + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/DbPointerToken.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/DbPointerToken.java new file mode 100644 index 0000000..8df8cac --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/DbPointerToken.java @@ -0,0 +1,25 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.AbstractValuedToken; +import net.roxymc.jserialize.token.TokenType; +import org.bson.BsonDbPointer; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class DbPointerToken extends AbstractValuedToken { + private final BsonDbPointer value; + + public DbPointerToken(BsonDbPointer value) { + this.value = nonNull(value, "value"); + } + + @Override + public BsonDbPointer value() { + return value; + } + + @Override + public TokenType.Valued type() { + return BsonTokenTypes.DB_POINTER; + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/Decimal128Token.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/Decimal128Token.java new file mode 100644 index 0000000..c762904 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/Decimal128Token.java @@ -0,0 +1,25 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.AbstractValuedToken; +import net.roxymc.jserialize.token.TokenType; +import org.bson.types.Decimal128; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class Decimal128Token extends AbstractValuedToken { + private final Decimal128 value; + + public Decimal128Token(Decimal128 value) { + this.value = nonNull(value, "value"); + } + + @Override + public Decimal128 value() { + return value; + } + + @Override + public TokenType.Valued type() { + return BsonTokenTypes.DECIMAL_128; + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/JavaScriptToken.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/JavaScriptToken.java new file mode 100644 index 0000000..cb4fe5d --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/JavaScriptToken.java @@ -0,0 +1,24 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.AbstractValuedToken; +import net.roxymc.jserialize.token.TokenType; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class JavaScriptToken extends AbstractValuedToken { + private final String value; + + public JavaScriptToken(String value) { + this.value = nonNull(value, "value"); + } + + @Override + public String value() { + return value; + } + + @Override + public TokenType.Valued type() { + return BsonTokenTypes.JAVA_SCRIPT; + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/JavaScriptWithScopeToken.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/JavaScriptWithScopeToken.java new file mode 100644 index 0000000..741d8a1 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/JavaScriptWithScopeToken.java @@ -0,0 +1,24 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.AbstractValuedToken; +import net.roxymc.jserialize.token.TokenType; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class JavaScriptWithScopeToken extends AbstractValuedToken { + private final String value; + + public JavaScriptWithScopeToken(String value) { + this.value = nonNull(value, "value"); + } + + @Override + public String value() { + return value; + } + + @Override + public TokenType.Valued type() { + return BsonTokenTypes.JAVA_SCRIPT_WITH_SCOPE; + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/ObjectIdToken.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/ObjectIdToken.java new file mode 100644 index 0000000..c3babcf --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/ObjectIdToken.java @@ -0,0 +1,25 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.AbstractValuedToken; +import net.roxymc.jserialize.token.TokenType; +import org.bson.types.ObjectId; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class ObjectIdToken extends AbstractValuedToken { + private final ObjectId value; + + public ObjectIdToken(ObjectId value) { + this.value = nonNull(value, "value"); + } + + @Override + public ObjectId value() { + return value; + } + + @Override + public TokenType.Valued type() { + return BsonTokenTypes.OBJECT_ID; + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/RegularExpressionToken.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/RegularExpressionToken.java new file mode 100644 index 0000000..a228e91 --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/RegularExpressionToken.java @@ -0,0 +1,25 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.AbstractValuedToken; +import net.roxymc.jserialize.token.TokenType; +import org.bson.BsonRegularExpression; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class RegularExpressionToken extends AbstractValuedToken { + private final BsonRegularExpression value; + + public RegularExpressionToken(BsonRegularExpression value) { + this.value = nonNull(value, "value"); + } + + @Override + public BsonRegularExpression value() { + return value; + } + + @Override + public TokenType.Valued type() { + return BsonTokenTypes.REGULAR_EXPRESSION; + } +} diff --git a/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/SymbolToken.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/SymbolToken.java new file mode 100644 index 0000000..ec8615b --- /dev/null +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/SymbolToken.java @@ -0,0 +1,24 @@ +package net.roxymc.jserialize.format.bson.token; + +import net.roxymc.jserialize.token.AbstractValuedToken; +import net.roxymc.jserialize.token.TokenType; + +import static net.roxymc.jserialize.util.ObjectUtils.nonNull; + +public final class SymbolToken extends AbstractValuedToken { + private final String value; + + public SymbolToken(String value) { + this.value = nonNull(value, "value"); + } + + @Override + public String value() { + return value; + } + + @Override + public TokenType.Valued type() { + return BsonTokenTypes.SYMBOL; + } +} diff --git a/core/src/main/java/net/roxymc/jserialize/token/writer/package-info.java b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/package-info.java similarity index 53% rename from core/src/main/java/net/roxymc/jserialize/token/writer/package-info.java rename to format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/package-info.java index 801481c..89a5aad 100644 --- a/core/src/main/java/net/roxymc/jserialize/token/writer/package-info.java +++ b/format/bson/src/main/java/net/roxymc/jserialize/format/bson/token/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package net.roxymc.jserialize.token.writer; +package net.roxymc.jserialize.format.bson.token; import org.jspecify.annotations.NullMarked; \ No newline at end of file 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 4a3a755..81466c0 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 @@ -8,10 +8,8 @@ import net.roxymc.jserialize.adapter.WriteContext; import net.roxymc.jserialize.adapter.object.FormatUtils; import net.roxymc.jserialize.adapter.object.MapLike; -import net.roxymc.jserialize.token.reader.TokenReader; -import net.roxymc.jserialize.token.reader.ValueTokenizer; -import net.roxymc.jserialize.token.writer.TokenWriter; -import net.roxymc.jserialize.token.writer.ValueDetokenizer; +import net.roxymc.jserialize.format.tree.TreeReader; +import net.roxymc.jserialize.format.tree.TreeWriter; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; import org.spongepowered.configurate.CommentedConfigurationNode; @@ -29,11 +27,11 @@ private ConfigurateUtils() { } static Reader newReader0(ConfigurationNode node) { - return new TokenReader<>(new ValueTokenizer<>(node, ConfigurationNodeAccessor.INSTANCE)); + return new TreeReader<>(ConfigurationNodeAccessor.INSTANCE, node); } static Writer newWriter0(ConfigurationNode node) { - return new TokenWriter<>(new ValueDetokenizer<>(node, ConfigurationNodeAccessor.INSTANCE)); + return new TreeWriter<>(ConfigurationNodeAccessor.INSTANCE, node); } @Override diff --git a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurationNodeAccessor.java b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurationNodeAccessor.java index abc5732..3b1b449 100644 --- a/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurationNodeAccessor.java +++ b/format/configurate/src/main/java/net/roxymc/jserialize/format/configurate/ConfigurationNodeAccessor.java @@ -1,75 +1,88 @@ package net.roxymc.jserialize.format.configurate; -import net.roxymc.jserialize.token.AbstractValueAccessor; -import net.roxymc.jserialize.token.ScalarToken; -import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.format.tree.TreeAccessor; +import net.roxymc.jserialize.format.tree.TreeNode; +import net.roxymc.jserialize.token.*; import org.jspecify.annotations.Nullable; import org.spongepowered.configurate.ConfigurationNode; -final class ConfigurationNodeAccessor extends AbstractValueAccessor { +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +final class ConfigurationNodeAccessor implements TreeAccessor { static final ConfigurationNodeAccessor INSTANCE = new ConfigurationNodeAccessor(); private ConfigurationNodeAccessor() { } - @Override - public String getName(ConfigurationNode value) { - return String.valueOf(value.key()); - } - - @Override - public boolean isObject(ConfigurationNode value) { - return value.isMap(); - } + private static ScalarToken wrapAsToken(Object scalar) { + if (scalar instanceof String) { + return new StringToken((String) scalar); + } else if (scalar instanceof Boolean) { + return BooleanToken.of((boolean) scalar); + } else if (scalar instanceof Float || scalar instanceof Double) { + return new DoubleToken(((Number) scalar).doubleValue()); + } else if (scalar instanceof Long) { + return new LongToken((long) scalar); + } else if (scalar instanceof Number) { + return new IntToken(((Number) scalar).intValue()); + } else if (scalar instanceof byte[]) { + return new BinaryToken((byte[]) scalar); + } - @Override - public boolean isArray(ConfigurationNode value) { - return value.isList(); + throw new IllegalArgumentException("Unsupported scalar: " + scalar); } @Override - public Iterable getValues(ConfigurationNode value) { + public TreeNode nodeOf(ConfigurationNode value) { if (value.isMap()) { - return value.childrenMap().values(); + return TreeNode.object(value.childrenMap().entrySet().stream() + .map(e -> Map.entry(String.valueOf(e.getKey()), e.getValue())) + .collect(Collectors.toList()) + ); } if (value.isList()) { - return value.childrenList(); + return TreeNode.array(value.childrenList()); } - throw new IllegalArgumentException("value is a scalar"); + Object scalar = value.rawScalar(); + return TreeNode.scalar(scalar != null ? wrapAsToken(scalar) : null); } @Override - public ConfigurationNode objectAppend(ConfigurationNode container, String name) { - return container.node(name); + public boolean isObject(ConfigurationNode value) { + return value.isMap(); } @Override - public ConfigurationNode listAppend(ConfigurationNode container) { - return container.appendListNode(); + public void initObject(ConfigurationNode value) { + value.raw(Collections.emptyMap()); } @Override - public void setScalar(ConfigurationNode value, @Nullable Object raw) { - value.raw(raw); + public boolean isArray(ConfigurationNode value) { + return value.isList(); } @Override - public TokenType getTokenType(ConfigurationNode value) { - if (value.isMap() || value.isList()) { - throw new IllegalArgumentException("value is not a scalar"); - } + public void initArray(ConfigurationNode value) { + value.raw(Collections.emptyList()); + } - return getRawTokenType(value.rawScalar()); + @Override + public void setScalar(ConfigurationNode value, @Nullable ScalarToken token) { + value.raw(token != null ? token.value() : null); } @Override - public ScalarToken toToken(ConfigurationNode value) { - if (value.isMap() || value.isList()) { - throw new IllegalArgumentException("value is not a scalar"); - } + public ConfigurationNode appendEntry(ConfigurationNode object, String name) { + return object.node(name); + } - return toRawToken(value.rawScalar()); + @Override + public ConfigurationNode appendElement(ConfigurationNode array) { + return array.appendListNode(); } } 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 761e0a7..eafe6e8 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 @@ -5,8 +5,8 @@ import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; import net.roxymc.jserialize.adapter.WriteContext; -import net.roxymc.jserialize.token.reader.TokenReader; -import net.roxymc.jserialize.token.writer.TokenWriter; +import net.roxymc.jserialize.format.tree.TreeReader; +import net.roxymc.jserialize.format.tree.TreeWriter; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; import org.spongepowered.configurate.ConfigurationNode; @@ -24,14 +24,14 @@ final class WrappedTypeSerializer implements TypeAdapter { @Override public T read(Reader reader, TypeRef type, ReadContext context) throws IOException { @SuppressWarnings("unchecked") - TokenReader tokenReader = (TokenReader) reader; - return serializer.deserialize(type.getAnnotatedType(), tokenReader.tokenizer().nextValue()); + 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 { @SuppressWarnings("unchecked") - TokenWriter tokenWriter = (TokenWriter) writer; - serializer.serialize(type.getAnnotatedType(), value, tokenWriter.detokenizer().value()); + TreeWriter tokenWriter = (TreeWriter) writer; + serializer.serialize(type.getAnnotatedType(), value, tokenWriter.currentValue()); } } diff --git a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonReaderAdapter.java b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonReaderAdapter.java index 1a4098e..dac7c3e 100644 --- a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonReaderAdapter.java +++ b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonReaderAdapter.java @@ -1,8 +1,12 @@ package net.roxymc.jserialize.format.gson; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; import net.roxymc.jserialize.AbstractReader; +import net.roxymc.jserialize.format.TokenTypeInfo; import net.roxymc.jserialize.token.TokenType; +import net.roxymc.jserialize.token.TokenTypes; import java.io.IOException; @@ -15,106 +19,71 @@ public GsonReaderAdapter(JsonReader reader) { @Override public TokenType peek() throws IOException { - switch (reader.peek()) { - case NAME: - return TokenType.NAME; - case BEGIN_OBJECT: - return TokenType.OBJECT_START; - case END_OBJECT: - return TokenType.OBJECT_END; - case BEGIN_ARRAY: - return TokenType.ARRAY_START; - case END_ARRAY: - return TokenType.ARRAY_END; - case STRING: - return TokenType.STRING; - case BOOLEAN: - return TokenType.BOOLEAN; - case NUMBER: - return TokenType.NUMBER; - case NULL: - return TokenType.NULL; - case END_DOCUMENT: - return TokenType.END; - default: - return TokenType.UNKNOWN; + JsonToken jsonToken = reader.peek(); + if (jsonToken == JsonToken.END_DOCUMENT) { + return TokenTypes.END; } - } - @Override - public String readName() throws IOException { - checkToken(peek(), TokenType.NAME); - return reader.nextName(); + return GsonUtils.TOKEN_TYPES.fromNative(jsonToken); } @Override - public void readObjectStart() throws IOException { - checkToken(peek(), TokenType.OBJECT_START); - reader.beginObject(); - } + public void read(TokenType.NonValued tokenType) throws IOException { + checkToken(peek(), tokenType); - @Override - public void readObjectEnd() throws IOException { - checkToken(peek(), TokenType.OBJECT_END); - reader.endObject(); - } + TokenTypeInfo.NonValued info = GsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } - @Override - public void readArrayStart() throws IOException { - checkToken(peek(), TokenType.ARRAY_START); - reader.beginArray(); + info.read(reader); } @Override - public void readArrayEnd() throws IOException { - checkToken(peek(), TokenType.ARRAY_END); - reader.endArray(); - } + public T read(TokenType.Valued tokenType) throws IOException { + if (tokenType == TokenTypes.INT || tokenType == TokenTypes.LONG || tokenType == TokenTypes.DOUBLE) { + checkToken(peek(), TokenTypes.NUMERIC); + } else if (tokenType == TokenTypes.STRING) { + checkToken(peek(), type -> type == TokenTypes.STRING || type == TokenTypes.NUMERIC); + } else { + checkToken(peek(), tokenType); + } - @Override - public String readString() throws IOException { - checkToken(peek(), TokenType.STRING); - return reader.nextString(); + TokenTypeInfo.Valued info = GsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } + + return info.read(reader); } @Override public boolean readBoolean() throws IOException { - checkToken(peek(), TokenType.BOOLEAN); + checkToken(peek(), TokenTypes.BOOLEAN); return reader.nextBoolean(); } @Override public int readInt() throws IOException { - checkToken(peek(), TokenType.NUMBER); + checkToken(peek(), TokenTypes.NUMERIC); return reader.nextInt(); } @Override public long readLong() throws IOException { - checkToken(peek(), TokenType.NUMBER); + checkToken(peek(), TokenTypes.NUMERIC); return reader.nextLong(); } @Override public double readDouble() throws IOException { - checkToken(peek(), TokenType.NUMBER); + checkToken(peek(), TokenTypes.NUMERIC); return reader.nextDouble(); } - @Override - public byte[] readBinary() { - throw notSupported(TokenType.BINARY); - } - - @Override - public void readNull() throws IOException { - checkToken(peek(), TokenType.NULL); - reader.nextNull(); - } - @Override public void skipValue() throws IOException { - checkToken(peek(), type -> type.kind().isValue()); + checkToken(peek(), type -> type.kind().marksValue()); reader.skipValue(); } } 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 65f2fb7..9315c0e 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 @@ -4,6 +4,9 @@ import com.google.gson.JsonObject; import com.google.gson.internal.bind.JsonTreeReader; import com.google.gson.internal.bind.JsonTreeWriter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; import net.roxymc.jserialize.Reader; import net.roxymc.jserialize.adapter.ReadContext; import net.roxymc.jserialize.adapter.TypeAdapter; @@ -11,6 +14,8 @@ import net.roxymc.jserialize.adapter.WriteContext; import net.roxymc.jserialize.adapter.object.FormatUtils; import net.roxymc.jserialize.adapter.object.MapLike; +import net.roxymc.jserialize.format.TokenTypeRegistry; +import net.roxymc.jserialize.token.TokenTypes; import net.roxymc.jserialize.type.TypeRef; import org.jspecify.annotations.Nullable; @@ -20,6 +25,21 @@ import java.util.Map; final class GsonUtils implements FormatUtils { + static final TokenTypeRegistry TOKEN_TYPES = TokenTypeRegistry.create(builder -> builder + .bind(TokenTypes.NAME, JsonToken.NAME, JsonReader::nextName, JsonWriter::name) + .bind(TokenTypes.OBJECT_START, JsonToken.BEGIN_OBJECT, JsonReader::beginObject, JsonWriter::beginObject) + .bind(TokenTypes.OBJECT_END, JsonToken.END_OBJECT, JsonReader::endObject, JsonWriter::endObject) + .bind(TokenTypes.ARRAY_START, JsonToken.BEGIN_ARRAY, JsonReader::beginArray, JsonWriter::beginArray) + .bind(TokenTypes.ARRAY_END, JsonToken.END_ARRAY, JsonReader::endArray, JsonWriter::endArray) + .bind(TokenTypes.STRING, JsonToken.STRING, JsonReader::nextString, JsonWriter::value) + .bind(TokenTypes.BOOLEAN, JsonToken.BOOLEAN, JsonReader::nextBoolean, JsonWriter::value) + .bind(TokenTypes.INT, null, JsonReader::nextInt, JsonWriter::value) + .bind(TokenTypes.LONG, null, JsonReader::nextLong, JsonWriter::value) + .bind(TokenTypes.DOUBLE, null, JsonReader::nextDouble, JsonWriter::value) + .bind(TokenTypes.NUMERIC, JsonToken.NUMBER) + .bind(TokenTypes.NULL, JsonToken.NULL, JsonReader::nextNull, JsonWriter::nullValue) + ); + static final GsonUtils INSTANCE = new GsonUtils(); private GsonUtils() { diff --git a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonWriterAdapter.java b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonWriterAdapter.java index e5ed800..15592a2 100644 --- a/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonWriterAdapter.java +++ b/format/gson/src/main/java/net/roxymc/jserialize/format/gson/GsonWriterAdapter.java @@ -1,7 +1,9 @@ package net.roxymc.jserialize.format.gson; +import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import net.roxymc.jserialize.AbstractWriter; +import net.roxymc.jserialize.format.TokenTypeInfo; import net.roxymc.jserialize.token.TokenType; import java.io.IOException; @@ -14,33 +16,23 @@ final class GsonWriterAdapter extends AbstractWriter { } @Override - public void writeName(String name) throws IOException { - writer.name(name); - } + public void write(TokenType.NonValued tokenType) throws IOException { + TokenTypeInfo.NonValued info = GsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } - @Override - public void writeObjectStart() throws IOException { - writer.beginObject(); + info.write(writer); } @Override - public void writeObjectEnd() throws IOException { - writer.endObject(); - } + public void write(TokenType.Valued tokenType, T value) throws IOException { + TokenTypeInfo.Valued info = GsonUtils.TOKEN_TYPES.get(tokenType); + if (info == null) { + throw notSupported(tokenType); + } - @Override - public void writeArrayStart() throws IOException { - writer.beginArray(); - } - - @Override - public void writeArrayEnd() throws IOException { - writer.endArray(); - } - - @Override - public void writeString(String value) throws IOException { - writer.value(value); + info.write(writer, value); } @Override @@ -62,14 +54,4 @@ public void writeLong(long value) throws IOException { public void writeDouble(double value) throws IOException { writer.value(value); } - - @Override - public void writeBinary(byte[] value) { - throw notSupported(TokenType.BINARY); - } - - @Override - public void writeNull() throws IOException { - writer.nullValue(); - } }