diff options
| author | mdxd44 <ogurec332@mail.ru> | 2021-12-17 19:31:55 +0900 |
|---|---|---|
| committer | mdxd44 <ogurec332@mail.ru> | 2021-12-17 19:31:55 +0900 |
| commit | cff1b4a22bb47c8bcf064d5e8da8c7d7ef67ea52 (patch) | |
| tree | 9fc8614b144288af2749c2376c8ca523cd2a0cc0 /src/main/java/net/elytrium/limboauth/config | |
| parent | f11b09654cc33f4c3d9239c04be5978cbe3cad2d (diff) | |
| download | LimboAuth-cff1b4a22bb47c8bcf064d5e8da8c7d7ef67ea52.tar.gz LimboAuth-cff1b4a22bb47c8bcf064d5e8da8c7d7ef67ea52.tar.bz2 LimboAuth-cff1b4a22bb47c8bcf064d5e8da8c7d7ef67ea52.zip | |
Split projects.
Diffstat (limited to 'src/main/java/net/elytrium/limboauth/config')
| -rw-r--r-- | src/main/java/net/elytrium/limboauth/config/Config.java | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/src/main/java/net/elytrium/limboauth/config/Config.java b/src/main/java/net/elytrium/limboauth/config/Config.java new file mode 100644 index 0000000..ed5b007 --- /dev/null +++ b/src/main/java/net/elytrium/limboauth/config/Config.java @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2021 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package net.elytrium.limboauth.config; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import net.elytrium.limboauth.LimboAuth; +import org.slf4j.Logger; +import org.yaml.snakeyaml.Yaml; + +public class Config { + + private static final Logger LOGGER = LimboAuth.getInstance().getLogger(); + private String oldPrefix = ""; + private String currentPrefix = ""; + + /** + * Set the value of a specific node. Probably throws some error if you supply non-existing keys or invalid values. + * + * @param key config node + * @param value value + */ + private void set(String key, Object value, Class<?> root) { + String[] split = key.split("\\."); + Object instance = this.getInstance(split, root); + if (instance != null) { + Field field = this.getField(split, instance); + if (field != null) { + try { + if (field.getAnnotation(Final.class) != null) { + return; + } + if (field.getType() == String.class && !(value instanceof String)) { + value = value + ""; + } + field.set(instance, value); + return; + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + LOGGER.debug("Failed to set config option: " + key + ": " + value + " | " + instance + " | " + root.getSimpleName() + ".yml"); + } + + @SuppressWarnings("unchecked") + public void set(Map<String, Object> input, String oldPath) { + for (Map.Entry<String, Object> entry : input.entrySet()) { + String key = oldPath + (oldPath.isEmpty() ? "" : ".") + entry.getKey(); + Object value = entry.getValue(); + + if (value instanceof Map) { + this.set((Map<String, Object>) value, key); + } else if (value instanceof String) { + if (key.equalsIgnoreCase("prefix") && !this.currentPrefix.equals(value)) { + this.currentPrefix = (String) value; + } + + this.set(key, ((String) value).replace("{NL}", "\n").replace("{PRFX}", this.currentPrefix), this.getClass()); + } else { + this.set(key, value, this.getClass()); + } + } + } + + public boolean load(File file, String prefix) { + this.oldPrefix = this.currentPrefix.isEmpty() ? prefix : this.currentPrefix; + this.currentPrefix = prefix; + if (!file.exists()) { + return false; + } + + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) { + this.set(new Yaml().load(reader), ""); + } catch (IOException e) { + LOGGER.warn("Unable to load config ", e); + return false; + } + + return true; + } + + /** + * Indicates that a field should be instantiated / created. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + public @interface Create { + + } + + /** + * Indicates that a field cannot be modified. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + public @interface Final { + + } + + /** + * Creates a comment. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface Comment { + + String[] value(); + } + + /** + * Any field or class with is not part of the config. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface Ignore { + + } + + private String toYamlString(Object value, String spacing, String fieldName) { + if (value instanceof List) { + Collection<?> listValue = (Collection<?>) value; + if (listValue.isEmpty()) { + return "[]"; + } + StringBuilder m = new StringBuilder(); + for (Object obj : listValue) { + m.append(System.lineSeparator()).append(spacing).append("- ").append(this.toYamlString(obj, spacing, fieldName)); + } + + return m.toString(); + } + + if (value instanceof String) { + String stringValue = (String) value; + if (stringValue.isEmpty()) { + return "\"\""; + } + + String quoted = "\"" + stringValue + "\""; + if (fieldName.equalsIgnoreCase("prefix")) { + return quoted; + } else { + return quoted.replace("\n", "{NL}").replace(this.currentPrefix.equals(this.oldPrefix) ? this.oldPrefix : this.currentPrefix, "{PRFX}"); + } + } + + return value != null ? value.toString() : "null"; + } + + /** + * Set all values in the file (load first to avoid overwriting). + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") + public void save(File file) { + try { + if (!file.exists()) { + File parent = file.getParentFile(); + if (parent != null) { + file.getParentFile().mkdirs(); + } + file.createNewFile(); + } + + PrintWriter writer = new PrintWriter(file, StandardCharsets.UTF_8); + Object instance = this; + this.save(writer, this.getClass(), instance, 0); + writer.close(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private void save(PrintWriter writer, Class<?> clazz, final Object instance, int indent) { + try { + String lineSeparator = System.lineSeparator(); + String spacing = this.repeat(" ", indent); + + for (Field field : clazz.getFields()) { + if (field.getAnnotation(Ignore.class) != null) { + continue; + } + Class<?> current = field.getType(); + if (field.getAnnotation(Ignore.class) != null) { + continue; + } + + Comment comment = field.getAnnotation(Comment.class); + if (comment != null) { + for (String commentLine : comment.value()) { + writer.write(spacing + "# " + commentLine + lineSeparator); + } + } + + Create create = field.getAnnotation(Create.class); + if (create != null) { + Object value = field.get(instance); + this.setAccessible(field); + if (indent == 0) { + writer.write(lineSeparator); + } + comment = current.getAnnotation(Comment.class); + if (comment != null) { + for (String commentLine : comment.value()) { + writer.write(spacing + "# " + commentLine + lineSeparator); + } + } + writer.write(spacing + this.toNodeName(current.getSimpleName()) + ":" + lineSeparator); + if (value == null) { + field.set(instance, value = current.getDeclaredConstructor().newInstance()); + } + this.save(writer, current, value, indent + 2); + } else { + String value = this.toYamlString(field.get(instance), spacing, field.getName()); + writer.write(spacing + this.toNodeName(field.getName() + ": ") + value + lineSeparator); + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + + /** + * Get the field for a specific config node and instance. + * + * <p>As expiry can have multiple blocks there will be multiple instances + * + * @param split the node (split by period) + * @param instance the instance + */ + private Field getField(String[] split, Object instance) { + try { + Field field = instance.getClass().getField(this.toFieldName(split[split.length - 1])); + this.setAccessible(field); + return field; + } catch (Throwable ignored) { + LOGGER.debug("Invalid config field: " + this.join(split, ".") + " for " + this.toNodeName(instance.getClass().getSimpleName())); + return null; + } + } + + /** + * Get the instance for a specific config node. + * + * @param split the node (split by period) + * @return The instance or null + */ + private Object getInstance(String[] split, Class<?> root) { + try { + Class<?> clazz = root == null ? MethodHandles.lookup().lookupClass() : root; + Object instance = this; + while (split.length > 0) { + if (split.length == 1) { + return instance; + } else { + Class<?> found = null; + if (clazz == null) { + return null; + } + + Class<?>[] classes = clazz.getDeclaredClasses(); + for (Class<?> current : classes) { + if (Objects.equals(current.getSimpleName(), this.toFieldName(split[0]))) { + found = current; + break; + } + } + + if (found == null) { + return null; + } + + try { + Field instanceField = clazz.getDeclaredField(this.toFieldName(split[0])); + this.setAccessible(instanceField); + Object value = instanceField.get(instance); + if (value == null) { + value = found.getDeclaredConstructor().newInstance(); + instanceField.set(instance, value); + } + + clazz = found; + instance = value; + split = Arrays.copyOfRange(split, 1, split.length); + continue; + } catch (NoSuchFieldException e) { + // + } + + split = Arrays.copyOfRange(split, 1, split.length); + clazz = found; + instance = clazz.getDeclaredConstructor().newInstance(); + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + + return null; + } + + /** + * Translate a node to a java field name. + */ + private String toFieldName(String node) { + return node.toUpperCase(Locale.ROOT).replaceAll("-", "_"); + } + + /** + * Translate a field to a config node. + */ + private String toNodeName(String field) { + return field.toLowerCase(Locale.ROOT).replace("_", "-"); + } + + /** + * Set some field to be accessible. + */ + private void setAccessible(Field field) throws NoSuchFieldException, IllegalAccessException { + field.setAccessible(true); + if (Modifier.isFinal(field.getModifiers())) { + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } + } + + @SuppressWarnings("SameParameterValue") + private String repeat(String s, int n) { + return IntStream.range(0, n).mapToObj(i -> s).collect(Collectors.joining()); + } + + @SuppressWarnings("SameParameterValue") + private String join(Object[] array, String delimiter) { + switch (array.length) { + case 0: { + return ""; + } + case 1: { + return array[0].toString(); + } + default: { + final StringBuilder result = new StringBuilder(); + for (int i = 0, j = array.length; i < j; ++i) { + if (i > 0) { + result.append(delimiter); + } + result.append(array[i]); + } + + return result.toString(); + } + } + } +} |
