// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.npc.asset.builder;

import java.util.ArrayList;
import com.hypixel.hytale.server.npc.util.expression.Scope;
import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper;
import com.hypixel.hytale.server.npc.asset.builder.validators.InstructionContextValidator;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayNoEmptyStringsValidator;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import com.hypixel.hytale.server.npc.asset.builder.validators.StateStringValidator;
import com.hypixel.hytale.function.consumer.TriConsumer;
import com.hypixel.hytale.server.npc.asset.builder.validators.ComponentOnlyValidator;
import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ParameterProviderEvaluator;
import com.hypixel.hytale.server.npc.asset.builder.validators.RequiresFeatureIfValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.RequiresFeatureIfEnumValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.RequiredFeatureValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.RequiresOneOfFeaturesValidator;
import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.UnconditionalParameterProviderEvaluator;
import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ProviderEvaluator;
import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.UnconditionalFeatureProviderEvaluator;
import com.hypixel.hytale.server.npc.asset.builder.validators.BooleanImplicationValidator;
import com.hypixel.hytale.server.npc.valuestore.ValueStore;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.function.ToIntFunction;
import com.hypixel.hytale.server.npc.asset.builder.validators.AtMostOneBooleanValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.AnyBooleanValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.ValidateAssetIfEnumIsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.ValidateIfEnumIsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.ExistsIfParameterSetValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.OneOrNonePresentValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.OnePresentValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.AnyPresentValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.AttributeRelationValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.RelationalOperator;
import com.hypixel.hytale.server.npc.asset.builder.validators.NoDuplicatesValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.ArraysOneSetValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringsOneSetValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringsAtMostOneValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringsNotEmptyValidator;
import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder;
import com.hypixel.hytale.common.util.StringUtil;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder;
import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.ArrayValidator;
import java.util.LinkedHashMap;
import com.hypixel.hytale.codec.schema.SchemaConvertable;
import com.hypixel.hytale.server.npc.asset.builder.holder.EnumSetHolder;
import com.hypixel.hytale.server.npc.asset.builder.validators.EnumArrayValidator;
import com.hypixel.hytale.server.npc.asset.builder.holder.EnumArrayHolder;
import com.hypixel.hytale.server.npc.role.RoleDebugFlags;
import java.lang.reflect.Array;
import java.util.EnumSet;
import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder;
import com.hypixel.hytale.function.consumer.BooleanConsumer;
import com.hypixel.hytale.codec.schema.config.BooleanSchema;
import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder;
import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder;
import com.hypixel.hytale.server.npc.asset.builder.validators.IntValidator;
import java.util.function.IntConsumer;
import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder;
import com.hypixel.hytale.function.consumer.FloatConsumer;
import java.util.function.Supplier;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleArrayValidator;
import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder;
import com.hypixel.hytale.codec.schema.config.IntegerSchema;
import com.hypixel.hytale.server.npc.asset.builder.validators.IntArrayValidator;
import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder;
import com.hypixel.hytale.codec.schema.config.NumberSchema;
import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleValidator;
import java.util.function.DoubleConsumer;
import java.util.function.BiConsumer;
import java.util.Arrays;
import com.hypixel.hytale.server.npc.asset.builder.validators.TemporalArrayValidator;
import com.hypixel.hytale.server.npc.asset.builder.holder.TemporalArrayHolder;
import com.hypixel.hytale.server.npc.asset.builder.holder.StringArrayHolder;
import com.hypixel.hytale.codec.schema.config.ArraySchema;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayValidator;
import com.hypixel.hytale.server.npc.asset.builder.util.StringListHelpers;
import java.util.function.Function;
import com.hypixel.hytale.common.util.ArrayUtil;
import java.util.Objects;
import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder;
import com.hypixel.hytale.server.npc.asset.builder.validators.Validator;
import com.hypixel.hytale.server.npc.asset.builder.expression.BuilderExpressionDynamic;
import com.hypixel.hytale.codec.schema.config.StringSchema;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringValidator;
import java.util.function.Consumer;
import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ParameterType;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.util.Iterator;
import java.util.logging.Level;
import com.hypixel.hytale.server.npc.NPCPlugin;
import java.util.Map;
import javax.annotation.Nonnull;
import com.google.gson.JsonElement;
import com.hypixel.hytale.server.npc.util.expression.ExecutionContext;
import java.util.HashSet;
import com.hypixel.hytale.codec.schema.SchemaContext;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.codec.schema.config.ObjectSchema;
import com.hypixel.hytale.server.npc.valuestore.ValueStoreValidator;
import com.hypixel.hytale.server.npc.asset.builder.holder.ValueHolder;
import com.hypixel.hytale.server.npc.decisionmaker.core.Evaluator;
import java.util.List;
import com.hypixel.hytale.codec.ExtraInfo;
import javax.annotation.Nullable;
import java.util.Set;
import java.util.regex.Pattern;

public abstract class BuilderBase<T> implements Builder<T>
{
    private static final Pattern PATTERN;
    protected String fileName;
    @Nullable
    protected Set<String> queriedKeys;
    protected boolean useDefaultsOnly;
    protected String label;
    protected String typeName;
    protected FeatureEvaluatorHelper evaluatorHelper;
    protected InternalReferenceResolver internalReferenceResolver;
    protected StateMappingHelper stateHelper;
    protected InstructionContextHelper instructionContextHelper;
    protected ExtraInfo extraInfo;
    protected List<Evaluator<?>> evaluators;
    protected BuilderValidationHelper validationHelper;
    @Nullable
    protected BuilderDescriptor builderDescriptor;
    protected BuilderParameters builderParameters;
    protected BuilderManager builderManager;
    protected BuilderContext owner;
    @Nullable
    protected List<String> readErrors;
    private List<ValueHolder> dynamicHolders;
    private List<ValueStoreValidator.ValueUsage> valueStoreUsages;
    @Nullable
    protected ObjectSchema builderSchema;
    protected Schema builderSchemaRaw;
    @Nullable
    protected SchemaContext builderSchemaContext;
    
    public BuilderBase() {
        this.queriedKeys = new HashSet<String>();
    }
    
    @Override
    public void setTypeName(final String name) {
        this.typeName = name;
    }
    
    @Override
    public String getTypeName() {
        return this.typeName;
    }
    
    @Override
    public String getLabel() {
        return this.label;
    }
    
    @Override
    public void setLabel(final String label) {
        this.label = label;
    }
    
    @Override
    public FeatureEvaluatorHelper getEvaluatorHelper() {
        return this.evaluatorHelper;
    }
    
    @Override
    public StateMappingHelper getStateMappingHelper() {
        return this.stateHelper;
    }
    
    @Override
    public InstructionContextHelper getInstructionContextHelper() {
        return this.instructionContextHelper;
    }
    
    @Override
    public void validateReferencedProvidedFeatures(final BuilderManager manager, final ExecutionContext context) {
        if (this.evaluatorHelper == null) {
            return;
        }
        this.evaluatorHelper.validateProviderReferences(manager, context);
    }
    
    @Override
    public boolean canRequireFeature() {
        return false;
    }
    
    @Override
    public boolean excludeFromRegularBuilding() {
        return false;
    }
    
    @Override
    public final void readConfig(final BuilderContext owner, @Nonnull final JsonElement data, final BuilderManager builderManager, final BuilderParameters builderParameters, final BuilderValidationHelper builderValidationHelper) {
        this.preReadConfig(owner, builderManager, builderParameters, builderValidationHelper);
        this.readCommonConfig(data);
        this.readConfig(data);
        this.postReadConfig(data);
    }
    
    private void preReadConfig(final BuilderContext owner, final BuilderManager builderManager, final BuilderParameters builderParameters, @Nullable final BuilderValidationHelper builderValidationHelper) {
        this.owner = owner;
        this.useDefaultsOnly = false;
        this.builderParameters = builderParameters;
        this.builderManager = builderManager;
        this.queriedKeys.add("Comment");
        this.queriedKeys.add("$Title");
        this.queriedKeys.add("$Comment");
        this.queriedKeys.add("$Author");
        this.queriedKeys.add("$TODO");
        this.queriedKeys.add("$Position");
        this.queriedKeys.add("$FloatingFunctionNodes");
        this.queriedKeys.add("$Groups");
        this.queriedKeys.add("$WorkspaceID");
        this.queriedKeys.add("$NodeEditorMetadata");
        this.queriedKeys.add("$NodeId");
        if (builderValidationHelper != null) {
            this.validationHelper = builderValidationHelper;
            this.fileName = builderValidationHelper.getName();
            this.evaluatorHelper = builderValidationHelper.getFeatureEvaluatorHelper();
            this.internalReferenceResolver = builderValidationHelper.getInternalReferenceResolver();
            this.stateHelper = builderValidationHelper.getStateMappingHelper();
            this.instructionContextHelper = builderValidationHelper.getInstructionContextHelper();
            this.extraInfo = builderValidationHelper.getExtraInfo();
            this.evaluators = builderValidationHelper.getEvaluators();
            this.readErrors = builderValidationHelper.getReadErrors();
        }
    }
    
    private void addQueryKey(final String name) {
        if (!this.queriedKeys.add(name)) {
            throw new IllegalArgumentException(String.valueOf(name));
        }
    }
    
    @Override
    public BuilderContext getOwner() {
        return this.owner;
    }
    
    @Override
    public void ignoreAttribute(final String name) {
        this.queriedKeys.add(name);
    }
    
    private void postReadConfig(@Nonnull final JsonElement data) {
        if (this.builderDescriptor == null && data.isJsonObject()) {
            this.queriedKeys.add("Type");
            for (Map.Entry<String, JsonElement> entry : data.getAsJsonObject().entrySet()) {
                final String key = entry.getKey();
                if (!this.queriedKeys.contains(key)) {
                    final String string = data.toString();
                    NPCPlugin.get().getLogger().at(Level.WARNING).log("Unknown JSON attribute '%s' found in %s: %s (JSON: %s)", (Object)key, (Object)this.getBreadCrumbs(), (Object)this.builderParameters.getFileName(), (string.length() > 60) ? (string.substring(60) + "...") : string);
                }
            }
        }
        this.queriedKeys = null;
        this.readErrors = null;
    }
    
    public Builder<T> readCommonConfig(final JsonElement data) {
        return this;
    }
    
    public Builder<T> readConfig(final JsonElement data) {
        return this;
    }
    
    public BuilderManager getBuilderManager() {
        return this.builderManager;
    }
    
    @Override
    public BuilderParameters getBuilderParameters() {
        return this.builderParameters;
    }
    
    protected JsonObject expectJsonObject(@Nonnull final JsonElement data, final String name) {
        if (data.isJsonObject()) {
            return data.getAsJsonObject();
        }
        this.checkForUnexpectedComputeObject(data, name);
        throw new IllegalStateException("Expected object when looking for parameter \"" + name + "\" but found '" + String.valueOf(data) + "' in context " + this.getBreadCrumbs());
    }
    
    protected JsonArray expectJsonArray(@Nonnull final JsonElement data, final String name) {
        if (data.isJsonArray()) {
            return data.getAsJsonArray();
        }
        this.checkForUnexpectedComputeObject(data, name);
        throw new IllegalStateException("Expected array when looking for parameter \"" + name + "\" but found '" + String.valueOf(data) + "' in context " + this.getBreadCrumbs());
    }
    
    @Nullable
    protected String expectString(@Nonnull final JsonElement data, final String name) {
        if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isString()) {
            return data.getAsJsonPrimitive().getAsString();
        }
        if (data.isJsonNull()) {
            return null;
        }
        this.checkForUnexpectedComputeObject(data, name);
        throw new IllegalStateException("Expected string when looking for parameter \"" + name + "\" but found '" + String.valueOf(data) + "' in context " + this.getBreadCrumbs());
    }
    
    protected double expectDouble(@Nonnull final JsonElement data, final String name) {
        if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isNumber()) {
            try {
                return data.getAsJsonPrimitive().getAsDouble();
            }
            catch (final NumberFormatException e) {
                throw new IllegalStateException("Invalid number when looking for parameter \"" + name + "\", found '" + String.valueOf(data) + "' in context " + this.getBreadCrumbs());
            }
        }
        this.checkForUnexpectedComputeObject(data, name);
        throw new IllegalStateException("Expected number when looking for parameter \"" + name + "\" but found '" + String.valueOf(data) + "' in context " + this.getBreadCrumbs());
    }
    
    protected int expectInteger(@Nonnull final JsonElement data, final String name) {
        if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isNumber()) {
            try {
                return data.getAsJsonPrimitive().getAsInt();
            }
            catch (final NumberFormatException e) {
                throw new IllegalStateException("Invalid integer number when looking for parameter \"" + name + "\", found '" + String.valueOf(data) + "' in context " + this.getBreadCrumbs());
            }
        }
        this.checkForUnexpectedComputeObject(data, name);
        throw new IllegalStateException("Expected integer number when looking for parameter \"" + name + "\" but found '" + String.valueOf(data) + "' in context " + this.getBreadCrumbs());
    }
    
    protected boolean expectBoolean(@Nonnull final JsonElement data, final String name) {
        if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isBoolean()) {
            return data.getAsJsonPrimitive().getAsBoolean();
        }
        this.checkForUnexpectedComputeObject(data, name);
        throw new IllegalStateException("Expected boolean value when looking for parameter \"" + name + "\" but found '" + String.valueOf(data) + "' in context " + this.getBreadCrumbs());
    }
    
    protected int[] expectIntArray(@Nonnull final JsonElement data, final String name, final int minSize, final int maxSize) {
        final JsonArray jsonArray = this.expectJsonArray(data, name, minSize, maxSize);
        final int count = jsonArray.size();
        final int[] array = new int[count];
        for (int i = 0; i < count; ++i) {
            array[i] = this.expectInteger(jsonArray.get(i), name);
        }
        return array;
    }
    
    protected int[] expectIntArray(@Nonnull final JsonElement data, final String name, final int size) {
        return this.expectIntArray(data, name, size, size);
    }
    
    protected double[] expectDoubleArray(@Nonnull final JsonElement data, final String name, final int minSize, final int maxSize) {
        final JsonArray jsonArray = this.expectJsonArray(data, name, minSize, maxSize);
        final int count = jsonArray.size();
        final double[] array = new double[count];
        for (int i = 0; i < count; ++i) {
            array[i] = this.expectDouble(jsonArray.get(i), name);
        }
        return array;
    }
    
    protected double[] expectDoubleArray(@Nonnull final JsonElement data, final String name, final int size) {
        return this.expectDoubleArray(data, name, size, size);
    }
    
    @Nonnull
    protected JsonArray expectJsonArray(@Nonnull final JsonElement data, final String name, final int minSize, final int maxSize) {
        final JsonArray jsonArray = this.expectJsonArray(data, name);
        final int count = jsonArray.size();
        if (count >= minSize && count <= maxSize) {
            return jsonArray;
        }
        if (maxSize == minSize) {
            throw new IllegalStateException("Expected array with " + maxSize + " elements when looking for parameter \"" + name + "\" but found " + count + " elements in context " + this.getBreadCrumbs());
        }
        throw new IllegalStateException("Expected array with " + minSize + " to " + maxSize + " elements when looking for parameter \"" + name + "\" but found " + count + " elements in context " + this.getBreadCrumbs());
    }
    
    protected void checkForUnexpectedComputeObject(@Nonnull final JsonElement data, final String name) {
        if (data.isJsonObject() && data.getAsJsonObject().has("Compute")) {
            throw new IllegalStateException("Parameter \"" + name + "\" of " + this.category().getSimpleName() + " " + this.getTypeName() + " is not computable (yet) in context " + this.getBreadCrumbs());
        }
    }
    
    @Nonnull
    protected JsonElement getRequiredJsonElement(@Nonnull final JsonElement data, final String name, final boolean addKey) {
        if (addKey) {
            this.addQueryKey(name);
        }
        final JsonElement element = this.expectJsonObject(data, name).get(name);
        if (element == null) {
            throw new IllegalStateException("Parameter \"" + name + "\" is missing in context " + this.getBreadCrumbs());
        }
        return element;
    }
    
    @Nonnull
    protected JsonElement getRequiredJsonElement(@Nonnull final JsonElement data, final String name) {
        return this.getRequiredJsonElement(data, name, true);
    }
    
    @Nullable
    protected JsonElement getRequiredJsonElementIfNotOverridden(@Nonnull final JsonElement data, final String name, @Nonnull final ParameterType type, final boolean addKey) {
        if (addKey) {
            this.addQueryKey(name);
        }
        final JsonElement element = this.expectJsonObject(data, name).get(name);
        if (element != null) {
            return element;
        }
        if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) {
            this.evaluatorHelper.addComponentRequirementValidator((helper, executionContext) -> this.validateOverriddenParameter(name, type, helper));
            return null;
        }
        if (this.hasOverriddenParameter(name, type, this.evaluatorHelper)) {
            return null;
        }
        if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) {
            this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> {
                this.resolveFeatureProviderReverences(manager);
                this.validateOverriddenParameter(name, type, this.evaluatorHelper);
                return;
            });
            return null;
        }
        throw new IllegalStateException(String.format("Parameter %s is missing and either not provided by a sensor, or provided with the wrong parameter type (expected %s) in context %s", name, type.get(), this.getBreadCrumbs()));
    }
    
    @Nullable
    protected JsonElement getRequiredJsonElementIfNotOverridden(@Nonnull final JsonElement data, final String name, @Nonnull final ParameterType type) {
        return this.getRequiredJsonElementIfNotOverridden(data, name, type, true);
    }
    
    @Nullable
    protected JsonElement getOptionalJsonElement(@Nonnull final JsonElement data, final String name, final boolean addKey) {
        JsonElement result = null;
        if (!this.useDefaultsOnly) {
            if (addKey) {
                this.addQueryKey(name);
            }
            result = this.expectJsonObject(data, name).get(name);
        }
        return result;
    }
    
    @Nullable
    protected JsonElement getOptionalJsonElement(@Nonnull final JsonElement data, final String name) {
        return this.getOptionalJsonElement(data, name, true);
    }
    
    public void requireString(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<String> setter, final StringValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription).required().validator(validator);
            return;
        }
        try {
            this.validateAndSet(this.expectString(this.getRequiredJsonElement(data, name), name), validator, setter, name);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getString(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<String> setter, String defaultValue, final StringValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription).optional(defaultValue).validator(validator);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                defaultValue = this.expectString(element, name);
            }
            this.validateAndSet(defaultValue, validator, setter, name);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireString(@Nonnull final JsonElement data, final String name, @Nonnull final StringHolder stringHolder, final StringValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            stringHolder.setName(name);
            this.builderDescriptor.addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription).required().computable().validator(validator);
            return;
        }
        Objects.requireNonNull(stringHolder, "stringHolder is null");
        try {
            stringHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters);
            this.trackDynamicHolder(stringHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean requireStringIfNotOverridden(@Nonnull final JsonElement data, final String name, @Nonnull final StringHolder stringHolder, final StringValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            stringHolder.setName(name);
            this.builderDescriptor.addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription).requiredIfNotOverridden().computable().validator(validator);
            return false;
        }
        Objects.requireNonNull(stringHolder, "stringHolder is null");
        try {
            final JsonElement element = this.getRequiredJsonElementIfNotOverridden(data, name, ParameterType.STRING);
            final boolean valueProvided = element != null;
            stringHolder.readJSON(element, null, valueProvided ? validator : null, name, this.builderParameters);
            this.trackDynamicHolder(stringHolder);
            return valueProvided;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public boolean getString(@Nonnull final JsonElement data, final String name, @Nonnull final StringHolder stringHolder, final String defaultValue, final StringValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new StringSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            stringHolder.setName(name);
            this.builderDescriptor.addAttribute(name, String.class.getSimpleName(), state, shortDescription, longDescription).optional(defaultValue).computable().validator(validator);
            return false;
        }
        Objects.requireNonNull(stringHolder, "stringHolder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            stringHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters);
            this.trackDynamicHolder(stringHolder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    private void validateAndSet(final String str, @Nullable final StringValidator validator, @Nonnull final Consumer<String> setter, final String name) {
        if (validator != null && !validator.test(str)) {
            throw new IllegalStateException(validator.errorMessage(str, name) + " in " + this.getBreadCrumbs());
        }
        setter.accept(str);
    }
    
    @Nonnull
    protected String[] nonNull(@Nullable final String[] array) {
        return (array == null) ? ArrayUtil.EMPTY_STRING_ARRAY : array;
    }
    
    @Nonnull
    public String[] expectStringArray(@Nonnull final JsonElement data, @Nullable Function<String, String> mapper, final String name, final boolean warning) {
        if (mapper == null) {
            mapper = Function.identity();
        }
        if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isString()) {
            if (warning) {
                NPCPlugin.get().getLogger().at(Level.WARNING).log("Use of strings for lists is deprecated for JSON attribute '%s' (use []) in %s: %s", name, this.getBreadCrumbs(), this.builderParameters.getFileName());
            }
            return StringListHelpers.splitToStringList(data.getAsJsonPrimitive().getAsString(), mapper).toArray(String[]::new);
        }
        if (!data.isJsonArray()) {
            throw new IllegalStateException("Expected string or array when looking for parameter \"" + name + "\" but found '" + String.valueOf(data) + "' in context " + this.getBreadCrumbs());
        }
        final JsonArray array = data.getAsJsonArray();
        final String[] result = new String[array.size()];
        for (int i = 0; i < array.size(); ++i) {
            final String s = mapper.apply(this.expectString(array.get(i), name).trim());
            if (s != null && !s.isEmpty()) {
                result[i] = s;
            }
        }
        return result;
    }
    
    @Nonnull
    public String[] expectStringArray(@Nonnull final JsonElement data, final Function<String, String> mapper, final String name) {
        return this.expectStringArray(data, mapper, name, true);
    }
    
    public boolean getStringArray(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<String[]> setter, final Function<String, String> mapper, final String[] defaultValue, final StringArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema();
            a.setItem(new StringSchema());
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "StringList", state, shortDescription, longDescription).optional(this.defaultArrayToString(defaultValue)).validator(validator);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            String[] array;
            if (haveValue) {
                array = this.expectStringArray(element, mapper, name);
            }
            else {
                array = defaultValue;
            }
            this.validateAndSet(array, validator, setter, name);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireStringArray(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<String[]> setter, final Function<String, String> mapper, final StringArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema();
            a.setItem(new StringSchema());
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "StringList", state, shortDescription, longDescription).required().validator(validator);
            return;
        }
        try {
            final JsonElement element = this.getRequiredJsonElement(data, name);
            this.validateAndSet(this.expectStringArray(element, mapper, name), validator, setter, name);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public void requireStringArray(@Nonnull final JsonElement data, final String name, @Nonnull final StringArrayHolder holder, final int minLength, final int maxLength, final StringArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema();
            a.setItem(new StringSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("String").length(minLength, maxLength).validator(validator).computable().required();
            return;
        }
        Objects.requireNonNull(holder, "string array holder is null");
        try {
            holder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters);
            this.trackDynamicHolder(holder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public void requireTemporalArray(@Nonnull final JsonElement data, final String name, @Nonnull final TemporalArrayHolder holder, final int minLength, final int maxLength, final TemporalArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema();
            a.setItem(new StringSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("TemporalAmount").length(minLength, maxLength).validator(validator).computable().required();
            return;
        }
        Objects.requireNonNull(holder, "temporal array holder is null");
        try {
            holder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters);
            this.trackDynamicHolder(holder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public void requireTemporalRange(@Nonnull final JsonElement data, final String name, @Nonnull final TemporalArrayHolder holder, final TemporalArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        this.requireTemporalArray(data, name, holder, 2, 2, validator, state, shortDescription, longDescription);
    }
    
    public boolean getStringArray(@Nonnull final JsonElement data, final String name, @Nonnull final StringArrayHolder holder, final String[] defaultValue, final int minLength, final int maxLength, final StringArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema();
            a.setItem(new StringSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("String").length(minLength, maxLength).validator(validator).computable().optional(defaultValue);
            return false;
        }
        Objects.requireNonNull(holder, "string array holder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            holder.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, validator, name, this.builderParameters);
            this.trackDynamicHolder(holder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    private void validateAndSet(final String[] value, @Nullable final StringArrayValidator validator, @Nonnull final Consumer<String[]> setter, final String name) {
        if (validator != null && !validator.test(value)) {
            throw new IllegalStateException(validator.errorMessage(name, value) + " in " + this.getBreadCrumbs());
        }
        setter.accept(value);
    }
    
    private String defaultArrayToString(@Nullable final String[] defaultValue) {
        return (defaultValue == null) ? null : Arrays.toString(defaultValue);
    }
    
    private boolean requireOrGetDictionary(@Nonnull final JsonElement data, final String name, final String domain, @Nonnull final BiConsumer<String, JsonElement> setter, final boolean required, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Dictionary", state, shortDescription, longDescription).required().domain(domain);
            return false;
        }
        try {
            final JsonElement element = required ? this.getRequiredJsonElement(data, name) : this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                final JsonObject object = this.expectJsonObject(element, name);
                object.entrySet().forEach(stringJsonElementEntry -> setter.accept(stringJsonElementEntry.getKey(), stringJsonElementEntry.getValue()));
            }
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireDictionary(@Nonnull final JsonElement data, final String name, final String domain, @Nonnull final BiConsumer<String, JsonElement> setter, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        this.requireOrGetDictionary(data, name, domain, setter, true, state, shortDescription, longDescription);
    }
    
    public boolean getDictionary(@Nonnull final JsonElement data, final String name, final String domain, @Nonnull final BiConsumer<String, JsonElement> setter, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        return this.requireOrGetDictionary(data, name, domain, setter, false, state, shortDescription, longDescription);
    }
    
    public void requireDouble(@Nonnull final JsonElement data, final String name, @Nonnull final DoubleConsumer setter, final DoubleValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).required().validator(validator);
            return;
        }
        try {
            this.validateAndSet(this.expectDouble(this.getRequiredJsonElement(data, name), name), validator, setter, name);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getDouble(@Nonnull final JsonElement data, final String name, @Nonnull final DoubleConsumer setter, double defaultValue, final DoubleValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).optional(Double.toString(defaultValue)).validator(validator);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                defaultValue = this.expectDouble(element, name);
            }
            this.validateAndSet(defaultValue, validator, setter, name);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireDouble(@Nonnull final JsonElement data, final String name, @Nonnull final DoubleHolder doubleHolder, final DoubleValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            doubleHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).required().computable().validator(validator);
            return;
        }
        Objects.requireNonNull(doubleHolder, "doubleHolder is null");
        try {
            doubleHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters);
            this.trackDynamicHolder(doubleHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean requireDoubleIfNotOverridden(@Nonnull final JsonElement data, final String name, @Nonnull final DoubleHolder doubleHolder, final DoubleValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            doubleHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).requiredIfNotOverridden().computable().validator(validator);
            return false;
        }
        Objects.requireNonNull(doubleHolder, "doubleHolder is null");
        try {
            final JsonElement element = this.getRequiredJsonElementIfNotOverridden(data, name, ParameterType.DOUBLE);
            final boolean valueProvided = element != null;
            doubleHolder.readJSON(element, -1.7976931348623157E308, valueProvided ? validator : null, name, this.builderParameters);
            this.trackDynamicHolder(doubleHolder);
            return valueProvided;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public boolean getDouble(@Nonnull final JsonElement data, final String name, @Nonnull final DoubleHolder doubleHolder, final double defaultValue, final DoubleValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            doubleHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).optional(Double.toString(defaultValue)).computable().validator(validator);
            return false;
        }
        Objects.requireNonNull(doubleHolder, "doubleHolder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            doubleHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters);
            this.trackDynamicHolder(doubleHolder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    private void validateAndSet(final double v, @Nullable final DoubleValidator validator, @Nonnull final DoubleConsumer setter, final String name) {
        if (validator != null && !validator.test(v)) {
            throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs());
        }
        setter.accept(v);
    }
    
    public void requireIntArray(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<int[]> setter, final int minLength, final int maxLength, final IntArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema(new IntegerSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("integer").length(minLength, maxLength).validator(validator).required();
            return;
        }
        try {
            this.validateAndSet(this.expectIntArray(this.getRequiredJsonElement(data, name), name, minLength, maxLength), validator, setter, name);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getIntArray(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<int[]> setter, int[] defaultValue, final int minLength, final int maxLength, final IntArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema(new IntegerSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("integer").length(minLength, maxLength).validator(validator).optional(defaultValue);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                defaultValue = this.expectIntArray(element, name, minLength, maxLength);
            }
            this.validateAndSet(defaultValue, validator, setter, name);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireIntArray(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final int minLength, final int maxLength, final IntArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema(new IntegerSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("integer").length(minLength, maxLength).validator(validator).computable().required();
            return;
        }
        Objects.requireNonNull(holder, "int array holder is null");
        try {
            holder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters);
            this.trackDynamicHolder(holder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getIntArray(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final int[] defaultValue, final int minLength, final int maxLength, final IntArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema(new IntegerSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("integer").length(minLength, maxLength).validator(validator).computable().optional(defaultValue);
            return false;
        }
        Objects.requireNonNull(holder, "int array holder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            holder.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, validator, name, this.builderParameters);
            this.trackDynamicHolder(holder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireIntRange(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<int[]> setter, final IntArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        this.requireIntArray(data, name, setter, 2, 2, validator, state, shortDescription, longDescription);
    }
    
    public boolean getIntRange(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<int[]> setter, final int[] defaultValue, final IntArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        return this.getIntArray(data, name, setter, defaultValue, 2, 2, validator, state, shortDescription, longDescription);
    }
    
    public void requireIntRange(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final IntArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        this.requireIntArray(data, name, holder, 2, 2, validator, state, shortDescription, longDescription);
    }
    
    public boolean getIntRange(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final int[] defaultValue, final IntArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        return this.getIntArray(data, name, holder, defaultValue, 2, 2, validator, state, shortDescription, longDescription);
    }
    
    private void validateAndSet(final int[] v, @Nullable final IntArrayValidator validator, @Nonnull final Consumer<int[]> setter, final String name) {
        if (validator != null && !validator.test(v)) {
            throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs());
        }
        setter.accept(v);
    }
    
    public void requireDoubleArray(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<double[]> setter, final int minLength, final int maxLength, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema(new NumberSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("Double").length(minLength, maxLength).validator(validator).required();
            return;
        }
        try {
            this.validateAndSet(this.expectDoubleArray(this.getRequiredJsonElement(data, name), name, minLength, maxLength), validator, setter, name);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getDoubleArray(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<double[]> setter, double[] defaultValue, final int minLength, final int maxLength, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema(new NumberSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("Double").length(minLength, maxLength).validator(validator).optional(defaultValue);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                defaultValue = this.expectDoubleArray(element, name, minLength, maxLength);
            }
            this.validateAndSet(defaultValue, validator, setter, name);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireDoubleArray(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final int minLength, final int maxLength, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema(new NumberSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("Double").length(minLength, maxLength).validator(validator).computable().required();
            return;
        }
        Objects.requireNonNull(holder, "double array holder is null");
        try {
            holder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters);
            this.trackDynamicHolder(holder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getDoubleArray(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final double[] defaultValue, final int minLength, final int maxLength, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema(new NumberSchema());
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).domain("Double").length(minLength, maxLength).validator(validator).computable().optional(defaultValue);
            return false;
        }
        Objects.requireNonNull(holder, "double array holder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            holder.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, validator, name, this.builderParameters);
            this.trackDynamicHolder(holder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireDoubleRange(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<double[]> setter, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        this.requireDoubleArray(data, name, setter, 2, 2, validator, state, shortDescription, longDescription);
    }
    
    public boolean getDoubleRange(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<double[]> setter, final double[] defaultValue, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        return this.getDoubleArray(data, name, setter, defaultValue, 2, 2, validator, state, shortDescription, longDescription);
    }
    
    public void requireVector3d(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<double[]> setter, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        this.requireDoubleArray(data, name, setter, 3, 3, validator, state, shortDescription, longDescription);
    }
    
    public boolean getVector3d(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<double[]> setter, final double[] defaultValue, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        return this.getDoubleArray(data, name, setter, defaultValue, 3, 3, validator, state, shortDescription, longDescription);
    }
    
    public void requireDoubleRange(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        this.requireDoubleArray(data, name, holder, 2, 2, validator, state, shortDescription, longDescription);
    }
    
    public boolean getDoubleRange(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final double[] defaultValue, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        return this.getDoubleArray(data, name, holder, defaultValue, 2, 2, validator, state, shortDescription, longDescription);
    }
    
    public void requireVector3d(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        this.requireDoubleArray(data, name, holder, 3, 3, validator, state, shortDescription, longDescription);
    }
    
    public boolean getVector3d(@Nonnull final JsonElement data, final String name, @Nonnull final NumberArrayHolder holder, final double[] defaultValue, final DoubleArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        return this.getDoubleArray(data, name, holder, defaultValue, 3, 3, validator, state, shortDescription, longDescription);
    }
    
    private void validateAndSet(final double[] v, @Nullable final DoubleArrayValidator validator, @Nonnull final Consumer<double[]> setter, final String name) {
        if (validator != null && !validator.test(v)) {
            throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs());
        }
        setter.accept(v);
    }
    
    @Nonnull
    public static Vector3d createVector3d(@Nonnull final double[] coordinates) {
        return new Vector3d(coordinates[0], coordinates[1], coordinates[2]);
    }
    
    public static Vector3d createVector3d(@Nullable final double[] coordinates, @Nonnull final Supplier<Vector3d> defaultSupplier) {
        return (coordinates != null) ? createVector3d(coordinates) : defaultSupplier.get();
    }
    
    public void requireFloat(@Nonnull final JsonElement data, final String name, @Nonnull final FloatConsumer setter, final DoubleValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).required().validator(validator);
            return;
        }
        try {
            this.validateAndSet((float)this.expectDouble(this.getRequiredJsonElement(data, name), name), validator, setter, name);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getFloat(@Nonnull final JsonElement data, final String name, @Nonnull final FloatConsumer setter, float defaultValue, final DoubleValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).optional(Double.toString(defaultValue)).validator(validator);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                defaultValue = (float)this.expectDouble(element, name);
            }
            this.validateAndSet(defaultValue, validator, setter, name);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireFloat(@Nonnull final JsonElement data, final String name, @Nonnull final FloatHolder floatHolder, final DoubleValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            floatHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).required().computable().validator(validator);
            return;
        }
        Objects.requireNonNull(floatHolder, "floatHolder is null");
        try {
            floatHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters);
            this.trackDynamicHolder(floatHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getFloat(@Nonnull final JsonElement data, final String name, @Nonnull final FloatHolder floatHolder, final double defaultValue, final DoubleValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new NumberSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            floatHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Double.class.getSimpleName(), state, shortDescription, longDescription).optional(Double.toString(defaultValue)).computable().validator(validator);
            return false;
        }
        Objects.requireNonNull(floatHolder, "floatHolder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            floatHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters);
            this.trackDynamicHolder(floatHolder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    private void validateAndSet(final float v, @Nullable final DoubleValidator validator, @Nonnull final FloatConsumer setter, final String name) {
        if (validator != null && !validator.test(v)) {
            throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs());
        }
        setter.accept(v);
    }
    
    public void requireInt(@Nonnull final JsonElement data, final String name, @Nonnull final IntConsumer setter, final IntValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription).required().validator(validator);
            return;
        }
        try {
            this.validateAndSet(this.expectInteger(this.getRequiredJsonElement(data, name), name), validator, setter, name);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getInt(@Nonnull final JsonElement data, final String name, @Nonnull final IntConsumer setter, int defaultValue, final IntValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription).optional(Integer.toString(defaultValue)).validator(validator);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                defaultValue = this.expectInteger(element, name);
            }
            this.validateAndSet(defaultValue, validator, setter, name);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireInt(@Nonnull final JsonElement data, final String name, @Nonnull final IntHolder intHolder, final IntValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            intHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription).required().computable().validator(validator);
            return;
        }
        Objects.requireNonNull(intHolder, "intHolder is null");
        try {
            intHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters);
            this.trackDynamicHolder(intHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean requireIntIfNotOverridden(@Nonnull final JsonElement data, final String name, @Nonnull final IntHolder intHolder, final IntValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            intHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription).requiredIfNotOverridden().computable().validator(validator);
            return false;
        }
        Objects.requireNonNull(intHolder, "intHolder is null");
        try {
            final JsonElement element = this.getRequiredJsonElementIfNotOverridden(data, name, ParameterType.INTEGER);
            final boolean valueProvided = element != null;
            intHolder.readJSON(element, Integer.MIN_VALUE, valueProvided ? validator : null, name, this.builderParameters);
            this.trackDynamicHolder(intHolder);
            return valueProvided;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public boolean getInt(@Nonnull final JsonElement data, final String name, @Nonnull final IntHolder intHolder, final int defaultValue, final IntValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new IntegerSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            intHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Integer.class.getSimpleName(), state, shortDescription, longDescription).optional(Double.toString(defaultValue)).computable().validator(validator);
            return false;
        }
        try {
            Objects.requireNonNull(intHolder, "intHolder is null");
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            intHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters);
            this.trackDynamicHolder(intHolder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    private void validateAndSet(final int v, @Nullable final IntValidator validator, @Nonnull final IntConsumer setter, final String name) {
        if (validator != null && !validator.test(v)) {
            throw new IllegalStateException(validator.errorMessage(v, name) + " in " + this.getBreadCrumbs());
        }
        setter.accept(v);
    }
    
    public void requireBoolean(@Nonnull final JsonElement data, final String name, @Nonnull final BooleanHolder booleanHolder, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new BooleanSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            booleanHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Boolean.class.getSimpleName(), state, shortDescription, longDescription).required().computable();
            return;
        }
        Objects.requireNonNull(booleanHolder, "booleanHolder is null");
        try {
            booleanHolder.readJSON(this.getRequiredJsonElement(data, name), name, this.builderParameters);
            this.trackDynamicHolder(booleanHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getBoolean(@Nonnull final JsonElement data, final String name, @Nonnull final BooleanHolder booleanHolder, final boolean defaultValue, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new BooleanSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            booleanHolder.setName(name);
            this.builderDescriptor.addAttribute(name, Boolean.class.getSimpleName(), state, shortDescription, longDescription).optional(Boolean.toString(defaultValue)).computable();
            return false;
        }
        Objects.requireNonNull(booleanHolder, "booleanHolder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            booleanHolder.readJSON(optionalJsonElement, defaultValue, name, this.builderParameters);
            this.trackDynamicHolder(booleanHolder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireBoolean(@Nonnull final JsonElement data, final String name, @Nonnull final BooleanConsumer setter, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new BooleanSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, Boolean.class.getSimpleName(), state, shortDescription, longDescription).required();
            return;
        }
        try {
            setter.accept(this.expectBoolean(this.getRequiredJsonElement(data, name), name));
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getBoolean(@Nonnull final JsonElement data, final String name, @Nonnull final BooleanConsumer setter, boolean defaultValue, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = BuilderExpressionDynamic.computableSchema(new BooleanSchema());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, Boolean.class.getSimpleName(), state, shortDescription, longDescription).optional(Boolean.toString(defaultValue));
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                defaultValue = this.expectBoolean(element, name);
            }
            setter.accept(defaultValue);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void getParameterBlock(@Nonnull final JsonElement data, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        if (this.isCreatingSchema()) {
            this.builderSchema.getProperties().put("Parameters", new ObjectSchema());
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute("Parameters", "Parameters", state, shortDescription, longDescription).optional("");
            return;
        }
        if (!data.isJsonObject()) {
            throw new IllegalStateException(String.format("Looking for parameter block in a JsonElement that isn't an object at %s", this.getBreadCrumbs()));
        }
        final BuilderParameters builderParameters = new BuilderParameters(this.builderParameters);
        builderParameters.readJSON(data.getAsJsonObject(), this.stateHelper);
        builderParameters.validateNoDuplicateParameters(this.builderParameters);
        builderParameters.addParametersToScope();
        this.builderParameters = builderParameters;
        this.addQueryKey("Parameters");
    }
    
    public void cleanupParameters() {
        if (this.isCreatingDescriptor()) {
            return;
        }
        this.builderParameters.disposeCompileContext();
    }
    
    @Nonnull
    protected <E extends Enum<E>> E resolveValue(final String txt, final E[] enumConstants, final String paramName) {
        try {
            return (E)stringToEnum(txt, (Enum[])enumConstants, paramName);
        }
        catch (final IllegalArgumentException e) {
            throw new IllegalArgumentException(e.getMessage() + " in " + this.getBreadCrumbs(), (Throwable)e);
        }
    }
    
    @Nonnull
    public static <E extends Enum<E>> E stringToEnum(@Nullable final String value, final E[] enumConstants, final String ident) {
        if (value != null && !value.isBlank()) {
            final String trimmed = value.trim();
            for (final E E : enumConstants) {
                if (E.name().equalsIgnoreCase(trimmed)) {
                    return E;
                }
            }
        }
        throw new IllegalArgumentException(String.format("Enum value '%s' is '%s', must be one of %s", ident, value, getDomain(enumConstants)));
    }
    
    @Nonnull
    public static <E extends Enum<E>> String getDomain(final E[] enumConstants) {
        return Arrays.toString(enumConstants);
    }
    
    @Nonnull
    private static String formatEnumCamelCase(@Nonnull final String name) {
        final boolean isLower = Character.isLowerCase(name.charAt(0));
        if (name.chars().anyMatch(v -> Character.isLowerCase(v) != isLower)) {
            return name;
        }
        final StringBuilder nameParts = new StringBuilder();
        for (final String part : name.split("_")) {
            nameParts.append(Character.toUpperCase(part.charAt(0))).append(part.substring(1).toLowerCase()).append('_');
        }
        nameParts.deleteCharAt(nameParts.length() - 1);
        return nameParts.toString();
    }
    
    @Nonnull
    private static <E extends Enum<E>> String[] getEnumValues(@Nonnull final Class<E> enumClass) {
        return Arrays.stream(enumClass.getEnumConstants()).map((Function<? super E, ?>)Enum::name).map((Function<? super Object, ?>)BuilderBase::formatEnumCamelCase).toArray(String[]::new);
    }
    
    public <E extends Enum<E> & Supplier<String>> void requireEnum(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<E> setter, @Nonnull final Class<E> clazz, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(s));
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Flag", state, shortDescription, longDescription).required().setEnum(clazz);
            return;
        }
        try {
            setter.accept(this.resolveValue(this.expectString(this.getRequiredJsonElement(data, name), name), clazz.getEnumConstants(), name));
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public <E extends Enum<E> & Supplier<String>> boolean getEnum(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<E> setter, @Nonnull final Class<E> clazz, @Nullable E defaultValue, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(s));
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Flag", state, shortDescription, longDescription).optional((defaultValue != null) ? defaultValue.toString() : "<context>").setEnum(clazz);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                defaultValue = this.resolveValue(this.expectString(element, name), clazz.getEnumConstants(), name);
            }
            setter.accept(defaultValue);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public <E extends Enum<E> & Supplier<String>> void requireEnum(@Nonnull final JsonElement data, final String name, @Nonnull final EnumHolder<E> enumHolder, @Nonnull final Class<E> clazz, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(s));
            return;
        }
        if (this.isCreatingDescriptor()) {
            enumHolder.setName(name);
            this.builderDescriptor.addAttribute(name, "Flag", state, shortDescription, longDescription).required().computable().setEnum(clazz);
            return;
        }
        Objects.requireNonNull(enumHolder, "enumHolder is null");
        try {
            enumHolder.readJSON(this.getRequiredJsonElement(data, name), clazz, name, this.builderParameters);
            this.trackDynamicHolder(enumHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public <E extends Enum<E> & Supplier<String>> boolean getEnum(@Nonnull final JsonElement data, final String name, @Nonnull final EnumHolder<E> enumHolder, @Nonnull final Class<E> clazz, @Nonnull final E defaultValue, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(s));
            return false;
        }
        if (this.isCreatingDescriptor()) {
            enumHolder.setName(name);
            this.builderDescriptor.addAttribute(name, "Flag", state, shortDescription, longDescription).optional(defaultValue.toString()).computable().setEnum(clazz);
            return false;
        }
        Objects.requireNonNull(enumHolder, "enumHolder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            enumHolder.readJSON(optionalJsonElement, clazz, defaultValue, name, this.builderParameters);
            this.trackDynamicHolder(enumHolder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    @Nonnull
    public static <E extends Enum<E>> String[] enumSetToStrings(@Nonnull final EnumSet<E> enumSet) {
        int count = 0;
        Iterator<E> it = enumSet.iterator();
        while (it.hasNext()) {
            ++count;
            it.next();
        }
        if (count == 0) {
            return ArrayUtil.EMPTY_STRING_ARRAY;
        }
        final String[] result = new String[count];
        it = enumSet.iterator();
        for (int i = 0; i < count; ++i) {
            result[i] = it.next().toString();
        }
        return result;
    }
    
    @Nonnull
    public static <E extends Enum<E>> EnumSet<E> stringsToEnumSet(@Nullable final String[] array, @Nonnull final Class<E> clazz, final E[] enumConstants, final String ident) {
        final EnumSet<E> value = EnumSet.noneOf(clazz);
        if (array == null) {
            return value;
        }
        for (final String s : array) {
            value.add(stringToEnum(s, enumConstants, ident));
        }
        return value;
    }
    
    @Nonnull
    public static <E extends Enum<E>> E[] stringsToEnumArray(@Nullable final String[] array, @Nonnull final Class<E> clazz, final E[] enumConstants, final String ident) {
        if (array == null || array.length == 0) {
            return (E[])Array.newInstance(clazz, 0);
        }
        final E[] value = (E[])Array.newInstance(clazz, array.length);
        for (int i = 0; i < array.length; ++i) {
            value[i] = stringToEnum(array[i], enumConstants, ident);
        }
        return value;
    }
    
    protected <E extends Enum<E>> void toSet(final String name, @Nonnull final Class<E> clazz, @Nonnull final EnumSet<E> t, @Nonnull final String elementAsString) {
        final E[] enumConstants = clazz.getEnumConstants();
        for (final String s : elementAsString.split(",")) {
            t.add(this.resolveValue(s.trim(), enumConstants, name));
        }
    }
    
    @Nonnull
    protected EnumSet<RoleDebugFlags> toDebugFlagSet(final String name, @Nonnull final String elementAsString) {
        try {
            return RoleDebugFlags.getFlags(BuilderBase.PATTERN.split(elementAsString.trim()));
        }
        catch (final IllegalArgumentException e) {
            throw new IllegalArgumentException(e.getMessage() + " in parameter " + name + " at " + this.getBreadCrumbs(), (Throwable)e);
        }
    }
    
    protected <E extends Enum<E>> void toSet(final String name, @Nonnull final Class<E> clazz, @Nonnull final EnumSet<E> t, @Nonnull final JsonArray jsonArray) {
        final E[] enumConstants = clazz.getEnumConstants();
        for (final JsonElement jsonElement : jsonArray) {
            t.add(this.resolveValue(this.expectString(jsonElement, name), enumConstants, name));
        }
    }
    
    protected <E extends Enum<E>> void toSet(final String name, @Nonnull final Class<E> clazz, @Nonnull final EnumSet<E> t, @Nonnull final JsonElement jsonElement) {
        if (jsonElement.isJsonArray()) {
            this.toSet(name, clazz, t, this.expectJsonArray(jsonElement, name));
            NPCPlugin.get().getLogger().at(Level.WARNING).log("Use of strings for enum sets is deprecated for JSON attribute '%s' (use []) in %s: %s", name, this.getBreadCrumbs(), this.builderParameters.getFileName());
        }
        else {
            this.toSet(name, clazz, t, this.expectString(jsonElement, name));
        }
    }
    
    public <E extends Enum<E> & Supplier<String>> void requireEnumArray(@Nonnull final JsonElement data, final String name, @Nonnull final EnumArrayHolder<E> enumArrayHolderHolder, @Nonnull final Class<E> clazz, final EnumArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            final ArraySchema a = new ArraySchema();
            a.setItem(s);
            this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a));
            return;
        }
        if (this.isCreatingDescriptor()) {
            enumArrayHolderHolder.setName(name);
            this.builderDescriptor.addAttribute(name, "FlagArray", state, shortDescription, longDescription).required().computable().validator(validator).setEnum(clazz);
            return;
        }
        Objects.requireNonNull(enumArrayHolderHolder, "enumArrayHolder is null");
        try {
            enumArrayHolderHolder.readJSON(this.getRequiredJsonElement(data, name), clazz, validator, name, this.builderParameters);
            this.trackDynamicHolder(enumArrayHolderHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public <E extends Enum<E> & Supplier<String>> void requireEnumSet(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<? super EnumSet<E>> setter, @Nonnull final Class<E> clazz, @Nonnull final Supplier<? extends EnumSet<E>> factory, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            final ArraySchema a = new ArraySchema();
            a.setItem(s);
            a.setUniqueItems(true);
            this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a));
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "FlagSet", state, shortDescription, longDescription).required().setEnum(clazz);
            return;
        }
        try {
            final EnumSet<E> t = (EnumSet<E>)factory.get();
            this.toSet(name, clazz, t, this.getRequiredJsonElement(data, name));
            setter.accept(t);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public <E extends Enum<E> & Supplier<String>> boolean getEnumSet(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<? super EnumSet<E>> setter, @Nonnull final Class<E> clazz, @Nonnull final Supplier<? extends EnumSet<E>> factory, @Nonnull final EnumSet<E> defaultValue, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            final ArraySchema a = new ArraySchema();
            a.setItem(s);
            a.setUniqueItems(true);
            this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a));
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "FlagSet", state, shortDescription, longDescription).optional(defaultValue.toString()).setEnum(clazz);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            if (element != null) {
                final EnumSet<E> t = (EnumSet<E>)factory.get();
                this.toSet(name, clazz, t, element);
                setter.accept(t);
                return true;
            }
            setter.accept(defaultValue);
        }
        catch (final Exception e) {
            this.addError(e);
        }
        return false;
    }
    
    public <E extends Enum<E> & Supplier<String>> void requireEnumSet(@Nonnull final JsonElement data, final String name, @Nonnull final EnumSetHolder<E> enumSetHolder, @Nonnull final Class<E> clazz, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            final ArraySchema a = new ArraySchema();
            a.setItem(s);
            a.setUniqueItems(true);
            this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a));
            return;
        }
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            final ArraySchema a = new ArraySchema();
            a.setItem(s);
            a.setUniqueItems(true);
            this.builderSchema.getProperties().put(name, a);
            return;
        }
        if (this.isCreatingDescriptor()) {
            enumSetHolder.setName(name);
            this.builderDescriptor.addAttribute(name, "FlagSet", state, shortDescription, longDescription).required().computable().setEnum(clazz);
            return;
        }
        Objects.requireNonNull(enumSetHolder, "enumSetHolder is null");
        try {
            enumSetHolder.readJSON(this.getRequiredJsonElement(data, name), clazz, name, this.builderParameters);
            this.trackDynamicHolder(enumSetHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public <E extends Enum<E> & Supplier<String>> boolean getEnumSet(@Nonnull final JsonElement data, final String name, @Nonnull final EnumSetHolder<E> enumSetHolder, @Nonnull final Class<E> clazz, @Nonnull final EnumSet<E> defaultValue, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema s = new StringSchema();
            s.setEnum(getEnumValues(clazz));
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            final ArraySchema a = new ArraySchema();
            a.setItem(s);
            a.setUniqueItems(true);
            this.builderSchema.getProperties().put(name, BuilderExpressionDynamic.computableSchema(a));
            return false;
        }
        if (this.isCreatingDescriptor()) {
            enumSetHolder.setName(name);
            this.builderDescriptor.addAttribute(name, "FlagSet", state, shortDescription, longDescription).optional(defaultValue.toString()).computable().setEnum(clazz);
            return false;
        }
        Objects.requireNonNull(enumSetHolder, "enumSetHolder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            enumSetHolder.readJSON(optionalJsonElement, defaultValue, clazz, name, this.builderParameters);
            this.trackDynamicHolder(enumSetHolder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    @Nonnull
    private Schema getObjectSchema(@Nonnull final Class<?> classType) {
        final BuilderFactory<Object> factory = this.builderManager.getFactory(classType);
        final Schema subSchema = this.builderSchemaContext.refDefinition(factory);
        final ObjectSchema ref = new ObjectSchema();
        ref.setProperties(new LinkedHashMap<String, Schema>());
        final Map<String, Schema> props = ref.getProperties();
        props.put("Reference", BuilderExpressionDynamic.computableSchema(new StringSchema()));
        props.put("Local", BuilderExpressionDynamic.computableSchema(new BooleanSchema()));
        props.put("$Label", BuilderExpressionDynamic.computableSchema(new StringSchema()));
        props.put("Nullable", BuilderExpressionDynamic.computableSchema(new BooleanSchema()));
        props.put("Interfaces", BuilderExpressionDynamic.computableSchema(new ArraySchema(new StringSchema())));
        props.put("Modify", BuilderModifier.toSchema(this.builderSchemaContext));
        final Schema comment = new Schema();
        comment.setDoNotSuggest(true);
        props.put("Comment", comment);
        props.put("$Title", comment);
        props.put("$Comment", comment);
        props.put("$TODO", comment);
        props.put("$Author", comment);
        props.put("$Position", comment);
        props.put("$FloatingFunctionNodes", comment);
        props.put("$Groups", comment);
        props.put("$WorkspaceID", comment);
        props.put("$NodeEditorMetadata", comment);
        props.put("$NodeId", comment);
        ref.setTitle("Object reference");
        ref.setRequired("Reference");
        ref.setAdditionalProperties(false);
        final Schema cond = new Schema();
        final ObjectSchema check = new ObjectSchema();
        check.setProperties(Map.of("Reference", BuilderExpressionDynamic.computableSchema(new StringSchema())));
        check.setRequired("Reference");
        cond.setIf(check);
        cond.setThen(ref);
        cond.setElse(subSchema);
        return BuilderExpressionDynamic.computableSchema(cond);
    }
    
    public boolean getObject(@Nonnull final JsonElement data, final String name, @Nonnull final BuilderObjectReferenceHelper<?> builderObjectReferenceHelper, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription, @Nonnull final BuilderValidationHelper builderValidationHelper) {
        if (this.isCreatingSchema()) {
            final Schema s = this.getObjectSchema(builderObjectReferenceHelper.getClassType());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "ObjectRef", state, shortDescription, longDescription).domain(this.builderManager.getCategoryName(builderObjectReferenceHelper.getClassType())).optional((String)null);
            return false;
        }
        this.addQueryKey(name);
        try {
            final JsonElement element = this.expectJsonObject(data, name).get(name);
            if (element != null) {
                builderObjectReferenceHelper.setLabel(name);
                this.extraInfo.pushKey(name);
                builderObjectReferenceHelper.readConfig(element, this.builderManager, this.builderParameters, builderValidationHelper);
                this.extraInfo.popKey();
                return true;
            }
        }
        catch (final Exception e) {
            this.addError(e);
        }
        return false;
    }
    
    public void requireObject(@Nonnull final JsonElement data, final String name, @Nonnull final BuilderObjectReferenceHelper<?> builderObjectReferenceHelper, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription, @Nonnull final BuilderValidationHelper builderValidationHelper) {
        if (this.isCreatingSchema()) {
            final Schema s = this.getObjectSchema(builderObjectReferenceHelper.getClassType());
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "ObjectRef", state, shortDescription, longDescription).domain(this.builderManager.getCategoryName(builderObjectReferenceHelper.getClassType())).required();
            return;
        }
        try {
            builderObjectReferenceHelper.setLabel(name);
            this.extraInfo.pushKey(name);
            builderObjectReferenceHelper.readConfig(this.getRequiredJsonElement(data, name), this.builderManager, this.builderParameters, builderValidationHelper);
            this.extraInfo.popKey();
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getCodecObject(@Nonnull final JsonElement data, final String name, @Nonnull final BuilderCodecObjectHelper<?> helper, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = this.builderSchemaContext.refDefinition(helper.codec);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "CodecObject", state, shortDescription, longDescription).domain(helper.getClassType().getSimpleName()).optional((String)null);
            return false;
        }
        this.addQueryKey(name);
        try {
            final JsonElement element = this.expectJsonObject(data, name).get(name);
            if (element != null) {
                this.extraInfo.pushKey(name);
                try {
                    helper.readConfig(element, this.extraInfo);
                }
                catch (final Exception e) {
                    this.addError(e);
                }
                this.extraInfo.popKey();
                return true;
            }
        }
        catch (final Exception e2) {
            this.addError(e2);
        }
        return false;
    }
    
    public void requireCodecObject(@Nonnull final JsonElement data, final String name, @Nonnull final BuilderCodecObjectHelper<?> helper, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final Schema s = this.builderSchemaContext.refDefinition(helper.codec);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "CodecObject", state, shortDescription, longDescription).domain(helper.getClassType().getSimpleName()).required();
            return;
        }
        this.extraInfo.pushKey(name);
        try {
            helper.readConfig(this.getRequiredJsonElement(data, name), this.extraInfo);
        }
        catch (final Exception e) {
            this.addError(e);
        }
        this.extraInfo.popKey();
    }
    
    public void requireEmbeddableArray(@Nonnull final JsonElement data, final String embedTag, @Nonnull final BuilderObjectArrayHelper<?, ?> builderObjectArrayHelper, @Nonnull final ArrayValidator arrayValidator, final BuilderDescriptorState state, final String shortDescription, final String longDescription, @Nonnull final BuilderValidationHelper builderValidationHelper) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(embedTag, "EmbeddableArray", state, shortDescription, longDescription).domain(this.builderManager.getCategoryName(builderObjectArrayHelper.getClassType())).validator(arrayValidator).required();
            return;
        }
        if (this.useDefaultsOnly) {
            throw new IllegalArgumentException("An embeddable array can be only used once!");
        }
        try {
            if (data.isJsonArray()) {
                builderObjectArrayHelper.readConfig(data, this.builderManager, this.builderParameters, builderValidationHelper);
                if (!arrayValidator.test(builderObjectArrayHelper)) {
                    throw new IllegalStateException(arrayValidator.errorMessage(builderObjectArrayHelper) + " at " + this.getBreadCrumbs());
                }
                this.useDefaultsOnly = true;
            }
            else {
                this.requireArray0(data, embedTag, builderObjectArrayHelper, arrayValidator, builderValidationHelper);
            }
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getArray(@Nonnull final JsonElement data, final String name, @Nonnull final BuilderObjectArrayHelper<?, ?> builderObjectArrayHelper, final ArrayValidator arrayValidator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription, @Nonnull final BuilderValidationHelper builderValidationHelper) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema();
            final Schema s = this.getObjectSchema(builderObjectArrayHelper.getClassType());
            a.setDescription((longDescription == null) ? shortDescription : longDescription);
            a.setItem(s);
            this.builderSchema.getProperties().put(name, a);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).optional((String)null).validator(arrayValidator).domain(this.builderManager.getCategoryName(builderObjectArrayHelper.getClassType()));
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            if (element != null) {
                this.requireArray0(element, name, builderObjectArrayHelper, arrayValidator, builderValidationHelper);
                return true;
            }
        }
        catch (final Exception e) {
            this.addError(e);
        }
        return false;
    }
    
    public void requireArray(@Nonnull final JsonElement data, final String name, @Nonnull final BuilderObjectArrayHelper<?, ?> builderObjectArrayHelper, final ArrayValidator arrayValidator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription, @Nonnull final BuilderValidationHelper builderValidationHelper) {
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema();
            final Schema s = this.getObjectSchema(builderObjectArrayHelper.getClassType());
            a.setDescription((longDescription == null) ? shortDescription : longDescription);
            a.setItem(s);
            this.builderSchema.getProperties().put(name, a);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Array", state, shortDescription, longDescription).required().validator(arrayValidator).domain(this.builderManager.getCategoryName(builderObjectArrayHelper.getClassType()));
            return;
        }
        try {
            this.requireArray0(this.getRequiredJsonElement(data, name), name, builderObjectArrayHelper, arrayValidator, builderValidationHelper);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    private void requireArray0(@Nonnull final JsonElement data, final String name, @Nonnull final BuilderObjectArrayHelper<?, ?> builderObjectArrayHelper, @Nullable final ArrayValidator validator, @Nonnull final BuilderValidationHelper builderValidationHelper) {
        builderObjectArrayHelper.setLabel(name);
        this.extraInfo.pushKey(name);
        builderObjectArrayHelper.readConfig(data, this.builderManager, this.builderParameters, builderValidationHelper);
        this.extraInfo.popKey();
        if (validator != null && !validator.test(builderObjectArrayHelper)) {
            throw new IllegalStateException(validator.errorMessage(builderObjectArrayHelper) + " at " + this.getBreadCrumbs());
        }
    }
    
    public void requireArray(@Nonnull final JsonElement data, @Nonnull final BuilderObjectArrayHelper<?, ?> builderObjectArrayHelper, final ArrayValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription, @Nonnull final BuilderValidationHelper builderValidationHelper) {
        if (this.isCreatingSchema()) {
            this.builderSchemaRaw = new ArraySchema();
            final Schema s = this.getObjectSchema(builderObjectArrayHelper.getClassType());
            this.builderSchemaRaw.setDescription((longDescription == null) ? shortDescription : longDescription);
            ((ArraySchema)this.builderSchemaRaw).setItem(s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(null, "Array", state, shortDescription, longDescription).domain(this.builderManager.getCategoryName(builderObjectArrayHelper.getClassType())).validator(validator).required();
            return;
        }
        try {
            builderObjectArrayHelper.readConfig(data, this.builderManager, this.builderParameters, builderValidationHelper);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public void requireAsset(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<String> setter, @Nonnull final AssetValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema assetS = new StringSchema();
            validator.updateSchema(assetS);
            final Schema s = BuilderExpressionDynamic.computableSchema(assetS);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Asset", state, shortDescription, longDescription).required().domain(validator.getDomain());
            return;
        }
        try {
            this.validateAndSet(this.expectString(this.getRequiredJsonElement(data, name), name), validator, setter, name);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getAsset(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<String> setter, String defaultValue, @Nonnull final AssetValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema assetS = new StringSchema();
            validator.updateSchema(assetS);
            final Schema s = BuilderExpressionDynamic.computableSchema(assetS);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "Asset", state, shortDescription, longDescription).optional(defaultValue).domain(validator.getDomain());
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            if (haveValue) {
                defaultValue = this.expectString(element, name);
            }
            this.validateAndSet(defaultValue, validator, setter, name);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireAsset(@Nonnull final JsonElement data, final String name, @Nonnull final AssetHolder assetHolder, @Nonnull final AssetValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema assetS = new StringSchema();
            validator.updateSchema(assetS);
            final Schema s = BuilderExpressionDynamic.computableSchema(assetS);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            assetHolder.setName(name);
            this.builderDescriptor.addAttribute(name, "Asset", state, shortDescription, longDescription).required().computable().domain(validator.getDomain());
            return;
        }
        Objects.requireNonNull(assetHolder, "assetHolder is null");
        try {
            assetHolder.readJSON(this.getRequiredJsonElement(data, name), validator, name, this.builderParameters);
            assetHolder.staticValidate();
            this.trackDynamicHolder(assetHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public boolean getAsset(@Nonnull final JsonElement data, final String name, @Nonnull final AssetHolder assetHolder, final String defaultValue, @Nonnull final AssetValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema assetS = new StringSchema();
            validator.updateSchema(assetS);
            final Schema s = BuilderExpressionDynamic.computableSchema(assetS);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            assetHolder.setName(name);
            this.builderDescriptor.addAttribute(name, "Asset", state, shortDescription, longDescription).optional(defaultValue).computable().domain(validator.getDomain());
            return false;
        }
        Objects.requireNonNull(assetHolder, "assetHolder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            assetHolder.readJSON(optionalJsonElement, defaultValue, validator, name, this.builderParameters);
            assetHolder.staticValidate();
            this.trackDynamicHolder(assetHolder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    private void validateAndSet(final String str, @Nullable final AssetValidator validator, @Nonnull final Consumer<String> setter, final String name) {
        if (validator != null) {
            this.validateSingleAsset(str, validator, name);
        }
        setter.accept(str);
    }
    
    public static boolean validateAssetList(@Nullable final String[] assetList, @Nonnull final AssetValidator validator, final String attributeName, final boolean testExistance) {
        if (assetList == null) {
            if (!validator.canListBeEmpty()) {
                throw new IllegalStateException("Null is not an allowed list of " + validator.getDomain() + " value for attribute \"" + attributeName);
            }
            return true;
        }
        else {
            if (assetList.length != 0) {
                for (final String asset : assetList) {
                    validateAsset(asset, validator, attributeName, testExistance);
                }
                return true;
            }
            if (!validator.canListBeEmpty()) {
                throw new IllegalStateException("List of " + validator.getDomain() + " value for attribute \"" + attributeName + "\" must not be empty");
            }
            return true;
        }
    }
    
    public static boolean validateAsset(@Nullable final String assetName, @Nonnull final AssetValidator validator, final String attributeName, final boolean testExistance) {
        if (assetName == null) {
            if (!validator.isNullable()) {
                throw new SkipSentryException(new IllegalStateException("Null is not an allowed " + validator.getDomain() + " value for attribute \"" + attributeName));
            }
            return true;
        }
        else if (assetName.isEmpty()) {
            if (!validator.canBeEmpty()) {
                throw new SkipSentryException(new IllegalStateException("Empty string is not an allowed " + validator.getDomain() + " value for attribute \"" + attributeName));
            }
            return true;
        }
        else {
            if (!testExistance) {
                return true;
            }
            if (validator.isMatcher() && StringUtil.isGlobPattern(assetName)) {
                return true;
            }
            if (!validator.test(assetName)) {
                throw new SkipSentryException(new IllegalStateException(validator.errorMessage(assetName, attributeName)));
            }
            return true;
        }
    }
    
    private void validateSingleAsset(final String assetName, @Nonnull final AssetValidator validator, final String attributeName) {
        try {
            if (!validateAsset(assetName, validator, attributeName, true)) {
                NPCPlugin.get().getLogger().at(Level.WARNING).log("%s in %s: %s", validator.errorMessage(assetName, attributeName), this.getBreadCrumbs(), this.builderParameters.getFileName());
            }
        }
        catch (final IllegalStateException e) {
            throw new IllegalStateException(e.getMessage() + " in " + this.getBreadCrumbs());
        }
    }
    
    public boolean getAssetArray(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<String[]> setter, final Function<String, String> mapper, final String[] defaultValue, @Nonnull final AssetValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema assetS = new StringSchema();
            validator.updateSchema(assetS);
            final ArraySchema a = new ArraySchema(assetS);
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "AssetArray", state, shortDescription, longDescription).optional(this.defaultArrayToString(defaultValue)).domain(validator.getDomain());
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            final boolean haveValue = element != null;
            String[] list;
            if (haveValue) {
                list = this.expectStringArray(element, mapper, name);
            }
            else {
                list = defaultValue;
            }
            this.validateAndSet(list, validator, setter, name);
            return haveValue;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public boolean getAssetArray(@Nonnull final JsonElement data, final String name, @Nonnull final AssetArrayHolder assetHolder, final String[] defaultValue, final int minLength, final int maxLength, @Nonnull final AssetValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema assetS = new StringSchema();
            validator.updateSchema(assetS);
            final ArraySchema a = new ArraySchema(assetS);
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            assetHolder.setName(name);
            this.builderDescriptor.addAttribute(name, "AssetArray", state, shortDescription, longDescription).optional(this.defaultArrayToString(defaultValue)).computable().domain(validator.getDomain());
            return false;
        }
        Objects.requireNonNull(assetHolder, "assetHolder is null");
        try {
            final JsonElement optionalJsonElement = this.getOptionalJsonElement(data, name);
            assetHolder.readJSON(optionalJsonElement, minLength, maxLength, defaultValue, validator, name, this.builderParameters);
            assetHolder.staticValidate();
            this.trackDynamicHolder(assetHolder);
            return optionalJsonElement != null;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    public void requireAssetArray(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<String[]> setter, final Function<String, String> mapper, @Nonnull final AssetValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema assetS = new StringSchema();
            validator.updateSchema(assetS);
            final ArraySchema a = new ArraySchema(assetS);
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "AssetArray", state, shortDescription, longDescription).required().domain(validator.getDomain());
            return;
        }
        try {
            final JsonElement element = this.getRequiredJsonElement(data, name);
            this.validateAndSet(this.expectStringArray(element, mapper, name), validator, setter, name);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    public void requireAssetArray(@Nonnull final JsonElement data, final String name, @Nonnull final AssetArrayHolder assetHolder, final int minLength, final int maxLength, @Nonnull final AssetValidator validator, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        if (this.isCreatingSchema()) {
            final StringSchema assetS = new StringSchema();
            validator.updateSchema(assetS);
            final ArraySchema a = new ArraySchema(assetS);
            if (minLength != 0) {
                a.setMinItems(minLength);
            }
            if (maxLength != Integer.MAX_VALUE) {
                a.setMaxItems(maxLength);
            }
            final Schema s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return;
        }
        if (this.isCreatingDescriptor()) {
            assetHolder.setName(name);
            this.builderDescriptor.addAttribute(name, "AssetArray", state, shortDescription, longDescription).required().computable().domain(validator.getDomain());
            return;
        }
        Objects.requireNonNull(assetHolder, "assetHolder is null");
        try {
            assetHolder.readJSON(this.getRequiredJsonElement(data, name), minLength, maxLength, validator, name, this.builderParameters);
            assetHolder.staticValidate();
            this.trackDynamicHolder(assetHolder);
        }
        catch (final Exception e) {
            this.addError(e);
        }
    }
    
    private void validateAndSet(@Nullable final String[] assetList, @Nullable final AssetValidator validator, @Nonnull final Consumer<String[]> setter, final String attributeName) {
        if (validator != null) {
            if (assetList == null) {
                if (!validator.canListBeEmpty()) {
                    throw new IllegalStateException("Null is not an allowed list of " + validator.getDomain() + " value for attribute \"" + attributeName + "\" in " + this.getBreadCrumbs());
                }
            }
            else if (assetList.length == 0) {
                if (!validator.canListBeEmpty()) {
                    throw new IllegalStateException("List of " + validator.getDomain() + " value for attribute \"" + attributeName + "\" must not be empty in " + this.getBreadCrumbs());
                }
            }
            else {
                for (final String asset : assetList) {
                    this.validateSingleAsset(asset, validator, attributeName);
                }
            }
        }
        setter.accept(assetList);
    }
    
    protected BuilderDescriptor createDescriptor(@Nonnull final Builder<?> builder, final String builderName, final String categoryName, final BuilderManager builderManager, final BuilderDescriptorState state, final String shortDescription, final String longDescription, final Set<String> tags) {
        this.builderDescriptor = new BuilderDescriptor(builderName, categoryName, shortDescription, longDescription, tags, state);
        try {
            builder.readConfig(null, null, builderManager, this.builderParameters, null);
            return this.builderDescriptor;
        }
        finally {
            this.builderDescriptor = null;
        }
    }
    
    protected boolean isCreatingDescriptor() {
        return this.builderDescriptor != null;
    }
    
    protected boolean isCreatingSchema() {
        return this.builderSchema != null;
    }
    
    @Nonnull
    @Override
    public String getSchemaName() {
        if (this.typeName == null) {
            return "NPC:Class:" + this.getClass().getSimpleName();
        }
        return "NPC:" + this.typeName;
    }
    
    @Nonnull
    @Override
    public Schema toSchema(@Nonnull final SchemaContext context) {
        try {
            this.builderSchemaContext = context;
            final ObjectSchema objectSchema = new ObjectSchema();
            this.builderSchema = objectSchema;
            this.builderSchemaRaw = objectSchema;
            this.builderSchema.setProperties(new LinkedHashMap<String, Schema>());
            this.builderSchema.setAdditionalProperties(false);
            this.builderDescriptor = new BuilderDescriptor(null, null, null, null, null, null);
            try {
                this.readConfig(null, null, BuilderManager.SCHEMA_BUILDER_MANAGER, this.builderParameters, null);
            }
            finally {
                this.builderDescriptor = null;
            }
            final Schema comment = new Schema();
            comment.setDoNotSuggest(true);
            this.builderSchema.getProperties().put("Comment", comment);
            this.builderSchema.getProperties().put("$Title", comment);
            this.builderSchema.getProperties().put("$Comment", comment);
            this.builderSchema.getProperties().put("$TODO", comment);
            this.builderSchema.getProperties().put("$Author", comment);
            this.builderSchema.getProperties().put("$Position", comment);
            this.builderSchema.getProperties().put("$FloatingFunctionNodes", comment);
            this.builderSchema.getProperties().put("$Groups", comment);
            this.builderSchema.getProperties().put("$WorkspaceID", comment);
            this.builderSchema.getProperties().put("$NodeEditorMetadata", comment);
            this.builderSchema.getProperties().put("$NodeId", comment);
            this.builderSchemaRaw.setTitle(this.typeName);
            this.builderSchemaRaw.setDescription(this.getLongDescription());
            return this.builderSchemaRaw;
        }
        finally {
            this.builderSchema = null;
            this.builderSchemaContext = null;
        }
    }
    
    @Override
    public final BuilderDescriptor getDescriptor(final String builderName, final String categoryName, final BuilderManager builderManager) {
        final HashSet<String> tags = new HashSet<String>();
        this.registerTags(tags);
        return this.createDescriptor(this, builderName, categoryName, builderManager, this.getBuilderDescriptorState(), this.getShortDescription(), this.getLongDescription(), tags);
    }
    
    @Nullable
    public abstract String getShortDescription();
    
    @Nullable
    public abstract String getLongDescription();
    
    public void registerTags(@Nonnull final Set<String> tags) {
        final String pkg = this.getClass().getPackageName().replaceFirst("\\.builders$", "");
        tags.add(pkg.substring(pkg.lastIndexOf(46) + 1));
    }
    
    @Nullable
    @Override
    public abstract BuilderDescriptorState getBuilderDescriptorState();
    
    protected void validateNotAllStringsEmpty(final String attribute1, final String string1, final String attribute2, final String string2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(StringsNotEmptyValidator.withAttributes(attribute1, attribute2));
            return;
        }
        if (!StringsNotEmptyValidator.test(string1, string2)) {
            this.addError(StringsNotEmptyValidator.errorMessage(string1, attribute1, string2, attribute2, this.getBreadCrumbs()));
        }
    }
    
    protected void validateAtMostOneString(final String attribute1, final String string1, final String attribute2, final String string2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(StringsAtMostOneValidator.withAttributes(attribute1, attribute2));
            return;
        }
        if (StringsAtMostOneValidator.test(string1, string2)) {
            this.addError(StringsAtMostOneValidator.errorMessage(string1, attribute1, string2, attribute2, this.getBreadCrumbs()));
        }
    }
    
    protected void validateOneSetString(final String attribute1, final String string1, final String attribute2, final String string2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(StringsOneSetValidator.withAttributes(attribute1, attribute2));
            return;
        }
        if (!StringsOneSetValidator.test(string1, string2)) {
            this.addError(StringsOneSetValidator.formatErrorMessage(string1, attribute1, string2, attribute2, this.getBreadCrumbs()));
        }
    }
    
    protected void validateOneSetAsset(@Nonnull final AssetHolder value1, final String attribute2, final String string2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(StringsOneSetValidator.withAttributes(value1.getName(), string2));
            return;
        }
        if (value1.isStatic()) {
            this.validateOneSetString(value1.getName(), value1.get(null), attribute2, string2);
            return;
        }
        value1.addRelationValidator((executionContext, v1) -> {
            if (!StringsOneSetValidator.test(v1, string2)) {
                throw new IllegalStateException(StringsOneSetValidator.formatErrorMessage(v1, value1.getName(), string2, attribute2, this.getBreadCrumbs()));
            }
        });
    }
    
    protected void validateOneSetAsset(@Nonnull final AssetHolder value1, @Nonnull final AssetHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(StringsOneSetValidator.withAttributes(value1.getName(), value2.getName()));
            return;
        }
        if (value1.isStatic()) {
            this.validateOneSetAsset(value2, value1.getName(), value1.get(null));
            return;
        }
        if (value2.isStatic()) {
            this.validateOneSetAsset(value1, value2.getName(), value2.get(null));
            return;
        }
        value1.addRelationValidator((executionContext, v1) -> {
            final String s2 = value2.rawGet(executionContext);
            if (!StringsOneSetValidator.test(v1, s2)) {
                throw new IllegalStateException(StringsOneSetValidator.formatErrorMessage(v1, value1.getName(), s2, value2.getName(), this.getBreadCrumbs()));
            }
            else {
                return;
            }
        });
        value2.addRelationValidator((executionContext, v2) -> {
            final String s3 = value1.rawGet(executionContext);
            if (!StringsOneSetValidator.test(s3, v2)) {
                throw new IllegalStateException(StringsOneSetValidator.formatErrorMessage(s3, value1.getName(), v2, value2.getName(), this.getBreadCrumbs()));
            }
        });
    }
    
    protected void validateOneSetAssetArray(@Nonnull final AssetArrayHolder value1, final String attribute2, final String[] value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(ArraysOneSetValidator.withAttributes(value1.getName(), attribute2));
            return;
        }
        if (value1.isStatic()) {
            if (!ArraysOneSetValidator.validate(value1.get(null), value2)) {
                this.addError(ArraysOneSetValidator.formatErrorMessage(value1.getName(), attribute2, this.getBreadCrumbs()));
            }
            return;
        }
        value1.addRelationValidator((executionContext, v1) -> {
            if (!ArraysOneSetValidator.validate(v1, value2)) {
                throw new IllegalStateException(ArraysOneSetValidator.formatErrorMessage(value1.getName(), attribute2, this.getBreadCrumbs()));
            }
        });
    }
    
    protected void validateOneSetAssetArray(@Nonnull final AssetArrayHolder value1, @Nonnull final AssetArrayHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(ArraysOneSetValidator.withAttributes(value1.getName(), value2.getName()));
            return;
        }
        if (value1.isStatic()) {
            this.validateOneSetAssetArray(value2, value1.getName(), value1.get(null));
            return;
        }
        if (value2.isStatic()) {
            this.validateOneSetAssetArray(value1, value2.getName(), value2.get(null));
            return;
        }
        value1.addRelationValidator((executionContext, v1) -> {
            final String[] s2 = value2.rawGet(executionContext);
            if (!ArraysOneSetValidator.validate(v1, s2)) {
                throw new IllegalStateException(ArraysOneSetValidator.formatErrorMessage(value1.getName(), value2.getName(), this.getBreadCrumbs()));
            }
            else {
                return;
            }
        });
        value2.addRelationValidator((executionContext, v2) -> {
            final String[] s3 = value1.rawGet(executionContext);
            if (!ArraysOneSetValidator.validate(s3, v2)) {
                throw new IllegalStateException(ArraysOneSetValidator.formatErrorMessage(value1.getName(), value2.getName(), this.getBreadCrumbs()));
            }
        });
    }
    
    protected <K> void validateNoDuplicates(final Iterable<K> list, final String variableName) {
        final NoDuplicatesValidator<K> validator = NoDuplicatesValidator.withAttributes(list, variableName);
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(validator);
            return;
        }
        if (!validator.test()) {
            this.addError(validator.errorMessage());
        }
    }
    
    protected void validateDoubleRelation(final String attribute1, final double value1, @Nonnull final RelationalOperator relation, final String attribute2, final double value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, attribute2));
            return;
        }
        if (!DoubleValidator.compare(value1, relation, value2)) {
            this.addError(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), attribute2, value2, this.getBreadCrumbs()));
        }
    }
    
    protected void validateDoubleRelation(@Nonnull final DoubleHolder value1, @Nonnull final RelationalOperator relation, final String attribute2, final double value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, attribute2));
            return;
        }
        if (value1.isStatic()) {
            this.validateDoubleRelation(value1.getName(), value1.get(null), relation, attribute2, value2);
        }
        else {
            value1.addRelationValidator((executionContext, v1) -> {
                if (!DoubleValidator.compare(v1, relation, value2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), attribute2, value2, this.getBreadCrumbs()));
                    throw;
                }
            });
        }
    }
    
    protected void validateDoubleRelation(final String attribute1, final double value1, @Nonnull final RelationalOperator relation, @Nonnull final DoubleHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, value2.getName()));
            return;
        }
        if (value2.isStatic()) {
            this.validateDoubleRelation(attribute1, value1, relation, value2.getName(), value2.get(null));
        }
        else {
            value2.addRelationValidator((executionContext, v2) -> {
                if (!DoubleValidator.compare(value1, relation, v2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs()));
                    throw;
                }
            });
        }
    }
    
    protected void validateDoubleRelation(@Nonnull final DoubleHolder value1, @Nonnull final RelationalOperator relation, @Nonnull final DoubleHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, value2.getName()));
            return;
        }
        if (value1.isStatic()) {
            this.validateDoubleRelation(value1.getName(), value1.get(null), relation, value2);
        }
        else if (value2.isStatic()) {
            this.validateDoubleRelation(value1, relation, value2.getName(), value2.get(null));
        }
        else {
            value1.addRelationValidator((executionContext1, v1) -> {
                final double v2 = value2.rawGet(executionContext1);
                if (!DoubleValidator.compare(v1, relation, v2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs()));
                    throw;
                }
                else {
                    return;
                }
            });
            value2.addRelationValidator((executionContext, v2) -> {
                final double v3 = value1.rawGet(executionContext);
                if (!DoubleValidator.compare(v3, relation, v2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v3, relation.asText(), value2.getName(), v2, this.getBreadCrumbs()));
                    throw;
                }
            });
        }
    }
    
    protected void validateFloatRelation(final String attribute1, final float value1, @Nonnull final RelationalOperator relation, final String attribute2, final float value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, attribute2));
            return;
        }
        if (!DoubleValidator.compare(value1, relation, value2)) {
            this.addError(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), attribute2, value2, this.getBreadCrumbs()));
        }
    }
    
    protected void validateFloatRelation(@Nonnull final FloatHolder value1, @Nonnull final RelationalOperator relation, final String attribute2, final float value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, attribute2));
            return;
        }
        if (value1.isStatic()) {
            this.validateFloatRelation(value1.getName(), value1.get(null), relation, attribute2, value2);
        }
        else {
            value1.addRelationValidator((executionContext, v1) -> {
                if (!DoubleValidator.compare(v1, relation, value2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), attribute2, value2, this.getBreadCrumbs()));
                    throw;
                }
            });
        }
    }
    
    protected void validateFloatRelation(final String attribute1, final float value1, @Nonnull final RelationalOperator relation, @Nonnull final FloatHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, value2.getName()));
            return;
        }
        if (value2.isStatic()) {
            this.validateFloatRelation(attribute1, value1, relation, value2.getName(), value2.get(null));
        }
        else {
            value2.addRelationValidator((executionContext, v2) -> {
                if (!DoubleValidator.compare(value1, relation, v2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs()));
                    throw;
                }
            });
        }
    }
    
    protected void validateFloatRelation(@Nonnull final FloatHolder value1, @Nonnull final RelationalOperator relation, @Nonnull final FloatHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, value2.getName()));
            return;
        }
        if (value1.isStatic()) {
            this.validateFloatRelation(value1.getName(), value1.get(null), relation, value2);
        }
        else if (value2.isStatic()) {
            this.validateFloatRelation(value1, relation, value2.getName(), value2.get(null));
        }
        else {
            value1.addRelationValidator((executionContext1, v1) -> {
                final double v2 = value2.rawGet(executionContext1);
                if (!DoubleValidator.compare(v1, relation, v2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs()));
                    throw;
                }
                else {
                    return;
                }
            });
            value2.addRelationValidator((executionContext, v2) -> {
                final double v3 = value1.rawGet(executionContext);
                if (!DoubleValidator.compare(v3, relation, v2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v3, relation.asText(), value2.getName(), v2, this.getBreadCrumbs()));
                    throw;
                }
            });
        }
    }
    
    protected void validateIntRelation(final String attribute1, final int value1, @Nonnull final RelationalOperator relation, final String attribute2, final int value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, attribute2));
            return;
        }
        if (!IntValidator.compare(value1, relation, value2)) {
            this.addError(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), attribute2, value2, this.getBreadCrumbs()));
        }
    }
    
    protected void validateIntRelation(@Nonnull final IntHolder value1, @Nonnull final RelationalOperator relation, final String attribute2, final int value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, attribute2));
            return;
        }
        if (value1.isStatic()) {
            this.validateIntRelation(value1.getName(), value1.get(null), relation, attribute2, value2);
        }
        else {
            value1.addRelationValidator((executionContext, v1) -> {
                if (!IntValidator.compare(v1, relation, value2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), attribute2, value2, this.getBreadCrumbs()));
                    throw;
                }
            });
        }
    }
    
    protected void validateIntRelation(final String attribute1, final int value1, @Nonnull final RelationalOperator relation, @Nonnull final IntHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(attribute1, relation, value2.getName()));
            return;
        }
        if (value2.isStatic()) {
            this.validateIntRelation(attribute1, value1, relation, value2.getName(), value2.get(null));
        }
        else {
            value2.addRelationValidator((executionContext, v2) -> {
                if (!IntValidator.compare(value1, relation, v2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", attribute1, value1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs()));
                    throw;
                }
            });
        }
    }
    
    protected void validateIntRelation(@Nonnull final IntHolder value1, @Nonnull final RelationalOperator relation, @Nonnull final IntHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, value2.getName()));
            return;
        }
        if (value1.isStatic()) {
            this.validateIntRelation(value1.getName(), value1.get(null), relation, value2);
        }
        else if (value2.isStatic()) {
            this.validateIntRelation(value1, relation, value2.getName(), value2.get(null));
        }
        else {
            value1.addRelationValidator((executionContext1, v1) -> {
                final int v2 = value2.rawGet(executionContext1);
                if (!IntValidator.compare(v1, relation, v2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v1, relation.asText(), value2.getName(), v2, this.getBreadCrumbs()));
                    throw;
                }
                else {
                    return;
                }
            });
            value2.addRelationValidator((executionContext, v2) -> {
                final int v3 = value1.rawGet(executionContext);
                if (!IntValidator.compare(v3, relation, v2)) {
                    new IllegalStateException(String.format("'%s'(=%s) should be %s '%s'(=%s) in %s", value1.getName(), v3, relation.asText(), value2.getName(), v2, this.getBreadCrumbs()));
                    throw;
                }
            });
        }
    }
    
    protected void validateIntRelationIfBooleanIs(final boolean targetValue, final boolean actualValue, @Nonnull final IntHolder value1, @Nonnull final RelationalOperator relation, @Nonnull final IntHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AttributeRelationValidator.withAttributes(value1.getName(), relation, value2.getName()));
            return;
        }
        if (actualValue != targetValue) {
            return;
        }
        this.validateIntRelation(value1, relation, value2);
    }
    
    protected void validateAnyPresent(final String attribute1, @Nonnull final BuilderObjectHelper<?> objectHelper1, final String attribute2, @Nonnull final BuilderObjectHelper<?> objectHelper2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AnyPresentValidator.withAttributes(attribute1, attribute2));
            return;
        }
        if (objectHelper1.isPresent() || objectHelper2.isPresent()) {
            return;
        }
        this.addError(AnyPresentValidator.errorMessage(new String[] { attribute1, attribute2 }) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateAnyPresent(final String attribute1, @Nonnull final BuilderObjectHelper<?> objectHelper1, final String attribute2, @Nonnull final BuilderObjectHelper<?> objectHelper2, final String attribute3, @Nonnull final BuilderObjectHelper<?> objectHelper3) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AnyPresentValidator.withAttributes(new String[] { attribute1, attribute2, attribute3 }));
            return;
        }
        if (objectHelper1.isPresent() || objectHelper2.isPresent() || objectHelper3.isPresent()) {
            return;
        }
        this.addError(AnyPresentValidator.errorMessage(new String[] { attribute1, attribute2, attribute3 }) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateAnyPresent(@Nonnull final String[] attributes, @Nonnull final BuilderObjectHelper<?>[] objectHelpers) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AnyPresentValidator.withAttributes(attributes));
            return;
        }
        if (AnyPresentValidator.test(objectHelpers)) {
            return;
        }
        this.addError(AnyPresentValidator.errorMessage(attributes) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateOnePresent(final String attribute1, @Nonnull final BuilderObjectHelper<?> objectHelper1, final String attribute2, @Nonnull final BuilderObjectHelper<?> objectHelper2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(OnePresentValidator.withAttributes(attribute1, attribute2));
            return;
        }
        if (OnePresentValidator.test(objectHelper1, objectHelper2)) {
            return;
        }
        this.addError(OnePresentValidator.errorMessage(new String[] { attribute1, attribute2 }, new BuilderObjectHelper[] { objectHelper1, objectHelper2 }) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateOnePresent(final String attribute1, @Nonnull final BuilderObjectHelper<?> objectHelper1, final String attribute2, @Nonnull final BuilderObjectHelper<?> objectHelper2, final String attribute3, @Nonnull final BuilderObjectHelper<?> objectHelper3) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(OnePresentValidator.withAttributes(attribute1, attribute2, attribute3));
            return;
        }
        if (OnePresentValidator.test(objectHelper1, objectHelper2, objectHelper3)) {
            return;
        }
        this.addError(OnePresentValidator.errorMessage(new String[] { attribute1, attribute2, attribute3 }, new BuilderObjectHelper[] { objectHelper1, objectHelper2, objectHelper3 }) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateOnePresent(@Nonnull final String[] attributes, @Nonnull final BuilderObjectHelper<?>[] objectHelpers) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(OnePresentValidator.withAttributes(attributes));
            return;
        }
        if (OnePresentValidator.test(objectHelpers)) {
            return;
        }
        this.addError(OnePresentValidator.errorMessage(attributes, objectHelpers) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateOnePresent(@Nonnull final String[] attributes, @Nonnull final boolean[] readStatus) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(OnePresentValidator.withAttributes(attributes));
            return;
        }
        if (OnePresentValidator.test(readStatus)) {
            return;
        }
        this.addError(OnePresentValidator.errorMessage(attributes, readStatus) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateOneOrNonePresent(final String attribute1, @Nonnull final BuilderObjectHelper<?> objectHelper1, final String attribute2, @Nonnull final BuilderObjectHelper<?> objectHelper2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(OneOrNonePresentValidator.withAttributes(attribute1, attribute2));
            return;
        }
        if (OneOrNonePresentValidator.test(objectHelper1, objectHelper2)) {
            return;
        }
        this.addError(OneOrNonePresentValidator.errorMessage(new String[] { attribute1, attribute2 }, new BuilderObjectHelper[] { objectHelper1, objectHelper2 }) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateOneOrNonePresent(final String attribute1, @Nonnull final BuilderObjectHelper<?> objectHelper1, final String attribute2, @Nonnull final BuilderObjectHelper<?> objectHelper2, final String attribute3, @Nonnull final BuilderObjectHelper<?> objectHelper3) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(OneOrNonePresentValidator.withAttributes(attribute1, attribute2, attribute3));
            return;
        }
        if (OneOrNonePresentValidator.test(objectHelper1, objectHelper2, objectHelper3)) {
            return;
        }
        this.addError(OneOrNonePresentValidator.errorMessage(new String[] { attribute1, attribute2, attribute3 }, new BuilderObjectHelper[] { objectHelper1, objectHelper2, objectHelper3 }) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateOneOrNonePresent(@Nonnull final String[] attributes, @Nonnull final BuilderObjectHelper<?>[] objectHelpers) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(OneOrNonePresentValidator.withAttributes(attributes));
            return;
        }
        if (OneOrNonePresentValidator.test(objectHelpers)) {
            return;
        }
        this.addError(OneOrNonePresentValidator.errorMessage(attributes, objectHelpers) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateOneOrNonePresent(@Nonnull final String[] attributes, @Nonnull final boolean[] readStatus) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(OneOrNonePresentValidator.withAttributes(attributes));
            return;
        }
        if (OneOrNonePresentValidator.test(readStatus)) {
            return;
        }
        this.addError(OneOrNonePresentValidator.errorMessage(attributes, readStatus) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateExistsIfParameterSet(final String parameter, final boolean value, final String attribute, @Nonnull final BuilderObjectHelper<?> objectHelper) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(ExistsIfParameterSetValidator.withAttributes(parameter, attribute));
            return;
        }
        if (!value) {
            return;
        }
        if (objectHelper.isPresent()) {
            return;
        }
        this.addError(ExistsIfParameterSetValidator.errorMessage(parameter, attribute) + " in " + this.getBreadCrumbs());
    }
    
    protected <E extends Enum<E> & Supplier<String>> void validateStringIfEnumIs(@Nonnull final StringHolder parameter, @Nonnull final StringValidator validator, @Nonnull final EnumHolder<E> enumParameter, final E targetValue) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(ValidateIfEnumIsValidator.withAttributes(parameter.getName(), validator, enumParameter.getName(), targetValue));
            return;
        }
        if (enumParameter.isStatic()) {
            this.validateStringIfEnumIs(parameter, validator, enumParameter.getName(), enumParameter.get(null), targetValue);
            return;
        }
        parameter.addRelationValidator((context, s) -> {
            final Enum enumValue = enumParameter.rawGet(context);
            if (enumValue != targetValue) {
                return;
            }
            else if (!validator.test(s)) {
                throw new IllegalStateException(validator.errorMessage(s, parameter.getName()) + " if " + enumParameter.getName() + " is " + String.valueOf(targetValue) + " in " + this.getBreadCrumbs());
            }
            else {
                return;
            }
        });
        enumParameter.addEnumRelationValidator((context, e) -> {
            if (e == targetValue) {
                final String stringValue = parameter.rawGet(context);
                if (!validator.test(stringValue)) {
                    throw new IllegalStateException(validator.errorMessage(stringValue, parameter.getName()) + " if " + enumParameter.getName() + " is " + String.valueOf(targetValue) + " in " + this.getBreadCrumbs());
                }
            }
        });
    }
    
    protected <E extends Enum<E> & Supplier<String>> void validateStringIfEnumIs(@Nonnull final StringHolder parameter, @Nonnull final StringValidator validator, final String enumName, final E targetValue, final E enumValue) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(ValidateIfEnumIsValidator.withAttributes(parameter.getName(), validator, enumName, targetValue));
            return;
        }
        if (targetValue != enumValue) {
            return;
        }
        if (parameter.isStatic()) {
            final String value = parameter.get(null);
            if (!validator.test(value)) {
                this.addError(validator.errorMessage(value, parameter.getName()) + " if " + enumName + " is " + String.valueOf(targetValue) + " in " + this.getBreadCrumbs());
            }
            return;
        }
        parameter.addRelationValidator((context, s) -> {
            if (!validator.test(s)) {
                throw new IllegalStateException(validator.errorMessage(s, parameter.getName()) + " if " + enumName + " is " + String.valueOf(targetValue) + " in " + this.getBreadCrumbs());
            }
        });
    }
    
    protected <E extends Enum<E> & Supplier<String>> void validateAssetIfEnumIs(@Nonnull final AssetHolder parameter, @Nonnull final AssetValidator validator, @Nonnull final EnumHolder<E> enumParameter, final E targetValue) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(ValidateAssetIfEnumIsValidator.withAttributes(parameter.getName(), validator, enumParameter.getName(), targetValue));
            return;
        }
        if (enumParameter.isStatic()) {
            this.validateAssetIfEnumIs(parameter, validator, enumParameter.getName(), enumParameter.get(null), targetValue);
            return;
        }
        parameter.addRelationValidator((context, s) -> {
            final Enum enumValue = enumParameter.rawGet(context);
            if (enumValue != targetValue) {
                return;
            }
            else if (!validator.test(s)) {
                throw new IllegalStateException(validator.errorMessage(s, parameter.getName()) + " if " + enumParameter.getName() + " is " + String.valueOf(targetValue) + " in " + this.getBreadCrumbs());
            }
            else {
                return;
            }
        });
        enumParameter.addEnumRelationValidator((context, e) -> {
            if (e == targetValue) {
                final String stringValue = parameter.rawGet(context);
                if (!validator.test(stringValue)) {
                    throw new IllegalStateException(validator.errorMessage(stringValue, parameter.getName()) + " if " + enumParameter.getName() + " is " + String.valueOf(targetValue) + " in " + this.getBreadCrumbs());
                }
            }
        });
    }
    
    protected <E extends Enum<E> & Supplier<String>> void validateAssetIfEnumIs(@Nonnull final AssetHolder parameter, @Nonnull final AssetValidator validator, final String enumName, final E targetValue, final E enumValue) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(ValidateAssetIfEnumIsValidator.withAttributes(parameter.getName(), validator, enumName, targetValue));
            return;
        }
        if (targetValue != enumValue) {
            return;
        }
        if (!parameter.isStatic()) {
            parameter.addRelationValidator((context, s) -> {
                if (!validator.test(s)) {
                    throw new IllegalStateException(validator.errorMessage(s, parameter.getName()) + " if " + enumName + " is " + String.valueOf(targetValue) + " in " + this.getBreadCrumbs());
                }
            });
            return;
        }
        final String value = parameter.get(null);
        if (!validator.test(value)) {
            throw new IllegalStateException(validator.errorMessage(value, parameter.getName()) + " if " + enumName + " is " + String.valueOf(targetValue) + " in " + this.getBreadCrumbs());
        }
    }
    
    protected void validateAny(final String attribute1, final boolean value1, final String attribute2, final boolean value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(attribute1, attribute2));
            return;
        }
        if (value1 || value2) {
            return;
        }
        this.addError(AnyBooleanValidator.errorMessage(new String[] { attribute1, attribute2 }) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateAny(@Nonnull final BooleanHolder value1, @Nonnull final BooleanHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(value1.getName(), value2.getName()));
            return;
        }
        boolean v1 = false;
        if (value1.isStatic()) {
            v1 = value1.get(null);
            if (!v1) {
                this.validateAny(value2, value1.getName(), v1);
            }
            return;
        }
        boolean v2 = false;
        if (value2.isStatic()) {
            v2 = value2.get(null);
            if (!v2) {
                this.validateAny(value1, value2.getName(), v2);
            }
            return;
        }
        value1.addRelationValidator((executionContext, v1) -> {
            final boolean v3 = value2.rawGet(executionContext);
            if (!v1 && !v3) {
                new IllegalStateException(AnyBooleanValidator.errorMessage(new String[] { value1.getName(), value2.getName() }) + " in " + this.getBreadCrumbs());
                throw;
            }
            else {
                return;
            }
        });
        value2.addRelationValidator((executionContext, v2) -> {
            final boolean v4 = value1.rawGet(executionContext);
            if (!v4 && !v2) {
                new IllegalStateException(AnyBooleanValidator.errorMessage(new String[] { value1.getName(), value2.getName() }) + " in " + this.getBreadCrumbs());
                throw;
            }
        });
    }
    
    protected void validateAny(@Nonnull final BooleanHolder value1, final String attribute2, final boolean value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(value1.getName(), attribute2));
            return;
        }
        if (value2) {
            return;
        }
        if (value1.isStatic()) {
            if (value1.get(null)) {
                return;
            }
            this.addError(AnyBooleanValidator.errorMessage(new String[] { value1.getName(), attribute2 }) + " in " + this.getBreadCrumbs());
        }
        value1.addRelationValidator((executionContext, v1) -> {
            if (!v1) {
                new IllegalStateException(AnyBooleanValidator.errorMessage(new String[] { value1.getName(), attribute2 }) + " in " + this.getBreadCrumbs());
                throw;
            }
        });
    }
    
    protected void validateAny(final String attribute1, final boolean value1, final String attribute2, final boolean value2, final String attribute3, final boolean value3) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(new String[] { attribute1, attribute2, attribute3 }));
            return;
        }
        if (value1 || value2 || value3) {
            return;
        }
        this.addError(AnyBooleanValidator.errorMessage(new String[] { attribute1, attribute2, attribute3 }) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateAny(@Nonnull final String[] attributes, @Nonnull final boolean[] values) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AnyBooleanValidator.withAttributes(attributes));
            return;
        }
        if (AnyBooleanValidator.test(values)) {
            return;
        }
        this.addError(AnyBooleanValidator.errorMessage(attributes) + " in " + this.getBreadCrumbs());
    }
    
    protected void validateAtMostOne(@Nonnull final BooleanHolder value1, @Nonnull final BooleanHolder value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AtMostOneBooleanValidator.withAttributes(value1.getName(), value2.getName()));
            return;
        }
        boolean v1 = false;
        if (value1.isStatic()) {
            v1 = value1.get(null);
            if (v1) {
                this.validateAtMostOne(value2, value1.getName(), v1);
            }
            return;
        }
        boolean v2 = false;
        if (value2.isStatic()) {
            v2 = value2.get(null);
            if (v2) {
                this.validateAtMostOne(value1, value2.getName(), v2);
            }
            return;
        }
        value1.addRelationValidator((executionContext, v1) -> {
            final boolean v3 = value2.rawGet(executionContext);
            if (v1 && v3) {
                new IllegalStateException(AtMostOneBooleanValidator.errorMessage(new String[] { value1.getName(), value2.getName() }) + " in " + this.getBreadCrumbs());
                throw;
            }
            else {
                return;
            }
        });
        value2.addRelationValidator((executionContext, v2) -> {
            final boolean v4 = value1.rawGet(executionContext);
            if (v4 && v2) {
                new IllegalStateException(AtMostOneBooleanValidator.errorMessage(new String[] { value1.getName(), value2.getName() }) + " in " + this.getBreadCrumbs());
                throw;
            }
        });
    }
    
    protected void validateAtMostOne(@Nonnull final BooleanHolder value1, final String attribute2, final boolean value2) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(AtMostOneBooleanValidator.withAttributes(value1.getName(), attribute2));
            return;
        }
        if (!value2) {
            return;
        }
        if (value1.isStatic() && value1.get(null)) {
            this.addError(AtMostOneBooleanValidator.errorMessage(new String[] { value1.getName(), attribute2 }) + " in " + this.getBreadCrumbs());
        }
        value1.addRelationValidator((executionContext, v1) -> {
            if (v1) {
                new IllegalStateException(AtMostOneBooleanValidator.errorMessage(new String[] { value1.getName(), attribute2 }) + " in " + this.getBreadCrumbs());
                throw;
            }
        });
    }
    
    protected void validateBooleanImplicationAnyAntecedent(final String[] attributes1, @Nonnull final boolean[] values1, final boolean antecedentState, final String[] attributes2, @Nonnull final boolean[] values2, final boolean consequentState) {
        this.validateBooleanImplication(attributes1, values1, antecedentState, attributes2, values2, consequentState, true);
    }
    
    protected void validateBooleanImplicationAllAntecedents(final String[] attributes1, @Nonnull final boolean[] values1, final boolean antecedentState, final String[] attributes2, @Nonnull final boolean[] values2, final boolean consequentState) {
        this.validateBooleanImplication(attributes1, values1, antecedentState, attributes2, values2, consequentState, false);
    }
    
    @Nonnull
    protected ToIntFunction<BuilderSupport> requireStringValueStoreParameter(final String parameter, final ValueStoreValidator.UseType useType) {
        if (this.valueStoreUsages == null) {
            this.valueStoreUsages = new ObjectArrayList<ValueStoreValidator.ValueUsage>();
        }
        this.valueStoreUsages.add(new ValueStoreValidator.ValueUsage(parameter, ValueStore.Type.String, useType, this));
        return support -> support.getValueStoreStringSlot(parameter);
    }
    
    @Nonnull
    protected ToIntFunction<BuilderSupport> requireIntValueStoreParameter(final String parameter, final ValueStoreValidator.UseType useType) {
        if (this.valueStoreUsages == null) {
            this.valueStoreUsages = new ObjectArrayList<ValueStoreValidator.ValueUsage>();
        }
        this.valueStoreUsages.add(new ValueStoreValidator.ValueUsage(parameter, ValueStore.Type.Int, useType, this));
        return support -> support.getValueStoreIntSlot(parameter);
    }
    
    @Nonnull
    protected ToIntFunction<BuilderSupport> requireDoubleValueStoreParameter(final String parameter, final ValueStoreValidator.UseType useType) {
        if (this.valueStoreUsages == null) {
            this.valueStoreUsages = new ObjectArrayList<ValueStoreValidator.ValueUsage>();
        }
        this.valueStoreUsages.add(new ValueStoreValidator.ValueUsage(parameter, ValueStore.Type.Double, useType, this));
        return support -> support.getValueStoreDoubleSlot(parameter);
    }
    
    private void validateBooleanImplication(final String[] attributes1, @Nonnull final boolean[] values1, final boolean antecedentState, final String[] attributes2, @Nonnull final boolean[] values2, final boolean consequentState, final boolean anyAntecedent) {
        final BooleanImplicationValidator validator = BooleanImplicationValidator.withAttributes(attributes1, antecedentState, attributes2, consequentState, anyAntecedent);
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(validator);
            return;
        }
        if (validator.test(values1, values2)) {
            return;
        }
        this.addError(validator.errorMessage() + " in " + this.getBreadCrumbs());
    }
    
    protected void provideFeature(@Nonnull final Feature feature) {
        this.provideFeatureOrParameters(new UnconditionalFeatureProviderEvaluator(feature));
    }
    
    protected void overrideParameters(@Nonnull final String[] parameters, @Nonnull final ParameterType... types) {
        if (!this.isCreatingDescriptor() && this.evaluatorHelper.isDisallowParameterProviders()) {
            return;
        }
        this.provideFeatureOrParameters(new UnconditionalParameterProviderEvaluator(parameters, types));
    }
    
    protected void preventParameterOverride() {
        if (this.isCreatingDescriptor()) {
            return;
        }
        this.evaluatorHelper.disallowParameterProviders();
    }
    
    private void provideFeatureOrParameters(final ProviderEvaluator evaluator) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addProviderEvaluator(evaluator);
            return;
        }
        this.evaluatorHelper.add(evaluator);
    }
    
    protected void provideFeature(@Nonnull final EnumSet<Feature> feature) {
        feature.forEach(this::provideFeature);
    }
    
    protected void requireFeature(@Nonnull final EnumSet<Feature> feature) {
        this.requireFeature(RequiresOneOfFeaturesValidator.withFeatures(feature));
    }
    
    protected <E extends Enum<E> & Supplier<String>> void requireFeatureIf(final String enumName, final E targetValue, final E enumValue, @Nonnull final EnumSet<Feature> feature) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(RequiresFeatureIfEnumValidator.withAttributes(enumName, targetValue, feature));
            return;
        }
        if (targetValue != enumValue) {
            return;
        }
        if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) {
            this.evaluatorHelper.addComponentRequirementValidator((helper, executionContext) -> this.validateRequiresFeatureIf(enumName, targetValue, enumValue, feature, helper));
            return;
        }
        if (RequiresFeatureIfEnumValidator.staticValidate(this.evaluatorHelper, feature, targetValue, enumValue)) {
            return;
        }
        if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) {
            this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> {
                this.resolveFeatureProviderReverences(manager);
                this.validateRequiresFeatureIf(enumName, targetValue, enumValue, feature, this.evaluatorHelper);
            });
            return;
        }
        final String[] description = getDescriptionArray(feature);
        this.addError(String.format("If %s is %s, one of %s must be provided at %s", enumName, targetValue, String.join(", ", (CharSequence[])description), this.getBreadCrumbs()));
    }
    
    protected void requireFeatureIf(final String attribute, final boolean requiredValue, final boolean value, @Nonnull final EnumSet<Feature> feature) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(RequiresFeatureIfValidator.withAttributes(attribute, requiredValue, feature));
            return;
        }
        if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) {
            this.evaluatorHelper.addComponentRequirementValidator((helper, executionContext) -> this.validateRequiresFeatureIf(attribute, requiredValue, value, feature, helper));
            return;
        }
        if (RequiresFeatureIfValidator.staticValidate(this.evaluatorHelper, feature, requiredValue, value)) {
            return;
        }
        if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) {
            this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> {
                this.resolveFeatureProviderReverences(manager);
                this.validateRequiresFeatureIf(attribute, requiredValue, value, feature, this.evaluatorHelper);
            });
            return;
        }
        final String[] description = getDescriptionArray(feature);
        this.addError(String.format("If %s is %s, one of %s must be provided at %s", attribute, requiredValue ? "set" : "not set", String.join(", ", (CharSequence[])description), this.getBreadCrumbs()));
    }
    
    protected void requireFeatureIf(@Nonnull final BooleanHolder parameter, final boolean requiredValue, @Nonnull final EnumSet<Feature> feature) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(RequiresFeatureIfValidator.withAttributes(parameter.getName(), requiredValue, feature));
            return;
        }
        if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) {
            this.evaluatorHelper.addComponentRequirementValidator((helper, executionContext) -> this.validateRequiresFeatureIf(parameter.getName(), requiredValue, parameter.get(executionContext), feature, helper));
            return;
        }
        if (parameter.isStatic() && RequiresFeatureIfValidator.staticValidate(this.evaluatorHelper, feature, requiredValue, parameter.get(null))) {
            return;
        }
        if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) {
            this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> {
                this.resolveFeatureProviderReverences(manager);
                this.validateRequiresFeatureIf(parameter.getName(), requiredValue, parameter.rawGet(context), feature, this.evaluatorHelper);
            });
            return;
        }
        parameter.addRelationValidator((context, value) -> this.validateRequiresFeatureIf(parameter.getName(), requiredValue, (boolean)value, feature, this.evaluatorHelper));
    }
    
    private boolean hasOverriddenParameter(final String parameter, final ParameterType type, @Nonnull final FeatureEvaluatorHelper helper) {
        for (final ProviderEvaluator provider : helper.getProviders()) {
            if (provider instanceof ParameterProviderEvaluator && ((ParameterProviderEvaluator)provider).hasParameter(parameter, type)) {
                return true;
            }
        }
        return false;
    }
    
    private void validateOverriddenParameter(final String parameter, @Nonnull final ParameterType type, @Nonnull final FeatureEvaluatorHelper helper) {
        if (this.hasOverriddenParameter(parameter, type, helper)) {
            return;
        }
        throw new IllegalStateException(String.format("Parameter %s is missing and either not provided by a sensor, or provided with the wrong parameter type (expected %s) in context %s", parameter, type.get(), this.getBreadCrumbs()));
    }
    
    private <E extends Enum<E> & Supplier<String>> void validateRequiresFeatureIf(final String attribute, final E requiredValue, final E value, @Nonnull final EnumSet<Feature> feature, @Nonnull final FeatureEvaluatorHelper helper) {
        if (RequiresFeatureIfEnumValidator.staticValidate(helper, feature, requiredValue, value)) {
            return;
        }
        final String[] description = getDescriptionArray(feature);
        throw new IllegalStateException(String.format("If %s is %s, one of %s must be provided at %s", attribute, requiredValue, String.join(", ", (CharSequence[])description), this.getBreadCrumbs()));
    }
    
    private void validateRequiresFeatureIf(final String attribute, final boolean requiredValue, final boolean value, @Nonnull final EnumSet<Feature> feature, @Nonnull final FeatureEvaluatorHelper helper) {
        if (RequiresFeatureIfValidator.staticValidate(helper, feature, requiredValue, value)) {
            return;
        }
        final String[] description = getDescriptionArray(feature);
        throw new IllegalStateException(String.format("If %s is %s, one of %s must be provided at %s", attribute, requiredValue ? "set" : "not set", String.join(", ", (CharSequence[])description), this.getBreadCrumbs()));
    }
    
    private void requireFeature(@Nonnull final RequiredFeatureValidator validator) {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(validator);
            return;
        }
        if (this.evaluatorHelper.belongsToFeatureRequiringComponent()) {
            this.evaluatorHelper.addComponentRequirementValidator((helper, executionContext) -> {
                if (!validator.validate(helper)) {
                    throw new IllegalStateException(validator.getErrorMessage(this.getBreadCrumbs()));
                }
            });
            return;
        }
        if (validator.validate(this.evaluatorHelper)) {
            return;
        }
        if (this.evaluatorHelper.requiresProviderReferenceEvaluation()) {
            this.evaluatorHelper.addProviderReferenceValidator((manager, context) -> {
                this.resolveFeatureProviderReverences(manager);
                if (!validator.validate(this.evaluatorHelper)) {
                    throw new IllegalStateException(validator.getErrorMessage(this.getBreadCrumbs()));
                }
            });
            return;
        }
        this.addError(validator.getErrorMessage(this.getBreadCrumbs()));
    }
    
    @Nonnull
    public static String[] getDescriptionArray(@Nonnull final EnumSet<Feature> feature) {
        final String[] description = new String[feature.size()];
        final Feature[] featureArray = feature.toArray(Feature[]::new);
        for (int i = 0; i < featureArray.length; ++i) {
            description[i] = featureArray[i].get();
        }
        return description;
    }
    
    private void resolveFeatureProviderReverences(final BuilderManager manager) {
        for (final ProviderEvaluator providerEvaluator : this.evaluatorHelper.getProviders()) {
            providerEvaluator.resolveReferences(manager);
        }
    }
    
    protected void registerStateSensor(final String name, final String subState, @Nonnull final BiConsumer<Integer, Integer> setter) {
        if (this.isCreatingDescriptor()) {
            return;
        }
        this.stateHelper.getAndPutSensorIndex(name, subState, setter);
        this.getParent().setCurrentStateName(name);
    }
    
    protected void registerStateSetter(final String name, final String subState, @Nonnull final BiConsumer<Integer, Integer> setter) {
        if (this.isCreatingDescriptor()) {
            return;
        }
        this.stateHelper.getAndPutSetterIndex(name, subState, setter);
    }
    
    protected void registerStateRequirer(final String name, final String subState, @Nonnull final BiConsumer<Integer, Integer> setter) {
        if (this.isCreatingDescriptor()) {
            return;
        }
        this.stateHelper.getAndPutStateRequirerIndex(name, subState, setter);
    }
    
    protected void validateIsComponent() {
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(ComponentOnlyValidator.get());
            return;
        }
        if (!this.isComponent()) {
            this.addError(String.format("Element not valid outside of component at: %s", this.getBreadCrumbs()));
        }
    }
    
    protected void requireStateString(@Nonnull final JsonElement data, final String name, final boolean componentAllowed, @Nonnull final TriConsumer<String, String, Boolean> setter, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        final StateStringValidator validator = StateStringValidator.get();
        this.requireString(data, name, v -> {}, validator, state, shortDescription, longDescription);
        if (this.isCreatingDescriptor()) {
            return;
        }
        String mainState = validator.hasMainState() ? validator.getMainState() : null;
        String subState = validator.hasSubState() ? validator.getSubState() : null;
        if (this.stateHelper.isComponent()) {
            if (!componentAllowed) {
                this.addError(String.format("Components not supported for state setter %s at %s", subState, this.getBreadCrumbs()));
            }
            if (!this.stateHelper.hasDefaultLocalState()) {
                this.addError("Component with local states must define a 'DefaultState' at the top of the file");
            }
            if (mainState != null) {
                this.addError(String.format("Components must not contain references to main states (%s) at %s", mainState, this.getBreadCrumbs()));
            }
            setter.accept(subState, null, false);
            return;
        }
        if (mainState == null) {
            mainState = this.stateHelper.getCurrentParentState();
        }
        boolean isDefaultSubState = false;
        if (subState == null) {
            subState = this.stateHelper.getDefaultSubState();
            isDefaultSubState = true;
        }
        if (mainState == null) {
            this.addError(String.format("Substate %s does not have a specified main state at %s", subState, this.getBreadCrumbs()));
        }
        setter.accept(mainState, subState, isDefaultSubState);
    }
    
    protected boolean getExistentStateSet(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<Int2ObjectMap<IntSet>> setter, @Nonnull final StateMappingHelper stateHelper, final BuilderDescriptorState state, final String shortDescription, @Nullable final String longDescription) {
        final StateStringValidator validator = StateStringValidator.requireMainState();
        Schema s = null;
        if (this.isCreatingSchema()) {
            final ArraySchema a = new ArraySchema();
            a.setItem(new StringSchema());
            s = BuilderExpressionDynamic.computableSchema(a);
            s.setTitle(name);
            s.setDescription((longDescription == null) ? shortDescription : longDescription);
            this.builderSchema.getProperties().put(name, s);
            return false;
        }
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addAttribute(name, "StringList", state, shortDescription, longDescription).required().validator(validator);
            return false;
        }
        try {
            final JsonElement element = this.getOptionalJsonElement(data, name);
            if (element == null) {
                setter.accept(null);
                return false;
            }
            final String[][] strings = new String[1][1];
            this.validateAndSet(this.expectStringArray(element, null, name), StringArrayNoEmptyStringsValidator.get(), s -> strings[0] = s, name);
            final String[] stringStates = strings[0];
            final Int2ObjectOpenHashMap<IntSet> stateSets = new Int2ObjectOpenHashMap<IntSet>();
            for (final String stringState : stringStates) {
                if (!validator.test(stringState)) {
                    throw new IllegalStateException(validator.errorMessage(stringState));
                }
                final String subState = validator.hasSubState() ? validator.getSubState() : stateHelper.getDefaultSubState();
                stateHelper.getAndPutStateRequirerIndex(validator.getMainState(), subState, (m, s) -> stateSets.computeIfAbsent(m, k -> new IntOpenHashSet()).add((int)s));
            }
            stateSets.trim();
            setter.accept(stateSets);
            return true;
        }
        catch (final Exception e) {
            this.addError(e);
            return false;
        }
    }
    
    protected boolean getDefaultSubState(@Nonnull final JsonElement data, final String name, @Nonnull final Consumer<String> setter, final StringValidator validator, final BuilderDescriptorState state, final String shortDescription, final String longDescription) {
        final String[] defaultSubState = { null };
        final boolean read = this.getString(data, name, v -> defaultSubState[0] = v, "Default", validator, state, shortDescription, longDescription);
        if (!this.isCreatingDescriptor()) {
            this.stateHelper.setDefaultSubState(defaultSubState[0]);
        }
        setter.accept(defaultSubState[0]);
        return read;
    }
    
    protected void increaseDepth() {
        if (this.isCreatingDescriptor()) {
            return;
        }
        this.stateHelper.increaseDepth();
    }
    
    protected void decreaseDepth() {
        if (this.isCreatingDescriptor()) {
            return;
        }
        this.stateHelper.decreaseDepth();
    }
    
    protected void setNotComponent() {
        if (this.isCreatingDescriptor()) {
            return;
        }
        this.stateHelper.setNotComponent();
    }
    
    protected boolean isComponent() {
        return !this.isCreatingDescriptor() && this.stateHelper.isComponent();
    }
    
    protected void requireInstructionType(@Nonnull final EnumSet<InstructionType> instructionType) {
        this.requireContext(instructionType, null);
    }
    
    protected void requireContext(@Nonnull final EnumSet<InstructionType> instructionType, final EnumSet<ComponentContext> componentContexts) {
        final InstructionContextValidator validator = InstructionContextValidator.inInstructions(instructionType, componentContexts);
        if (this.isCreatingDescriptor()) {
            this.builderDescriptor.addValidator(validator);
            return;
        }
        if (this.instructionContextHelper.isComponent()) {
            this.instructionContextHelper.addComponentContextEvaluator((type, extraContext) -> {
                final boolean correctInstruction2 = InstructionContextHelper.isInCorrectInstruction(instructionType, type);
                final boolean correctExtraContext2 = InstructionContextHelper.extraContextMatches(componentContexts, extraContext);
                if (!correctInstruction2 || !correctExtraContext2) {
                    throw new IllegalStateException(InstructionContextValidator.getErrorMessage(this.getTypeName(), type, correctInstruction2, extraContext, correctExtraContext2, this.getBreadCrumbs()));
                }
            });
            return;
        }
        final boolean correctInstruction = this.instructionContextHelper.isInCorrectInstruction(instructionType);
        final boolean correctExtraContext = this.instructionContextHelper.extraContextMatches(componentContexts);
        if (correctInstruction && correctExtraContext) {
            return;
        }
        this.addError(InstructionContextValidator.getErrorMessage(this.getTypeName(), this.instructionContextHelper.getInstructionContext(), correctInstruction, this.instructionContextHelper.getComponentContext(), correctExtraContext, this.getBreadCrumbs()));
    }
    
    @Override
    public IntSet getDependencies() {
        return this.builderParameters.getDependencies();
    }
    
    @Override
    public boolean validate(final String configName, @Nonnull final NPCLoadTimeValidationHelper validationHelper, final ExecutionContext context, final Scope globalScope, @Nonnull final List<String> errors) {
        boolean result = true;
        if (this.dynamicHolders != null) {
            for (final ValueHolder assetHolder : this.dynamicHolders) {
                result &= this.validateDynamicHolder(configName, assetHolder, context, errors);
            }
        }
        final ValueStoreValidator valueStoreValidator = validationHelper.getValueStoreValidator();
        if (this.valueStoreUsages != null) {
            for (final ValueStoreValidator.ValueUsage usage : this.valueStoreUsages) {
                valueStoreValidator.registerValueUsage(usage);
            }
        }
        result &= this.runLoadTimeValidationHelper(configName, validationHelper, context, errors);
        return result;
    }
    
    protected void runLoadTimeValidationHelper0(final String configName, final NPCLoadTimeValidationHelper loadTimeValidationHelper, final ExecutionContext context, final List<String> errors) {
    }
    
    private boolean runLoadTimeValidationHelper(final String configName, final NPCLoadTimeValidationHelper loadTimeValidationHelper, final ExecutionContext context, @Nonnull final List<String> errors) {
        try {
            this.runLoadTimeValidationHelper0(configName, loadTimeValidationHelper, context, errors);
        }
        catch (final Exception e) {
            errors.add(String.format("%s: %s", configName, e.getMessage()));
            return false;
        }
        return true;
    }
    
    private boolean validateDynamicHolder(final String configName, @Nonnull final ValueHolder holder, final ExecutionContext context, @Nonnull final List<String> errors) {
        try {
            holder.validate(context);
        }
        catch (final Exception e) {
            errors.add(String.format("%s: %s", configName, e.getMessage()));
            return false;
        }
        return true;
    }
    
    private void trackDynamicHolder(@Nonnull final ValueHolder holder) {
        if (holder.isStatic()) {
            return;
        }
        if (this.dynamicHolders == null) {
            this.dynamicHolders = new ArrayList<ValueHolder>();
        }
        this.dynamicHolders.add(holder);
    }
    
    public static String readString(@Nonnull final JsonObject object, final String key) {
        return expectStringElement(expectKey(object, key), key);
    }
    
    public static String readString(@Nonnull final JsonObject jsonObject, final String key, final String defaultValue) {
        final JsonElement value = jsonObject.get(key);
        if (value == null) {
            return defaultValue;
        }
        return expectStringElement(value, key);
    }
    
    public static boolean readBoolean(@Nonnull final JsonObject jsonObject, final String key, final boolean defaultValue) {
        final JsonElement value = jsonObject.get(key);
        if (value == null) {
            return defaultValue;
        }
        return expectBooleanElement(value, key);
    }
    
    @Nonnull
    public static JsonElement expectKey(@Nonnull final JsonObject jsonObject, final String key) {
        final JsonElement value = jsonObject.get(key);
        if (value == null) {
            throw new IllegalStateException("'" + key + "' missing in JSON object");
        }
        return value;
    }
    
    public static String expectStringElement(@Nonnull final JsonElement element, final String key) {
        if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) {
            return element.getAsJsonPrimitive().getAsString();
        }
        throw new IllegalStateException("'" + key + "' must be a string");
    }
    
    public static boolean expectBooleanElement(@Nonnull final JsonElement element, final String key) {
        if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean()) {
            return element.getAsJsonPrimitive().getAsBoolean();
        }
        throw new IllegalStateException("'" + key + "' must be a boolean value");
    }
    
    public static JsonObject expectObject(@Nonnull final JsonElement element) {
        if (!element.isJsonObject()) {
            throw new IllegalStateException("Expected a JSON object");
        }
        return element.getAsJsonObject();
    }
    
    public static JsonObject expectObject(@Nonnull final JsonElement element, final String key) {
        if (!element.isJsonObject()) {
            throw new IllegalStateException("'" + key + "' must be an object: " + String.valueOf(element));
        }
        return element.getAsJsonObject();
    }
    
    public static String[] readStringArray(@Nonnull final JsonObject object, final String key, @Nonnull final StringValidator validator, final String[] defaultValue) {
        final JsonElement value = object.get(key);
        if (value == null) {
            return defaultValue;
        }
        return readStringArray(value, key, validator);
    }
    
    @Nonnull
    public static String[] readStringArray(@Nonnull final JsonElement element, final String key, @Nonnull final StringValidator validator) {
        if (!element.isJsonArray()) {
            throw new IllegalStateException(key + " must be an array: " + String.valueOf(element));
        }
        final JsonArray array = element.getAsJsonArray();
        final String[] ret = new String[array.size()];
        for (int i = 0; i < array.size(); ++i) {
            final String string = expectStringElement(array.get(i), String.format("%s element at position %s", key, i));
            if (!validator.test(string)) {
                throw new IllegalStateException(validator.errorMessage(string));
            }
            ret[i] = string;
        }
        return ret;
    }
    
    protected void addError(final String error) {
        this.readErrors.add(this.fileName + ": " + error);
    }
    
    protected void addError(@Nonnull final Exception e) {
        this.addError(e.getMessage());
    }
    
    static {
        PATTERN = Pattern.compile("\\s*,\\s*");
    }
}
