Rewrite config properties
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user