KeyValuesWriter.java

package io.jstach.rainbowgum.json.encoder;

import java.util.Set;

import org.jspecify.annotations.Nullable;

import io.jstach.rainbowgum.KeyValues;
import io.jstach.rainbowgum.KeyValues.KeyValuesConsumer;
import io.jstach.rainbowgum.json.JsonBuffer;

/*
 * Writes key values as top-level fields, except a key value named like a field the
 * encoder writes itself. Like Json5Formatter, those go in a keyValues object, or as
 * keyValues. prefixed fields where the format is flat, so no field is written twice.
 * The consumers are created once per encoder so writing an event allocates nothing.
 */
final class KeyValuesWriter {

	static final String KEY_VALUES_FIELD = "keyValues";

	private static final String KEY_VALUES_PREFIX = KEY_VALUES_FIELD + ".";

	enum Placement {

		NESTED, DOTTED

	}

	private final Placement placement;

	private final Set<String> fields;

	private final int flag;

	private final KeyValuesConsumer<JsonBuffer> topLevel;

	private final KeyValuesConsumer<JsonBuffer> colliding;

	private final KeyValuesConsumer<@Nullable Void> counter;

	KeyValuesWriter(Placement placement, Set<String> fields, int flag) {
		this.placement = placement;
		this.fields = Set.copyOf(fields);
		this.flag = flag;
		this.topLevel = (kvs, k, v, index, buffer) -> collides(k) ? index : buffer.write(k, v, index, this.flag);
		this.colliding = switch (placement) {
			case NESTED -> (kvs, k, v, index, buffer) -> collides(k) ? buffer.write(k, v, index, 0) : index;
			case DOTTED -> (kvs, k, v, index, buffer) -> collides(k)
					? buffer.write(KEY_VALUES_PREFIX + k, v, index, this.flag) : index;
		};
		this.counter = (kvs, k, v, count, storage) -> v != null && collides(k) ? count + 1 : count;
	}

	private boolean collides(String key) {
		return fields.contains(key) || key.equals(KEY_VALUES_FIELD)
				|| (placement == Placement.DOTTED && key.startsWith(KEY_VALUES_PREFIX));
	}

	int write(KeyValues kvs, JsonBuffer buffer, int index) {
		index = kvs.forEach(topLevel, index, buffer);
		if (kvs.forEach(counter, 0, null) == 0) {
			return index;
		}
		return switch (placement) {
			case NESTED -> {
				int nested = buffer.writeObjectStart(KEY_VALUES_FIELD, index, flag);
				kvs.forEach(colliding, nested, buffer);
				buffer.writeObjectEnd();
				yield index + 1;
			}
			case DOTTED -> kvs.forEach(colliding, index, buffer);
		};
	}

}