diff --git a/bukkit-utils/pom.xml b/bukkit-utils/pom.xml
index 2c244ea..579dd63 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
@@ -66,12 +66,14 @@
io.papermc.paper
paper-api
1.21.11-R0.1-SNAPSHOT
+ provided
com.mojang
authlib
3.16.29
+ provided
diff --git a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/BukkitSerializer.java b/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/BukkitSerializer.java
deleted file mode 100644
index 2903814..0000000
--- a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/BukkitSerializer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package dev.spoocy.utils.bukkit.serializers;
-
-import dev.spoocy.utils.config.serializer.Serializer;
-import org.bukkit.configuration.serialization.ConfigurationSerializable;
-import org.bukkit.configuration.serialization.ConfigurationSerialization;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Map;
-
-/**
- * @author Spoocy99 | GitHub: Spoocy99
- */
-
-public class BukkitSerializer implements Serializer {
-
- private final Class clazz;
-
- public BukkitSerializer(@NotNull Class clazz) {
- this.clazz = clazz;
- }
-
- @Override
- public @NotNull Map serialize(@NotNull V object) {
- return object.serialize();
- }
-
- @Override
- public @NotNull V deserialize(@NotNull Map map) {
- try {
- ConfigurationSerializable obj = ConfigurationSerialization.deserializeObject(map, clazz);
- if (obj == null) {
- throw new IllegalArgumentException("Deserialized object is null.");
- }
- return (V) obj;
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException("Failed to deserialize object: " + e.getMessage());
- }
- }
-}
diff --git a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/GameProfileSerializer.java b/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/GameProfileSerializer.java
index 5be5a93..8c87dfb 100644
--- a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/GameProfileSerializer.java
+++ b/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/GameProfileSerializer.java
@@ -4,7 +4,6 @@
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
-import dev.spoocy.utils.config.serializer.Serializer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Type;
@@ -16,58 +15,12 @@
* @author Spoocy99 | GitHub: Spoocy99
*/
-public class GameProfileSerializer implements Serializer, JsonSerializer, JsonDeserializer {
+public class GameProfileSerializer implements JsonSerializer, JsonDeserializer {
public static final GameProfileSerializer INSTANCE = new GameProfileSerializer();
private GameProfileSerializer() { }
- @Override
- public @NotNull Map serialize(@NotNull GameProfile object) {
- Map result = new HashMap<>();
-
- if (object.getId() != null) {
- result.put("id", object.getId());
- }
-
- if (object.getName() != null) {
- result.put("name", object.getName());
- }
-
- if (!object.getProperties().isEmpty()) {
- Map properties = new HashMap<>();
- for (Map.Entry entry : object.getProperties().entries()) {
- properties.put(entry.getKey(), entry.getValue().getValue());
- }
- result.put("properties", properties);
- }
-
- return result;
- }
-
- @Override
- public @NotNull GameProfile deserialize(@NotNull Map map) {
- UUID id = (UUID) map.get("id");
- String name = (String) map.get("name");
- GameProfile profile = new GameProfile(id, name);
-
- if (map.containsKey("properties")) {
- Map properties = null;
-
- try {
- properties = (Map) map.get("properties");
- } catch (IllegalArgumentException ignored) { }
-
- if(properties == null) {
- for (Map.Entry entry : properties.entrySet()) {
- profile.getProperties().put(entry.getKey(), new Property(entry.getKey(), (String) entry.getValue()));
- }
- }
- }
-
- return profile;
- }
-
@Override
public JsonElement serialize(GameProfile gameProfile, Type type, JsonSerializationContext jsonSerializationContext) {
JsonObject result = new JsonObject();
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
deleted file mode 100644
index 9c3dd24..0000000
--- a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/InventorySerializer.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package dev.spoocy.utils.bukkit.serializers;
-
-import dev.spoocy.utils.bukkit.biz.source_code.base64Coder.Base64Coder;
-import dev.spoocy.utils.common.exceptions.WrappedException;
-import dev.spoocy.utils.config.serializer.Serializer;
-import org.bukkit.Bukkit;
-import org.bukkit.inventory.Inventory;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.util.io.BukkitObjectInputStream;
-import org.bukkit.util.io.BukkitObjectOutputStream;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.util.Map;
-
-/**
- * @author Spoocy99 | GitHub: Spoocy99
- */
-
-public class InventorySerializer implements Serializer {
-
- public static final InventorySerializer INSTANCE = new InventorySerializer();
-
- private InventorySerializer() {
- super();
- }
-
- @Override
- public @NotNull Map serialize(@NotNull Inventory object) {
- try {
- 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.close();
- String base64 = Base64Coder.encodeLines(outputStream.toByteArray());
-
- return Map.of("base64", base64);
- } catch (Throwable e) {
- WrappedException.rethrow(e);
- }
-
- throw new IllegalStateException("No Handle");
- }
-
- @Override
- public @NotNull Inventory deserialize(@NotNull Map map) {
- if(!map.containsKey("base64")) {
- throw new IllegalArgumentException("Map does not contain key 'base64'.");
- }
-
- try {
- String base64 = (String) map.get("base64");
- byte[] data = Base64Coder.decode(base64);
- ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
- BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
- Inventory inventory = Bukkit.getServer().createInventory(null, dataInput.readInt());
-
- for (int i = 0; i < inventory.getSize(); i++) {
- inventory.setItem(i, (ItemStack) dataInput.readObject());
- }
- dataInput.close();
- return inventory;
- } catch (Throwable e) {
- WrappedException.rethrow(e);
- }
-
- throw new IllegalStateException("No Handle");
- }
-}
diff --git a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/ItemStackSerializer.java b/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/ItemStackSerializer.java
deleted file mode 100644
index 32db923..0000000
--- a/bukkit-utils/src/main/java/dev/spoocy/utils/bukkit/serializers/ItemStackSerializer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package dev.spoocy.utils.bukkit.serializers;
-
-import dev.spoocy.utils.bukkit.biz.source_code.base64Coder.Base64Coder;
-import dev.spoocy.utils.common.exceptions.WrappedException;
-import dev.spoocy.utils.config.serializer.Serializer;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.util.io.BukkitObjectInputStream;
-import org.bukkit.util.io.BukkitObjectOutputStream;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.util.Map;
-
-/**
- * @author Spoocy99 | GitHub: Spoocy99
- */
-
-public class ItemStackSerializer implements Serializer {
-
- public static final ItemStackSerializer INSTANCE = new ItemStackSerializer();
-
- private ItemStackSerializer() {
- super();
- }
-
- @Override
- public @NotNull Map serialize(@NotNull ItemStack object) {
- try {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
-
- dataOutput.writeObject(object);
- dataOutput.close();
-
- String base64 = Base64Coder.encodeLines(outputStream.toByteArray());
-
- return Map.of("base64", base64);
- } catch (Throwable e) {
- WrappedException.rethrow(e);
- }
-
- throw new IllegalStateException("No Handle");
- }
-
- @Override
- public @NotNull ItemStack deserialize(@NotNull Map map) {
- if(!map.containsKey("base64")) {
- throw new IllegalArgumentException("Map does not contain key 'base64'.");
- }
-
- try {
-
- String base64 = (String) map.get("base64");
-
- ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(base64));
- BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
- ItemStack item = (ItemStack) dataInput.readObject();
- dataInput.close();
-
- return item;
- } catch (Exception e) {
- WrappedException.rethrow(e);
- }
-
- throw new IllegalStateException("No Handle");
- }
-}
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/ClassFinder.java b/common-utils/src/main/java/dev/spoocy/utils/common/misc/ClassFinder.java
index 8dfc499..c12ce68 100644
--- a/common-utils/src/main/java/dev/spoocy/utils/common/misc/ClassFinder.java
+++ b/common-utils/src/main/java/dev/spoocy/utils/common/misc/ClassFinder.java
@@ -1,5 +1,8 @@
package dev.spoocy.utils.common.misc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
/**
* @author Spoocy99 | GitHub: Spoocy99
*/
@@ -8,10 +11,12 @@ public class ClassFinder extends SecurityManager {
private static final ClassFinder INSTANCE = new ClassFinder();
+ @NotNull
public static String callingClassName() {
return callingClassName(2);
}
+ @NotNull
public static String callingClassName(final int depth) {
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
if (elements.length <= depth) {
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..b7e10b9 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,186 @@ 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)));
+ }
+
+ /**
+ * Converts the package name of a given class into a path-like resource representation.
+ * If the class is null or does not belong to any package, an empty string will be returned.
+ *
+ * @param clazz the class whose package name will be converted; can be null
+ * @return the package name as a resource path with path separators, or an empty string if the class is null or not in a package
+ */
+ 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..169a766 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,7 @@
package dev.spoocy.utils.common.scheduler;
-import dev.spoocy.utils.common.scheduler.task.ScheduledTask;
+import dev.spoocy.utils.common.misc.Args;
+import dev.spoocy.utils.common.scheduler.task.CompletableTask;
import dev.spoocy.utils.common.scheduler.task.Task;
import org.jetbrains.annotations.NotNull;
@@ -11,10 +12,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(@NotNull ExecutorService executorService, @NotNull ScheduledExecutorService scheduledExecutorService) {
+ this.executorService = Args.notNull(executorService, "executorService");
+ this.scheduledExecutorService = Args.notNull(scheduledExecutorService, "scheduledExecutorService");
+ }
@Override
public void executeSync(@NotNull Runnable runnable) {
@@ -23,7 +34,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 +93,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 +105,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 +134,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/ClassFinderTest.java b/common-utils/src/test/java/ClassFinderTest.java
index de3ba6a..25dc578 100644
--- a/common-utils/src/test/java/ClassFinderTest.java
+++ b/common-utils/src/test/java/ClassFinderTest.java
@@ -1,4 +1,5 @@
import dev.spoocy.utils.common.misc.ClassFinder;
+import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -18,6 +19,7 @@ void testCallingClassName() {
}
// Helper method to add an extra stack frame
+ @NotNull
private String helperCallingClassName() {
return ClassFinder.callingClassName();
}
@@ -28,6 +30,7 @@ void testCallingClassNameWithDepth() {
assertEquals(this.getClass().getName(), className);
}
+ @NotNull
private String helperCallingClassName(int depth) {
return ClassFinder.callingClassName(depth);
}
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..b9592cf 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,15 @@
org.json
json
+ true
+ compile
+
+
+
+ org.yaml
+ snakeyaml
+ true
+ compile
diff --git a/config-utils/src/main/java/dev/spoocy/utils/config/AbstractConfig.java b/config-utils/src/main/java/dev/spoocy/utils/config/AbstractConfig.java
new file mode 100644
index 0000000..25fc1b0
--- /dev/null
+++ b/config-utils/src/main/java/dev/spoocy/utils/config/AbstractConfig.java
@@ -0,0 +1,124 @@
+package dev.spoocy.utils.config;
+
+import dev.spoocy.utils.common.misc.Args;
+import dev.spoocy.utils.common.misc.FileUtils;
+import dev.spoocy.utils.config.io.Resource;
+import dev.spoocy.utils.config.io.WriteableResource;
+import dev.spoocy.utils.config.nodes.*;
+import dev.spoocy.utils.config.representer.Representer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * @author Spoocy99 | GitHub: Spoocy99
+ */
+
+public abstract class AbstractConfig extends MemorySection implements Config {
+
+ protected List header = Collections.emptyList();
+ protected List footer = Collections.emptyList();
+
+ public AbstractConfig() {
+ super();
+ }
+
+ @Override
+ public void save(@NotNull WriteableResource file, @NotNull Representer representer) throws IOException {
+ Args.notNull(file, "File cannot be null");
+ Args.notNull(representer, "Representer cannot be null");
+// if (file.exists() && !file.isWritable()) {
+// throw new IOException("Cannot write to file: " + file.getFile().getPath());
+// }
+
+ // Ensure parent directories exist before attempting to write.
+ // If the parent directories cannot be created due to permissions or other I/O issues, this
+ // will throw an IOException which we propagate to the caller.
+ FileUtils.createParentDirs(file.getFile());
+
+ // Attempt to open the output stream and write. If the file cannot be written
+ // due to permissions or other I/O issues, the underlying calls will throw
+ // an IOException which we propagate to the caller.
+ try (Writer writer = new OutputStreamWriter(file.getOutputStream())) {
+ writer.write(saveToString(representer));
+ }
+ }
+
+ @Override
+ public @NotNull Document withRelation(@NotNull Resource resource) {
+ return new FileDocument(this, resource);
+ }
+
+ @Override
+ public @NotNull List getFooterComments() {
+ return this.footer;
+ }
+
+ @Override
+ public @NotNull List getHeaderComments() {
+ return this.header;
+ }
+
+ @Override
+ public void setHeaderComments(@Nullable List comments) {
+ this.header = comments == null ? Collections.emptyList() : Collections.unmodifiableList(comments);
+ }
+
+ @Override
+ public void setFooterComments(@Nullable List comments) {
+ this.footer = comments == null ? Collections.emptyList() : Collections.unmodifiableList(comments);
+ }
+
+ @NotNull
+ protected Map, ?> representAsMap(@NotNull Representer representer) {
+ NodeTree tree = representer.createTree(this);
+ return unpackMap(tree);
+ }
+
+ protected Object unpack(@NotNull Node node) {
+ if (node instanceof NodeTree) {
+ return unpackMap((NodeTree) node);
+ }
+
+ if (node instanceof SequenceNode) {
+ return unpackSequence((SequenceNode) node);
+ }
+
+ if (node instanceof ScalarNode) {
+ return unpackScalar((ScalarNode) node);
+ }
+
+ throw new IllegalStateException("Unsupported node type: " + node.getClass().getName());
+ }
+
+ protected Map, ?> unpackMap(@NotNull NodeTree tree) {
+ Map