From 399ff5f8b7b97de73e39ece22c95d07c598db7b2 Mon Sep 17 00:00:00 2001
From: Spoocy99 <76441091+Spoocy99@users.noreply.github.com>
Date: Fri, 3 Apr 2026 12:11:25 +0200
Subject: [PATCH 1/2] Refactored config system
---
bukkit-utils/pom.xml | 2 +-
.../bukkit/serializers/BukkitSerializers.java | 125 ++++
.../serializers/InventorySerializer.java | 56 +-
bukkit-utils/src/main/java/module-info.java | 2 +-
common-utils/pom.xml | 2 +-
.../collections/NormalizedArrayList.java | 2 +
.../utils/common/collections/SortedArray.java | 159 ----
.../common/collections/SortedArrayList.java | 222 ++++++
.../common/exceptions/WrappedException.java | 52 +-
.../log/factory/DefaultLoggerFactory.java | 3 +-
.../log/factory/Slf4jLoggerFactory.java | 14 +-
.../dev/spoocy/utils/common/misc/Args.java | 27 +-
.../spoocy/utils/common/misc/FileUtils.java | 419 ++++++++---
.../spoocy/utils/common/misc/Reference.java | 3 +
.../utils/common/scheduler/IScheduler.java | 117 ---
.../utils/common/scheduler/JavaScheduler.java | 87 ++-
.../utils/common/scheduler/Scheduler.java | 169 +++--
.../utils/common/scheduler/Schedulers.java | 36 +
...cheduledTask.java => CompletableTask.java} | 14 +-
.../utils/common/scheduler/task/Task.java | 38 +-
.../spoocy/utils/common/text/StringUtils.java | 77 +-
.../spoocy/utils/common/version/Version.java | 2 +
common-utils/src/main/java/module-info.java | 4 +-
.../collections/SortedArrayListTest.java | 79 ++
.../exceptions/WrappedExceptionTest.java | 59 ++
config-utils-yaml/pom.xml | 38 -
.../dev/spoocy/utils/config/YamlConfig.java | 8 -
.../src/main/java/module-info.java | 10 -
config-utils/pom.xml | 9 +-
.../utils/config/BaseResourceResolver.java | 246 ++++++
.../dev/spoocy/utils/config/CommentType.java | 11 -
.../dev/spoocy/utils/config/Commentable.java | 95 +--
.../java/dev/spoocy/utils/config/Config.java | 285 +------
.../spoocy/utils/config/ConfigProvider.java | 20 +
.../spoocy/utils/config/ConfigSection.java | 107 +++
.../dev/spoocy/utils/config/Document.java | 403 +---------
.../dev/spoocy/utils/config/FileManager.java | 82 --
.../java/dev/spoocy/utils/config/Json.java | 19 -
.../dev/spoocy/utils/config/Readable.java | 425 ++++++++++-
.../spoocy/utils/config/ResourceProvider.java | 21 +
.../spoocy/utils/config/ResourceResolver.java | 74 ++
.../dev/spoocy/utils/config/Resources.java | 130 ++++
.../utils/config/SafeResourceResolver.java | 41 +
.../dev/spoocy/utils/config/SectionArray.java | 15 -
.../dev/spoocy/utils/config/Writeable.java | 17 +-
.../config/components/AbstractConfig.java | 466 ++----------
.../config/components/AbstractDataHolder.java | 356 +++++++++
.../config/components/AbstractDocument.java | 39 -
.../utils/config/components/ConfigNode.java | 123 +++
.../utils/config/components/DocumentFile.java | 48 --
.../utils/config/components/FileDocument.java | 398 ++++++++++
.../config/components/MemorySection.java | 409 ++++++++++
.../config/constructor/BaseConstructor.java | 75 ++
.../utils/config/constructor/Construct.java | 22 +
.../utils/config/constructor/Constructor.java | 16 +
.../constructor/SerializerAssignable.java | 18 +
.../constructor/SerializingConstructor.java | 183 +++++
.../utils/config/io/AbstractResource.java | 78 ++
.../utils/config/io/ClassPathResource.java | 183 +++++
.../utils/config/io/FileSystemResource.java | 169 +++++
.../utils/config/io/InputStreamSource.java | 28 +
.../spoocy/utils/config/io/PathResource.java | 139 ++++
.../utils/config/io/ResolvableResource.java | 259 +++++++
.../dev/spoocy/utils/config/io/Resource.java | 142 ++++
.../spoocy/utils/config/io/UrlResource.java | 187 +++++
.../utils/config/io/WriteableResource.java | 31 +
.../config/loader/AnnotatedConfigLoader.java | 704 ++++++++++++++++++
.../utils/config/loader/ConfigIgnore.java | 17 +
.../utils/config/loader/ConfigLoader.java | 73 ++
.../utils/config/loader/ConfigProperty.java | 43 ++
.../utils/config/loader/ConfigSource.java | 66 ++
.../utils/config/loader/JsonConfigLoader.java | 112 +++
.../spoocy/utils/config/loader/PostLoad.java | 39 +
.../utils/config/loader/PostLoadResult.java | 23 +
.../spoocy/utils/config/loader/PreLoad.java | 29 +
.../utils/config/loader/YamlConfigLoader.java | 229 ++++++
.../utils/config/loader/YamlProcessor.java | 118 +++
.../spoocy/utils/config/misc/SectionList.java | 47 --
.../config/representer/BaseRepresenter.java | 65 ++
.../utils/config/representer/Represent.java | 23 +
.../utils/config/representer/Representer.java | 22 +
.../representer/SerializingRepresenter.java | 116 +++
.../config/serializer/BaseSerializers.java | 60 ++
.../config/serializer/ConfigSerializable.java | 14 +-
...gSerializer.java => NamedSerializers.java} | 181 ++---
.../config/serializer/SerializableAs.java | 2 +-
.../utils/config/serializer/Serializers.java | 69 ++
.../spoocy/utils/config/serializer/Tag.java | 170 +++++
.../serializer/impl/AtomicsSerializer.java | 4 +-
.../serializer/impl/DurationSerializer.java | 4 +-
.../serializer/impl/EnumSerializer.java | 58 --
.../serializer/impl/JavaSerializer.java | 4 +-
.../utils/config/types/ConfigSettings.java | 103 +++
.../spoocy/utils/config/types/JsonConfig.java | 300 +-------
.../utils/config/types/JsonSettings.java | 42 ++
.../spoocy/utils/config/types/MapConfig.java | 126 ----
.../utils/config/types/MemoryConfig.java | 37 +
.../spoocy/utils/config/types/YamlConfig.java | 130 ++++
.../utils/config/types/YamlSettings.java | 104 +++
.../utils/config/update/ConfigMigration.java | 126 ++++
.../utils/config/update/ConfigUpdater.java | 44 ++
.../config/update/ConfigUpdaterChain.java | 346 +++++++++
.../config/update/PathVersionResolver.java | 46 ++
.../utils/config/update/VersionMatcher.java | 161 ++++
.../utils/config/update/VersionResolver.java | 41 +
.../config/update/base/BaseMigration.java | 96 +++
.../update/base/ResourceBasedMigration.java | 89 +++
.../config/update/match/ExactMatcher.java | 47 ++
.../config/update/match/GreaterMatcher.java | 46 ++
.../config/update/match/LowerMatcher.java | 46 ++
.../config/update/match/WildcardMatcher.java | 67 ++
.../migrations/MissingFieldsMigration.java | 141 ++++
.../migrations/TransformationMigration.java | 261 +++++++
.../migrations/ValidationMigration.java | 261 +++++++
config-utils/src/main/java/module-info.java | 14 +-
.../src/test/java/EnumSerializerTest.java | 62 --
.../config/AbstractAnnotatedConfigTest.java | 215 ++++++
.../config/BaseResourceResolverTest.java | 225 ++++++
.../config/SafeResourceResolverTest.java | 92 +++
.../config/io/ClassPathResourceTest.java | 111 +++
.../config/io/FileSystemResourceTest.java | 165 ++++
.../utils/config/io/PathResourceTest.java | 207 +++++
.../spoocy/utils/config/io/ResourceTest.java | 41 +
.../loader/AnnotatedConfigLoaderTest.java | 108 +++
.../serializer}/AtomicsSerializerTest.java | 19 +-
.../serializer}/JavaSerializerTest.java | 2 +
.../config/serializer}/SerializerTest.java | 76 +-
.../utils/config/types/JsonConfigTest.java | 269 +++++++
.../config/types/SerializableExample.java | 55 ++
.../utils/config/types/YamlConfigTest.java | 208 ++++++
.../config/update/ConfigUpdaterChainTest.java | 242 ++++++
.../config/update/ConfigUpdaterTestBase.java | 65 ++
.../update/MissingFieldsMigrationTest.java | 157 ++++
.../update/TransformationMigrationTest.java | 182 +++++
.../update/ValidationMigrationTest.java | 151 ++++
.../config/update/base/BaseMigrationTest.java | 91 +++
.../base/ResourceBasedMigrationTest.java | 83 +++
.../spoocy/utils/config/io/example.properties | 1 +
.../dev/spoocy/utils/config/io/example.xml | 5 +
.../config/loader/annotation/example.yml | 9 +
.../config/loader/annotation/serializable.yml | 5 +
.../spoocy/utils/config/types/example.json | 10 +
.../dev/spoocy/utils/config/types/example.yml | 6 +
.../update/defaults-without-version.yml | 6 +
.../spoocy/utils/config/update/defaults.yml | 7 +
pom.xml | 3 +-
reflection-utils/pom.xml | 2 +-
.../spoocy/utils/reflection/Reflection.java | 130 ++--
.../src/main/java/module-info.java | 2 +-
security-utils/pom.xml | 2 +-
.../utils/security/SecurityManager.java | 6 +-
.../dev/spoocy/utils/security/TestResult.java | 23 +-
security-utils/src/main/java/module-info.java | 2 +-
153 files changed, 12423 insertions(+), 2783 deletions(-)
create mode 100644 bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/BukkitSerializers.java
delete mode 100644 common-utils/src/main/java/dev/spoocy/utils/common/collections/SortedArray.java
create mode 100644 common-utils/src/main/java/dev/spoocy/utils/common/collections/SortedArrayList.java
delete mode 100644 common-utils/src/main/java/dev/spoocy/utils/common/scheduler/IScheduler.java
create mode 100644 common-utils/src/main/java/dev/spoocy/utils/common/scheduler/Schedulers.java
rename common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/{ScheduledTask.java => CompletableTask.java} (93%)
create mode 100644 common-utils/src/test/java/dev/spoocy/utils/common/collections/SortedArrayListTest.java
create mode 100644 common-utils/src/test/java/dev/spoocy/utils/common/exceptions/WrappedExceptionTest.java
delete mode 100644 config-utils-yaml/pom.xml
delete mode 100644 config-utils-yaml/src/main/java/dev/spoocy/utils/config/YamlConfig.java
delete mode 100644 config-utils-yaml/src/main/java/module-info.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/BaseResourceResolver.java
delete mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/CommentType.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/ConfigProvider.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/ConfigSection.java
delete mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/FileManager.java
delete mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/Json.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/ResourceProvider.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/ResourceResolver.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/Resources.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/SafeResourceResolver.java
delete mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/SectionArray.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/components/AbstractDataHolder.java
delete mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/components/AbstractDocument.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/components/ConfigNode.java
delete mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/components/DocumentFile.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/components/FileDocument.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/components/MemorySection.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/constructor/BaseConstructor.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/constructor/Construct.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/constructor/Constructor.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/constructor/SerializerAssignable.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/constructor/SerializingConstructor.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/io/AbstractResource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/io/ClassPathResource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/io/FileSystemResource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/io/InputStreamSource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/io/PathResource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/io/ResolvableResource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/io/Resource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/io/UrlResource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/io/WriteableResource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/AnnotatedConfigLoader.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/ConfigIgnore.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/ConfigLoader.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/ConfigProperty.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/ConfigSource.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/JsonConfigLoader.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/PostLoad.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/PostLoadResult.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/PreLoad.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/YamlConfigLoader.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/loader/YamlProcessor.java
delete mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/misc/SectionList.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/representer/BaseRepresenter.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/representer/Represent.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/representer/Representer.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/representer/SerializingRepresenter.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/serializer/BaseSerializers.java
rename config-utils/src/main/java/dev/spoocy/utils/config/serializer/{ConfigSerializer.java => NamedSerializers.java} (57%)
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/serializer/Serializers.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/serializer/Tag.java
delete mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/serializer/impl/EnumSerializer.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/types/ConfigSettings.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/types/JsonSettings.java
delete mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/types/MapConfig.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/types/MemoryConfig.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/types/YamlConfig.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/types/YamlSettings.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/ConfigMigration.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/ConfigUpdater.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/ConfigUpdaterChain.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/PathVersionResolver.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/VersionMatcher.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/VersionResolver.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/base/BaseMigration.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/base/ResourceBasedMigration.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/match/ExactMatcher.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/match/GreaterMatcher.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/match/LowerMatcher.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/match/WildcardMatcher.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/migrations/MissingFieldsMigration.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/migrations/TransformationMigration.java
create mode 100644 config-utils/src/main/java/dev/spoocy/utils/config/update/migrations/ValidationMigration.java
delete mode 100644 config-utils/src/test/java/EnumSerializerTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/AbstractAnnotatedConfigTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/BaseResourceResolverTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/SafeResourceResolverTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/io/ClassPathResourceTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/io/FileSystemResourceTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/io/PathResourceTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/io/ResourceTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/loader/AnnotatedConfigLoaderTest.java
rename config-utils/src/test/java/{ => dev/spoocy/utils/config/serializer}/AtomicsSerializerTest.java (84%)
rename config-utils/src/test/java/{ => dev/spoocy/utils/config/serializer}/JavaSerializerTest.java (98%)
rename config-utils/src/test/java/{ => dev/spoocy/utils/config/serializer}/SerializerTest.java (57%)
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/types/JsonConfigTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/types/SerializableExample.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/types/YamlConfigTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/update/ConfigUpdaterChainTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/update/ConfigUpdaterTestBase.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/update/MissingFieldsMigrationTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/update/TransformationMigrationTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/update/ValidationMigrationTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/update/base/BaseMigrationTest.java
create mode 100644 config-utils/src/test/java/dev/spoocy/utils/config/update/base/ResourceBasedMigrationTest.java
create mode 100644 config-utils/src/test/resources/dev/spoocy/utils/config/io/example.properties
create mode 100644 config-utils/src/test/resources/dev/spoocy/utils/config/io/example.xml
create mode 100644 config-utils/src/test/resources/dev/spoocy/utils/config/loader/annotation/example.yml
create mode 100644 config-utils/src/test/resources/dev/spoocy/utils/config/loader/annotation/serializable.yml
create mode 100644 config-utils/src/test/resources/dev/spoocy/utils/config/types/example.json
create mode 100644 config-utils/src/test/resources/dev/spoocy/utils/config/types/example.yml
create mode 100644 config-utils/src/test/resources/dev/spoocy/utils/config/update/defaults-without-version.yml
create mode 100644 config-utils/src/test/resources/dev/spoocy/utils/config/update/defaults.yml
diff --git a/bukkit-utils/pom.xml b/bukkit-utils/pom.xml
index 2c244ea..d268ad9 100644
--- a/bukkit-utils/pom.xml
+++ b/bukkit-utils/pom.xml
@@ -7,7 +7,7 @@
dev.spoocy.utils
root
- 1.0.12
+ 1.0.13
bukkit-utils
diff --git a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/BukkitSerializers.java b/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/BukkitSerializers.java
new file mode 100644
index 0000000..5939d3c
--- /dev/null
+++ b/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/BukkitSerializers.java
@@ -0,0 +1,125 @@
+package dev.spoocy.utils.bukkit.serializers;
+
+import com.mojang.authlib.GameProfile;
+import dev.spoocy.utils.config.serializer.ConfigSerializable;
+import dev.spoocy.utils.config.serializer.NamedSerializers;
+import dev.spoocy.utils.config.serializer.Serializer;
+import org.bukkit.Color;
+import org.bukkit.FireworkEffect;
+import org.bukkit.Location;
+import org.bukkit.attribute.AttributeModifier;
+import org.bukkit.block.banner.Pattern;
+import org.bukkit.block.spawner.SpawnRule;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.util.BlockVector;
+import org.bukkit.util.BoundingBox;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Spoocy99 | GitHub: Spoocy99
+ */
+
+public class BukkitSerializers extends NamedSerializers {
+
+ private static final BukkitSerializers INSTANCE = new BukkitSerializers();
+
+ private final Map, Serializer>> assignable = new ConcurrentHashMap<>();
+
+ private BukkitSerializers() {
+ super();
+ this.register(GameProfile.class, GameProfileSerializer.INSTANCE);
+ this.registerAssignable(Inventory.class, InventorySerializer.INSTANCE);
+ this.registerBukkit(Vector.class);
+ this.registerBukkit(BlockVector.class);
+ this.registerBukkit(ItemStack.class);
+ this.registerBukkit(Color.class);
+ this.registerBukkit(PotionEffect.class);
+ this.registerBukkit(FireworkEffect.class);
+ this.registerBukkit(Pattern.class);
+ this.registerBukkit(Location.class);
+ try {
+ this.registerBukkit(AttributeModifier.class);
+ } catch (Throwable ignored) {}
+ try {
+ this.registerBukkit(BoundingBox.class);
+ } catch (Throwable ignored) {}
+ try {
+ this.registerBukkit(SpawnRule.class);
+ } catch (Throwable ignored) {}
+ }
+
+ public static @NotNull BukkitSerializers getInstance() {
+ return INSTANCE;
+ }
+
+ public static @Nullable Serializer resolveSerializer(@NotNull Class clazz) {
+ return INSTANCE.resolve(clazz);
+ }
+
+ public static void registerConfigSerializable(@NotNull Class extends ConfigSerializable> clazz) {
+ INSTANCE.register(clazz);
+ }
+
+ public static void registerBukkitSerializer(@NotNull Class extends ConfigurationSerializable> clazz) {
+ INSTANCE.registerBukkit(clazz);
+ }
+
+ public static void registerAssignableSerializer(@NotNull Class clazz, @NotNull Serializer serializer) {
+ INSTANCE.registerAssignable(clazz, serializer);
+ }
+
+ @Override
+ public void register(@NotNull Class extends ConfigSerializable> clazz) {
+ super.register(clazz);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public @Nullable Serializer resolve(@NotNull Class clazz) {
+ Serializer serializer = super.resolve(clazz);
+ if (serializer != null) return serializer;
+
+ Class> matchedType = null;
+ Serializer> matchedSerializer = null;
+
+ for (Map.Entry, Serializer>> entry : this.assignable.entrySet()) {
+ Class> assignableType = entry.getKey();
+ if (!assignableType.isAssignableFrom(clazz)) {
+ continue;
+ }
+
+ // Prefer the most specific assignable type when multiple serializers match.
+ if (matchedType == null || matchedType.isAssignableFrom(assignableType)) {
+ matchedType = assignableType;
+ matchedSerializer = entry.getValue();
+ }
+ }
+
+ return (Serializer) matchedSerializer;
+
+ }
+
+ public void registerBukkit(@NotNull Class clazz) {
+ String name = getBukkitName(clazz);
+ this.registerName(clazz, name);
+ this.register(clazz, new BukkitSerializer<>(clazz));
+ }
+
+ public void registerAssignable(@NotNull Class clazz, @NotNull Serializer serializer) {
+ this.assignable.put(clazz, serializer);
+ }
+
+ private String getBukkitName(@NotNull Class clazz) {
+ return ConfigurationSerialization.getAlias(clazz);
+ }
+
+}
diff --git a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/InventorySerializer.java b/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/InventorySerializer.java
index 9c3dd24..952a7df 100644
--- a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/InventorySerializer.java
+++ b/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/InventorySerializer.java
@@ -4,6 +4,7 @@
import dev.spoocy.utils.common.exceptions.WrappedException;
import dev.spoocy.utils.config.serializer.Serializer;
import org.bukkit.Bukkit;
+import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.io.BukkitObjectInputStream;
@@ -32,16 +33,16 @@ private InventorySerializer() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
- dataOutput.writeInt(object.getSize());
-
- for (int i = 0; i < object.getSize(); i++) {
- dataOutput.writeObject(object.getItem(i));
- }
-
+ dataOutput.writeObject(object.getContents());
dataOutput.close();
+
String base64 = Base64Coder.encodeLines(outputStream.toByteArray());
- return Map.of("base64", base64);
+ return Map.of(
+ "type", object.getType().name(),
+ "size", object.getSize(),
+ "base64", base64
+ );
} catch (Throwable e) {
WrappedException.rethrow(e);
}
@@ -51,23 +52,48 @@ private InventorySerializer() {
@Override
public @NotNull Inventory deserialize(@NotNull Map map) {
- if(!map.containsKey("base64")) {
+ if (!map.containsKey("type")) {
+ throw new IllegalArgumentException("Map does not contain key 'type'.");
+ }
+ if (!map.containsKey("size")) {
+ throw new IllegalArgumentException("Map does not contain key 'size'.");
+ }
+ if (!map.containsKey("base64")) {
throw new IllegalArgumentException("Map does not contain key 'base64'.");
}
try {
+ String typeName = (String) map.get("type");
+ int size = ((Number) map.get("size")).intValue();
String base64 = (String) map.get("base64");
- byte[] data = Base64Coder.decode(base64);
- ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+
+ InventoryType type = InventoryType.valueOf(typeName);
+
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(base64));
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
- Inventory inventory = Bukkit.getServer().createInventory(null, dataInput.readInt());
+ ItemStack[] contents = (ItemStack[]) dataInput.readObject();
+ dataInput.close();
+
+ Inventory inventory;
+ if (type == InventoryType.CHEST) {
+ if (size <= 0 || size > 54 || size % 9 != 0) {
+ throw new IllegalArgumentException("Invalid chest inventory size: " + size);
+ }
- for (int i = 0; i < inventory.getSize(); i++) {
- inventory.setItem(i, (ItemStack) dataInput.readObject());
+ inventory = Bukkit.createInventory(null, size);
+ } else {
+ inventory = Bukkit.createInventory(null, type);
}
- dataInput.close();
+
+ if (contents.length != inventory.getSize()) {
+ ItemStack[] resized = new ItemStack[inventory.getSize()];
+ System.arraycopy(contents, 0, resized, 0, Math.min(contents.length, resized.length));
+ contents = resized;
+ }
+
+ inventory.setContents(contents);
return inventory;
- } catch (Throwable e) {
+ } catch (Exception e) {
WrappedException.rethrow(e);
}
diff --git a/bukkit-utils/src/main/java/module-info.java b/bukkit-utils/src/main/java/module-info.java
index c52d3b2..208d563 100644
--- a/bukkit-utils/src/main/java/module-info.java
+++ b/bukkit-utils/src/main/java/module-info.java
@@ -3,7 +3,7 @@
*/
module dev.spoocy.utils.bukkit {
- requires org.jetbrains.annotations;
+ requires static org.jetbrains.annotations;
requires dev.spoocy.utils.common;
requires dev.spoocy.utils.config;
requires org.bukkit;
diff --git a/common-utils/pom.xml b/common-utils/pom.xml
index 4486957..8aa7e8e 100644
--- a/common-utils/pom.xml
+++ b/common-utils/pom.xml
@@ -7,7 +7,7 @@
dev.spoocy.utils
root
- 1.0.12
+ 1.0.13
common-utils
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/collections/NormalizedArrayList.java b/common-utils/src/main/java/dev/spoocy/utils/common/collections/NormalizedArrayList.java
index 646d276..7aa0336 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/collections/NormalizedArrayList.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/collections/NormalizedArrayList.java
@@ -5,6 +5,8 @@
import java.util.function.UnaryOperator;
/**
+ * A custom implementation of ArrayList that normalizes the index to be 1-based instead of 0-based.
+ *
* @author Spoocy99 | GitHub: Spoocy99
*/
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/collections/SortedArray.java b/common-utils/src/main/java/dev/spoocy/utils/common/collections/SortedArray.java
deleted file mode 100644
index 408fc44..0000000
--- a/common-utils/src/main/java/dev/spoocy/utils/common/collections/SortedArray.java
+++ /dev/null
@@ -1,159 +0,0 @@
-package dev.spoocy.utils.common.collections;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.*;
-import java.util.Objects;
-
-/**
- * @author Spoocy99 | GitHub: Spoocy99
- */
-
-public class SortedArray> implements Collection {
-
- private volatile List list;
-
- public SortedArray() {
- this.list = new ArrayList<>();
- }
-
- public SortedArray(@NotNull Collection wrapped) {
- this.list = new ArrayList<>(wrapped);
- Collections.sort(list);
- }
-
- public int size() {
- return list.size();
- }
-
- @Override
- public boolean isEmpty() {
- return list.isEmpty();
- }
-
- @Override
- public Iterator iterator() {
- return list.iterator();
- }
-
- @Override
- public boolean contains(@NotNull Object value) {
- return list.contains(value);
- }
-
- @Override
- public synchronized boolean add(@NotNull T value) {
- List copy = new ArrayList<>();
- boolean inserted = false;
-
- for (T element : list) {
- if (!inserted && value.compareTo(element) < 0) {
- copy.add(value);
- inserted = true;
- }
- copy.add(element);
- }
-
- if (!inserted) {
- copy.add(value);
- }
-
- list = copy;
- return true;
- }
-
- @Override
- public synchronized boolean remove(@NotNull Object value) {
- List copy = new ArrayList<>();
- boolean result = false;
-
- for (T element : list) {
- if (!Objects.equals(value, element)) {
- copy.add(element);
- } else {
- result = true;
- }
- }
-
- list = copy;
- return result;
- }
-
- @Override
- public boolean containsAll(@NotNull Collection> values) {
- return new HashSet<>(list).containsAll(values);
- }
-
- @Override
- public synchronized boolean addAll(@NotNull Collection extends T> values) {
-
- if (values.isEmpty()) {
- return false;
- }
-
- List copy = new ArrayList<>(list);
-
- copy.addAll(values);
- Collections.sort(copy);
-
- list = copy;
- return true;
- }
-
- @Override
- public boolean removeAll(@NotNull Collection> values) {
- if (values.isEmpty()) {
- return false;
- }
-
- List copy = new ArrayList<>(list);
- copy.removeAll(values);
-
- list = copy;
- return true;
- }
-
- @Override
- public boolean retainAll(@NotNull Collection> values) {
- if (values.isEmpty()) return false;
-
- List copy = new ArrayList<>(list);
- copy.removeAll(values);
-
- list = copy;
- return true;
- }
-
- @Override
- public void clear() {
- list = new ArrayList<>();
- }
-
- @Override
- public Object[] toArray() {
- return list.toArray();
- }
-
- @NotNull
- @Override
- public T1[] toArray(@NotNull T1[] a) {
- return list.toArray(a);
- }
-
- @Override
- public String toString() {
- return list.toString();
- }
-
- public T get(int index) {
- return list.get(index);
- }
-
- public synchronized void remove(int index) {
- List copy = new ArrayList<>(this.list);
-
- copy.remove(index);
- list = copy;
- }
-
-}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/collections/SortedArrayList.java b/common-utils/src/main/java/dev/spoocy/utils/common/collections/SortedArrayList.java
new file mode 100644
index 0000000..a49565b
--- /dev/null
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/collections/SortedArrayList.java
@@ -0,0 +1,222 @@
+package dev.spoocy.utils.common.collections;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * @author Spoocy99 | GitHub: Spoocy99
+ */
+
+public class SortedArrayList> implements List {
+
+ private volatile List list;
+
+ private static > int findInsertIndex(@NotNull List values, @NotNull E value) {
+ int index = Collections.binarySearch(values, value);
+ return index >= 0 ? index : -index - 1;
+ }
+
+ public SortedArrayList() {
+ this.list = new ArrayList<>();
+ }
+
+ public SortedArrayList(@NotNull Collection wrapped) {
+ this.list = new ArrayList<>(wrapped);
+ Collections.sort(list);
+ }
+
+ @Override
+ public int size() {
+ return this.list.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.list.isEmpty();
+ }
+
+ @Override
+ public boolean contains(@NotNull Object value) {
+ return this.list.contains(value);
+ }
+
+ @Override
+ public synchronized boolean add(@NotNull T value) {
+ List copy = new ArrayList<>(this.list);
+ copy.add(findInsertIndex(copy, value), value);
+ this.list = copy;
+ return true;
+ }
+
+ @Override
+ public synchronized boolean remove(@NotNull Object value) {
+ List copy = new ArrayList<>(this.list);
+ int index = copy.indexOf(value);
+
+ if (index < 0) {
+ return false;
+ }
+
+ copy.remove(index);
+ this.list = copy;
+ return true;
+ }
+
+ @Override
+ public boolean containsAll(@NotNull Collection> values) {
+ return new HashSet<>(this.list).containsAll(values);
+ }
+
+ @Override
+ public synchronized boolean addAll(@NotNull Collection extends T> values) {
+
+ if (values.isEmpty()) {
+ return false;
+ }
+
+ List copy = new ArrayList<>(list);
+
+ copy.addAll(values);
+ Collections.sort(copy);
+
+ list = copy;
+ return true;
+ }
+
+ @Override
+ public synchronized boolean addAll(int index, @NotNull Collection extends T> c) {
+ if (index < 0 || index > this.list.size()) {
+ throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + this.list.size());
+ }
+ return this.addAll(c);
+ }
+
+ @Override
+ public synchronized boolean removeAll(@NotNull Collection> values) {
+ if (values.isEmpty()) {
+ return false;
+ }
+
+ List copy = new ArrayList<>(this.list);
+ boolean changed = copy.removeAll(values);
+ if (!changed) {
+ return false;
+ }
+
+ this.list = copy;
+ return true;
+ }
+
+ @Override
+ public synchronized boolean retainAll(@NotNull Collection> values) {
+ if (values.isEmpty() && this.list.isEmpty()) {
+ return false;
+ }
+
+ List copy = new ArrayList<>(this.list);
+ boolean changed = copy.retainAll(values);
+ if (!changed) {
+ return false;
+ }
+
+ this.list = copy;
+ return true;
+ }
+
+ @Override
+ public synchronized void clear() {
+ this.list = new ArrayList<>();
+ }
+
+ @Override
+ public Object @NotNull [] toArray() {
+ return this.list.toArray();
+ }
+
+ @NotNull
+ @Override
+ public T1 @NotNull [] toArray(@NotNull T1[] a) {
+ return this.list.toArray(a);
+ }
+
+ @Override
+ public String toString() {
+ return this.list.toString();
+ }
+
+ @Override
+ public T get(int index) {
+ return this.list.get(index);
+ }
+
+ @Override
+ public T set(int index, T element) {
+ throw new UnsupportedOperationException("set(index, element) is not supported by SortedArrayList");
+ }
+
+ @Override
+ public void add(int index, T element) {
+ throw new UnsupportedOperationException("add(index, element) is not supported by SortedArrayList");
+ }
+
+ @Override
+ public synchronized T remove(int index) {
+ List copy = new ArrayList<>(this.list);
+ T removed = copy.remove(index);
+ this.list = copy;
+ return removed;
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return this.list.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return this.list.lastIndexOf(o);
+ }
+
+ @Override
+ public @NotNull ListIterator listIterator() {
+ List snapshot = List.copyOf(this.list);
+ return snapshot.listIterator();
+ }
+
+ @Override
+ public @NotNull ListIterator listIterator(int index) {
+ List snapshot = List.copyOf(this.list);
+ return snapshot.listIterator(index);
+ }
+
+ @Override
+ public @NotNull List subList(int fromIndex, int toIndex) {
+ List snapshot = List.copyOf(this.list);
+ return snapshot.subList(fromIndex, toIndex);
+ }
+
+ @Override
+ public @NotNull Iterator iterator() {
+ List snapshot = List.copyOf(this.list);
+ return snapshot.iterator();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof List>)) {
+ return false;
+ }
+ List> other = (List>) o;
+ return this.list.equals(other);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.list.hashCode();
+ }
+
+}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/exceptions/WrappedException.java b/common-utils/src/main/java/dev/spoocy/utils/common/exceptions/WrappedException.java
index 94b0851..38491c3 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/exceptions/WrappedException.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/exceptions/WrappedException.java
@@ -1,23 +1,56 @@
package dev.spoocy.utils.common.exceptions;
+import dev.spoocy.utils.common.misc.Args;
import org.jetbrains.annotations.NotNull;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Objects;
+
/**
+ * A wrapper exception that can be used to wrap checked exceptions in a runtime exception.
+ *
* @author Spoocy99 | GitHub: Spoocy99
*/
public class WrappedException extends RuntimeException {
- public WrappedException(Throwable cause) {
+ private static final long serialVersionUID = 1L;
+
+ public WrappedException(@NotNull Throwable cause) {
super(cause);
}
- public WrappedException(String message, Throwable cause) {
+ public WrappedException(@NotNull String message, @NotNull Throwable cause) {
super(message, cause);
}
- public static WrappedException wrap(@NotNull Throwable throwable) {
- return new WrappedException(throwable);
+ public static RuntimeException wrap(@NotNull Throwable throwable) {
+ Throwable checked = Args.notNull(throwable, "throwable");
+
+ if (checked instanceof Error) {
+ throw (Error) checked;
+ }
+
+ if (checked instanceof RuntimeException) {
+ return (RuntimeException) checked;
+ }
+
+ if (checked instanceof IOException) {
+ return wrapIO((IOException) checked);
+ }
+
+ return new WrappedException(checked);
+ }
+
+ public static UncheckedIOException wrapIO(@NotNull IOException exception) {
+ return new UncheckedIOException(Args.notNull(exception, "exception"));
+ }
+
+ @Override
+ public String getMessage() {
+ Throwable cause = this.getCause();
+ return cause != null ? cause.getMessage() : super.getMessage();
}
@Override
@@ -26,14 +59,7 @@ public synchronized Throwable fillInStackTrace() {
}
public static void rethrow(@NotNull Throwable throwable) {
- if (throwable instanceof Error) {
- throw (Error) throwable;
- }
-
- if (throwable instanceof RuntimeException) {
- throw (RuntimeException) throwable;
- }
-
- throw new WrappedException(throwable);
+ Throwable checked = Objects.requireNonNull(throwable, "throwable");
+ throw wrap(checked);
}
}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/log/factory/DefaultLoggerFactory.java b/common-utils/src/main/java/dev/spoocy/utils/common/log/factory/DefaultLoggerFactory.java
index 6776fdc..cbb940a 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/log/factory/DefaultLoggerFactory.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/log/factory/DefaultLoggerFactory.java
@@ -6,6 +6,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -14,8 +15,8 @@
public abstract class DefaultLoggerFactory implements ILoggerFactory {
+ private final Map loggers = new ConcurrentHashMap<>();
private LogLevel level;
- private final ConcurrentHashMap loggers = new ConcurrentHashMap<>();
public DefaultLoggerFactory() {
this.level = LogLevel.DEFAULT_LEVEL;
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/log/factory/Slf4jLoggerFactory.java b/common-utils/src/main/java/dev/spoocy/utils/common/log/factory/Slf4jLoggerFactory.java
index b82ab4d..07256b3 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/log/factory/Slf4jLoggerFactory.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/log/factory/Slf4jLoggerFactory.java
@@ -8,6 +8,7 @@
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -16,14 +17,17 @@
public class Slf4jLoggerFactory implements ILoggerFactory {
- protected final ConcurrentHashMap loggers = new ConcurrentHashMap<>();
+ protected final Map loggers = new ConcurrentHashMap<>();
- public Slf4jLoggerFactory() { }
+ public Slf4jLoggerFactory() {
+ }
@Override
public @NotNull ILogger getOrCreateLogger(@Nullable String name) {
- return loggers.computeIfAbsent(name, log -> new Slf4jLogger(
- LoggerFactory.getLogger(check(name))
+ final String finalName = check(name);
+
+ return loggers.computeIfAbsent(finalName, log -> new Slf4jLogger(
+ LoggerFactory.getLogger(finalName)
));
}
@@ -33,7 +37,7 @@ public ILoggerFactory setLevel(@NotNull LogLevel level) {
return this;
}
- private String check(String caller) {
+ private String check(@Nullable String caller) {
return caller != null ? caller : "Logger";
}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/misc/Args.java b/common-utils/src/main/java/dev/spoocy/utils/common/misc/Args.java
index 86f4eb3..01c2468 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/misc/Args.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/misc/Args.java
@@ -1,5 +1,8 @@
package dev.spoocy.utils.common.misc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Map;
@@ -357,6 +360,18 @@ public static boolean isEmpty(final Object object) {
return false;
}
+ public static void noNullElements(@Nullable T[] array, @NotNull String name) {
+ if (array == null) {
+ throw nullPointerException(name);
+ }
+
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ throw illegalArgumentException("%s must not contain null elements, but element at index %d is null", name, i);
+ }
+ }
+ }
+
private static IllegalArgumentException illegalArgumentException(final String format, final Object... args) {
return new IllegalArgumentException(String.format(format, args));
}
@@ -369,8 +384,18 @@ private static NullPointerException nullPointerException(final String name) {
return new NullPointerException(name + " must not be null");
}
+ public static T[] combineArgs(@NotNull T first, @NotNull T[] second) {
+ notNull(first, "first");
+ notNull(second, "second");
+
+ @SuppressWarnings("unchecked")
+ T[] combined = (T[]) Array.newInstance(first.getClass(), second.length + 1);
+ combined[0] = first;
+ System.arraycopy(second, 0, combined, 1, second.length);
+ return combined;
+ }
+
private Args() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
-
}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/misc/FileUtils.java b/common-utils/src/main/java/dev/spoocy/utils/common/misc/FileUtils.java
index cfd0774..4eea2e8 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/misc/FileUtils.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/misc/FileUtils.java
@@ -7,13 +7,12 @@
import org.jetbrains.annotations.Nullable;
import java.io.*;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLConnection;
+import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
+import java.util.ArrayDeque;
import java.util.Collections;
+import java.util.Deque;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -24,24 +23,29 @@
public final class FileUtils {
- public static String getFileName(@NotNull File file) {
- return getFileName(file.getName());
- }
-
- public static String getFileName(@NotNull Path path) {
- return getFileName(path.toString());
- }
+ private static final char PACKAGE_SEPARATOR = '.';
+ private static final char PATH_SEPARATOR = '/';
+ private static final char FOLDER_SEPARATOR_CHAR = '/';
+ private static final String FOLDER_SEPARATOR = String.valueOf(FOLDER_SEPARATOR_CHAR);
+ private static final char WINDOWS_FOLDER_SEPARATOR_CHAR = '\\';
+ private static final String WINDOWS_FOLDER_SEPARATOR = String.valueOf(WINDOWS_FOLDER_SEPARATOR_CHAR);
+ private static final String DOUBLE_BACKSLASHES = "\\\\";
+ private static final String CURRENT_PATH = ".";
+ private static final String TOP_PATH = "..";
+ private static final String DOT_CHAR = ".";
public static String getPath(@NotNull String path, char separator) {
return path.replace(File.separatorChar, separator);
}
public static String getPath(@NotNull Path path, char separator) {
- return path.toString().replace(File.separatorChar, separator);
+ return path.toString()
+ .replace(File.separatorChar, separator);
}
public static String getPath(@NotNull File file, char separator) {
- return file.getPath().replace(File.separatorChar, separator);
+ return file.getPath()
+ .replace(File.separatorChar, separator);
}
public static String removePath(@NotNull String path, @NotNull String separator) {
@@ -49,30 +53,43 @@ public static String removePath(@NotNull String path, @NotNull String separator)
return path;
}
return StringUtils.getAfterLast(path, separator);
- }
+ }
- public static String removePath(@NotNull String path) {
+ public static String removePath(@NotNull String path) {
return removePath(getPath(path, '/'), "/");
- }
+ }
- public static String getFileName(@NotNull String path) {
- path = removePath(path);
- int extension = path.lastIndexOf('.');
- return extension == -1 ? path : path.substring(0, extension);
- }
+ public static String getFileName(@NotNull File file, boolean withExtension) {
+ return getFileName(file.getName(), withExtension);
+ }
- public static String getFileExtension(@NotNull File file) {
- return getFileExtension(file.getName());
- }
+ public static String getFileName(@NotNull Path path, boolean withExtension) {
+ return getFileName(path.toString(), withExtension);
+ }
- public static String getFileExtension(@NotNull Path path) {
- return getFileExtension(path.toString());
- }
+ public static String getFileName(@NotNull String path, boolean withExtension) {
+ path = removePath(path);
+
+ if (withExtension) {
+ return path;
+ }
+
+ int extension = path.lastIndexOf('.');
+ return extension == -1 ? path : path.substring(0, extension);
+ }
+
+ public static String getFileExtension(@NotNull File file) {
+ return getFileExtension(file.getName());
+ }
- public static String getFileExtension(@NotNull String path) {
+ public static String getFileExtension(@NotNull Path path) {
+ return getFileExtension(path.toString());
+ }
+
+ public static String getFileExtension(@NotNull String path) {
path = removePath(path);
return StringUtils.getAfterLast(path, ".");
- }
+ }
public static InputStream createInputStream(@NotNull Path path) throws IOException {
return Files.newInputStream(path);
@@ -99,111 +116,117 @@ public static Writer createWriter(@NotNull File file) throws IOException {
}
public static void createDirectory(@NotNull Path path) throws IOException {
- if (!Files.notExists(path)) return;
+ if (!Files.notExists(path)) return;
Files.createDirectories(path);
- }
+ }
public static void createFile(@NotNull File file) throws IOException {
- if (file.exists()) return;
+ if (file.exists()) return;
- if (file.isDirectory()) {
- file.mkdirs();
- }
+ if (file.isDirectory()) {
+ file.mkdirs();
+ return;
+ }
- file.createNewFile();
- }
+ file.createNewFile();
+ }
- public static void createFile(@NotNull Path path) throws IOException {
- if (!Files.notExists(path)) return;
+ public static void createFile(@NotNull Path path) throws IOException {
+ if (!Files.notExists(path)) return;
if (path.getParent() != null) {
Files.createDirectories(path.getParent());
}
Files.createFile(path);
- }
+ }
- public static void deleteFile(@NotNull Path file) throws IOException {
+ public static void deleteFile(@NotNull Path file) throws IOException {
Files.deleteIfExists(file);
- }
+ }
public static void copy(@NotNull Path from, @NotNull Path to) throws IOException {
- copy(from, to, new byte[8192]);
- }
+ copy(from, to, new byte[8192]);
+ }
- public static void copy(@NotNull Path from, @NotNull Path to, byte @NotNull [] buffer) throws IOException {
- if (Files.notExists(to)) {
- createDirectory(to.getParent());
- }
+ public static void copy(@NotNull Path from, @NotNull Path to, byte @NotNull [] buffer) throws IOException {
+ if (Files.notExists(to)) {
+ createDirectory(to.getParent());
+ }
InputStream stream = Files.newInputStream(from);
OutputStream target = Files.newOutputStream(to);
copy(stream, target, buffer);
- }
+ }
+
+ public static void copy(byte[] input, @NotNull OutputStream output) throws IOException {
+ output.write(input);
+ output.flush();
+ }
public static void copy(@NotNull InputStream input, @NotNull OutputStream output) throws IOException {
- copy(input, output, new byte[8192]);
- }
+ copy(input, output, new byte[8192]);
+ }
- public static void copy(@NotNull InputStream input, @NotNull OutputStream output, byte[] buffer) throws IOException {
- copy(input, output, buffer, null);
- }
+ public static void copy(@NotNull InputStream input, @NotNull OutputStream output, byte[] buffer)
+ throws IOException {
+ copy(input, output, buffer, null);
+ }
- public static void copy(@NotNull InputStream inputStream, @NotNull OutputStream outputStream, byte[] buffer, @Nullable Consumer lengthInputListener) throws IOException {
- int len;
- while ((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
+ public static void copy(
+ @NotNull InputStream inputStream,
+ @NotNull OutputStream outputStream,
+ byte[] buffer,
+ @Nullable Consumer lengthInputListener
+ ) throws IOException {
+ int len;
+ while ((len = inputStream.read(buffer, 0, buffer.length)) != -1) {
- if (lengthInputListener != null) {
- lengthInputListener.accept(len);
- }
+ if (lengthInputListener != null) {
+ lengthInputListener.accept(len);
+ }
- outputStream.write(buffer, 0, len);
- outputStream.flush();
- }
+ outputStream.write(buffer, 0, len);
+ outputStream.flush();
+ }
- }
+ }
/**
* Saves the input stream to the given path.
*
- * @param path the path to save the file to
- * @param in the input stream to save
+ * @param path the path to save the file to
+ * @param in the input stream to save
* @param replace whether to replace the file if it already exists
+ *
* @return true if the file was saved / overwritten successfully, false otherwise
*
* @throws IOException if an error occurs while saving the file
*/
- public static boolean save(@NotNull String path, @NotNull InputStream in, boolean replace) throws IOException {
- File outFile = new File(path);
- int lastIndex = path.lastIndexOf(47);
-
- File outDir = new File(path.substring(0, lastIndex >= 0 ? lastIndex : 0));
- if (!outDir.exists()) {
- outDir.mkdirs();
- }
-
- if (outFile.exists() && !replace) {
- return false;
+ public static boolean save(@NotNull Path path, @NotNull InputStream in, boolean replace) throws IOException {
+ if (Files.exists(path)) {
+ if (!replace) {
+ return false;
+ }
+ deleteFile(path);
}
- OutputStream out = createOutputStream(outFile);
- byte[] buf = new byte[1024];
-
- int len;
- while((len = in.read(buf)) > 0) {
- out.write(buf, 0, len);
+ createDirectory(path.getParent());
+ try (OutputStream out = Files.newOutputStream(path)) {
+ copy(in, out);
}
- out.close();
- in.close();
return true;
}
- public static String getJarFile(Class> clazz) {
- return clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
+ public static String getJarFile(@NotNull Class> clazz) {
+ return clazz.getProtectionDomain()
+ .getCodeSource()
+ .getLocation()
+ .getFile();
}
- public static String getJarPath(Class> clazz) {
+ public static String getJarPath(@NotNull Class> clazz) {
String jarPath = null;
try {
@@ -214,32 +237,18 @@ public static String getJarPath(Class> clazz) {
.getPath()
.replaceAll(" ", "%20");
} catch (URISyntaxException e) {
- ILogger.forThisClass().error("Failed to get jar path. ", e);
+ ILogger.forThisClass()
+ .error("Failed to get jar path. ", e);
}
return "jar:file:" + jarPath;
}
- public static List getResources(Class> loader, String directory) throws IOException {
- List result;
-
- String jarPath = getJarPath(loader);
- URI uri = URI.create(jarPath);
-
- try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) {
- result = Files.walk(fs.getPath(directory))
- .filter(Files::isRegularFile)
- .collect(Collectors.toList());
- }
-
- return result;
-
- }
-
@Nullable
public static InputStream getResource(@NotNull Class> loader, @NotNull String resourcePath) {
try {
- URL url = loader.getClassLoader().getResource(resourcePath);
+ URL url = loader.getClassLoader()
+ .getResource(resourcePath);
if (url == null) {
return null;
@@ -253,8 +262,28 @@ public static InputStream getResource(@NotNull Class> loader, @NotNull String
}
}
- public static void saveResource(@NotNull Class> loader, @NotNull String resourcePath, @NotNull String path, boolean replace) {
- if(resourcePath == null || resourcePath.isEmpty()) {
+ public static List listResources(@NotNull Class> loader, @NotNull String directory) throws IOException {
+ List result;
+
+ String jarPath = getJarPath(loader);
+ URI uri = URI.create(jarPath);
+
+ try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) {
+ result = Files.walk(fs.getPath(directory))
+ .filter(Files::isRegularFile)
+ .collect(Collectors.toList());
+ }
+
+ return result;
+ }
+
+ public static void saveResource(
+ @NotNull Class> loader,
+ @NotNull String resourcePath,
+ @NotNull String path,
+ boolean replace
+ ) throws IOException {
+ if (resourcePath.isEmpty()) {
throw new IllegalArgumentException("ResourcePath cannot be null or empty");
}
@@ -265,11 +294,179 @@ public static void saveResource(@NotNull Class> loader, @NotNull String resour
throw new IllegalArgumentException("The embedded resource '" + resourcePath + "' cannot be found.");
}
+ save(Path.of(path), in, replace);
+ }
+
+ public static void createParentDirs(@NotNull File file) {
+ File parent = file.getParentFile();
+ if (parent != null && !parent.exists()) {
+ parent.mkdirs();
+ }
+ }
+
+ public static byte[] copyToByteArray(@NotNull InputStream inputStream) throws IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy(inputStream, output);
+ return output.toByteArray();
+ }
+
+ public static String copyToString(@NotNull InputStreamReader inputStreamReader) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ char[] buffer = new char[8192];
+ int read;
+
+ while ((read = inputStreamReader.read(buffer)) != -1) {
+ sb.append(buffer, 0, read);
+ }
+
+ return sb.toString();
+ }
+
+ public static URI toURI(@NotNull String path) throws URISyntaxException {
+ return new URI(cleanPath(path));
+ }
+
+ public static URL toURL(@NotNull String path) throws MalformedURLException {
try {
- save(path, in, replace);
- } catch (IOException e) {
- throw WrappedException.wrap(e);
+ return toURI(cleanPath(path)).toURL();
+ } catch (URISyntaxException | IllegalArgumentException ex) {
+ return new URL(path);
}
}
+ public static boolean isFileURL(@NotNull URL url) {
+ return url.getProtocol()
+ .equals("file");
+ }
+
+ public static boolean isJarURL(@NotNull URL url) {
+ return url.getProtocol()
+ .equals("jar");
+ }
+
+ public static String applyRelativePath(@NotNull String path, @NotNull String relativePath) {
+ int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR_CHAR);
+
+ if (separatorIndex != -1) {
+ String newPath = path.substring(0, separatorIndex);
+ if (!relativePath.startsWith(FOLDER_SEPARATOR)) {
+ newPath += FOLDER_SEPARATOR_CHAR;
+ }
+
+ return newPath + relativePath;
+ }
+
+ return relativePath;
+ }
+
+ public static URL toRelativeURL(URL root, String relativePath) throws MalformedURLException {
+ relativePath = StringUtils.replace(relativePath, "#", "%23");
+ return new URL(root, FileUtils.cleanPath(FileUtils.applyRelativePath(root.toString(), relativePath)));
+ }
+
+ public static String classPackageAsResourcePath(@Nullable Class> clazz) {
+ if (clazz == null) {
+ return "";
+ }
+ String className = clazz.getName();
+
+ int packageEndIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
+ if (packageEndIndex == -1) {
+ return "";
+ }
+
+ String packageName = className.substring(0, packageEndIndex);
+ return packageName.replace(PACKAGE_SEPARATOR, PATH_SEPARATOR);
+ }
+
+ public static String cleanPath(@NotNull String path) {
+ if (StringUtils.isNullOrEmpty(path)) {
+ return path;
+ }
+
+ String normalizedPath;
+
+ if (path.indexOf(WINDOWS_FOLDER_SEPARATOR_CHAR) != -1) {
+ normalizedPath = StringUtils.replace(path, DOUBLE_BACKSLASHES, FOLDER_SEPARATOR);
+ normalizedPath = StringUtils.replace(normalizedPath, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
+ } else {
+ normalizedPath = path;
+ }
+
+
+ String pathToUse = normalizedPath;
+
+ // path doesn't contain "." or ".." so skip
+ if (!pathToUse.contains(DOT_CHAR)) {
+ return pathToUse;
+ }
+
+ // We need to parse the path element by element, and deal with special folders based on their names
+ int prefixIndex = pathToUse.indexOf(':');
+ String prefix = "";
+
+ if (prefixIndex != -1) {
+ prefix = pathToUse.substring(0, prefixIndex + 1);
+
+ if (prefix.contains(FOLDER_SEPARATOR)) {
+ prefix = "";
+ } else {
+ pathToUse = pathToUse.substring(prefixIndex + 1);
+ }
+
+ }
+
+ if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
+ prefix = prefix + FOLDER_SEPARATOR;
+ pathToUse = pathToUse.substring(1);
+ }
+
+ String[] pathArray = StringUtils.tokenizeToStringArray(pathToUse, FOLDER_SEPARATOR);
+
+ // we never require more elements than pathArray and in the common case the same number
+ Deque pathElements = new ArrayDeque<>(pathArray.length);
+ int tops = 0;
+
+ for (int i = pathArray.length - 1; i >= 0; i--) {
+ String element = pathArray[i];
+
+ if (CURRENT_PATH.equals(element)) {
+ continue;
+ }
+
+ if (TOP_PATH.equals(element)) {
+ // Registering top path found
+ tops++;
+ continue;
+ }
+
+
+ if (tops > 0) {
+ // Merging path element with registered top path
+ tops--;
+ continue;
+ }
+ // Normal path element found
+ pathElements.addFirst(element);
+ }
+
+ // If nothing needs to be retained, return the normalized path
+ if (pathArray.length == pathElements.size()) {
+ return normalizedPath;
+ }
+
+ // Remaining top path need to be retained. Adding such number of top paths to the start of the path
+ for (int i = 0; i < tops; i++) {
+ pathElements.addFirst(TOP_PATH);
+ }
+
+ // If the path is now empty, there was only references to current path
+ if (pathElements.size() == 1 && pathElements.getLast()
+ .isEmpty() && !prefix.endsWith(FOLDER_SEPARATOR)) {
+ pathElements.addFirst(CURRENT_PATH);
+ }
+
+ String joined = StringUtils.collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
+ return (prefix.isEmpty() ? joined : prefix + joined);
+ }
}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/misc/Reference.java b/common-utils/src/main/java/dev/spoocy/utils/common/misc/Reference.java
index a6a0684..bc42691 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/misc/Reference.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/misc/Reference.java
@@ -6,6 +6,9 @@
import java.util.function.Supplier;
/**
+ * A simple wrapper class that can hold a value or a supplier for lazy initialization.
+ * The value is only computed when the get() method is called for the first time, and then cached for subsequent calls.
+ *
* @author Spoocy99 | GitHub: Spoocy99
*/
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/IScheduler.java b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/IScheduler.java
deleted file mode 100644
index e0a2c46..0000000
--- a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/IScheduler.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package dev.spoocy.utils.common.scheduler;
-
-import dev.spoocy.utils.common.scheduler.task.Task;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
-
-/**
- * @author Spoocy99 | GitHub: Spoocy99
- */
-
-public interface IScheduler {
-
- /**
- * Executes the given runnable synchronously.
- *
- * @param runnable the runnable to execute
- */
- void executeSync(@NotNull Runnable runnable);
-
- /**
- * Executes the given runnable asynchronously.
- *
- * @param runnable the runnable to execute
- */
- void executeAsync(@NotNull Runnable runnable);
-
- /**
- * Schedules a runnable to be executed synchronously.
- *
- * @param runnable the runnable to execute
- * @param the type of the value returned by the runnable
- * @return the task representing the runnable
- */
- Task runSync(@NotNull Runnable runnable);
-
- /**
- * Schedules a runnable to be executed asynchronously.
- *
- * @param runnable the runnable to execute
- * @param the type of the value returned by the runnable
- * @return the task representing the runnable
- */
- Task runAsync(@NotNull Runnable runnable);
-
- /**
- * Schedules a runnable to be executed asynchronously after a delay.
- *
- * @param runnable the runnable to execute
- * @param delay the delay before execution
- * @param unit the unit of the delay
- * @param the type of the value returned by the runnable
- * @return the task representing the runnable
- */
- Task runDelayed(@NotNull Runnable runnable, long delay, @NotNull TimeUnit unit);
-
- /**
- * Schedules a callable to be executed synchronously.
- *
- * @param callable the callable to execute
- * @param the type of the value returned by the callable
- * @return the task representing the callable
- */
- Task runSyncCallable(@NotNull Callable callable);
-
- /**
- * Schedules a callable to be executed asynchronously.
- *
- * @param callable the callable to execute
- * @param the type of the value returned by the callable
- * @return the task representing the callable
- */
- Task runAsyncCallable(@NotNull Callable callable);
-
- /**
- * Schedules a callable to be executed asynchronously after a delay.
- *
- * @param callable the callable to execute
- * @param delay the delay before execution
- * @param unit the unit of the delay
- * @param the type of the value returned by the callable
- * @return the task representing the callable
- */
- Task runDelayedCallable(@NotNull Callable callable, long delay, @NotNull TimeUnit unit);
-
- /**
- * Schedules a supplier to be executed synchronously.
- *
- * @param supplier the supplier to execute
- * @param the type of the value returned by the supplier
- * @return the task representing the supplier
- */
- Task runSyncSupplier(@NotNull Supplier supplier);
-
- /**
- * Schedules a supplier to be executed asynchronously.
- *
- * @param supplier the supplier to execute
- * @param the type of the value returned by the supplier
- * @return the task representing the supplier
- */
- Task runAsyncSupplier(@NotNull Supplier supplier);
-
- /**
- * Schedules a supplier to be executed asynchronously after a delay.
- *
- * @param supplier the supplier to execute
- * @param delay the delay before execution
- * @param unit the unit of the delay
- * @param the type of the value returned by the supplier
- * @return the task representing the supplier
- */
- Task runDelayedSupplier(@NotNull Supplier supplier, long delay, @NotNull TimeUnit unit);
-
-}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/JavaScheduler.java b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/JavaScheduler.java
index de593c8..fa86461 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/JavaScheduler.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/JavaScheduler.java
@@ -1,6 +1,6 @@
package dev.spoocy.utils.common.scheduler;
-import dev.spoocy.utils.common.scheduler.task.ScheduledTask;
+import dev.spoocy.utils.common.scheduler.task.CompletableTask;
import dev.spoocy.utils.common.scheduler.task.Task;
import org.jetbrains.annotations.NotNull;
@@ -11,10 +11,20 @@
* @author Spoocy99 | GitHub: Spoocy99
*/
-public class JavaScheduler implements IScheduler {
+public class JavaScheduler implements Scheduler, AutoCloseable {
- private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();
- private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newScheduledThreadPool(1);
+ private final ExecutorService executorService;
+ private final ScheduledExecutorService scheduledExecutorService;
+ private volatile boolean shutdown = false;
+
+ public JavaScheduler(int corePoolSize) {
+ this(Executors.newCachedThreadPool(), Executors.newScheduledThreadPool(corePoolSize));
+ }
+
+ public JavaScheduler(ExecutorService executorService, ScheduledExecutorService scheduledExecutorService) {
+ this.executorService = executorService;
+ this.scheduledExecutorService = scheduledExecutorService;
+ }
@Override
public void executeSync(@NotNull Runnable runnable) {
@@ -23,7 +33,8 @@ public void executeSync(@NotNull Runnable runnable) {
@Override
public void executeAsync(@NotNull Runnable runnable) {
- EXECUTOR_SERVICE.execute(runnable);
+ checkShutdown();
+ executorService.execute(runnable);
}
@Override
@@ -81,7 +92,7 @@ public Task runDelayedSupplier(@NotNull Supplier supplier, long delay,
}
private Task callSync(@NotNull Callable callable) {
- Task task = ScheduledTask.create();
+ CompletableTask task = CompletableTask.empty();
try {
task.complete(callable.call());
@@ -93,23 +104,25 @@ private Task callSync(@NotNull Callable callable) {
}
private Task callAsync(@NotNull Callable callable) {
- Task task = ScheduledTask.create();
+ checkShutdown();
+ CompletableTask task = CompletableTask.empty();
- EXECUTOR_SERVICE.execute( ()-> {
- try {
- task.complete(callable.call());
- } catch (Throwable ex) {
- task.fail(ex);
- }
- });
+ executorService.execute(() -> {
+ try {
+ task.complete(callable.call());
+ } catch (Throwable ex) {
+ task.fail(ex);
+ }
+ });
- return task;
+ return task;
}
private Task callAsyncDelayed(@NotNull Callable callable, long delay, TimeUnit unit) {
- Task task = ScheduledTask.create();
+ checkShutdown();
+ CompletableTask task = CompletableTask.empty();
- SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
+ scheduledExecutorService.schedule(() -> {
try {
task.complete(callable.call());
} catch (Throwable ex) {
@@ -120,4 +133,44 @@ private Task callAsyncDelayed(@NotNull Callable callable, long delay,
return task;
}
+ private void checkShutdown() {
+ if (shutdown) {
+ throw new IllegalStateException("JavaScheduler has been shut down");
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (shutdown) {
+ return;
+ }
+
+ shutdown = true;
+
+ // Shutdown executor service
+ executorService.shutdown();
+ if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
+ executorService.shutdownNow();
+ }
+
+ // Shutdown scheduled executor service
+ scheduledExecutorService.shutdown();
+ if (!scheduledExecutorService.awaitTermination(10, TimeUnit.SECONDS)) {
+ scheduledExecutorService.shutdownNow();
+ }
+ }
+
+ /**
+ * Shuts down the scheduler immediately without waiting for termination.
+ * Useful when graceful shutdown is not required.
+ */
+ public void shutdown() {
+ if (shutdown) {
+ return;
+ }
+
+ shutdown = true;
+ executorService.shutdownNow();
+ scheduledExecutorService.shutdownNow();
+ }
}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/Scheduler.java b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/Scheduler.java
index 35d96b1..0999e7e 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/Scheduler.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/Scheduler.java
@@ -1,12 +1,9 @@
package dev.spoocy.utils.common.scheduler;
-import dev.spoocy.utils.common.log.ILogger;
import dev.spoocy.utils.common.scheduler.task.Task;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@@ -14,69 +11,107 @@
* @author Spoocy99 | GitHub: Spoocy99
*/
-public class Scheduler {
+public interface Scheduler {
+
+ /**
+ * Executes the given runnable synchronously.
+ *
+ * @param runnable the runnable to execute
+ */
+ void executeSync(@NotNull Runnable runnable);
+
+ /**
+ * Executes the given runnable asynchronously.
+ *
+ * @param runnable the runnable to execute
+ */
+ void executeAsync(@NotNull Runnable runnable);
+
+ /**
+ * Schedules a runnable to be executed synchronously.
+ *
+ * @param runnable the runnable to execute
+ * @param the type of the value returned by the runnable
+ * @return the task representing the runnable
+ */
+ Task runSync(@NotNull Runnable runnable);
+
+ /**
+ * Schedules a runnable to be executed asynchronously.
+ *
+ * @param runnable the runnable to execute
+ * @param the type of the value returned by the runnable
+ * @return the task representing the runnable
+ */
+ Task runAsync(@NotNull Runnable runnable);
+
+ /**
+ * Schedules a runnable to be executed asynchronously after a delay.
+ *
+ * @param runnable the runnable to execute
+ * @param delay the delay before execution
+ * @param unit the unit of the delay
+ * @param the type of the value returned by the runnable
+ * @return the task representing the runnable
+ */
+ Task runDelayed(@NotNull Runnable runnable, long delay, @NotNull TimeUnit unit);
+
+ /**
+ * Schedules a callable to be executed synchronously.
+ *
+ * @param callable the callable to execute
+ * @param the type of the value returned by the callable
+ * @return the task representing the callable
+ */
+ Task runSyncCallable(@NotNull Callable callable);
+
+ /**
+ * Schedules a callable to be executed asynchronously.
+ *
+ * @param callable the callable to execute
+ * @param the type of the value returned by the callable
+ * @return the task representing the callable
+ */
+ Task runAsyncCallable(@NotNull Callable callable);
+
+ /**
+ * Schedules a callable to be executed asynchronously after a delay.
+ *
+ * @param callable the callable to execute
+ * @param delay the delay before execution
+ * @param unit the unit of the delay
+ * @param the type of the value returned by the callable
+ * @return the task representing the callable
+ */
+ Task runDelayedCallable(@NotNull Callable callable, long delay, @NotNull TimeUnit unit);
+
+ /**
+ * Schedules a supplier to be executed synchronously.
+ *
+ * @param supplier the supplier to execute
+ * @param the type of the value returned by the supplier
+ * @return the task representing the supplier
+ */
+ Task runSyncSupplier(@NotNull Supplier supplier);
+
+ /**
+ * Schedules a supplier to be executed asynchronously.
+ *
+ * @param supplier the supplier to execute
+ * @param the type of the value returned by the supplier
+ * @return the task representing the supplier
+ */
+ Task runAsyncSupplier(@NotNull Supplier supplier);
+
+ /**
+ * Schedules a supplier to be executed asynchronously after a delay.
+ *
+ * @param supplier the supplier to execute
+ * @param delay the delay before execution
+ * @param unit the unit of the delay
+ * @param the type of the value returned by the supplier
+ * @return the task representing the supplier
+ */
+ Task runDelayedSupplier(@NotNull Supplier supplier, long delay, @NotNull TimeUnit unit);
- private static IScheduler scheduler;
- private static final ILogger logger = ILogger.forThisClass();
-
- private static IScheduler getScheduler() {
- if (scheduler == null) {
- logger.debug("Scheduler is not set, using JavaScheduler.");
- setScheduler(new JavaScheduler());
- }
- return scheduler;
- }
-
- public static void setScheduler(IScheduler scheduler) {
- Scheduler.scheduler = scheduler;
- }
-
- public static void executeSync(Runnable runnable) {
- getScheduler().executeSync(runnable);
- }
-
- public static void executeAsync(Runnable runnable) {
- getScheduler().executeAsync(runnable);
- }
-
- public static Task runSync(Runnable runnable) {
- return getScheduler().runSync(runnable);
- }
-
- public static Task runAsync(Runnable runnable) {
- return getScheduler().runAsync(runnable);
- }
-
- public static Task runDelayed(Runnable runnable, long delay, TimeUnit unit) {
- return getScheduler().runDelayed(runnable, delay, unit);
- }
-
- public static Task runSyncCallable(Callable callable) {
- return getScheduler().runSyncCallable(callable);
- }
-
- public static Task runAsyncCallable(Callable callable) {
- return getScheduler().runAsyncCallable(callable);
- }
-
- public static Task runDelayedCallable(Callable callable, long delay, TimeUnit unit) {
- return getScheduler().runDelayedCallable(callable, delay, unit);
- }
-
- public static Task callSyncSupplier(Supplier supplier) {
- return getScheduler().runSyncSupplier(supplier);
- }
-
- public static Task callAsyncSupplier(Supplier supplier) {
- return getScheduler().runAsyncSupplier(supplier);
- }
-
- public static Task callDelayedSupplier(Supplier supplier, long delay, TimeUnit unit) {
- return getScheduler().runDelayedSupplier(supplier, delay, unit);
- }
-
- @NotNull
- public static ScheduledExecutorService newScheduledThreadPool(int i) {
- return Executors.newScheduledThreadPool(i);
- }
}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/Schedulers.java b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/Schedulers.java
new file mode 100644
index 0000000..639c48b
--- /dev/null
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/Schedulers.java
@@ -0,0 +1,36 @@
+package dev.spoocy.utils.common.scheduler;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Factory and helper methods for creating scheduler instances.
+ *
+ * @author Spoocy99 | GitHub: Spoocy99
+ */
+public final class Schedulers {
+
+ private Schedulers() {
+ throw new UnsupportedOperationException("This utility class cannot be instantiated");
+ }
+
+ @NotNull
+ public static Scheduler javaScheduler(int corePoolSize) {
+ return new JavaScheduler(corePoolSize);
+ }
+
+ @NotNull
+ public static Scheduler javaScheduler(@NotNull ExecutorService executorService,
+ @NotNull ScheduledExecutorService scheduledExecutorService) {
+ return new JavaScheduler(executorService, scheduledExecutorService);
+ }
+
+ @NotNull
+ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
+ return Executors.newScheduledThreadPool(corePoolSize);
+ }
+}
+
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/ScheduledTask.java b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/CompletableTask.java
similarity index 93%
rename from common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/ScheduledTask.java
rename to common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/CompletableTask.java
index 554bc48..6d959de 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/ScheduledTask.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/CompletableTask.java
@@ -12,17 +12,17 @@
* @author Spoocy99 | GitHub: Spoocy99
*/
-public class ScheduledTask implements Task, Callable, Future {
+public class CompletableTask implements Task, Callable, Future {
- public static ScheduledTask create() {
- return new ScheduledTask<>();
+ public static CompletableTask empty() {
+ return new CompletableTask<>();
}
private final List> results;
private final CompletableFuture future;
private Throwable exceptionWhileExecution;
- private ScheduledTask() {
+ private CompletableTask() {
this.future = new CompletableFuture<>();
this.results = new CopyOnWriteArrayList<>();
}
@@ -127,7 +127,6 @@ public void onException(@NotNull Task task, Throwable throwable) {
});
}
- @Override
public void complete(@Nullable V value) {
future.complete(value);
@@ -139,7 +138,6 @@ public void complete(@Nullable V value) {
results.forEach(result -> result.onSuccess(this, value));
}
- @Override
public void fail(@NotNull Throwable throwable) {
this.exceptionWhileExecution = throwable;
this.future.completeExceptionally(throwable);
@@ -160,12 +158,12 @@ public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
- public ScheduledTask clearResults() {
+ public CompletableTask clearResults() {
this.results.clear();
return this;
}
- public ScheduledTask addResult(@NotNull TaskResult result) {
+ public CompletableTask addResult(@NotNull TaskResult result) {
check(result);
results.add(result);
return this;
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/Task.java b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/Task.java
index 6266944..4d6dc15 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/Task.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/scheduler/task/Task.java
@@ -1,12 +1,17 @@
package dev.spoocy.utils.common.scheduler.task;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
+ * Represents a unit of work that can be executed, tracked, and monitored for its completion status.
+ * This interface provides methods to check the task's state (e.g., completed, cancelled, or failed)
+ * and to attach callback mechanisms for responding to various outcomes of the task execution.
+ *
+ * @param the type of the result produced by the task
+ *
* @author Spoocy99 | GitHub: Spoocy99
*/
@@ -40,7 +45,7 @@ public interface Task {
*
* @return this task for chaining
*/
- Task onSuccess(Runnable runnable);
+ Task onSuccess(@NotNull Runnable runnable);
/**
* Executes the given consumer when the task is successfully completed.
@@ -67,7 +72,7 @@ public interface Task {
*
* @return this task for chaining
*/
- Task onCancelled(Runnable runnable);
+ Task onCancelled(@NotNull Runnable runnable);
/**
* Executes the given consumer when the task is cancelled.
@@ -76,7 +81,7 @@ public interface Task {
*
* @return this task for chaining
*/
- Task onCancelled(@NotNull Consumer super Task> consumer);
+ Task onCancelled(@NotNull Consumer super Task> consumer);
/**
* Executes the given runnable when the task is cancelled.
@@ -85,7 +90,7 @@ public interface Task {
*
* @return this task for chaining
*/
- Task onException(Runnable runnable);
+ Task onException(@NotNull Runnable runnable);
/**
* Executes the given consumer when the task fails to complete.
@@ -105,27 +110,4 @@ public interface Task {
*/
Task onException(@NotNull BiConsumer super Task, ? super Throwable> consumer);
- /**
- * Completes the task with the given value.
- *
- * @param value the value to complete the task with
- */
- void complete(@Nullable V value);
-
- /**
- * Fails the task with the given exception.
- *
- * @param throwable the exception to fail the task with
- */
- void fail(@NotNull Throwable throwable);
-
- /**
- * Cancels the task.
- *
- * @param mayInterruptIfRunning whether the task should be interrupted if it is running
- *
- * @return {@code true} if the task is now cancelled, {@code false} otherwise
- */
- boolean cancel(boolean mayInterruptIfRunning);
-
}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/text/StringUtils.java b/common-utils/src/main/java/dev/spoocy/utils/common/text/StringUtils.java
index 65bfbb7..70e2b23 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/text/StringUtils.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/text/StringUtils.java
@@ -3,6 +3,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Deque;
import java.util.List;
import java.util.Optional;
@@ -16,6 +17,14 @@ public static boolean isNullOrEmpty(@Nullable String s) {
return s == null || s.isEmpty();
}
+ public static boolean isNullOrEmpty(@Nullable CharSequence s) {
+ return s == null || s.length() == 0;
+ }
+
+ public static boolean isBlank(@Nullable String s) {
+ return s == null || s.trim().isEmpty();
+ }
+
public static String[] splitToLines(@NotNull String s) {
return s.split("\n");
}
@@ -47,19 +56,65 @@ public static String getAfterLast(@NotNull String s, @NotNull String separator)
.orElse("");
}
- public static String join(@NotNull List strings, @NotNull String separator) {
- return join(strings.toArray(new String[0]), separator);
- }
+ public static String nullSafe(@Nullable String nullable, @NotNull String replacement) {
+ return nullable == null ? replacement : nullable;
+ }
+
+ public static String replace(@Nullable String input, @Nullable String pattern, @Nullable String replacement) {
+ if (!isNullOrEmpty(input) || !isNullOrEmpty(pattern) || replacement == null) {
+ return input;
+ }
+ int index = input.indexOf(pattern);
+
+ if (index == -1) {
+ return input;
+ }
- public static String join(@NotNull String[] strings, @NotNull String separator) {
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < strings.length; i++) {
- builder.append(strings[i]);
- if (i < strings.length - 1) {
- builder.append(separator);
- }
+ int capacity = input.length();
+ if (replacement.length() > pattern.length()) {
+ capacity += 16;
}
- return builder.toString();
+
+ StringBuilder sb = new StringBuilder(capacity);
+
+ int pos = 0;
+ int patLen = pattern.length();
+
+ while (index >= 0) {
+ sb.append(input, pos, index);
+ sb.append(replacement);
+ pos = index + patLen;
+ index = input.indexOf(pattern, pos);
+ }
+
+ sb.append(input, pos, input.length());
+ return sb.toString();
}
+ public static String[] tokenizeToStringArray(@Nullable String input, @NotNull String token) {
+ if (isNullOrEmpty(input)) {
+ return new String[0];
+ }
+
+ String[] tokens = input.split(token);
+ List result = new java.util.ArrayList<>();
+ for (String t : tokens) {
+ if (!isNullOrEmpty(t)) {
+ result.add(t);
+ }
+ }
+
+ return result.toArray(new String[0]);
+ }
+
+ public static String collectionToDelimitedString(@NotNull Deque elements, @NotNull String appender) {
+ StringBuilder sb = new StringBuilder();
+ for (String element : elements) {
+ sb.append(element).append(appender);
+ }
+ if (sb.length() > 0) {
+ sb.setLength(sb.length() - appender.length());
+ }
+ return sb.toString();
+ }
}
diff --git a/common-utils/src/main/java/dev/spoocy/utils/common/version/Version.java b/common-utils/src/main/java/dev/spoocy/utils/common/version/Version.java
index 02bd2c6..0316886 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/version/Version.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/version/Version.java
@@ -15,6 +15,8 @@
public interface Version extends Comparable, Serializable {
+ Version ZERO = new SimpleVersion(0, 0, 0, null, null);
+
/**
* @return The major version
*/
diff --git a/common-utils/src/main/java/module-info.java b/common-utils/src/main/java/module-info.java
index 5ae097b..029e72f 100644
--- a/common-utils/src/main/java/module-info.java
+++ b/common-utils/src/main/java/module-info.java
@@ -3,8 +3,8 @@
*/
module dev.spoocy.utils.common {
- requires org.jetbrains.annotations;
- requires org.slf4j;
+ requires static org.jetbrains.annotations;
+ requires static org.slf4j;
requires java.logging;
exports dev.spoocy.utils.common.cache;
diff --git a/common-utils/src/test/java/dev/spoocy/utils/common/collections/SortedArrayListTest.java b/common-utils/src/test/java/dev/spoocy/utils/common/collections/SortedArrayListTest.java
new file mode 100644
index 0000000..a645677
--- /dev/null
+++ b/common-utils/src/test/java/dev/spoocy/utils/common/collections/SortedArrayListTest.java
@@ -0,0 +1,79 @@
+package dev.spoocy.utils.common.collections;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class SortedArrayListTest {
+
+ @Test
+ public void addKeepsElementsSorted() {
+ SortedArrayList list = new SortedArrayList<>();
+
+ list.add(5);
+ list.add(1);
+ list.add(3);
+
+ assertEquals(Arrays.asList(1, 3, 5), list);
+ }
+
+ @Test
+ public void removeObjectRemovesOnlyFirstMatch() {
+ SortedArrayList list = new SortedArrayList<>(Arrays.asList(1, 2, 2, 3));
+
+ boolean removed = list.remove(Integer.valueOf(2));
+
+ assertTrue(removed);
+ assertEquals(Arrays.asList(1, 2, 3), list);
+ }
+
+ @Test
+ public void removeByIndexReturnsRemovedElement() {
+ SortedArrayList list = new SortedArrayList<>(Arrays.asList(1, 2, 3));
+
+ Integer removed = list.remove(2);
+
+ assertEquals(3, removed);
+ assertEquals(Arrays.asList(1, 2), list);
+ }
+
+ @Test
+ public void retainAllRetainsMatchingElements() {
+ SortedArrayList list = new SortedArrayList<>(Arrays.asList(1, 2, 3, 4));
+
+ boolean changed = list.retainAll(Arrays.asList(2, 4, 8));
+
+ assertTrue(changed);
+ assertEquals(Arrays.asList(2, 4), list);
+ }
+
+ @Test
+ public void positionalMutatorsAreUnsupported() {
+ SortedArrayList list = new SortedArrayList<>(Arrays.asList(1, 2, 3));
+
+ assertThrows(UnsupportedOperationException.class, () -> list.set(1, 10));
+ assertThrows(UnsupportedOperationException.class, () -> list.add(1, 10));
+ }
+
+ @Test
+ public void iteratorIsReadOnlySnapshot() {
+ SortedArrayList list = new SortedArrayList<>(Arrays.asList(1, 2, 3));
+ Iterator iterator = list.iterator();
+
+ assertEquals(1, iterator.next());
+ assertThrows(UnsupportedOperationException.class, iterator::remove);
+ }
+
+ @Test
+ public void addAllAtIndexValidatesBoundsAndKeepsSortedOrder() {
+ SortedArrayList list = new SortedArrayList<>(Arrays.asList(2, 4));
+
+ assertTrue(list.addAll(1, Arrays.asList(3, 1)));
+ assertEquals(Arrays.asList(1, 2, 3, 4), list);
+ assertThrows(IndexOutOfBoundsException.class, () -> list.addAll(9, Arrays.asList(5)));
+ }
+}
+
diff --git a/common-utils/src/test/java/dev/spoocy/utils/common/exceptions/WrappedExceptionTest.java b/common-utils/src/test/java/dev/spoocy/utils/common/exceptions/WrappedExceptionTest.java
new file mode 100644
index 0000000..0d7a539
--- /dev/null
+++ b/common-utils/src/test/java/dev/spoocy/utils/common/exceptions/WrappedExceptionTest.java
@@ -0,0 +1,59 @@
+package dev.spoocy.utils.common.exceptions;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class WrappedExceptionTest {
+
+ @Test
+ void wrapKeepsRuntimeExceptionsUntouched() {
+ RuntimeException runtimeException = new IllegalStateException("boom");
+
+ RuntimeException wrapped = WrappedException.wrap(runtimeException);
+
+ assertSame(runtimeException, wrapped);
+ }
+
+ @Test
+ void wrapConvertsIOExceptionToUncheckedIOException() {
+ IOException ioException = new IOException("io");
+
+ RuntimeException wrapped = WrappedException.wrap(ioException);
+
+ assertSame(UncheckedIOException.class, wrapped.getClass());
+ assertSame(ioException, wrapped.getCause());
+ }
+
+ @Test
+ void wrapRethrowsErrors() {
+ AssertionError error = new AssertionError("fatal");
+
+ AssertionError thrown = assertThrows(AssertionError.class, () -> WrappedException.wrap(error));
+
+ assertSame(error, thrown);
+ }
+
+ @Test
+ void rethrowConvertsIOExceptionToUncheckedIOException() {
+ IOException ioException = new IOException("io");
+
+ UncheckedIOException thrown = assertThrows(UncheckedIOException.class, () -> WrappedException.rethrow(ioException));
+
+ assertSame(ioException, thrown.getCause());
+ }
+
+ @Test
+ void rethrowWrapsCheckedExceptions() {
+ Exception checked = new Exception("checked");
+
+ WrappedException thrown = assertThrows(WrappedException.class, () -> WrappedException.rethrow(checked));
+
+ assertSame(checked, thrown.getCause());
+ }
+}
+
diff --git a/config-utils-yaml/pom.xml b/config-utils-yaml/pom.xml
deleted file mode 100644
index 95af7ee..0000000
--- a/config-utils-yaml/pom.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
- 4.0.0
-
- dev.spoocy.utils
- root
- 1.0.12
-
-
- config-utils-yaml
-
-
-
-
- org.jetbrains
- annotations
-
-
-
- dev.spoocy.utils
- common-utils
-
-
-
- dev.spoocy.utils
- config-utils
-
-
-
- org.yaml
- snakeyaml
-
-
-
-
-
\ No newline at end of file
diff --git a/config-utils-yaml/src/main/java/dev/spoocy/utils/config/YamlConfig.java b/config-utils-yaml/src/main/java/dev/spoocy/utils/config/YamlConfig.java
deleted file mode 100644
index 252dcdb..0000000
--- a/config-utils-yaml/src/main/java/dev/spoocy/utils/config/YamlConfig.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package dev.spoocy.utils.config;
-
-/**
- * @author Spoocy99 | GitHub: Spoocy99
- */
-
-public class YamlConfig {
-}
diff --git a/config-utils-yaml/src/main/java/module-info.java b/config-utils-yaml/src/main/java/module-info.java
deleted file mode 100644
index 1ae31d4..0000000
--- a/config-utils-yaml/src/main/java/module-info.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * @author Spoocy99 | GitHub: Spoocy99
- */
-
-module dev.spoocy.utils.config {
- requires org.jetbrains.annotations;
- requires org.yaml.snakeyaml;
-
- exports dev.spoocy.utils.config;
-}
\ No newline at end of file
diff --git a/config-utils/pom.xml b/config-utils/pom.xml
index 1829653..3122ab2 100644
--- a/config-utils/pom.xml
+++ b/config-utils/pom.xml
@@ -7,7 +7,7 @@
dev.spoocy.utils
root
- 1.0.12
+ 1.0.13
config-utils
@@ -32,6 +32,13 @@
org.json
json
+ true
+
+
+
+ org.yaml
+ snakeyaml
+ true
diff --git a/config-utils/src/main/java/dev/spoocy/utils/config/BaseResourceResolver.java b/config-utils/src/main/java/dev/spoocy/utils/config/BaseResourceResolver.java
new file mode 100644
index 0000000..39ef309
--- /dev/null
+++ b/config-utils/src/main/java/dev/spoocy/utils/config/BaseResourceResolver.java
@@ -0,0 +1,246 @@
+package dev.spoocy.utils.config;
+
+import dev.spoocy.utils.common.misc.Args;
+import dev.spoocy.utils.common.misc.FileUtils;
+import dev.spoocy.utils.config.constructor.Constructor;
+import dev.spoocy.utils.config.io.ClassPathResource;
+import dev.spoocy.utils.config.io.FileSystemResource;
+import dev.spoocy.utils.config.io.Resource;
+import dev.spoocy.utils.config.io.WriteableResource;
+import dev.spoocy.utils.config.loader.AnnotatedConfigLoader;
+import dev.spoocy.utils.config.loader.ConfigLoader;
+import dev.spoocy.utils.config.loader.ConfigSource;
+import dev.spoocy.utils.config.representer.Representer;
+import dev.spoocy.utils.config.update.ConfigUpdater;
+import dev.spoocy.utils.reflection.Reflection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Spoocy99 | GitHub: Spoocy99
+ */
+
+public class BaseResourceResolver implements ResourceResolver {
+
+ private static final AnnotatedConfigLoader CONFIG_LOADER = new AnnotatedConfigLoader();
+ private static final String CLASSPATH_FILE_PREFIX = "classpath:";
+ private static final String EXTERNAL_FILE_PREFIX = "file:";
+
+ private final Map> loaders = new HashMap<>();
+
+ @Nullable
+ private final ClassLoader classLoader;
+
+ public BaseResourceResolver(@Nullable ClassLoader classLoader, @NotNull ConfigLoader, ?>... loader) {
+ this.classLoader = classLoader;
+
+ for (ConfigLoader, ?> configLoader : loader) {
+ registerLoader(configLoader);
+ }
+ }
+
+ public void registerLoader(@NotNull ConfigLoader, ?> loader) {
+ Args.notNull(loader, "loader");
+
+ for (String extension : loader.getSupportedExtensions()) {
+ this.loaders.put(extension.toLowerCase(), loader);
+ }
+ }
+
+ protected Config createEmpty(@NotNull Resource resource) {
+ return requireLoader(resource).createEmpty();
+ }
+
+ @Override
+ public @NotNull Resource resolve(@NotNull String location) {
+ Args.notNull(location, "location");
+
+ if (location.startsWith(CLASSPATH_FILE_PREFIX)) {
+ String path = location.substring(CLASSPATH_FILE_PREFIX.length());
+ return new ClassPathResource(path, this.classLoader);
+ }
+
+ if (location.startsWith(EXTERNAL_FILE_PREFIX)) {
+ String path = location.substring(EXTERNAL_FILE_PREFIX.length());
+ return new FileSystemResource(path);
+ }
+
+ return Resources.fromPath(location);
+ }
+
+ @Nullable
+ protected ConfigLoader extends Config, ?> resolveLoader(@NotNull Resource resource) {
+ String filename = resource.getFilename();
+ String extension = filename == null ? "" : FileUtils.getFileExtension(filename);
+ return resolveLoaderByExtension(extension);
+ }
+
+ @Nullable
+ protected ConfigLoader, ?> resolveLoaderByExtension(@NotNull String extension) {
+ return this.loaders.get(extension.toLowerCase());
+ }
+
+ @NotNull
+ protected ConfigLoader, ?> requireLoader(@NotNull Resource resource) {
+ ConfigLoader, ?> loader = resolveLoader(resource);
+ if (loader == null) {
+ throw new IllegalArgumentException("No config loader for resource " + resource);
+ }
+ return loader;
+ }
+
+ @Override
+ public @NotNull Config loadConfig(@NotNull Resource resource, @NotNull Constructor constructor) throws IOException {
+ Args.notNull(resource, "resource");
+ Args.notNull(constructor, "constructor");
+
+ ConfigLoader extends Config, ?> loader = requireLoader(resource);
+ return loader.load(resource, constructor);
+ }
+
+ @Override
+ public @NotNull Config loadConfig(
+ @NotNull Resource resource,
+ @NotNull Constructor constructor,
+ @NotNull ConfigUpdater updater
+ ) throws IOException {
+ Config config = loadConfig(resource, constructor);
+ updater.run(config);
+ return config;
+ }
+
+ @Override
+ public @NotNull Document loadDocument(@NotNull Resource resource, @NotNull Constructor constructor)
+ throws IOException {
+ return loadConfig(resource, constructor).withRelation(resource);
+ }
+
+ @Override
+ public @NotNull Document loadDocument(
+ @NotNull Resource resource,
+ @NotNull Constructor constructor,
+ @NotNull ConfigUpdater updater
+ ) throws IOException {
+ Document document = loadDocument(resource, constructor);
+
+ if (updater.run(document) > 0) {
+
+ Resource relation = document.getRelation();
+
+ if (relation instanceof WriteableResource) {
+ document.save((WriteableResource) relation);
+ }
+
+ }
+
+ return document;
+ }
+
+ @Override
+ public @NotNull T load(@NotNull Class type, @NotNull Readable readable) {
+ return CONFIG_LOADER.load(type, readable);
+ }
+
+ @Override
+ public @NotNull T load(@NotNull Class type, @NotNull Constructor constructor) throws IOException {
+ Args.notNull(type, "type");
+ Args.notNull(constructor, "constructor");
+
+ ConfigSource source = requireSource(type);
+ Resource resource = resolveSourceResource(type, source);
+ return load(type, constructor, resource, source);
+ }
+
+ @Override
+ public @NotNull T load(@NotNull Class type, @NotNull Constructor constructor, @NotNull Resource resource)
+ throws IOException {
+ Args.notNull(type, "type");
+ Args.notNull(constructor, "constructor");
+ Args.notNull(resource, "resource");
+
+ ConfigSource source = Reflection.getAnnotation(type, ConfigSource.class, false);
+ return load(type, constructor, resource, source);
+ }
+
+ @NotNull
+ private T load(
+ @NotNull Class type,
+ @NotNull Constructor constructor,
+ @NotNull Resource resource,
+ @Nullable ConfigSource source
+ ) throws IOException {
+ Document document = loadAnnotatedDocument(resource, constructor, source);
+ AnnotatedConfigLoader.LoadResult result = CONFIG_LOADER.loadResult(type, document);
+
+ if (result.mutations() > 0) {
+ Resource relation = document.getRelation();
+ if (relation instanceof WriteableResource) {
+ document.save((WriteableResource) relation);
+ }
+ }
+
+ return result.instance();
+ }
+
+ @NotNull
+ private Document loadAnnotatedDocument(
+ @NotNull Resource resource,
+ @NotNull Constructor constructor,
+ @Nullable ConfigSource source
+ ) throws IOException {
+ if (source != null && source.allowMissingResource() && !resource.exists()) {
+ return createEmpty(resource).withRelation(resource);
+ }
+
+ return loadDocument(resource, constructor);
+ }
+
+ @Override
+ public void save(@NotNull Object instance, @NotNull Representer representer) throws IOException {
+ Args.notNull(instance, "instance");
+ Args.notNull(representer, "representer");
+
+ ConfigSource source = requireSource(instance.getClass());
+ Resource resource = resolveSourceResource(instance.getClass(), source);
+ save(instance, representer, resource);
+ }
+
+ @Override
+ public void save(@NotNull Object instance, @NotNull Representer representer, @NotNull Resource resource) throws IOException {
+ Args.notNull(instance, "instance");
+ Args.notNull(resource, "resource");
+
+ Document document = createEmpty(resource).withRelation(resource);
+ Resource relation = document.getRelation();
+
+ if (!(relation instanceof WriteableResource)) {
+ throw new IllegalArgumentException("Resource is not writeable: " + resource);
+ }
+
+ CONFIG_LOADER.write(instance, document);
+ document.save((WriteableResource) relation, representer);
+ }
+
+ @NotNull
+ private static ConfigSource requireSource(@NotNull Class> type) {
+ ConfigSource source = Reflection.getAnnotation(type, ConfigSource.class, false);
+ if (source == null) {
+ throw new IllegalArgumentException("Missing @ConfigSource for class: " + type.getName());
+ }
+ return source;
+ }
+
+ @NotNull
+ private Resource resolveSourceResource(@NotNull Class> type, @NotNull ConfigSource source) {
+ String location = source.value().trim();
+ if (location.isEmpty()) {
+ throw new IllegalArgumentException("Missing @ConfigSource(value) for class: " + type.getName());
+ }
+
+ return resolve(location);
+ }
+}
diff --git a/config-utils/src/main/java/dev/spoocy/utils/config/CommentType.java b/config-utils/src/main/java/dev/spoocy/utils/config/CommentType.java
deleted file mode 100644
index e31cb5e..0000000
--- a/config-utils/src/main/java/dev/spoocy/utils/config/CommentType.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package dev.spoocy.utils.config;
-
-/**
- * @author Spoocy99 | GitHub: Spoocy99
- */
-
-public enum CommentType {
- HEADER,
- PATH,
- INLINE,
-}
diff --git a/config-utils/src/main/java/dev/spoocy/utils/config/Commentable.java b/config-utils/src/main/java/dev/spoocy/utils/config/Commentable.java
index 884c613..f8a5f59 100644
--- a/config-utils/src/main/java/dev/spoocy/utils/config/Commentable.java
+++ b/config-utils/src/main/java/dev/spoocy/utils/config/Commentable.java
@@ -3,7 +3,6 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.Collection;
import java.util.List;
/**
@@ -13,49 +12,18 @@
public interface Commentable {
/**
- * Checks if this config supports comments.
- *
- * @return {@code true} if this config supports comments, {@code false} otherwise.
- */
- default boolean isCommentable() {
- return false;
- }
-
- /**
- * Gets the comments of the specified type and path.
- *
- * @param type the type of comments to get.
- * @param path the path to get the comments of, or an empty string for header comments.
- *
- * @return a list of comments for the specified type and path, or an empty list if there are no comments for the specified type and path.
- *
- * @throws UnsupportedOperationException if this config does not support comments.
- */
- default List getComments(@NotNull CommentType type, @NotNull String path) {
- throw new UnsupportedOperationException("This config does not support comments.");
- }
-
- /**
- * Sets the comments of the specified type and path.
- *
- * @param type the type of comments to set.
- * @param path the path to set the comments of, or an empty string for header comments.
- * @param comments the comments to set for the specified type and path, or {@code null} to remove all comments for the specified type and path.
+ * Gets the header comments of this config.
*
- * @throws UnsupportedOperationException if this config does not support comments.
+ * @return a list of header comments, or an empty list if there are no header comments.
*/
- default void setComments(@NotNull CommentType type, @NotNull String path, @Nullable Collection comments) {
- throw new UnsupportedOperationException("This config does not support comments.");
- }
+ List getHeaderComments();
/**
- * Gets the header comments of this config.
+ * Gets the footer comments of this config.
*
- * @return a list of header comments, or an empty list if there are no header comments.
+ * @return a list of footer comments, or an empty list if there are no footer comments.
*/
- default List getHeaderComments() {
- return getComments(CommentType.HEADER, "");
- }
+ List getFooterComments();
/**
* Gets the comments of the specified path.
@@ -64,9 +32,7 @@ default List getHeaderComments() {
*
* @return a list of comments for the specified path, or an empty list if there are no comments for the specified path.
*/
- default List getComments(@NotNull final String path) {
- return getComments(CommentType.PATH, path);
- }
+ List getComments(@NotNull final String path);
/**
* Gets the inline comments of the specified path.
@@ -75,18 +41,21 @@ default List getComments(@NotNull final String path) {
*
* @return a list of inline comments for the specified path, or an empty list if there are no inline comments for the specified path.
*/
- default List getInlineComments(@NotNull final String path) {
- return getComments(CommentType.INLINE, path);
- }
+ List getInlineComments(@NotNull final String path);
/**
* Sets the header comments of this config.
*
* @param comments the header comments to set, or {@code null} to remove all header comments.
*/
- default void setHeaderComments(@Nullable List comments) {
- setComments(CommentType.HEADER, "", comments);
- }
+ void setHeaderComments(@Nullable List