package com.hypixel.hytale.codec.lookup; import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.ExtraInfo; import com.hypixel.hytale.codec.exception.CodecException; import com.hypixel.hytale.codec.schema.SchemaContext; import com.hypixel.hytale.codec.schema.config.ObjectSchema; import com.hypixel.hytale.codec.schema.config.Schema; import com.hypixel.hytale.codec.util.RawJsonReader; import com.hypixel.hytale.codec.validation.ValidatableCodec; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nonnull; import org.bson.BsonDocument; import org.bson.BsonValue; public abstract class AMapProvidedMapCodec> implements Codec, ValidatableCodec { protected final Map codecProvider; protected final Function> mapper; protected final boolean unmodifiable; public AMapProvidedMapCodec(Map codecProvider, Function> mapper) { this(codecProvider, mapper, true); } public AMapProvidedMapCodec(Map codecProvider, Function> mapper, boolean unmodifiable) { this.codecProvider = codecProvider; this.mapper = mapper; this.unmodifiable = unmodifiable; } public abstract M createMap(); public void handleUnknown(M map, @Nonnull String key, BsonValue value, @Nonnull ExtraInfo extraInfo) { extraInfo.addUnknownKey(key); } public void handleUnknown(M map, @Nonnull String key, @Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { extraInfo.addUnknownKey(key); reader.skipValue(); } public M decode(@Nonnull BsonValue bsonValue, @Nonnull ExtraInfo extraInfo) { BsonDocument bsonDocument = bsonValue.asDocument(); M map = this.createMap(); for (Entry entry : bsonDocument.entrySet()) { extraInfo.pushKey(entry.getKey()); try { K key = this.getKeyForId(entry.getKey()); if (key == null) { this.handleUnknown(map, entry.getKey(), entry.getValue(), extraInfo); } else { Codec codecFor = this.getCodecFor(key); map.put(key, codecFor.decode(entry.getValue(), extraInfo)); } } finally { extraInfo.popKey(); } } if (this.unmodifiable) { map = this.unmodifiableMap(map); } return map; } @Nonnull public BsonValue encode(@Nonnull M map, ExtraInfo extraInfo) { BsonDocument document = new BsonDocument(); for (Entry entry : map.entrySet()) { Codec codecFor = this.getCodecFor(entry.getKey()); document.put(this.getIdForKey(entry.getKey()), codecFor.encode(entry.getValue(), extraInfo)); } this.encodeExtra(document, map, extraInfo); return document; } protected void encodeExtra(BsonDocument document, M map, ExtraInfo extraInfo) { } public M decodeJson(@Nonnull RawJsonReader reader, @Nonnull ExtraInfo extraInfo) throws IOException { reader.expect('{'); reader.consumeWhiteSpace(); if (reader.tryConsume('}')) { return this.unmodifiable ? this.emptyMap() : this.createMap(); } else { M map = this.createMap(); while (true) { String id = reader.readString(); reader.consumeWhiteSpace(); reader.expect(':'); reader.consumeWhiteSpace(); extraInfo.pushKey(id, reader); try { K key = this.getKeyForId(id); if (key == null) { this.handleUnknown(map, id, reader, extraInfo); } else { Codec codec = this.getCodecFor(key); map.put(key, codec.decodeJson(reader, extraInfo)); } } catch (Exception var10) { throw new CodecException("Failed to decode", reader, extraInfo, var10); } finally { extraInfo.popKey(); } reader.consumeWhiteSpace(); if (reader.tryConsumeOrExpect('}', ',')) { if (this.unmodifiable) { map = this.unmodifiableMap(map); } return map; } reader.consumeWhiteSpace(); } } } @Nonnull @Override public Schema toSchema(@Nonnull SchemaContext context) { ObjectSchema obj = new ObjectSchema(); obj.setAdditionalProperties(false); LinkedHashMap props = this.codecProvider.keySet().stream().map(key -> { Codec codec = this.getCodecFor((K)key); return Map.entry(this.getIdForKey((K)key), codec.toSchema(context)); }).sorted(Entry.comparingByKey()).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> a, LinkedHashMap::new)); obj.setProperties(props); return obj; } public void validate(@Nonnull M map, ExtraInfo extraInfo) { for (Entry entry : map.entrySet()) { Codec codec = this.getCodecFor(entry.getKey()); if (codec instanceof ValidatableCodec) { ((ValidatableCodec)codec).validate(entry.getValue(), extraInfo); } } } @Override public void validateDefaults(ExtraInfo extraInfo, @Nonnull Set> tested) { if (tested.add(this)) { for (P value : this.codecProvider.values()) { Codec codec = this.mapper.apply(value); ValidatableCodec.validateDefaults(codec, extraInfo, tested); } } } private Codec getCodecFor(K key) { return this.mapper.apply(this.codecProvider.get(key)); } protected abstract String getIdForKey(K var1); protected abstract K getKeyForId(String var1); protected abstract M emptyMap(); protected abstract M unmodifiableMap(M var1); }