Skip to content

Commit

Permalink
Add StringCodec, tweak StructuredDataFunction.Mapper to use checked f…
Browse files Browse the repository at this point in the history
…unctions instead of optionals
  • Loading branch information
falkreon committed Aug 1, 2024
1 parent 02de369 commit 159993f
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 35 deletions.
92 changes: 92 additions & 0 deletions src/main/java/blue/endless/jankson/api/codec/StringCodec.java
Original file line number Diff line number Diff line change
@@ -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<Object, String> encoder;
private final Function<String, Object> decoder;

@SuppressWarnings("unchecked")
public <T> StringCodec(Class<T> clazz, Function<T, String> encoder, Function<String, T> decoder) {
this.targetClass = clazz;
this.encoder = (Function<Object, String>) encoder;
this.decoder = (Function<String, Object>) 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 <T> StructuredDataFunction<T> getWriter() {
@SuppressWarnings("unchecked")
CheckedFunction<String, T, SyntaxError> shimmedDecoder = (String encoded) -> (T) decoder.apply(encoded);

return new StructuredDataFunction.Mapper<String, T>(new StringElementWriter(), shimmedDecoder);
}

@Override
public Class<?> getTargetClass() {
return targetClass;
}

private class StringElementWriter implements StructuredDataFunction<String> {
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;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,11 +69,11 @@ public <T> StructuredDataFunction<T> getWriter() {
StructuredDataFunction<ValueElement> function = new ValueElementWriter();

@SuppressWarnings("unchecked")
Function<ValueElement, Optional<T>> mapper = (ValueElement val) -> {
CheckedFunction<ValueElement, T, SyntaxError> 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);
}
};

Expand Down
52 changes: 26 additions & 26 deletions src/main/java/blue/endless/jankson/api/io/ObjectWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -177,7 +177,7 @@ public static StructuredDataFunction<?> getObjectWriter(Type type, StructuredDat
return new ArrayFunction<>(targetClass);
}

Function<PrimitiveElement, Optional<Object>> selectedMapper = primitiveMappers.get(targetClass);
CheckedFunction<PrimitiveElement, Object, SyntaxError> selectedMapper = primitiveMappers.get(targetClass);
if (selectedMapper != null) {
return new StructuredDataFunction.Mapper<>(new PrimitiveFunction(), selectedMapper);
}
Expand Down Expand Up @@ -296,44 +296,44 @@ public T toObject() {
}

//private static Map<Class<?>, Supplier<SingleValueFunction<?>>> classConfigs = new HashMap<>();
private static Map<Class<?>, Function<PrimitiveElement, Optional<Object>>> primitiveMappers = new HashMap<>();
private static Map<Class<?>, CheckedFunction<PrimitiveElement, Object, SyntaxError>> primitiveMappers = new HashMap<>();

static {
primitiveMappers.put(String.class, (prim) -> (Optional<Object>) (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<Object> 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<BigInteger> is castable to Optional<Object>
primitiveMappers.put(BigInteger.class, (prim) -> (Optional<Object>) (Object) prim.asBigInteger());
primitiveMappers.put(BigDecimal.class, (prim) -> (Optional<Object>) (Object) prim.asBigDecimal());
primitiveMappers.put(BigInteger.class, (prim) -> (Optional<Object>) (Object) prim.asBigInteger().orElseThrow(()->new SyntaxError("Required: BigInteger")));
primitiveMappers.put(BigDecimal.class, (prim) -> (Optional<Object>) (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")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,9 +43,10 @@ public interface StructuredDataFunction<T> extends StructuredDataWriter {

public class Mapper<S, T> implements StructuredDataFunction<T> {
private final StructuredDataFunction<S> function;
private final Function<S, Optional<T>> mapper;
private final CheckedFunction<S, T, SyntaxError> mapper;
private T result = null;

public Mapper(StructuredDataFunction<S> function, Function<S, Optional<T>> mapper) {
public Mapper(StructuredDataFunction<S> function, CheckedFunction<S, T, SyntaxError> mapper) {
this.function = function;
this.mapper = mapper;
}
Expand All @@ -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<T> result = mapper.apply(function.getResult());
return result.orElse(null);
return result;
}

}
Expand Down

0 comments on commit 159993f

Please sign in to comment.