All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.activej.json.ObjectJsonCodec Maven / Gradle / Ivy

The newest version!
package io.activej.json;

import io.activej.common.builder.AbstractBuilder;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;

import static io.activej.common.Utils.*;

@SuppressWarnings("unchecked")
public final class ObjectJsonCodec extends AbstractMapJsonCodec {

	public interface JsonCodecProvider extends JsonEncoderProvider, JsonDecoderProvider {
	}

	public interface JsonEncoderProvider {
		@Nullable JsonCodec encoder(String key, int index, T item, V value);

		static  JsonEncoderProvider of(JsonCodec codec) {
			return (key, index, item, value) -> codec;
		}
	}

	public interface JsonDecoderProvider {
		@Nullable JsonCodec decoder(String key, int index, A accumulator) throws JsonValidationException;

		static  JsonDecoderProvider of(JsonCodec codec) {
			return (key, index, accumulator) -> codec;
		}
	}

	private record Field(int index, String key,
		Function getter, JsonSetter setter,
		JsonEncoderProvider encoderFn, JsonDecoderProvider decoderFn
	) {
	}

	private final Supplier accumulatorSupplier;

	private final Field[] fields;
	private final Map> map;
	private final JsonFunction constructor;

	private ObjectJsonCodec(Supplier accumulatorSupplier, JsonFunction constructor,
		Field[] fields, Map> map
	) {
		this.accumulatorSupplier = accumulatorSupplier;
		this.constructor = constructor;
		this.fields = fields;
		this.map = map;
	}

	public static  BuilderObject builder(Supplier accumulatorSupplier, JsonFunction constructor) {
		return new BuilderObject<>(accumulatorSupplier, constructor);
	}

	public static  BuilderArray builder(JsonConstructorN constructor) {
		return new BuilderArray<>(constructor);
	}

	public static class BuilderObject extends AbstractBuilder, ObjectJsonCodec> {
		private final Supplier accumulatorSupplier;
		private final JsonFunction constructor;
		private final List> fields = new ArrayList<>();

		public BuilderObject(Supplier accumulatorSupplier, JsonFunction constructor) {
			this.accumulatorSupplier = accumulatorSupplier;
			this.constructor = constructor;
		}

		public  BuilderObject with(String key,
			Function getter, JsonSetter setter,
			JsonCodec codec
		) {
			return with(key, getter, setter, JsonEncoderProvider.of(codec), JsonDecoderProvider.of(codec));
		}

		public  BuilderObject with(String key,
			Function getter, JsonSetter setter,
			JsonCodecProvider codecFn
		) {
			return with(key, getter, setter, codecFn, codecFn);
		}

		public  BuilderObject with(String key,
			Function getter, JsonSetter setter,
			JsonEncoderProvider encoderFn, JsonDecoderProvider decoderFn
		) {
			Field field = new Field<>(fields.size(), key, getter, setter, encoderFn, decoderFn);
			fields.add((Field) field);
			return this;
		}

		@Override
		protected ObjectJsonCodec doBuild() {
			return new ObjectJsonCodec<>(
				accumulatorSupplier,
				constructor,
				fields.toArray(Field[]::new),
				fields.stream().collect(toHashMap(f -> f.key, f -> f)));
		}
	}

	public static final class BuilderArray extends AbstractBuilder, ObjectJsonCodec> {
		private static final Object NO_DEFAULT_VALUE = new Object();
		private final List> fields = new ArrayList<>();
		private final JsonConstructorN constructor;

		private final List prototype = new ArrayList<>();

		private BuilderArray(JsonConstructorN constructor) {this.constructor = constructor;}

		public  BuilderArray with(String key, Function getter, JsonCodec codec) {
			int index = fields.size();
			Field field = new Field<>(index, key,
				getter,
				(array, value) -> array[index] = value,
				JsonEncoderProvider.of(codec),
				JsonDecoderProvider.of(codec));
			fields.add((Field) field);
			prototype.add(NO_DEFAULT_VALUE);
			return this;
		}

		public  BuilderArray with(String key, Function getter, JsonCodec codec, V defaultValue) {
			int index = fields.size();
			Field field = new Field<>(index, key,
				getter,
				(array, value) -> array[index] = value,
				(key_, index_, item, value) -> Objects.equals(defaultValue, value) ? null : codec,
				JsonDecoderProvider.of(codec));
			fields.add((Field) field);
			prototype.add(defaultValue);
			return this;
		}

		@Override
		protected ObjectJsonCodec doBuild() {
			Object[] prototype = this.prototype.toArray(Object[]::new);
			return Arrays.stream(prototype).anyMatch(v -> v == NO_DEFAULT_VALUE) ?
				new ObjectJsonCodec<>(
					() -> Arrays.copyOf(prototype, prototype.length),
					array -> {
						//noinspection ForLoopReplaceableByForEach
						for (int i = 0; i < array.length; i++) {
							if (array[i] == NO_DEFAULT_VALUE) {
								throw new JsonValidationException();
							}
						}
						return constructor.create(array);
					},
					fields.toArray(Field[]::new),
					fields.stream().collect(toHashMap(f -> f.key, f -> f))) :
				new ObjectJsonCodec<>(
					() -> new Object[prototype.length],
					constructor::create,
					fields.toArray(Field[]::new),
					fields.stream().collect(toHashMap(f -> f.key, f -> f)));
		}
	}

	@Override
	protected Iterator> iterate(T item) {
		return transformIterator(iteratorOf(fields), field -> new JsonMapEntry<>(field.key, field.getter.apply(item)));
	}

	@Override
	protected @Nullable JsonEncoder encoder(String key, int index, T item, Object value) {
		return ((JsonEncoderProvider) fields[index].encoderFn).encoder(key, index, item, value);
	}

	@Override
	protected @Nullable JsonDecoder decoder(String key, int index, A accumulator) throws JsonValidationException {
		Field field = map.get(key);
		if (field == null) throw new JsonValidationException("Key not found: " + key);
		return field.decoderFn.decoder(key, index, accumulator);
	}

	@Override
	protected A accumulator() {
		return accumulatorSupplier.get();
	}

	@Override
	protected void accumulate(A accumulator, String key, int index, Object value) throws JsonValidationException {
		map.get(key).setter.set(accumulator, value);
	}

	@Override
	protected T result(A accumulator, int count) throws JsonValidationException {
		return constructor.apply(accumulator);
	}

}