From 26f4e0746bc19eb070c3c558599ede707f609f42 Mon Sep 17 00:00:00 2001 From: Paul Reilly Date: Mon, 28 Aug 2023 21:52:40 -0500 Subject: [PATCH] Replace TOML wrapper with generic support # Changes: - Removed specific TOML wrapper in favor of GenericConfiguration. - Added ConfigType enum to define configuration formats, parsers, and writers for TOML and JSON. - Created FileUtils class containing useful file and directory creation methods - Added @ApiStatus.Internal to both the BukkitDelegate class and Completions annotation to specify that they should not be used externally. --- .../fns/patchwork/command/BukkitDelegate.java | 4 + .../command/annotation/Completions.java | 2 + .../java/fns/patchwork/config/ConfigType.java | 88 +++++++ .../config/GenericConfiguration.java | 224 ++++++++++++++++++ .../config/WrappedTomlConfiguration.java | 184 -------------- .../java/fns/patchwork/utils/FileUtils.java | 129 ++++++++++ 6 files changed, 447 insertions(+), 184 deletions(-) create mode 100644 Patchwork/src/main/java/fns/patchwork/config/ConfigType.java create mode 100644 Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java delete mode 100644 Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java create mode 100644 Patchwork/src/main/java/fns/patchwork/utils/FileUtils.java diff --git a/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java b/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java index 8e8c1fa..01162c6 100644 --- a/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java +++ b/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java @@ -43,6 +43,7 @@ import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; /** @@ -60,6 +61,8 @@ import org.jetbrains.annotations.NotNull; *
* This class is not meant to be used outside Patchwork. */ +@ApiStatus.Internal +@ApiStatus.NonExtendable public final class BukkitDelegate extends Command implements PluginIdentifiableCommand { private final JavaPlugin plugin; @@ -274,3 +277,4 @@ public final class BukkitDelegate extends Command implements PluginIdentifiableC return this.plugin; } } + diff --git a/Patchwork/src/main/java/fns/patchwork/command/annotation/Completions.java b/Patchwork/src/main/java/fns/patchwork/command/annotation/Completions.java index 54f2a30..20be942 100644 --- a/Patchwork/src/main/java/fns/patchwork/command/annotation/Completions.java +++ b/Patchwork/src/main/java/fns/patchwork/command/annotation/Completions.java @@ -27,6 +27,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.jetbrains.annotations.ApiStatus; /** * A marker interface which represents a holder for multiple {@link Completion} annotations. @@ -36,6 +37,7 @@ import java.lang.annotation.Target; */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@ApiStatus.Internal public @interface Completions { /** diff --git a/Patchwork/src/main/java/fns/patchwork/config/ConfigType.java b/Patchwork/src/main/java/fns/patchwork/config/ConfigType.java new file mode 100644 index 0000000..1a77e45 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/config/ConfigType.java @@ -0,0 +1,88 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.patchwork.config; + +import com.electronwill.nightconfig.core.Config; +import com.electronwill.nightconfig.core.ConfigFormat; +import com.electronwill.nightconfig.core.io.ConfigParser; +import com.electronwill.nightconfig.core.io.ConfigWriter; +import com.electronwill.nightconfig.json.FancyJsonWriter; +import com.electronwill.nightconfig.json.JsonFormat; +import com.electronwill.nightconfig.json.JsonParser; +import com.electronwill.nightconfig.json.MinimalJsonWriter; +import com.electronwill.nightconfig.toml.TomlFormat; +import com.electronwill.nightconfig.toml.TomlParser; +import com.electronwill.nightconfig.toml.TomlWriter; + +public enum ConfigType +{ + TOML(TomlFormat.instance(), + ".toml", + new TomlWriter(), + new TomlParser()), + JSON(JsonFormat.minimalInstance(), + ".json", + new MinimalJsonWriter(), + new JsonParser()), + JSON_FANCY(JsonFormat.fancyInstance(), + ".json", + new FancyJsonWriter(), + new JsonParser()); + + private final ConfigFormat format; + private final String fileExtension; + private final ConfigWriter writer; + private final ConfigParser parser; + + ConfigType(final ConfigFormat format, + final String fileExtension, + final ConfigWriter writer, + final ConfigParser parser) + { + this.format = format; + this.fileExtension = fileExtension; + this.writer = writer; + this.parser = parser; + } + + public ConfigFormat getFormat() + { + return this.format; + } + + public String getExtension() + { + return this.fileExtension; + } + + public ConfigWriter getWriter() + { + return this.writer; + } + + public ConfigParser getParser() + { + return this.parser; + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java new file mode 100644 index 0000000..410e791 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java @@ -0,0 +1,224 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.patchwork.config; + +import com.electronwill.nightconfig.core.Config; +import com.electronwill.nightconfig.core.ConfigFormat; +import com.electronwill.nightconfig.core.UnmodifiableConfig; +import fns.patchwork.utils.FileUtils; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +public final class GenericConfiguration implements Configuration +{ + private final File configFile; + private final String fileName; + private final Config config; + private final ConfigType configType; + + public GenericConfiguration(@NotNull final ConfigType configType, + @Nullable final JavaPlugin plugin, + @NotNull final File dataFolder, + @NotNull final String fileName, + final boolean isConcurrent) throws IOException + { + if (!fileName.endsWith(configType.getExtension())) + throw new IllegalArgumentException("File name must end with " + configType.getExtension() + "!"); + + // Ternary just to piss off Allink :) + final Optional file = (plugin != null) ? + FileUtils.getOrCreateFileWithResource(dataFolder, fileName, plugin) : + FileUtils.getOrCreateFile(dataFolder, fileName); + + if (file.isEmpty()) + throw new FileNotFoundException(); + + this.configFile = file.get(); + this.fileName = fileName; + this.configType = configType; + + final ConfigFormat format = configType.getFormat(); + + // Another ternary just to piss off Allink :) + this.config = isConcurrent ? format.createConcurrentConfig() : format.createConfig(); + + this.load(); + } + + public GenericConfiguration(final ConfigType type, final File dataFolder, final String fileName) + throws IOException + { + this(type, null, dataFolder, fileName, false); + } + + public GenericConfiguration(final ConfigType type, final JavaPlugin plugin, final String fileName) + throws IOException + { + this(type, plugin, plugin.getDataFolder(), fileName, false); + } + + public GenericConfiguration(final ConfigType type, final File dataFolder, final String fileName, + final boolean isConcurrent) + throws IOException + { + this(type, null, dataFolder, fileName, isConcurrent); + } + + @Override + public void save() throws IOException + { + final File backup = new File(this.configFile.getParentFile(), this.fileName + ".bak"); + + if (backup.exists()) + Files.delete(backup.toPath()); + + Files.copy(this.configFile.toPath(), backup.toPath()); + + try (final FileWriter writer = new FileWriter(this.configFile)) + { + this.configType.getWriter().write(this.getConfig(), writer); + } + } + + @Override + public void load() throws IOException { + try (final FileReader reader = new FileReader(this.configFile)) { + this.config.clear(); + + final UnmodifiableConfig parsed = this.configType.getParser().parse(reader).unmodifiable(); + this.config.putAll(parsed); + } + } + + @Override + public String getFileName() + { + return fileName; + } + + @Override + public File getConfigurationFile() + { + return configFile; + } + + @Override + public String getString(final String path) + { + if (!(this.getConfig().get(path) instanceof String)) + throw new IllegalArgumentException(String.format("Value at path %s is not a string!", path)); + + return this.getConfig().get(path); + } + + @Override + public boolean getBoolean(String path) + { + if (!(this.getConfig().get(path) instanceof Boolean)) + throw new IllegalArgumentException(String.format("Value at path %s is not a boolean!", path)); + + return this.getConfig().get(path); + } + + @Override + @ApiStatus.Internal + public @Unmodifiable List getList(String path, Class clazz) + { + // TODO: Figure out how to parse lists with Night Config. + + return new ArrayList<>(); + } + + @Override + @ApiStatus.Internal + public @Unmodifiable List getStringList(String path) + { + // TODO: Figure out how to parse lists with Night Config. + + return new ArrayList<>(); + } + + @Override + public int getInt(String path) + { + return this.getConfig().getInt(path); + } + + @Override + public long getLong(String path) + { + return this.getConfig().getLong(path); + } + + @Override + public double getDouble(String path) + { + if (!(this.getConfig().get(path) instanceof Double)) + throw new IllegalArgumentException(String.format("Value at path %s is not a double!", path)); + + return this.getConfig().get(path); + } + + @Override + public Optional get(String path, Class clazz) + { + // I love ternary statements, sorry Allink :) + return clazz.isInstance(this.getConfig().get(path)) ? + Optional.of(clazz.cast(this.getConfig().get(path))) : + Optional.empty(); + } + + @Override + public T getOrDefault(String path, Class clazz, T fallback) + { + return this.get(path, clazz).orElse(fallback); + } + + @Override + public void set(final String path, final T value) { + this.config.set(path, value); + } + + private UnmodifiableConfig getConfig() + { + return config.unmodifiable(); + } + + public ConfigType getConfigType() + { + return configType; + } +} diff --git a/Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java deleted file mode 100644 index 585ed4e..0000000 --- a/Patchwork/src/main/java/fns/patchwork/config/WrappedTomlConfiguration.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * This file is part of Freedom-Network-Suite - https://github.com/AtlasMediaGroup/Freedom-Network-Suite - * Copyright (C) 2023 Total Freedom Server Network and contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fns.patchwork.config; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.Unmodifiable; -import org.tomlj.Toml; -import org.tomlj.TomlParseResult; - -// TODO: Finish implementation -public class WrappedTomlConfiguration implements Configuration -{ - private final Map previousValues = new HashMap<>(); - private final TomlParseResult toml; - private final File file; - - - public WrappedTomlConfiguration(final JavaPlugin plugin, final File file) throws IOException - { - if (!file.exists() && file.createNewFile()) - { - plugin.saveResource(file.getName(), true); - } - - this.toml = Toml.parse(Path.of(file.toURI())); - this.file = file; - } - - @Override - public void save() throws IOException - { - // Create a backup file - final File backup = new File(this.file.getParentFile(), this.file.getName() + ".bak"); - if (backup.exists() && !Files.deleteIfExists(Path.of(backup.toURI()))) - { - throw new IOException("Failed to delete existing backup file: " + backup.getName()); - } - - // Serialize the current configuration to a temporary file - final File tempFile = new File(this.file.getParentFile(), this.file.getName() + ".temp"); - try (FileWriter tempFileWriter = new FileWriter(tempFile)) - { - // Convert the updated TomlTable to TOML format and write it to the temporary file - String tomlString = this.toml.toToml(); - tempFileWriter.write(tomlString); - } - - // Compare the new configuration with the previous one - TomlParseResult newToml = Toml.parse(Path.of(tempFile.toURI())); - for (Map.Entry entry : newToml.toMap().entrySet()) - { - String key = entry.getKey(); - Object newValue = entry.getValue(); - Object oldValue = previousValues.get(key); - - if (oldValue == null || !oldValue.equals(newValue)) - { - // Value has changed, update it - this.toml.toMap().replace(key, newValue); - previousValues.put(key, newValue); - } - } - - // Save the updated configuration to the original file - try (FileWriter fileWriter = new FileWriter(this.file)) - { - // Convert the updated TomlTable to TOML format and write it to the original file - String tomlString = this.toml.toToml(); - fileWriter.write(tomlString); - } - - // Delete the temporary file and the backup file - Files.delete(Path.of(tempFile.toURI())); - Files.delete(Path.of(backup.toURI())); - } - - @Override - public void load() throws IOException - { - // TODO: Implement - } - - @Override - public String getFileName() - { - return null; - } - - @Override - public File getConfigurationFile() - { - return null; - } - - @Override - public String getString(String path) - { - return null; - } - - @Override - public boolean getBoolean(String path) - { - return false; - } - - @Override - public @Unmodifiable List getList(String path, Class clazz) - { - return null; - } - - @Override - public @Unmodifiable List getStringList(String path) - { - return null; - } - - @Override - public int getInt(String path) - { - return 0; - } - - @Override - public long getLong(String path) - { - return 0; - } - - @Override - public double getDouble(String path) - { - return 0; - } - - @Override - public void set(String path, T value) - { - // TODO: Implement - } - - @Override - public Optional get(String path, Class clazz) - { - return Optional.empty(); - } - - @Override - public T getOrDefault(String path, Class clazz, T fallback) - { - return null; - } -} diff --git a/Patchwork/src/main/java/fns/patchwork/utils/FileUtils.java b/Patchwork/src/main/java/fns/patchwork/utils/FileUtils.java new file mode 100644 index 0000000..0f62b70 --- /dev/null +++ b/Patchwork/src/main/java/fns/patchwork/utils/FileUtils.java @@ -0,0 +1,129 @@ +/* + * This file is part of FreedomNetworkSuite - https://github.com/SimplexDevelopment/FreedomNetworkSuite + * Copyright (C) 2023 Simplex Development and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fns.patchwork.utils; + +import fns.patchwork.utils.logging.FNS4J; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public final class FileUtils +{ + @NonNls + private static final String CREATED_DIRECTORY = "Created directory "; + + private FileUtils() + { + throw new AssertionError(); + } + + public static String getExtension(@NotNull final File file) + { + return file.getName() + .substring(file.getName() + .lastIndexOf('.')); + } + + public static Optional getOrCreateDirectory(final File parentDirectory, final String directoryName) + { + if (parentDirectory.mkdirs()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + parentDirectory.getAbsolutePath()); + + final File directory = new File(parentDirectory, directoryName); + if (directory.mkdirs()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + directory.getAbsolutePath()); + + if (directory.exists()) + return Optional.of(directory); + + return Optional.empty(); + } + + public static Optional getOrCreateDirectory(final Path directoryPath) + { + Optional directory = Optional.empty(); + + if (directoryPath.toFile().mkdirs()) + directory = Optional.of(directoryPath.toFile()); + + if (directory.isPresent()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + directoryPath.toAbsolutePath()); + + return directory; + } + + public static Optional getOrCreateFile(final File parentDirectory, final String fileName) + { + if (parentDirectory.mkdirs()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + parentDirectory.getAbsolutePath()); + + final File file = new File(parentDirectory, fileName); + try + { + if (file.createNewFile()) + FNS4J.PATCHWORK.info("Created file " + file.getAbsolutePath()); + + return Optional.of(file); + } + catch (final IOException ex) + { + FNS4J.PATCHWORK.error("Failed to create file " + fileName + ": " + ex.getMessage()); + return Optional.empty(); + } + } + + public static Optional getOrCreateFileWithResource(final File parentDirectory, + final String fileName, + final JavaPlugin plugin) + { + if (parentDirectory.mkdirs()) + FNS4J.PATCHWORK.info(CREATED_DIRECTORY + parentDirectory.getAbsolutePath()); + + final File file = new File(parentDirectory, fileName); + try + { + if (file.createNewFile()) + { + FNS4J.PATCHWORK.info("Created file " + file.getAbsolutePath()); + FNS4J.PATCHWORK.info( + "Copying default file from resources/" + fileName + " to " + file.getAbsolutePath()); + + plugin.saveResource(fileName, true); + FNS4J.PATCHWORK.info( + "Successfully copied default file from resources/" + fileName + " to " + file.getAbsolutePath()); + } + + return Optional.of(file); + } + catch (final IOException ex) + { + FNS4J.PATCHWORK.error("Failed to create file " + fileName + ": " + ex.getMessage()); + return Optional.empty(); + } + } +}