package com.hypixel.hytale.server.npc.asset.builder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder;
import com.hypixel.hytale.server.npc.asset.builder.providerevaluators.ReferenceProviderEvaluator;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayNotEmptyValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator;
import com.hypixel.hytale.server.npc.util.expression.ExecutionContext;
import com.hypixel.hytale.server.npc.util.expression.Scope;
import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class BuilderObjectReferenceHelper<T> extends BuilderObjectHelper<T> {
public static final String KEY_REFERENCE = "Reference";
public static final String KEY_LOCAL = "Local";
public static final String KEY_INTERFACE_LIST = "Interfaces";
public static final String KEY_NULLABLE = "Nullable";
public static final String NULL_COMPONENT = "$Null";
public static final String KEY_LABEL = "$Label";
@Nullable
protected Builder<T> builder = null;
protected final StringHolder fileReference = new StringHolder();
protected String[] componentInterfaces;
protected int referenceIndex = -2147483648;
protected boolean isReference;
protected boolean isNullable;
@Nullable
protected BuilderModifier modifier = null;
protected FeatureEvaluatorHelper evaluatorHelper;
protected InternalReferenceResolver internalReferenceResolver;
protected boolean isInternalReference;
protected String label;
public BuilderObjectReferenceHelper(Class<?> classType, BuilderContext owner) {
super(classType, owner);
}
public boolean excludeFromRegularBuild() {
return this.builder == null ? false : this.builder.excludeFromRegularBuilding();
}
@Nullable
public T build(@Nonnull BuilderSupport builderSupport) {
if (!this.isPresent()) {
return null;
} else {
Builder<T> builder = this.getBuilder(builderSupport.getBuilderManager(), builderSupport, this.isNullable);
if (builder == null) {
return null;
} else {
StateMappingHelper mappingHelper = builder.getStateMappingHelper();
boolean hasLocalComponentStates = this.builder == null && mappingHelper != null && mappingHelper.hasComponentStates();
if (hasLocalComponentStates) {
mappingHelper.initialiseComponentState(builderSupport);
}
if (this.modifier == null) {
this.validateRequiredFeatures(builder, builderSupport.getBuilderManager(), builderSupport.getExecutionContext());
T instance = (T)(builder.isEnabled(builderSupport.getExecutionContext()) ? builder.build(builderSupport) : null);
if (hasLocalComponentStates) {
mappingHelper.popComponentState(builderSupport);
}
return instance;
} else {
Scope globalScope = null;
if (this.isInternalReference) {
globalScope = builderSupport.getGlobalScope();
Objects.requireNonNull(globalScope, "Global scope should not be null when applying to an internal component");
}
if (this.modifier.exportedStateCount() != builder.getStateMappingHelper().importedStateCount()) {
throw new SkipSentryException(new IllegalStateException(String.format("Number of exported states does not match imported states in component %s", this.fileReference.get(builderSupport.getExecutionContext()))));
} else {
ExecutionContext context = builderSupport.getExecutionContext();
Scope newScope = this.modifier.createScope(builderSupport, builder.getBuilderParameters(), globalScope);
Scope oldScope = context.setScope(newScope);
if (this.modifier.exportedStateCount() > 0) {
this.modifier.applyComponentStateMap(builderSupport);
}
this.validateRequiredFeatures(builder, builderSupport.getBuilderManager(), context);
this.validateInstructionContext(builder, builderSupport);
T instance = (T)(builder.isEnabled(builderSupport.getExecutionContext()) ? builder.build(builderSupport) : null);
if (this.modifier.exportedStateCount() > 0) {
this.modifier.popComponentStateMap(builderSupport);
}
if (hasLocalComponentStates) {
mappingHelper.popComponentState(builderSupport);
}
builderSupport.getExecutionContext().setScope(oldScope);
return instance;
}
}
}
}
}
public boolean validate(String configName, NPCLoadTimeValidationHelper loadTimeValidationHelper, @Nonnull BuilderManager manager, @Nonnull ExecutionContext context, Scope globalScope, @Nonnull List<String> errors) {
if (!this.isPresent()) {
return true;
} else {
Builder<T> builder;
try {
builder = this.getBuilder(manager, context, (Builder)null);
} catch (Exception e) {
errors.add(String.format("%s: %s", configName, e.getMessage()));
return false;
}
if (builder == null) {
if (this.isNullable) {
return true;
} else {
errors.add(String.format("%s: %s is not a nullable component reference but a null component was passed", configName, this.fileReference.getExpressionString()));
return false;
}
} else if (this.modifier == null) {
if (!builder.isEnabled(context)) {
return true;
} else {
boolean result = true;
try {
this.validateRequiredFeatures(builder, manager, context);
} catch (Exception e) {
errors.add(String.format("%s: %s", configName, e.getMessage()));
result = false;
}
result &= builder.validate(configName, loadTimeValidationHelper, context, globalScope, errors);
return result;
}
} else {
boolean result = true;
if (this.modifier.exportedStateCount() != builder.getStateMappingHelper().importedStateCount()) {
errors.add(String.format("%s: Number of exported states does not match imported states in component %s", configName, this.fileReference.get(context)));
result = false;
}
Scope additionalScope = this.isInternalReference ? globalScope : null;
Scope newScope;
try {
newScope = this.modifier.createScope(context, builder.getBuilderParameters(), additionalScope);
} catch (Exception e) {
errors.add(String.format("%s: %s", configName, e.getMessage()));
return false;
}
Scope oldScope = context.setScope(newScope);
if (builder.isEnabled(context)) {
try {
this.validateRequiredFeatures(builder, manager, context);
} catch (Exception e) {
errors.add(String.format("%s: %s", configName, e.getMessage()));
result = false;
}
result &= builder.validate(configName, loadTimeValidationHelper, context, globalScope, errors);
}
context.setScope(oldScope);
return result;
}
}
}
@Nullable
public Builder<T> getBuilder(@Nonnull BuilderManager builderManager, @Nonnull BuilderSupport support, boolean nullable) {
Builder<T> builder = this.getBuilder(builderManager, support.getExecutionContext(), support.getParentSpawnable());
if (!nullable && builder == null) {
throw new NullPointerException(String.format("ReferenceHelper failed to get builder: %s", this.getClassType().getSimpleName()));
} else {
return builder;
}
}
@Nullable
public Builder<T> getBuilder(@Nonnull BuilderManager builderManager, ExecutionContext context, @Nullable Builder<?> parentSpawnable) {
if (this.builder != null) {
return this.builder;
} else if (this.isInternalReference) {
return this.internalReferenceResolver.<T>getBuilder(this.referenceIndex, this.classType);
} else if (this.referenceIndex >= 0) {
Builder<T> builder = builderManager.<T>tryGetCachedValidBuilder(this.referenceIndex, this.classType);
if (builder == null) {
throw new SkipSentryException(new IllegalStateException(String.format("Builder %s exists but is not valid!", builderManager.lookupName(this.referenceIndex))));
} else {
return builder;
}
} else {
String reference = this.fileReference.get(context);
if (reference.equals("$Null")) {
return null;
} else {
int idx = builderManager.getIndex(reference);
if (idx >= 0) {
if (parentSpawnable != null) {
parentSpawnable.addDynamicDependency(idx);
}
Builder<T> builder = builderManager.<T>getCachedBuilder(idx, this.classType);
String builderInterfaceCode = builder.getBuilderParameters().getInterfaceCode();
this.validateComponentInterfaceMatch(builderInterfaceCode);
return builder;
} else if (!reference.isEmpty()) {
throw new SkipSentryException(new IllegalStateException("Failed to find builder for: " + reference));
} else {
return null;
}
}
}
}
public void readConfig(@Nonnull JsonElement data, @Nonnull BuilderManager builderManager, @Nonnull BuilderParameters builderParameters, @Nonnull BuilderValidationHelper builderValidationHelper) {
this.readConfig(data, builderManager.getFactory(this.classType), builderManager, builderParameters, builderValidationHelper);
}
public void readConfig(@Nonnull JsonElement data, @Nonnull BuilderFactory<T> factory, @Nonnull BuilderManager builderManager, @Nonnull BuilderParameters builderParameters, @Nonnull BuilderValidationHelper builderValidationHelper) {
super.readConfig(data, builderManager, builderParameters, builderValidationHelper);
if (data.isJsonNull()) {
this.builder = null;
} else if (data.isJsonPrimitive() && data.getAsJsonPrimitive().isString()) {
List var11 = builderValidationHelper.getReadErrors();
String var12 = builderValidationHelper.getName();
var11.add(var12 + ": String reference '" + data.getAsString() + "' to a component is deprecated. Use the 'Reference' parameter instead.");
} else {
JsonObject jsonObject = data.isJsonObject() ? data.getAsJsonObject() : null;
JsonElement referenceValue = jsonObject != null ? jsonObject.get("Reference") : null;
if (referenceValue != null) {
try {
if (BuilderBase.readBoolean(jsonObject, "Local", false)) {
BuilderModifier.readModifierObject(data.getAsJsonObject(), builderParameters, this.fileReference, (holder) -> this.setInternalReference(holder, builderValidationHelper.getInternalReferenceResolver()), (modifier) -> this.modifier = modifier, builderValidationHelper.getStateMappingHelper(), builderValidationHelper.getExtraInfo());
} else {
JsonObject dataObj = data.getAsJsonObject();
BuilderModifier.readModifierObject(dataObj, builderParameters, this.fileReference, (holder) -> this.setFileReference(holder, dataObj, builderManager), (modifier) -> this.modifier = modifier, builderValidationHelper.getStateMappingHelper(), builderValidationHelper.getExtraInfo());
}
FeatureEvaluatorHelper evaluatorHelper = builderValidationHelper.getFeatureEvaluatorHelper();
if (evaluatorHelper != null) {
if (evaluatorHelper.canAddProvider()) {
evaluatorHelper.add(new ReferenceProviderEvaluator(this.referenceIndex, this.classType));
evaluatorHelper.setContainsReference();
return;
}
this.evaluatorHelper = evaluatorHelper;
}
} catch (IllegalStateException | IllegalArgumentException e) {
List var10000 = builderValidationHelper.getReadErrors();
String var10001 = builderValidationHelper.getName();
var10000.add(var10001 + ": " + ((RuntimeException)e).getMessage() + " at " + this.getBreadCrumbs());
}
} else {
this.builder = factory.createBuilder(data);
if (this.builder.isDeprecated()) {
builderManager.checkIfDeprecated(this.builder, factory, data, builderParameters.getFileName(), this.getBreadCrumbs());
}
if (data.isJsonObject() && data.getAsJsonObject().has("$Label") && data.getAsJsonObject().get("$Label").isJsonPrimitive()) {
this.builder.setLabel(data.getAsJsonObject().get("$Label").getAsString());
} else {
this.builder.setLabel(factory.getKeyName(data));
}
this.builder.readConfig(this, data, builderManager, builderParameters, builderValidationHelper);
}
}
}
protected void setInternalReference(@Nonnull StringHolder holder, InternalReferenceResolver referenceResolver) {
this.isInternalReference = true;
this.isReference = true;
if (holder.isStatic()) {
this.internalReferenceResolver = referenceResolver;
this.referenceIndex = this.internalReferenceResolver.getOrCreateIndex(holder.get((ExecutionContext)null));
}
}
protected void setFileReference(@Nonnull StringHolder holder, @Nonnull JsonObject jsonObject, @Nonnull BuilderManager builderManager) {
this.isInternalReference = false;
this.isReference = true;
this.componentInterfaces = BuilderBase.readStringArray(jsonObject, "Interfaces", StringNotEmptyValidator.get(), (String[])null);
this.isNullable = BuilderBase.readBoolean(jsonObject, "Nullable", false);
if (holder.isStatic()) {
this.referenceIndex = builderManager.getOrCreateIndex(holder.get((ExecutionContext)null));
this.builderParameters.addDependency(this.referenceIndex);
} else if (!StringArrayNotEmptyValidator.get().test(this.componentInterfaces)) {
throw new SkipSentryException(new IllegalStateException("Computable references must define a list of 'Interfaces' to control which components can be attached."));
}
}
private void validateRequiredFeatures(@Nonnull Builder<T> builder, BuilderManager manager, ExecutionContext context) {
builder.validateReferencedProvidedFeatures(manager, context);
if (this.evaluatorHelper != null) {
this.evaluatorHelper.validateProviderReferences(manager, context);
builder.getEvaluatorHelper().validateComponentRequirements(this.evaluatorHelper, context);
}
}
private void validateInstructionContext(@Nonnull Builder<T> builder, @Nonnull BuilderSupport support) {
InstructionContextHelper instructionContextHelper = builder.getInstructionContextHelper();
if (instructionContextHelper != null && !this.isInternalReference) {
instructionContextHelper.validateComponentContext(support.getCurrentInstructionContext(), support.getCurrentComponentContext());
}
}
private void validateComponentInterfaceMatch(String builderInterfaceCode) {
for(String componentInterface : this.componentInterfaces) {
if (componentInterface.equals(builderInterfaceCode)) {
return;
}
}
throw new SkipSentryException(new IllegalStateException(String.format("Component code %s does not match any of slot codes: %s.", builderInterfaceCode, Arrays.toString(this.componentInterfaces))));
}
public boolean isPresent() {
return this.isFinal() || this.isReference;
}
public boolean isFinal() {
return this.builder != null;
}
public String getLabel() {
return this.label;
}
public void setLabel(String label) {
this.label = label;
}
}