package com.hypixel.hytale.server.npc.asset.builder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.hypixel.hytale.codec.schema.NamedSchema; import com.hypixel.hytale.codec.schema.SchemaContext; import com.hypixel.hytale.codec.schema.SchemaConvertable; import com.hypixel.hytale.codec.schema.config.ObjectSchema; import com.hypixel.hytale.codec.schema.config.Schema; import com.hypixel.hytale.codec.schema.config.StringSchema; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class BuilderFactory implements SchemaConvertable, NamedSchema { public static final String DEFAULT_TYPE = "Type"; public static final String COMPONENT_TYPE = "Component"; private final String typeTag; private final Supplier> defaultBuilder; private final Class category; private final Map>> buildersSuppliers = new HashMap<>(); public BuilderFactory(Class category, String typeTag) { this(category, typeTag, null); } public BuilderFactory(Class category, String typeTag, Supplier> defaultBuilder) { this.category = category; this.typeTag = typeTag; this.defaultBuilder = defaultBuilder; this.add("Component", () -> new BuilderComponent<>(category)); } @Nonnull public BuilderFactory add(String name, Supplier> builder) { if (this.buildersSuppliers.containsKey(name)) { throw new IllegalArgumentException(String.format("Builder with name %s already exists", name)); } else if (this.typeTag.isEmpty()) { throw new IllegalArgumentException("Can't add named builder to array builder factory"); } else { this.buildersSuppliers.put(name, builder); return this; } } public Class getCategory() { return this.category; } public Builder createBuilder(@Nonnull JsonElement config) { if (!config.isJsonObject()) { if (this.defaultBuilder == null) { throw new IllegalArgumentException(String.format("Array builder must have default builder defined: %s", config)); } else { return this.defaultBuilder.get(); } } else { return this.createBuilder(config.getAsJsonObject(), this.typeTag); } } public String getKeyName(@Nonnull JsonElement config) { if (!config.isJsonObject()) { return "-"; } else { JsonElement element = config.getAsJsonObject().get(this.typeTag); return element != null ? element.getAsString() : "???"; } } @Nonnull public Builder createBuilder(String name) { if (!this.buildersSuppliers.containsKey(name)) { throw new IllegalArgumentException(String.format("Builder %s does not exist", name)); } else { Builder builder = this.buildersSuppliers.get(name).get(); if (builder.category() != this.getCategory()) { throw new IllegalArgumentException( String.format("Builder %s has category %s which does not match %s", name, builder.category().getName(), this.getCategory().getName()) ); } else { builder.setTypeName(name); return builder; } } } @Nullable public Builder tryCreateDefaultBuilder() { return this.defaultBuilder != null ? this.defaultBuilder.get() : null; } @Nonnull public List getBuilderNames() { return new ObjectArrayList(this.buildersSuppliers.keySet()); } private Builder createBuilder(@Nonnull JsonObject config, @Nonnull String tag) { if (config == null) { throw new IllegalArgumentException("JSON config cannot be null when creating builder"); } else if (tag != null && !tag.trim().isEmpty()) { JsonElement element = config.get(tag); if (element == null && this.defaultBuilder != null) { return this.defaultBuilder.get(); } else if (element == null) { throw new IllegalArgumentException(String.format("Builder tag of type %s must be supplied if no default is defined in %s", tag, config)); } else { return this.createBuilder(element.getAsString()); } } else { throw new IllegalArgumentException(String.format("Tag cannot be null or empty when creating builder with content %s", config)); } } @Nonnull @Override public String getSchemaName() { return "NPCType:" + this.getCategory().getSimpleName(); } @Nonnull @Override public Schema toSchema(@Nonnull SchemaContext context) { return this.toSchema(context, false); } @Nonnull public Schema toSchema(@Nonnull SchemaContext context, boolean isRoot) { int index = 0; Schema[] schemas = new Schema[this.getBuilderNames().size()]; ObjectSchema check = new ObjectSchema(); check.setRequired(this.typeTag); StringSchema keys = new StringSchema(); keys.setEnum(this.getBuilderNames().toArray(String[]::new)); check.setProperties(Map.of(this.typeTag, keys)); Schema root = new Schema(); if (this.defaultBuilder == null && this.getBuilderNames().isEmpty()) { root.setAnyOf(schemas); } else { root.setIf(check); root.setThen(Schema.anyOf(schemas)); } for (String builderName : this.getBuilderNames()) { Builder builder = this.createBuilder(builderName); Schema schemaRef = context.refDefinition(builder); ObjectSchema schema = (ObjectSchema)context.getRawDefinition(builder); LinkedHashMap newProps = new LinkedHashMap<>(); Schema type = StringSchema.constant(builderName); if (builder instanceof BuilderBase) { type.setDescription(((BuilderBase)builder).getLongDescription()); } newProps.put(this.typeTag, type); if (isRoot) { newProps.put("TestType", new StringSchema()); newProps.put("FailReason", new StringSchema()); newProps.put("Parameters", BuilderParameters.toSchema(context)); } newProps.putAll(schema.getProperties()); schema.setProperties(newProps); Schema cond = new Schema(); ObjectSchema checkType = new ObjectSchema(); checkType.setProperties(Map.of(this.typeTag, StringSchema.constant(builderName))); checkType.setRequired(this.typeTag); cond.setIf(checkType); cond.setThen(schemaRef); cond.setElse(false); schemas[index++] = cond; } if (this.defaultBuilder != null) { Builder builderx = this.defaultBuilder.get(); Schema schemaRefx = context.refDefinition(builderx); root.setElse(schemaRefx); } else { root.setElse(false); } root.setHytaleSchemaTypeField(new Schema.SchemaTypeField(this.typeTag, null, this.getBuilderNames().toArray(String[]::new))); root.setTitle(this.getCategory().getSimpleName()); return root; } }