From 51954163d45fff1731bef6ad808044277ff2e32e Mon Sep 17 00:00:00 2001 From: Glavo Date: Sun, 14 Sep 2025 21:06:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9B=E5=BB=BA=20RawPreservingProperty=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=20(#4464)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/setting/Config.java | 21 ++++--- .../util/gson/EnumOrdinalDeserializer.java | 14 +++-- .../hmcl/util/gson/ObservableField.java | 43 ++++++++++---- .../gson/RawPreservingObjectProperty.java | 57 +++++++++++++++++++ .../hmcl/util/gson/RawPreservingProperty.java | 32 +++++++++++ 5 files changed, 141 insertions(+), 26 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingObjectProperty.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingProperty.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index e1a3c5d74..10aa58dfc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -34,10 +34,7 @@ import org.hildan.fxgson.creators.ObservableSetCreator; import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; -import org.jackhuang.hmcl.util.gson.EnumOrdinalDeserializer; -import org.jackhuang.hmcl.util.gson.FileTypeAdapter; -import org.jackhuang.hmcl.util.gson.ObservableField; -import org.jackhuang.hmcl.util.gson.PaintAdapter; +import org.jackhuang.hmcl.util.gson.*; import org.jackhuang.hmcl.util.i18n.Locales; import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale; import org.jackhuang.hmcl.util.javafx.DirtyTracker; @@ -260,7 +257,7 @@ public final class Config implements Observable { } @SerializedName("commonDirType") - private final ObjectProperty commonDirType = new SimpleObjectProperty<>(EnumCommonDirectory.DEFAULT); + private final ObjectProperty commonDirType = new RawPreservingObjectProperty<>(EnumCommonDirectory.DEFAULT); public ObjectProperty commonDirTypeProperty() { return commonDirType; @@ -397,7 +394,7 @@ public final class Config implements Observable { } @SerializedName("backgroundType") - private final ObjectProperty backgroundImageType = new SimpleObjectProperty<>(EnumBackgroundImage.DEFAULT); + private final ObjectProperty backgroundImageType = new RawPreservingObjectProperty<>(EnumBackgroundImage.DEFAULT); public ObjectProperty backgroundImageTypeProperty() { return backgroundImageType; @@ -769,9 +766,7 @@ public final class Config implements Observable { for (var field : FIELDS) { Observable observable = field.get(config); if (config.tracker.isDirty(observable)) { - JsonElement serialized = field.serialize(config, context); - if (serialized != null && !serialized.isJsonNull()) - result.add(field.getSerializedName(), serialized); + field.serialize(result, config, context); } } config.unknownFields.forEach(result::add); @@ -791,6 +786,14 @@ public final class Config implements Observable { var values = new LinkedHashMap<>(json.getAsJsonObject().asMap()); for (ObservableField field : FIELDS) { JsonElement value = values.remove(field.getSerializedName()); + if (value == null) { + for (String alternateName : field.getAlternateNames()) { + value = values.remove(alternateName); + if (value != null) + break; + } + } + if (value != null) { config.tracker.markDirty(field.get(config)); field.deserialize(config, value, context); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/EnumOrdinalDeserializer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/EnumOrdinalDeserializer.java index d6bf65655..73f2027b3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/EnumOrdinalDeserializer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/EnumOrdinalDeserializer.java @@ -17,10 +17,7 @@ */ package org.jackhuang.hmcl.util.gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; +import com.google.gson.*; import com.google.gson.annotations.SerializedName; import java.lang.reflect.Type; @@ -57,7 +54,14 @@ public final class EnumOrdinalDeserializer> implements JsonDes @Override public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - return mapping.get(json.getAsString()); + if (json == null || json.isJsonNull()) + return null; + + String name = json.getAsString(); + T value = mapping.get(name); + if (value == null) + throw new JsonParseException("No enum constant with name " + name); + return value; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/ObservableField.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/ObservableField.java index 2a2abdcfd..1208600d3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/ObservableField.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/ObservableField.java @@ -17,10 +17,7 @@ */ package org.jackhuang.hmcl.util.gson; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonSerializationContext; +import com.google.gson.*; import com.google.gson.annotations.SerializedName; import javafx.beans.Observable; import javafx.beans.property.ListProperty; @@ -120,7 +117,7 @@ public abstract class ObservableField { return (Observable) varHandle.get(value); } - public abstract JsonElement serialize(T value, JsonSerializationContext context); + public abstract void serialize(JsonObject result, T value, JsonSerializationContext context); public abstract void deserialize(T value, JsonElement element, JsonDeserializationContext context); @@ -133,14 +130,36 @@ public abstract class ObservableField { } @Override - public JsonElement serialize(T value, JsonSerializationContext context) { - return context.serialize(((Property) get(value)).getValue(), elementType); + public void serialize(JsonObject result, T value, JsonSerializationContext context) { + Property property = (Property) get(value); + + if (property instanceof RawPreservingProperty rawPreserving) { + JsonElement rawJson = rawPreserving.getRawJson(); + if (rawJson != null) { + result.add(getSerializedName(), rawJson); + return; + } + } + + JsonElement serialized = context.serialize(property.getValue(), elementType); + if (serialized != null && !serialized.isJsonNull()) + result.add(getSerializedName(), serialized); } @Override @SuppressWarnings({"unchecked", "rawtypes"}) public void deserialize(T value, JsonElement element, JsonDeserializationContext context) { - ((Property) get(value)).setValue(context.deserialize(element, elementType)); + Property property = (Property) get(value); + + try { + property.setValue(context.deserialize(element, elementType)); + } catch (Throwable e) { + if (property instanceof RawPreservingProperty) { + ((RawPreservingProperty) property).setRawJson(element); + } else { + throw e; + } + } } } @@ -158,8 +177,8 @@ public abstract class ObservableField { } @Override - public JsonElement serialize(T value, JsonSerializationContext context) { - return context.serialize(get(value), collectionType); + public void serialize(JsonObject result, T value, JsonSerializationContext context) { + result.add(getSerializedName(), context.serialize(get(value), collectionType)); } @SuppressWarnings({"unchecked"}) @@ -193,8 +212,8 @@ public abstract class ObservableField { } @Override - public JsonElement serialize(T value, JsonSerializationContext context) { - return context.serialize(get(value), mapType); + public void serialize(JsonObject result, T value, JsonSerializationContext context) { + result.add(getSerializedName(), context.serialize(get(value), mapType)); } @SuppressWarnings({"unchecked"}) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingObjectProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingObjectProperty.java new file mode 100644 index 000000000..8a1a92fe7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingObjectProperty.java @@ -0,0 +1,57 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.gson; + +import com.google.gson.JsonElement; +import javafx.beans.property.SimpleObjectProperty; +import org.jetbrains.annotations.Nullable; + +/// @author Glavo +public class RawPreservingObjectProperty extends SimpleObjectProperty implements RawPreservingProperty { + private JsonElement rawJson; + + public RawPreservingObjectProperty() { + } + + public RawPreservingObjectProperty(T initialValue) { + super(initialValue); + } + + public RawPreservingObjectProperty(Object bean, String name) { + super(bean, name); + } + + public RawPreservingObjectProperty(Object bean, String name, T initialValue) { + super(bean, name, initialValue); + } + + @Override + public void setRawJson(JsonElement value) { + this.rawJson = value; + } + + @Override + public @Nullable JsonElement getRawJson() { + return rawJson; + } + + @Override + protected void invalidated() { + this.rawJson = null; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingProperty.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingProperty.java new file mode 100644 index 000000000..977c99114 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/RawPreservingProperty.java @@ -0,0 +1,32 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.gson; + +import com.google.gson.JsonElement; +import javafx.beans.property.Property; +import org.jetbrains.annotations.Nullable; + +/// If a Property implementing this interface fails to deserialize a json object into a value, it will store the original JsonElement internally. +/// If the value does not change at runtime, the original JsonElement will be written back during serialization. +/// +/// @author Glavo +public interface RawPreservingProperty extends Property { + void setRawJson(JsonElement value); + + @Nullable JsonElement getRawJson(); +}