412 lines
14 KiB
Java
412 lines
14 KiB
Java
package com.hypixel.hytale.codec.builder;
|
|
|
|
import com.hypixel.hytale.codec.Codec;
|
|
import com.hypixel.hytale.codec.ExtraInfo;
|
|
import com.hypixel.hytale.codec.InheritCodec;
|
|
import com.hypixel.hytale.codec.KeyedCodec;
|
|
import com.hypixel.hytale.codec.PrimitiveCodec;
|
|
import com.hypixel.hytale.codec.schema.SchemaContext;
|
|
import com.hypixel.hytale.codec.schema.config.Schema;
|
|
import com.hypixel.hytale.codec.schema.metadata.Metadata;
|
|
import com.hypixel.hytale.codec.util.RawJsonReader;
|
|
import com.hypixel.hytale.codec.validation.LateValidator;
|
|
import com.hypixel.hytale.codec.validation.LegacyValidator;
|
|
import com.hypixel.hytale.codec.validation.ValidatableCodec;
|
|
import com.hypixel.hytale.codec.validation.ValidationResults;
|
|
import com.hypixel.hytale.codec.validation.Validator;
|
|
import com.hypixel.hytale.codec.validation.Validators;
|
|
import com.hypixel.hytale.codec.validation.validator.DeprecatedValidator;
|
|
import com.hypixel.hytale.codec.validation.validator.NonNullValidator;
|
|
import com.hypixel.hytale.function.consumer.TriConsumer;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.io.IOException;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.function.BiFunction;
|
|
import java.util.function.Predicate;
|
|
import java.util.function.Supplier;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
import org.bson.BsonDocument;
|
|
|
|
public class BuilderField<Type, FieldType> {
|
|
@Nonnull
|
|
protected final KeyedCodec<FieldType> codec;
|
|
@Nonnull
|
|
protected final TriConsumer<Type, FieldType, ExtraInfo> setter;
|
|
@Nonnull
|
|
protected final BiFunction<Type, ExtraInfo, FieldType> getter;
|
|
protected final TriConsumer<Type, Type, ExtraInfo> inherit;
|
|
@Nullable
|
|
protected final List<Validator<? super FieldType>> validators;
|
|
@Nullable
|
|
protected final List<Metadata> metadata;
|
|
protected final int minVersion;
|
|
protected final int maxVersion;
|
|
@Nullable
|
|
protected final String documentation;
|
|
@Nullable
|
|
protected final NonNullValidator<? super FieldType> nonNullValidator;
|
|
protected final boolean isPrimitive;
|
|
|
|
protected BuilderField(@Nonnull BuilderField.FieldBuilder<Type, FieldType, ?> builder) {
|
|
this.codec = builder.codec;
|
|
this.setter = builder.setter;
|
|
this.getter = builder.getter;
|
|
this.inherit = builder.inherit;
|
|
this.validators = builder.validators;
|
|
this.metadata = builder.metadata;
|
|
this.minVersion = builder.minVersion;
|
|
this.maxVersion = builder.maxVersion;
|
|
this.documentation = builder.documentation;
|
|
if (builder.validators == null) {
|
|
this.nonNullValidator = null;
|
|
} else {
|
|
NonNullValidator<? super FieldType> found = null;
|
|
|
|
for (Validator<? super FieldType> validator : builder.validators) {
|
|
if (validator instanceof NonNullValidator) {
|
|
found = (NonNullValidator<? super FieldType>)validator;
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.nonNullValidator = found;
|
|
}
|
|
|
|
this.isPrimitive = this.codec.getChildCodec() instanceof PrimitiveCodec;
|
|
}
|
|
|
|
protected BuilderField(
|
|
@Nonnull KeyedCodec<FieldType> codec,
|
|
TriConsumer<Type, FieldType, ExtraInfo> setter,
|
|
BiFunction<Type, ExtraInfo, FieldType> getter,
|
|
TriConsumer<Type, Type, ExtraInfo> inherit
|
|
) {
|
|
this.codec = Objects.requireNonNull(codec, "codec parameter can't be null");
|
|
this.setter = Objects.requireNonNull(setter, "setter parameter can't be null");
|
|
this.getter = Objects.requireNonNull(getter, "getter parameter can't be null");
|
|
this.inherit = inherit;
|
|
this.validators = null;
|
|
this.metadata = null;
|
|
this.minVersion = Integer.MIN_VALUE;
|
|
this.maxVersion = Integer.MAX_VALUE;
|
|
this.documentation = null;
|
|
this.nonNullValidator = null;
|
|
this.isPrimitive = codec.getChildCodec() instanceof PrimitiveCodec;
|
|
}
|
|
|
|
@Nonnull
|
|
public KeyedCodec<FieldType> getCodec() {
|
|
return this.codec;
|
|
}
|
|
|
|
public int getMinVersion() {
|
|
return this.minVersion;
|
|
}
|
|
|
|
public int getMaxVersion() {
|
|
return this.maxVersion;
|
|
}
|
|
|
|
public int getHighestSupportedVersion() {
|
|
return this.maxVersion != Integer.MAX_VALUE ? this.maxVersion : this.minVersion;
|
|
}
|
|
|
|
public boolean supportsVersion(int version) {
|
|
return version == Integer.MIN_VALUE || version >= this.minVersion && version <= this.maxVersion;
|
|
}
|
|
|
|
@Nullable
|
|
public List<Validator<? super FieldType>> getValidators() {
|
|
return this.validators;
|
|
}
|
|
|
|
public boolean hasNonNullValidator() {
|
|
return this.nonNullValidator != null;
|
|
}
|
|
|
|
@Nullable
|
|
public String getDocumentation() {
|
|
return this.documentation;
|
|
}
|
|
|
|
public void decode(BsonDocument document, Type t, @Nonnull ExtraInfo extraInfo) {
|
|
FieldType value = this.codec.getOrNull(document, extraInfo);
|
|
this.setValue(t, value, extraInfo);
|
|
}
|
|
|
|
public void decodeAndInherit(BsonDocument document, Type t, @Nullable Type parent, @Nonnull ExtraInfo extraInfo) {
|
|
FieldType parentValue = parent != null ? this.getter.apply(parent, extraInfo) : null;
|
|
FieldType value = this.codec.getAndInherit(document, parentValue, extraInfo).orElse(null);
|
|
this.setValue(t, value, extraInfo);
|
|
}
|
|
|
|
public void encode(@Nonnull BsonDocument document, Type t, @Nonnull ExtraInfo extraInfo) {
|
|
FieldType value = this.getter.apply(t, extraInfo);
|
|
if (value != null) {
|
|
this.codec.put(document, value, extraInfo);
|
|
}
|
|
}
|
|
|
|
public void inherit(Type t, Type parent, ExtraInfo extraInfo) {
|
|
if (this.inherit != null) {
|
|
this.inherit.accept(t, parent, extraInfo);
|
|
}
|
|
}
|
|
|
|
public void decodeJson(@Nonnull RawJsonReader reader, Type t, @Nonnull ExtraInfo extraInfo) throws IOException {
|
|
int read = reader.peek();
|
|
if (read == -1) {
|
|
throw new IOException("Unexpected EOF!");
|
|
} else {
|
|
switch (read) {
|
|
case 78:
|
|
case 110:
|
|
reader.readNullValue();
|
|
this.setValue(t, null, extraInfo);
|
|
return;
|
|
default:
|
|
FieldType value = this.codec.getChildCodec().decodeJson(reader, extraInfo);
|
|
this.setValue(t, value, extraInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void decodeAndInheritJson(@Nonnull RawJsonReader reader, Type t, @Nullable Type parent, @Nonnull ExtraInfo extraInfo) throws IOException {
|
|
int read = reader.peek();
|
|
if (read == -1) {
|
|
throw new IOException("Unexpected EOF!");
|
|
} else {
|
|
switch (read) {
|
|
case 78:
|
|
case 110:
|
|
reader.readNullValue();
|
|
this.setValue(t, null, extraInfo);
|
|
return;
|
|
default:
|
|
Codec<FieldType> child = this.codec.getChildCodec();
|
|
if (child instanceof InheritCodec) {
|
|
FieldType parentValue = parent != null ? this.getter.apply(parent, extraInfo) : null;
|
|
FieldType value = ((InheritCodec)child).decodeAndInheritJson(reader, parentValue, extraInfo);
|
|
this.setValue(t, value, extraInfo);
|
|
} else {
|
|
FieldType value = child.decodeJson(reader, extraInfo);
|
|
this.setValue(t, value, extraInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setValue(Type t, @Nullable FieldType value, @Nonnull ExtraInfo extraInfo) {
|
|
if (this.validators != null) {
|
|
ValidationResults results = extraInfo.getValidationResults();
|
|
|
|
for (int i = 0; i < this.validators.size(); i++) {
|
|
this.validators.get(i).accept(value, results);
|
|
}
|
|
|
|
results._processValidationResults();
|
|
}
|
|
|
|
if (this.isPrimitive && value == null) {
|
|
ValidationResults results = extraInfo.getValidationResults();
|
|
Validators.nonNull().accept(null, results);
|
|
results._processValidationResults();
|
|
} else {
|
|
this.setter.accept(t, value, extraInfo);
|
|
}
|
|
}
|
|
|
|
public void validate(Type t, @Nonnull ExtraInfo extraInfo) {
|
|
FieldType value = this.getter.apply(t, extraInfo);
|
|
this.validateValue(value, extraInfo, null);
|
|
}
|
|
|
|
public void validateDefaults(Type t, @Nonnull ExtraInfo extraInfo, Set<Codec<?>> tested) {
|
|
FieldType defaultValue = this.getter.apply(t, extraInfo);
|
|
if (defaultValue != null && !this.codec.isRequired() && this.nonNullValidator == null) {
|
|
this.validateValue(defaultValue, extraInfo, v -> v instanceof DeprecatedValidator);
|
|
}
|
|
|
|
Codec<FieldType> childCodec = this.codec.getChildCodec();
|
|
ValidatableCodec.validateDefaults(childCodec, extraInfo, tested);
|
|
}
|
|
|
|
private void validateValue(FieldType value, @Nonnull ExtraInfo extraInfo, @Nullable Predicate<Validator<? super FieldType>> filter) {
|
|
if (this.codec instanceof ValidatableCodec) {
|
|
((ValidatableCodec)this.codec).validate(value, extraInfo);
|
|
}
|
|
|
|
if (this.validators != null) {
|
|
ValidationResults results = extraInfo.getValidationResults();
|
|
|
|
for (int i = 0; i < this.validators.size(); i++) {
|
|
Validator<? super FieldType> validator = this.validators.get(i);
|
|
if ((filter == null || !filter.test(validator)) && !(validator instanceof LateValidator)) {
|
|
validator.accept(value, results);
|
|
}
|
|
}
|
|
|
|
results._processValidationResults();
|
|
}
|
|
}
|
|
|
|
public void nullValidate(Type t, @Nonnull ValidationResults results, ExtraInfo extraInfo) {
|
|
if (this.nonNullValidator != null) {
|
|
FieldType apply = this.getter.apply(t, extraInfo);
|
|
if (apply == null) {
|
|
this.nonNullValidator.accept(null, results);
|
|
results._processValidationResults();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void updateSchema(SchemaContext context, @Nonnull Schema target) {
|
|
if (this.validators != null) {
|
|
for (int i = 0; i < this.validators.size(); i++) {
|
|
Validator<? super FieldType> validator = this.validators.get(i);
|
|
validator.updateSchema(context, target);
|
|
}
|
|
}
|
|
|
|
if (this.metadata != null) {
|
|
for (int i = 0; i < this.metadata.size(); i++) {
|
|
Metadata meta = this.metadata.get(i);
|
|
meta.modify(target);
|
|
}
|
|
}
|
|
|
|
if (this.inherit != null) {
|
|
target.getHytale().setInheritsProperty(true);
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public String toString() {
|
|
return "BuilderField{codec=" + this.codec + ", setter=" + this.setter + ", getter=" + this.getter + "}";
|
|
}
|
|
|
|
public static class FieldBuilder<T, FieldType, Builder extends BuilderCodec.BuilderBase<T, Builder>> {
|
|
@Nonnull
|
|
protected final Builder parentBuilder;
|
|
@Nonnull
|
|
protected final KeyedCodec<FieldType> codec;
|
|
@Nonnull
|
|
protected final TriConsumer<T, FieldType, ExtraInfo> setter;
|
|
@Nonnull
|
|
protected final BiFunction<T, ExtraInfo, FieldType> getter;
|
|
protected final TriConsumer<T, T, ExtraInfo> inherit;
|
|
protected List<Validator<? super FieldType>> validators;
|
|
protected List<Metadata> metadata;
|
|
protected int minVersion = Integer.MIN_VALUE;
|
|
protected int maxVersion = Integer.MAX_VALUE;
|
|
protected String documentation;
|
|
|
|
public FieldBuilder(
|
|
Builder parentBuilder,
|
|
KeyedCodec<FieldType> codec,
|
|
TriConsumer<T, FieldType, ExtraInfo> setter,
|
|
BiFunction<T, ExtraInfo, FieldType> getter,
|
|
TriConsumer<T, T, ExtraInfo> inherit
|
|
) {
|
|
this.parentBuilder = Objects.requireNonNull(parentBuilder, "parentBuilder parameter can't be null");
|
|
this.codec = Objects.requireNonNull(codec, "codec parameter can't be null");
|
|
this.setter = Objects.requireNonNull(setter, "setter parameter can't be null");
|
|
this.getter = Objects.requireNonNull(getter, "getter parameter can't be null");
|
|
this.inherit = inherit;
|
|
}
|
|
|
|
@Nonnull
|
|
public BuilderField.FieldBuilder<T, FieldType, Builder> addValidator(Validator<? super FieldType> validator) {
|
|
if (this.validators == null) {
|
|
this.validators = new ObjectArrayList();
|
|
}
|
|
|
|
this.validators.add(validator);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
@Deprecated(forRemoval = true)
|
|
public BuilderField.FieldBuilder<T, FieldType, Builder> addValidator(LegacyValidator<? super FieldType> validator) {
|
|
if (this.validators == null) {
|
|
this.validators = new ObjectArrayList();
|
|
}
|
|
|
|
this.validators.add(validator);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public BuilderField.FieldBuilder<T, FieldType, Builder> addValidatorLate(@Nonnull final Supplier<LateValidator<? super FieldType>> validatorSupplier) {
|
|
if (this.validators == null) {
|
|
this.validators = new ObjectArrayList();
|
|
}
|
|
|
|
this.validators.add(new LateValidator<FieldType>() {
|
|
private LateValidator<? super FieldType> validator;
|
|
|
|
@Override
|
|
public void accept(FieldType fieldType, ValidationResults results) {
|
|
if (this.validator == null) {
|
|
this.validator = validatorSupplier.get();
|
|
}
|
|
|
|
this.validator.accept(fieldType, results);
|
|
}
|
|
|
|
@Override
|
|
public void acceptLate(FieldType fieldType, ValidationResults results, ExtraInfo extraInfo) {
|
|
if (this.validator == null) {
|
|
this.validator = validatorSupplier.get();
|
|
}
|
|
|
|
this.validator.acceptLate(fieldType, results, extraInfo);
|
|
}
|
|
|
|
@Override
|
|
public void updateSchema(SchemaContext context, Schema target) {
|
|
if (this.validator == null) {
|
|
this.validator = validatorSupplier.get();
|
|
}
|
|
|
|
this.validator.updateSchema(context, target);
|
|
}
|
|
});
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public BuilderField.FieldBuilder<T, FieldType, Builder> setVersionRange(int minVersion, int maxVersion) {
|
|
this.minVersion = minVersion;
|
|
this.maxVersion = maxVersion;
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public BuilderField.FieldBuilder<T, FieldType, Builder> documentation(String doc) {
|
|
this.documentation = doc;
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public BuilderField.FieldBuilder<T, FieldType, Builder> metadata(Metadata metadata) {
|
|
if (this.metadata == null) {
|
|
this.metadata = new ObjectArrayList();
|
|
}
|
|
|
|
this.metadata.add(metadata);
|
|
return this;
|
|
}
|
|
|
|
@Nonnull
|
|
public Builder add() {
|
|
this.parentBuilder.addField(new BuilderField<>(this));
|
|
return this.parentBuilder;
|
|
}
|
|
}
|
|
}
|