Rewrite config properties

This commit is contained in:
yushijinhun
2018-11-23 21:01:45 +08:00
parent a4e4782f2f
commit 1b40916046
2 changed files with 172 additions and 24 deletions

View File

@@ -40,13 +40,12 @@ import org.jackhuang.hmcl.util.gson.FileTypeAdapter;
import org.jackhuang.hmcl.util.i18n.Locales; import org.jackhuang.hmcl.util.i18n.Locales;
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale; import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
import org.jackhuang.hmcl.util.javafx.ObservableHelper; import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
import java.io.File; import java.io.File;
import java.lang.reflect.Modifier;
import java.net.Proxy; import java.net.Proxy;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.stream.Stream;
public final class Config implements Cloneable, Observable { public final class Config implements Cloneable, Observable {
@@ -68,10 +67,9 @@ public final class Config implements Cloneable, Observable {
.create(); .create();
public static Config fromJson(String json) throws JsonParseException { public static Config fromJson(String json) throws JsonParseException {
Config instance = CONFIG_GSON.fromJson(json, Config.class); Config loaded = CONFIG_GSON.fromJson(json, Config.class);
// Gson will replace the property fields (even they are final!) Config instance = new Config();
// So we have to add the listeners again after deserialization PropertyUtils.copyProperties(loaded, instance);
instance.addListenerToProperties();
return instance; return instance;
} }
@@ -166,24 +164,7 @@ public final class Config implements Cloneable, Observable {
private transient ObservableHelper helper = new ObservableHelper(this); private transient ObservableHelper helper = new ObservableHelper(this);
public Config() { public Config() {
addListenerToProperties(); PropertyUtils.attachListener(this, helper);
}
private void addListenerToProperties() {
Stream.of(getClass().getDeclaredFields())
.filter(it -> {
int modifiers = it.getModifiers();
return !Modifier.isTransient(modifiers) && !Modifier.isStatic(modifiers);
})
.filter(it -> Observable.class.isAssignableFrom(it.getType()))
.map(it -> {
try {
return (Observable) it.get(this);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Failed to get my own properties");
}
})
.forEach(helper::receiveUpdatesFrom);
} }
@Override @Override

View File

@@ -0,0 +1,167 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.util.javafx;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.Property;
import javafx.beans.value.WritableValue;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
public final class PropertyUtils {
private PropertyUtils() {
}
public static class PropertyHandle {
public final WritableValue<Object> accessor;
public final Observable observable;
public PropertyHandle(WritableValue<Object> accessor, Observable observable) {
this.accessor = accessor;
this.observable = observable;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Map<String, Function<Object, PropertyHandle>> getPropertyHandleFactories(Class<?> type) {
Map<String, Method> collectionGetMethods = new LinkedHashMap<>();
Map<String, Method> propertyMethods = new LinkedHashMap<>();
for (Method method : type.getMethods()) {
Class<?> returnType = method.getReturnType();
if (method.getParameterCount() == 0
&& !returnType.equals(void.class)) {
String name = method.getName();
if (name.endsWith("Property")) {
String propertyName = name.substring(0, name.length() - "Property".length());
if (!propertyName.isEmpty() && Property.class.isAssignableFrom(returnType)) {
propertyMethods.put(propertyName, method);
}
} else if (name.startsWith("get")) {
String propertyName = name.substring("get".length());
if (!propertyName.isEmpty() &&
(ObservableList.class.isAssignableFrom(returnType)
|| ObservableSet.class.isAssignableFrom(returnType)
|| ObservableMap.class.isAssignableFrom(returnType))) {
propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
collectionGetMethods.put(propertyName, method);
}
}
}
}
propertyMethods.keySet().forEach(collectionGetMethods::remove);
Map<String, Function<Object, PropertyHandle>> result = new LinkedHashMap<>();
propertyMethods.forEach((propertyName, method) -> {
result.put(propertyName, instance -> {
Property returnValue;
try {
returnValue = (Property<?>) method.invoke(instance);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
return new PropertyHandle(returnValue, returnValue);
});
});
collectionGetMethods.forEach((propertyName, method) -> {
result.put(propertyName, instance -> {
Object returnValue;
try {
returnValue = method.invoke(instance);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
WritableValue<Object> accessor;
if (returnValue instanceof ObservableList) {
accessor = new WritableValue<Object>() {
@Override
public Object getValue() {
return returnValue;
}
@Override
public void setValue(Object value) {
((ObservableList) returnValue).setAll((List) value);
}
};
} else if (returnValue instanceof ObservableSet) {
accessor = new WritableValue<Object>() {
@Override
public Object getValue() {
return returnValue;
}
@Override
public void setValue(Object value) {
ObservableSet target = (ObservableSet) returnValue;
target.clear();
target.addAll((Set) value);
}
};
} else if (returnValue instanceof ObservableMap) {
accessor = new WritableValue<Object>() {
@Override
public Object getValue() {
return returnValue;
}
@Override
public void setValue(Object value) {
ObservableMap target = (ObservableMap) returnValue;
target.clear();
target.putAll((Map) value);
}
};
} else {
throw new IllegalStateException();
}
return new PropertyHandle(accessor, (Observable) returnValue);
});
});
return result;
}
public static void copyProperties(Object from, Object to) {
Class<?> type = from.getClass();
while (!type.isInstance(to))
type = type.getSuperclass();
getPropertyHandleFactories(type)
.forEach((name, factory) -> {
PropertyHandle src = factory.apply(from);
PropertyHandle target = factory.apply(to);
target.accessor.setValue(src.accessor.getValue());
});
}
public static void attachListener(Object instance, InvalidationListener listener) {
getPropertyHandleFactories(instance.getClass())
.forEach((name, factory) -> {
factory.apply(instance).observable.addListener(listener);
});
}
}