|
@@ -1,82 +1,66 @@
|
|
package me.lortseam.completeconfig.data;
|
|
package me.lortseam.completeconfig.data;
|
|
|
|
|
|
-import com.google.common.collect.MoreCollectors;
|
|
|
|
import lombok.AccessLevel;
|
|
import lombok.AccessLevel;
|
|
-import lombok.AllArgsConstructor;
|
|
|
|
import lombok.Getter;
|
|
import lombok.Getter;
|
|
-import lombok.Setter;
|
|
|
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
|
+import me.lortseam.completeconfig.api.ConfigEntry;
|
|
import me.lortseam.completeconfig.api.ConfigEntryContainer;
|
|
import me.lortseam.completeconfig.api.ConfigEntryContainer;
|
|
-import net.minecraft.client.resource.language.I18n;
|
|
|
|
|
|
+import me.lortseam.completeconfig.data.gui.TranslationIdentifier;
|
|
|
|
+import me.lortseam.completeconfig.exception.IllegalAnnotationParameterException;
|
|
|
|
+import me.lortseam.completeconfig.exception.IllegalAnnotationTargetException;
|
|
import net.minecraft.text.Text;
|
|
import net.minecraft.text.Text;
|
|
-import net.minecraft.text.TranslatableText;
|
|
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
import org.apache.logging.log4j.Logger;
|
|
|
|
+import org.spongepowered.configurate.CommentedConfigurationNode;
|
|
|
|
+import org.spongepowered.configurate.serialize.SerializationException;
|
|
|
|
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Field;
|
|
-import java.lang.reflect.InvocationTargetException;
|
|
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Method;
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
|
+import java.util.function.Consumer;
|
|
|
|
|
|
-public class Entry<T> {
|
|
|
|
|
|
+public class Entry<T> implements EntryAccessor<T>, DataPart<Field> {
|
|
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
- private static final Set<Entry> ENTRIES = new HashSet<>();
|
|
|
|
|
|
+ private static final Map<Field, EntryAccessor> ENTRIES = new HashMap<>();
|
|
|
|
|
|
- static Entry<?> of(Collection parent, String fieldName, Class<? extends ConfigEntryContainer> parentClass) {
|
|
|
|
|
|
+ static EntryAccessor<?> of(String fieldName, Class<? extends ConfigEntryContainer> parentClass) {
|
|
try {
|
|
try {
|
|
- Field field = parentClass.getDeclaredField(fieldName);
|
|
|
|
- if (!field.isAccessible()) {
|
|
|
|
- field.setAccessible(true);
|
|
|
|
- }
|
|
|
|
- return of(parent, field);
|
|
|
|
|
|
+ return of(parentClass.getDeclaredField(fieldName));
|
|
} catch (NoSuchFieldException e) {
|
|
} catch (NoSuchFieldException e) {
|
|
throw new RuntimeException(e);
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private static <T> Entry<T> of(Collection parent, Field field) {
|
|
|
|
- return ENTRIES.stream().filter(entry -> entry.field.equals(field)).collect(MoreCollectors.toOptional()).orElseGet(() -> {
|
|
|
|
- Entry<T> entry = new Entry<>(parent, field, (Class<T>) field.getType());
|
|
|
|
- ENTRIES.add(entry);
|
|
|
|
- return entry;
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- static <T> Entry<T> of(Collection parent, Field field, ConfigEntryContainer parentObject) {
|
|
|
|
- Entry<T> entry = of(parent, field);
|
|
|
|
- entry.parentObject = parentObject;
|
|
|
|
- entry.defaultValue = entry.getValue();
|
|
|
|
- return entry;
|
|
|
|
|
|
+ static EntryAccessor<?> of(Field field) {
|
|
|
|
+ return ENTRIES.computeIfAbsent(field, absentField -> new Draft<>(field));
|
|
}
|
|
}
|
|
|
|
|
|
- @Getter(AccessLevel.PACKAGE)
|
|
|
|
- private final Collection parent;
|
|
|
|
@Getter
|
|
@Getter
|
|
private final Field field;
|
|
private final Field field;
|
|
- @Setter(AccessLevel.PACKAGE)
|
|
|
|
- private String customID;
|
|
|
|
@Getter
|
|
@Getter
|
|
private final Class<T> type;
|
|
private final Class<T> type;
|
|
- private ConfigEntryContainer parentObject;
|
|
|
|
- @Getter
|
|
|
|
- private T defaultValue;
|
|
|
|
- @Setter(AccessLevel.PACKAGE)
|
|
|
|
- private String customTranslationKey;
|
|
|
|
- @Setter(AccessLevel.PACKAGE)
|
|
|
|
- private String[] customTooltipKeys;
|
|
|
|
|
|
+ private final ConfigEntryContainer parentObject;
|
|
|
|
+ private String customID;
|
|
@Getter
|
|
@Getter
|
|
- private final Extras<T> extras = new Extras<>(this);
|
|
|
|
- private final List<Listener> listeners = new ArrayList<>();
|
|
|
|
- @Setter(AccessLevel.PACKAGE)
|
|
|
|
|
|
+ private final T defaultValue;
|
|
|
|
+ private final TranslationIdentifier parentTranslation;
|
|
|
|
+ private TranslationIdentifier customTranslation;
|
|
|
|
+ private TranslationIdentifier[] customTooltipTranslations;
|
|
private boolean forceUpdate;
|
|
private boolean forceUpdate;
|
|
- @Setter(AccessLevel.PACKAGE)
|
|
|
|
private boolean requiresRestart;
|
|
private boolean requiresRestart;
|
|
|
|
+ @Getter
|
|
|
|
+ private final Extras<T> extras = new Extras<>(this);
|
|
|
|
+ private final List<Listener<T>> listeners = new ArrayList<>();
|
|
|
|
|
|
- private Entry(Collection parent, Field field, Class<T> type) {
|
|
|
|
- this.parent = parent;
|
|
|
|
|
|
+ private Entry(Field field, Class<T> type, ConfigEntryContainer parentObject, TranslationIdentifier parentTranslation) {
|
|
this.field = field;
|
|
this.field = field;
|
|
this.type = type;
|
|
this.type = type;
|
|
|
|
+ this.parentObject = parentObject;
|
|
|
|
+ defaultValue = getValue();
|
|
|
|
+ this.parentTranslation = parentTranslation;
|
|
}
|
|
}
|
|
|
|
|
|
public T getValue() {
|
|
public T getValue() {
|
|
@@ -120,81 +104,183 @@ public class Entry<T> {
|
|
}
|
|
}
|
|
|
|
|
|
private void set(T value) {
|
|
private void set(T value) {
|
|
- if (listeners.stream().noneMatch(listener -> listener.parentObject == parentObject) || forceUpdate) {
|
|
|
|
|
|
+ if (listeners.stream().noneMatch(listener -> listener.getParentObject() == parentObject) || forceUpdate) {
|
|
try {
|
|
try {
|
|
field.set(parentObject, value);
|
|
field.set(parentObject, value);
|
|
} catch (IllegalAccessException e) {
|
|
} catch (IllegalAccessException e) {
|
|
throw new RuntimeException(e);
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- for (Listener listener : listeners) {
|
|
|
|
- try {
|
|
|
|
- listener.method.invoke(listener.parentObject, value);
|
|
|
|
- } catch (IllegalAccessException | InvocationTargetException e) {
|
|
|
|
- throw new RuntimeException(e);
|
|
|
|
- }
|
|
|
|
|
|
+ for (Listener<T> listener : listeners) {
|
|
|
|
+ listener.invoke(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void addListener(Method method, ConfigEntryContainer parentObject) {
|
|
void addListener(Method method, ConfigEntryContainer parentObject) {
|
|
- listeners.add(new Listener(method, parentObject));
|
|
|
|
|
|
+ listeners.add(new Listener<>(method, parentObject));
|
|
}
|
|
}
|
|
|
|
|
|
String getID() {
|
|
String getID() {
|
|
return customID != null ? customID : field.getName();
|
|
return customID != null ? customID : field.getName();
|
|
}
|
|
}
|
|
|
|
|
|
- String getTranslationKey() {
|
|
|
|
- return customTranslationKey != null ? customTranslationKey : parent.getTranslationKey() + "." + getID();
|
|
|
|
|
|
+ TranslationIdentifier getTranslation() {
|
|
|
|
+ return customTranslation != null ? customTranslation : parentTranslation.append(getID());
|
|
}
|
|
}
|
|
|
|
|
|
public Text getText() {
|
|
public Text getText() {
|
|
- return new TranslatableText(getTranslationKey());
|
|
|
|
|
|
+ return getTranslation().translate();
|
|
}
|
|
}
|
|
|
|
|
|
public Optional<Text[]> getTooltip() {
|
|
public Optional<Text[]> getTooltip() {
|
|
- String[] keys = null;
|
|
|
|
- if (customTooltipKeys != null) {
|
|
|
|
- keys = customTooltipKeys;
|
|
|
|
|
|
+ TranslationIdentifier[] translations = null;
|
|
|
|
+ if (customTooltipTranslations != null) {
|
|
|
|
+ translations = customTooltipTranslations;
|
|
} else {
|
|
} else {
|
|
- String defaultTooltipTranslationKey = getTranslationKey() + ".tooltip";
|
|
|
|
- if (I18n.hasTranslation(defaultTooltipTranslationKey)) {
|
|
|
|
- keys = new String[] {defaultTooltipTranslationKey};
|
|
|
|
|
|
+ TranslationIdentifier defaultTooltipTranslation = getTranslation().append("tooltip");
|
|
|
|
+ if (defaultTooltipTranslation.exists()) {
|
|
|
|
+ translations = new TranslationIdentifier[] {defaultTooltipTranslation};
|
|
} else {
|
|
} else {
|
|
- List<String> defaultTooltipTranslationKeys = new ArrayList<>();
|
|
|
|
|
|
+ List<TranslationIdentifier> defaultTooltipTranslations = new ArrayList<>();
|
|
for(int i = 0;; i++) {
|
|
for(int i = 0;; i++) {
|
|
- String key = defaultTooltipTranslationKey + "." + i;
|
|
|
|
- if(I18n.hasTranslation(key)) {
|
|
|
|
- defaultTooltipTranslationKeys.add(key);
|
|
|
|
|
|
+ TranslationIdentifier key = defaultTooltipTranslation.append(Integer.toString(i));
|
|
|
|
+ if(key.exists()) {
|
|
|
|
+ defaultTooltipTranslations.add(key);
|
|
} else {
|
|
} else {
|
|
- if (!defaultTooltipTranslationKeys.isEmpty()) {
|
|
|
|
- keys = defaultTooltipTranslationKeys.toArray(new String[0]);
|
|
|
|
|
|
+ if (!defaultTooltipTranslations.isEmpty()) {
|
|
|
|
+ translations = defaultTooltipTranslations.toArray(new TranslationIdentifier[0]);
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return keys != null ? Optional.of(Arrays.stream(keys).map(TranslatableText::new).toArray(Text[]::new)) : Optional.empty();
|
|
|
|
|
|
+ return translations != null ? Optional.of(Arrays.stream(translations).map(TranslationIdentifier::translate).toArray(Text[]::new)) : Optional.empty();
|
|
}
|
|
}
|
|
|
|
|
|
- <N extends Number> void setBounds(N min, N max, boolean slider) {
|
|
|
|
- extras.setBounds(min, max, slider);
|
|
|
|
|
|
+ public boolean requiresRestart() {
|
|
|
|
+ return requiresRestart;
|
|
}
|
|
}
|
|
|
|
|
|
- void setEnumOptions(EnumOptions.DisplayType displayType) {
|
|
|
|
- extras.setEnumOptions(displayType);
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void connect(Consumer<Entry<T>> modifier) {
|
|
|
|
+ modifier.accept(this);
|
|
}
|
|
}
|
|
|
|
|
|
- public boolean requiresRestart() {
|
|
|
|
- return requiresRestart;
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void resolve(Field field) {
|
|
|
|
+ if (field.isAnnotationPresent(ConfigEntry.class)) {
|
|
|
|
+ ConfigEntry annotation = field.getDeclaredAnnotation(ConfigEntry.class);
|
|
|
|
+ String id = annotation.value();
|
|
|
|
+ if (!StringUtils.isBlank(id)) {
|
|
|
|
+ customID = id;
|
|
|
|
+ }
|
|
|
|
+ String customTranslationKey = annotation.customTranslationKey();
|
|
|
|
+ if (!StringUtils.isBlank(customTranslationKey)) {
|
|
|
|
+ customTranslation = parentTranslation.getModTranslation().appendKey(customTranslationKey);
|
|
|
|
+ }
|
|
|
|
+ String[] customTooltipKeys = annotation.customTooltipKeys();
|
|
|
|
+ if (customTooltipKeys.length > 0) {
|
|
|
|
+ if (Arrays.stream(customTooltipKeys).anyMatch(StringUtils::isBlank)) {
|
|
|
|
+ throw new IllegalAnnotationParameterException("Entry tooltip key(s) must not be blank");
|
|
|
|
+ }
|
|
|
|
+ customTooltipTranslations = Arrays.stream(customTooltipKeys).map(key -> parentTranslation.getModTranslation().appendKey(key)).toArray(TranslationIdentifier[]::new);
|
|
|
|
+ }
|
|
|
|
+ forceUpdate = annotation.forceUpdate();
|
|
|
|
+ requiresRestart = annotation.requiresRestart();
|
|
|
|
+ }
|
|
|
|
+ if (field.isAnnotationPresent(ConfigEntry.Bounded.Integer.class)) {
|
|
|
|
+ if (field.getType() != int.class && field.getType() != Integer.class) {
|
|
|
|
+ throw new IllegalAnnotationTargetException("Cannot apply Integer bound to non Integer field " + field);
|
|
|
|
+ }
|
|
|
|
+ ConfigEntry.Bounded.Integer bounds = field.getDeclaredAnnotation(ConfigEntry.Bounded.Integer.class);
|
|
|
|
+ extras.setBounds(bounds.min(), bounds.max(), bounds.slider());
|
|
|
|
+ }
|
|
|
|
+ if (field.isAnnotationPresent(ConfigEntry.Bounded.Long.class)) {
|
|
|
|
+ if (field.getType() != long.class && field.getType() != Long.class) {
|
|
|
|
+ throw new IllegalAnnotationTargetException("Cannot apply Long bound to non Long field " + field);
|
|
|
|
+ }
|
|
|
|
+ ConfigEntry.Bounded.Long bounds = field.getDeclaredAnnotation(ConfigEntry.Bounded.Long.class);
|
|
|
|
+ extras.setBounds(bounds.min(), bounds.max(), bounds.slider());
|
|
|
|
+ }
|
|
|
|
+ if (field.isAnnotationPresent(ConfigEntry.Bounded.Float.class)) {
|
|
|
|
+ if (field.getType() != float.class && field.getType() != Float.class) {
|
|
|
|
+ throw new IllegalAnnotationTargetException("Cannot apply Float bound to non Float field " + field);
|
|
|
|
+ }
|
|
|
|
+ ConfigEntry.Bounded.Float bounds = field.getDeclaredAnnotation(ConfigEntry.Bounded.Float.class);
|
|
|
|
+ extras.setBounds(bounds.min(), bounds.max(), false);
|
|
|
|
+ }
|
|
|
|
+ if (field.isAnnotationPresent(ConfigEntry.Bounded.Double.class)) {
|
|
|
|
+ if (field.getType() != double.class && field.getType() != Double.class) {
|
|
|
|
+ throw new IllegalAnnotationTargetException("Cannot apply Double bound to non Double field " + field);
|
|
|
|
+ }
|
|
|
|
+ ConfigEntry.Bounded.Double bounds = field.getDeclaredAnnotation(ConfigEntry.Bounded.Double.class);
|
|
|
|
+ extras.setBounds(bounds.min(), bounds.max(), false);
|
|
|
|
+ }
|
|
|
|
+ if (Enum.class.isAssignableFrom(field.getType())) {
|
|
|
|
+ if (field.isAnnotationPresent(ConfigEntry.EnumOptions.class)) {
|
|
|
|
+ extras.setEnumOptions(field.getDeclaredAnnotation(ConfigEntry.EnumOptions.class).displayType());
|
|
|
|
+ } else {
|
|
|
|
+ extras.setEnumOptions(EnumOptions.DisplayType.DEFAULT);
|
|
|
|
+ }
|
|
|
|
+ } else if (field.isAnnotationPresent(ConfigEntry.EnumOptions.class)) {
|
|
|
|
+ throw new IllegalAnnotationTargetException("Cannot apply enum options to non enum field " + field);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void apply(CommentedConfigurationNode node) {
|
|
|
|
+ try {
|
|
|
|
+ setValue(node.get(type));
|
|
|
|
+ } catch (SerializationException e) {
|
|
|
|
+ //TODO
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void fetch(CommentedConfigurationNode node) {
|
|
|
|
+ try {
|
|
|
|
+ //TODO: Fails when setting enum value
|
|
|
|
+ node.set(type, getValue());
|
|
|
|
+ } catch (SerializationException e) {
|
|
|
|
+ //TODO
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- @AllArgsConstructor
|
|
|
|
- private static class Listener {
|
|
|
|
|
|
+ @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
|
|
|
+ static class Draft<T> implements EntryAccessor<T> {
|
|
|
|
|
|
- private final Method method;
|
|
|
|
- private final ConfigEntryContainer parentObject;
|
|
|
|
|
|
+ static <T> Draft<T> of(Field field) {
|
|
|
|
+ EntryAccessor<T> accessor = (EntryAccessor<T>) Entry.of(field);
|
|
|
|
+ if (!(accessor instanceof Draft)) {
|
|
|
|
+ throw new UnsupportedOperationException("Entry draft of " + field + " was already built");
|
|
|
|
+ }
|
|
|
|
+ return (Draft<T>) accessor;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private final Field field;
|
|
|
|
+ private final List<Consumer<Entry<T>>> modifiers = new ArrayList<>();
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public Class<T> getType() {
|
|
|
|
+ return (Class<T>) field.getType();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void connect(Consumer<Entry<T>> modifier) {
|
|
|
|
+ modifiers.add(modifier);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Entry<T> build(ConfigEntryContainer parentObject, TranslationIdentifier parentTranslation) {
|
|
|
|
+ Entry<T> entry = new Entry<>(field, getType(), parentObject, parentTranslation);
|
|
|
|
+ for (Consumer<Entry<T>> modifier : modifiers) {
|
|
|
|
+ modifier.accept(entry);
|
|
|
|
+ }
|
|
|
|
+ ENTRIES.put(field, entry);
|
|
|
|
+ return entry;
|
|
|
|
+ }
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|