isXander / YetAnotherConfigLib

YetAnotherConfigLib (yacl) is just that. A builder-based configuration library for Minecraft.
GNU Lesser General Public License v3.0
96 stars 37 forks source link

GsonConfigInstance.Builder.appendGsonBuilder does not work #64

Closed Crendgrim closed 1 year ago

Crendgrim commented 1 year ago

Trying to use appendGsonBuilder to set Gson options (such as pretty printing or field naming policy) fails in an infinite recursion loop on game start.

Note for others running into the same problem: this can be worked around by using overrideGsonBuilder, but that means one has to copy the whole default setup including the (as it's private) ConfigExclusionStrategy class:

            .overrideGsonBuilder(new GsonBuilder()
                    .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
                    .setPrettyPrinting()
                    .setExclusionStrategies(new ConfigExclusionStrategy())
                    .registerTypeHierarchyAdapter(Text.class, new Text.Serializer())
                    .registerTypeHierarchyAdapter(Style.class, new Style.Serializer())
                    .registerTypeHierarchyAdapter(Color.class, new GsonConfigInstance.ColorTypeAdapter())
                    .serializeNulls()
            )
Qendolin commented 1 year ago

Instead of

public Builder<T> appendGsonBuilder(UnaryOperator<GsonBuilder> gsonBuilder) {
    this.gsonBuilder = builder -> gsonBuilder.apply(this.gsonBuilder.apply(builder));
    return this;
}

this would fix the issue:

public Builder<T> appendGsonBuilder(UnaryOperator<GsonBuilder> gsonBuilder) {
    final UnaryOperator<GsonBuilder> prev = gsonBuilder;
    this.gsonBuilder = builder -> operator.apply(prev.apply(builder));
    return this;
}

For anyone looking for a workaround I'ld suggest this mixin:

@Mixin(value = GsonConfigInstance.Builder.class, remap = false)
public abstract class GsonConfigInstanceBuilderMixin<T> implements GsonConfigInstanceBuilderDuck<T> {

    @Shadow
    private UnaryOperator<GsonBuilder> gsonBuilder;

    // GsonConfigInstance.Builder#appendGsonBuilder causes infinite recursion, see issue 64
    public GsonConfigInstance.Builder<T> mymodid$appendGsonBuilder(UnaryOperator<GsonBuilder> operator) {
        final UnaryOperator<GsonBuilder> prev = gsonBuilder;
        this.gsonBuilder = builder -> operator.apply(prev.apply(builder));
        //noinspection unchecked
        return (GsonConfigInstance.Builder<T>) (Object) this;
    }
}

// In a separate file
public interface GsonConfigInstanceBuilderDuck<T> {
    GsonConfigInstance.Builder<T> mymodid$appendGsonBuilder(UnaryOperator<GsonBuilder> operator);
}

which can be used as:

// just to make sure, but not really required
if (builder instanceof GsonConfigInstanceBuilderDuck) {
    //noinspection unchecked
    GsonConfigInstanceBuilderDuck<Config> duck = (GsonConfigInstanceBuilderDuck<Config>) builder;
    builder = duck.mymodid$appendGsonBuilder(b -> b.setLenient().setPrettyPrinting());
}
Crendgrim commented 1 year ago

@isXander any chance of getting Qendolin's suggestion merged?