Skip to content

Commit

Permalink
rename Hierarchical(De)Serializer to Recursive(De)Serializer, add doc…
Browse files Browse the repository at this point in the history
…umentation to Recursive(De)Serializer and MapCarrier
  • Loading branch information
gliscowo committed Dec 13, 2023
1 parent 5f2b55d commit b9074c0
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
*/
public interface StructEndec<T> extends Endec<T> {

void encodeStruct(Serializer.Struct struct, T value);

T decodeStruct(Deserializer.Struct struct);

@Override
default void encode(Serializer<?> serializer, T value) {
try (var struct = serializer.struct()) {
Expand All @@ -18,9 +22,4 @@ default void encode(Serializer<?> serializer, T value) {
default T decode(Deserializer<?> deserializer) {
return this.decodeStruct(deserializer.struct());
}

void encodeStruct(Serializer.Struct struct, T value);

T decodeStruct(Deserializer.Struct struct);

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import com.google.common.collect.ImmutableSet;
import io.wispforest.owo.serialization.*;
import io.wispforest.owo.serialization.util.HierarchicalDeserializer;
import io.wispforest.owo.serialization.util.RecursiveDeserializer;
import org.jetbrains.annotations.Nullable;

import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public class EdmDeserializer extends HierarchicalDeserializer<EdmElement<?>> implements SelfDescribedDeserializer<EdmElement<?>> {
public class EdmDeserializer extends RecursiveDeserializer<EdmElement<?>> implements SelfDescribedDeserializer<EdmElement<?>> {

private final Set<SerializationAttribute> attributes;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import io.wispforest.owo.serialization.Endec;
import io.wispforest.owo.serialization.SerializationAttribute;
import io.wispforest.owo.serialization.Serializer;
import io.wispforest.owo.serialization.util.HierarchicalSerializer;
import io.wispforest.owo.serialization.util.RecursiveSerializer;

import java.util.*;

public class EdmSerializer extends HierarchicalSerializer<EdmElement<?>> {
public class EdmSerializer extends RecursiveSerializer<EdmElement<?>> {

private final Set<SerializationAttribute> attributes;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import io.wispforest.owo.serialization.*;
import io.wispforest.owo.serialization.util.HierarchicalDeserializer;
import io.wispforest.owo.serialization.util.RecursiveDeserializer;
import org.jetbrains.annotations.Nullable;

import java.util.EnumSet;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;

public class JsonDeserializer extends HierarchicalDeserializer<JsonElement> implements SelfDescribedDeserializer<JsonElement> {
public class JsonDeserializer extends RecursiveDeserializer<JsonElement> implements SelfDescribedDeserializer<JsonElement> {

private static final Set<SerializationAttribute> ATTRIBUTES = EnumSet.of(
SerializationAttribute.SELF_DESCRIBING,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import io.wispforest.owo.serialization.Endec;
import io.wispforest.owo.serialization.SerializationAttribute;
import io.wispforest.owo.serialization.Serializer;
import io.wispforest.owo.serialization.util.HierarchicalSerializer;
import io.wispforest.owo.serialization.util.RecursiveSerializer;

import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;

public class JsonSerializer extends HierarchicalSerializer<JsonElement> {
public class JsonSerializer extends RecursiveSerializer<JsonElement> {

private static final Set<SerializationAttribute> ATTRIBUTES = EnumSet.allOf(SerializationAttribute.class);
private JsonElement prefix;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.wispforest.owo.serialization.format.nbt;

import io.wispforest.owo.serialization.*;
import io.wispforest.owo.serialization.util.HierarchicalDeserializer;
import io.wispforest.owo.serialization.util.RecursiveDeserializer;
import net.minecraft.nbt.*;
import org.jetbrains.annotations.Nullable;

import java.util.*;

public class NbtDeserializer extends HierarchicalDeserializer<NbtElement> implements SelfDescribedDeserializer<NbtElement> {
public class NbtDeserializer extends RecursiveDeserializer<NbtElement> implements SelfDescribedDeserializer<NbtElement> {

private static final Set<SerializationAttribute> ATTRIBUTES = EnumSet.of(
SerializationAttribute.SELF_DESCRIBING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import io.wispforest.owo.serialization.Endec;
import io.wispforest.owo.serialization.SerializationAttribute;
import io.wispforest.owo.serialization.Serializer;
import io.wispforest.owo.serialization.util.HierarchicalSerializer;
import io.wispforest.owo.serialization.util.RecursiveSerializer;
import net.minecraft.nbt.*;
import net.minecraft.network.encoding.VarInts;
import net.minecraft.network.encoding.VarLongs;
Expand All @@ -12,7 +12,7 @@
import java.util.Optional;
import java.util.Set;

public class NbtSerializer extends HierarchicalSerializer<NbtElement> {
public class NbtSerializer extends RecursiveSerializer<NbtElement> {

private static final Set<SerializationAttribute> ATTRIBUTES = EnumSet.of(
SerializationAttribute.SELF_DESCRIBING
Expand Down

This file was deleted.

43 changes: 40 additions & 3 deletions src/main/java/io/wispforest/owo/serialization/util/MapCarrier.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,45 @@

public interface MapCarrier {

// Interface specification

/**
* Get the value stored under {@code key} in this object's associated map.
* If no such value exists, the default value of {@code key} is returned
* <p>
* Any exceptions thrown during decoding are propagated to the caller
*/
default <T> T getWithErrors(@NotNull KeyedEndec<T> key) {
throw new UnsupportedOperationException("Interface default method called");
}

/**
* Store {@code value} under {@code key} in this object's associated map
*/
default <T> void put(@NotNull KeyedEndec<T> key, @NotNull T value) {
throw new UnsupportedOperationException("Interface default method called");
}

/**
* Delete the value stored under {@code key} from this object's associated map,
* if it is present
*/
default <T> void delete(@NotNull KeyedEndec<T> key) {
throw new UnsupportedOperationException("Interface default method called");
}

/**
* Test whether there is a value stored under {@code key} in this object's associated map
*/
default <T> boolean has(@NotNull KeyedEndec<T> key) {
throw new UnsupportedOperationException("Interface default method called");
}

// Default implementations
// ---

/**
* Get the value stored under {@code key} in this object's associated map.
* If no such value exists <i>or</i> an exception is thrown during decoding,
* the default value of {@code key} is returned
*/
default <T> T get(@NotNull KeyedEndec<T> key) {
try {
return this.getWithErrors(key);
Expand All @@ -36,20 +55,38 @@ default <T> T get(@NotNull KeyedEndec<T> key) {
}
}

/**
* If {@code value} is not {@code null}, store it under {@code key} in this
* object's associated map
*/
default <T> void putIfNotNull(@NotNull KeyedEndec<T> key, @Nullable T value) {
if (value == null) return;
this.put(key, value);
}

/**
* Store the value associated with {@code key} in this object's associated map
* into the associated map of {@code other} under {@code key}
* <p>
* Importantly, this does not copy the value itself - be careful with mutable types
*/
default <T> void copy(@NotNull KeyedEndec<T> key, @NotNull MapCarrier other) {
other.put(key, this.get(key));
}

/**
* Like {@link #copy(KeyedEndec, MapCarrier)}, but only if this object's associated map
* has a value stored under {@code key}
*/
default <T> void copyIfPresent(@NotNull KeyedEndec<T> key, @NotNull MapCarrier other) {
if (!this.has(key)) return;
this.copy(key, other);
}

/**
* Get the value stored under {@code key} in this object's associated map, apply
* {@code mutator} to it and store the result under {@code key}
*/
default <T> void mutate(@NotNull KeyedEndec<T> key, @NotNull Function<T, T> mutator) {
this.put(key, mutator.apply(this.get(key)));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.wispforest.owo.serialization.util;

import io.wispforest.owo.serialization.Deserializer;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* A template class for implementing deserializers which consume an
* instance of some recursive data structure (like JSON, NBT or EDM)
* <p>
* Importantly, this class also supplies an implementation for {@link #tryRead(Function)}
* which backs up the decoding frames and restores them upon failure. If this, for some reason,
* is not the appropriate behavior for your input format, provide a custom implementation
* <p>
* Check {@link io.wispforest.owo.serialization.format.edm.EdmSerializer} or
* {@link io.wispforest.owo.serialization.format.json.JsonDeserializer} for some reference
* implementations
*/
public abstract class RecursiveDeserializer<T> implements Deserializer<T> {

protected final Deque<Frame<T>> frames = new ArrayDeque<>();
protected final T serialized;

protected RecursiveDeserializer(T serialized) {
this.serialized = serialized;
this.frames.push(new Frame<>(() -> this.serialized, false));
}

/**
* Get the value currently to be decoded
* <p>
* This value is altered by {@link #frame(Supplier, Supplier, boolean)} and
* initially returns the entire serialized input
*/
protected T getValue() {
return this.frames.peek().source.get();
}

/**
* Whether this deserializer is currently decoding a field
* of a struct - useful for, for instance, an optimized optional representation
* by skipping the field to indicate an absent optional
*/
protected boolean isReadingStructField() {
return this.frames.peek().isStructField;
}

/**
* Decode the next value down the tree, given by {@code nextValue}, by pushing that frame
* onto the decoding stack, invoking {@code action}, and popping the frame again. Consequently,
* all decoding of {@code nextValue} must happen inside {@code action}
* <p>
* If {@code nextValue} is reading the field of a struct, {@code isStructField} must be set
*/
protected <V> V frame(Supplier<T> nextValue, Supplier<V> action, boolean isStructField) {
try {
this.frames.push(new Frame<>(nextValue, isStructField));
return action.get();
} finally {
this.frames.pop();
}
}

@Override
public <V> V tryRead(Function<Deserializer<T>, V> reader) {
var framesBackup = new ArrayDeque<>(this.frames);

try {
return reader.apply(this);
} catch (Exception e) {
this.frames.clear();
this.frames.addAll(framesBackup);

throw e;
}
}

protected record Frame<T>(Supplier<T> source, boolean isStructField) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,52 @@
import java.util.Deque;
import java.util.function.Consumer;

public abstract class HierarchicalSerializer<T> implements Serializer<T> {
/**
* A template class for implementing serializers which produce as result an
* instance of some recursive data structure (like JSON, NBT or EDM)
* <p>
* Check {@link io.wispforest.owo.serialization.format.edm.EdmSerializer} or
* {@link io.wispforest.owo.serialization.format.json.JsonSerializer} for some reference
* implementations
*/
public abstract class RecursiveSerializer<T> implements Serializer<T> {

protected final Deque<Frame<T>> frames = new ArrayDeque<>();
protected T result;

protected HierarchicalSerializer(T initialResult) {
protected RecursiveSerializer(T initialResult) {
this.result = initialResult;
this.frames.push(new Frame<>(t -> this.result = t, false));
}

/**
* Store {@code value} into the current encoding location
* <p>
* This location is altered by {@link #frame(FrameAction, boolean)} and
* initially is just the serializer's result directly
*/
protected void consume(T value) {
this.frames.peek().sink.accept(value);
}

/**
* Whether this deserializer is currently decoding a field
* of a struct - useful for, for instance, an optimized optional representation
* by skipping the field to indicate an absent optional
*/
protected boolean isWritingStructField() {
return this.frames.peek().isStructField;
}

/**
* Encode the next value down the tree by pushing a new frame
* onto the encoding stack and invoking {@code action}
* <p>
* {@code action} receives {@code encoded}, which is where the next call
* to {@link #consume(Object)} (which {@code action} must somehow cause) will
* store the value and allow {@code action} to retrieve it using {@link EncodedValue#get()}
* or, preferably, {@link EncodedValue#require(String)}
*/
protected void frame(FrameAction<T> action, boolean isStructField) {
var encoded = new EncodedValue<T>();

Expand Down

0 comments on commit b9074c0

Please sign in to comment.