From 159993fdefc7b9f5188834cf250630ae727c373b Mon Sep 17 00:00:00 2001 From: Falkreon Date: Wed, 31 Jul 2024 20:18:34 -0500 Subject: [PATCH] Add StringCodec, tweak StructuredDataFunction.Mapper to use checked functions instead of optionals --- .../jankson/api/codec/StringCodec.java | 92 +++++++++++++++++++ .../api/codec/StructuredDataCodec.java | 1 - .../jankson/api/codec/ValueElementCodec.java | 8 +- .../endless/jankson/api/io/ObjectWriter.java | 52 +++++------ .../api/io/StructuredDataFunction.java | 10 +- 5 files changed, 128 insertions(+), 35 deletions(-) create mode 100644 src/main/java/blue/endless/jankson/api/codec/StringCodec.java diff --git a/src/main/java/blue/endless/jankson/api/codec/StringCodec.java b/src/main/java/blue/endless/jankson/api/codec/StringCodec.java new file mode 100644 index 0000000..17e3649 --- /dev/null +++ b/src/main/java/blue/endless/jankson/api/codec/StringCodec.java @@ -0,0 +1,92 @@ +/* + * MIT License + * + * Copyright (c) 2018-2024 Falkreon (Isaac Ellingson) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package blue.endless.jankson.api.codec; + +import java.io.IOException; +import java.util.Optional; +import java.util.function.Function; + +import blue.endless.jankson.api.SyntaxError; +import blue.endless.jankson.api.function.CheckedFunction; +import blue.endless.jankson.api.io.StructuredData; +import blue.endless.jankson.api.io.StructuredDataBuffer; +import blue.endless.jankson.api.io.StructuredDataFunction; +import blue.endless.jankson.api.io.StructuredDataReader; + +public class StringCodec implements ClassTargetCodec { + private final Class targetClass; + private final Function encoder; + private final Function decoder; + + @SuppressWarnings("unchecked") + public StringCodec(Class clazz, Function encoder, Function decoder) { + this.targetClass = clazz; + this.encoder = (Function) encoder; + this.decoder = (Function) decoder; + } + + @Override + public StructuredDataReader getReader(Object o) { + StructuredDataBuffer buf = new StructuredDataBuffer(); + buf.write(new StructuredData(StructuredData.Type.PRIMITIVE, encoder.apply(o))); + return buf; + } + + @Override + public StructuredDataFunction getWriter() { + @SuppressWarnings("unchecked") + CheckedFunction shimmedDecoder = (String encoded) -> (T) decoder.apply(encoded); + + return new StructuredDataFunction.Mapper(new StringElementWriter(), shimmedDecoder); + } + + @Override + public Class getTargetClass() { + return targetClass; + } + + private class StringElementWriter implements StructuredDataFunction { + private String result; + + @Override + public boolean isComplete() { + return (result != null); + } + + @Override + public void write(StructuredData data) throws SyntaxError, IOException { + if (!data.type().isSemantic()) return; + if (!data.isPrimitive()) throw new SyntaxError("Required: String, found "+data.type()); + + result = (data.value() instanceof String str) ? str : data.value().toString(); + } + + @Override + public String getResult() { + return result; + } + + } +} diff --git a/src/main/java/blue/endless/jankson/api/codec/StructuredDataCodec.java b/src/main/java/blue/endless/jankson/api/codec/StructuredDataCodec.java index 1e87ed8..4220a98 100644 --- a/src/main/java/blue/endless/jankson/api/codec/StructuredDataCodec.java +++ b/src/main/java/blue/endless/jankson/api/codec/StructuredDataCodec.java @@ -28,7 +28,6 @@ import blue.endless.jankson.api.io.StructuredDataFunction; import blue.endless.jankson.api.io.StructuredDataReader; -import blue.endless.jankson.impl.io.objectwriter.SingleValueFunction; /** * Factory for StructuredDataReaders and StructuredDataWriters for a particular type. diff --git a/src/main/java/blue/endless/jankson/api/codec/ValueElementCodec.java b/src/main/java/blue/endless/jankson/api/codec/ValueElementCodec.java index e107775..b7e9dca 100644 --- a/src/main/java/blue/endless/jankson/api/codec/ValueElementCodec.java +++ b/src/main/java/blue/endless/jankson/api/codec/ValueElementCodec.java @@ -25,9 +25,9 @@ package blue.endless.jankson.api.codec; import java.io.IOException; -import java.util.Optional; import java.util.function.Function; +import blue.endless.jankson.api.SyntaxError; import blue.endless.jankson.api.document.ArrayElement; import blue.endless.jankson.api.document.ObjectElement; import blue.endless.jankson.api.document.PrimitiveElement; @@ -69,11 +69,11 @@ public StructuredDataFunction getWriter() { StructuredDataFunction function = new ValueElementWriter(); @SuppressWarnings("unchecked") - Function> mapper = (ValueElement val) -> { + CheckedFunction mapper = (ValueElement val) -> { try { - return Optional.ofNullable((T) deserializer.apply(val)); + return (T) deserializer.apply(val); } catch (IOException e) { - return Optional.empty(); + throw new SyntaxError("Error applying deserializer function.", e); } }; diff --git a/src/main/java/blue/endless/jankson/api/io/ObjectWriter.java b/src/main/java/blue/endless/jankson/api/io/ObjectWriter.java index 56f6c67..3e55927 100644 --- a/src/main/java/blue/endless/jankson/api/io/ObjectWriter.java +++ b/src/main/java/blue/endless/jankson/api/io/ObjectWriter.java @@ -42,12 +42,12 @@ import java.util.Optional; import java.util.Queue; import java.util.Set; -import java.util.function.Function; import javax.annotation.Nullable; import blue.endless.jankson.api.SyntaxError; import blue.endless.jankson.api.document.PrimitiveElement; +import blue.endless.jankson.api.function.CheckedFunction; import blue.endless.jankson.impl.io.objectwriter.ArrayFunction; import blue.endless.jankson.impl.io.objectwriter.CollectionFunction; import blue.endless.jankson.impl.io.objectwriter.MapFunction; @@ -177,7 +177,7 @@ public static StructuredDataFunction getObjectWriter(Type type, StructuredDat return new ArrayFunction<>(targetClass); } - Function> selectedMapper = primitiveMappers.get(targetClass); + CheckedFunction selectedMapper = primitiveMappers.get(targetClass); if (selectedMapper != null) { return new StructuredDataFunction.Mapper<>(new PrimitiveFunction(), selectedMapper); } @@ -296,44 +296,44 @@ public T toObject() { } //private static Map, Supplier>> classConfigs = new HashMap<>(); - private static Map, Function>> primitiveMappers = new HashMap<>(); + private static Map, CheckedFunction> primitiveMappers = new HashMap<>(); static { - primitiveMappers.put(String.class, (prim) -> (Optional) (Object) prim.asString()); + primitiveMappers.put(String.class, (prim) -> prim.asString().orElseThrow()); - primitiveMappers.put(Integer.class, (prim) -> prim.mapAsInt((it)-> it)); - primitiveMappers.put(Long.class, (prim) -> prim.mapAsLong((it)-> it)); - primitiveMappers.put(Short.class, (prim) -> prim.mapAsInt((it) -> (short) it)); - primitiveMappers.put(Byte.class, (prim) -> prim.mapAsInt((it) -> (byte) it)); - primitiveMappers.put(Double.class, (prim) -> prim.mapAsDouble((it)-> it)); - primitiveMappers.put(Float.class, (prim) -> prim.mapAsDouble((it) -> (float) it)); - primitiveMappers.put(Boolean.class, (prim) -> prim.mapAsBoolean((it) -> it)); - primitiveMappers.put(Integer.TYPE, (prim) -> prim.mapAsInt((it)-> it)); - primitiveMappers.put(Long.TYPE, (prim) -> prim.mapAsLong((it)-> it)); - primitiveMappers.put(Double.TYPE, (prim) -> prim.mapAsDouble((it) -> it)); - primitiveMappers.put(Short.TYPE, (prim) -> prim.mapAsInt((it) -> (short) it)); - primitiveMappers.put(Float.TYPE, (prim) -> prim.mapAsDouble((it) -> (float) it)); - primitiveMappers.put(Boolean.TYPE, (prim) -> prim.mapAsBoolean((it) -> it)); + primitiveMappers.put(Integer.class, (prim) -> prim.mapAsInt((it)-> it).orElseThrow(()->new SyntaxError("Required: Int"))); + primitiveMappers.put(Long.class, (prim) -> prim.mapAsLong((it)-> it).orElseThrow(()->new SyntaxError("Required: Long"))); + primitiveMappers.put(Short.class, (prim) -> prim.mapAsInt((it) -> (short) it).orElseThrow(()->new SyntaxError("Required: Short"))); + primitiveMappers.put(Byte.class, (prim) -> prim.mapAsInt((it) -> (byte) it).orElseThrow(()->new SyntaxError("Required: Byte"))); + primitiveMappers.put(Double.class, (prim) -> prim.mapAsDouble((it)-> it).orElseThrow(()->new SyntaxError("Required: Double"))); + primitiveMappers.put(Float.class, (prim) -> prim.mapAsDouble((it) -> (float) it).orElseThrow(()->new SyntaxError("Required: Float"))); + primitiveMappers.put(Boolean.class, (prim) -> prim.mapAsBoolean((it) -> it).orElseThrow(()->new SyntaxError("Required: Boolean"))); + primitiveMappers.put(Integer.TYPE, (prim) -> prim.mapAsInt((it)-> it).orElseThrow(()->new SyntaxError("Required: Int"))); + primitiveMappers.put(Long.TYPE, (prim) -> prim.mapAsLong((it)-> it).orElseThrow(()->new SyntaxError("Required: Long"))); + primitiveMappers.put(Double.TYPE, (prim) -> prim.mapAsDouble((it) -> it).orElseThrow(()->new SyntaxError("Required: Double"))); + primitiveMappers.put(Short.TYPE, (prim) -> prim.mapAsInt((it) -> (short) it).orElseThrow(()->new SyntaxError("Required: Short"))); + primitiveMappers.put(Float.TYPE, (prim) -> prim.mapAsDouble((it) -> (float) it).orElseThrow(()->new SyntaxError("Required: Float"))); + primitiveMappers.put(Boolean.TYPE, (prim) -> prim.mapAsBoolean((it) -> it).orElseThrow(()->new SyntaxError("Required: Boolean"))); // This one's complex because no good canonical serialization makes sense primitiveMappers.put(Character.class, (prim) -> { Optional value = prim.getValue(); - if (value.isEmpty()) return Optional.empty(); + if (value.isEmpty()) return null; if (value.get() instanceof String s) { - if (s.length() != 1) return Optional.empty(); - return Optional.of(Character.valueOf(s.charAt(0))); + if (s.length() != 1) return null; + return Character.valueOf(s.charAt(0)); } else { - return prim.mapAsInt((it) -> (char) it); + return prim.mapAsInt((it) -> (char) it).orElseThrow(()->new SyntaxError("Required: Character")); } }); // PrimitiveElement has convenience methods for these two, so let's set consistent expectations // It's truly unfortunate that Java can't tell that e.g. Optional is castable to Optional - primitiveMappers.put(BigInteger.class, (prim) -> (Optional) (Object) prim.asBigInteger()); - primitiveMappers.put(BigDecimal.class, (prim) -> (Optional) (Object) prim.asBigDecimal()); + primitiveMappers.put(BigInteger.class, (prim) -> (Optional) (Object) prim.asBigInteger().orElseThrow(()->new SyntaxError("Required: BigInteger"))); + primitiveMappers.put(BigDecimal.class, (prim) -> (Optional) (Object) prim.asBigDecimal().orElseThrow(()->new SyntaxError("Required: BigDecimal"))); - primitiveMappers.put(LocalDate.class, (prim) -> prim.mapAsString(LocalDate::parse)); - primitiveMappers.put(LocalTime.class, (prim) -> prim.mapAsString(LocalTime::parse)); - primitiveMappers.put(LocalDateTime.class, (prim) -> prim.mapAsString(LocalDateTime::parse)); + primitiveMappers.put(LocalDate.class, (prim) -> prim.mapAsString(LocalDate::parse).orElseThrow(() -> new SyntaxError("Required: LocalDate"))); + primitiveMappers.put(LocalTime.class, (prim) -> prim.mapAsString(LocalTime::parse).orElseThrow(() -> new SyntaxError("Required: LocalTime"))); + primitiveMappers.put(LocalDateTime.class, (prim) -> prim.mapAsString(LocalDateTime::parse).orElseThrow(() -> new SyntaxError("Required: LocalDateTime"))); } } diff --git a/src/main/java/blue/endless/jankson/api/io/StructuredDataFunction.java b/src/main/java/blue/endless/jankson/api/io/StructuredDataFunction.java index e87e81b..3100f51 100644 --- a/src/main/java/blue/endless/jankson/api/io/StructuredDataFunction.java +++ b/src/main/java/blue/endless/jankson/api/io/StructuredDataFunction.java @@ -29,6 +29,7 @@ import java.util.function.Function; import blue.endless.jankson.api.SyntaxError; +import blue.endless.jankson.api.function.CheckedFunction; /** * A StructuredDataFunction is a job that consumes StructuredData over time, and produces a value @@ -42,9 +43,10 @@ public interface StructuredDataFunction extends StructuredDataWriter { public class Mapper implements StructuredDataFunction { private final StructuredDataFunction function; - private final Function> mapper; + private final CheckedFunction mapper; + private T result = null; - public Mapper(StructuredDataFunction function, Function> mapper) { + public Mapper(StructuredDataFunction function, CheckedFunction mapper) { this.function = function; this.mapper = mapper; } @@ -57,12 +59,12 @@ public boolean isComplete() { @Override public void write(StructuredData data) throws SyntaxError, IOException { function.write(data); + if (function.isComplete()) result = mapper.apply(function.getResult()); } @Override public T getResult() { - Optional result = mapper.apply(function.getResult()); - return result.orElse(null); + return result; } }