diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
new file mode 100644
index 0000000..d362284
--- /dev/null
+++ b/.github/workflows/format.yml
@@ -0,0 +1,14 @@
+name: Format
+
+on:
+ push:
+ branches: [ main ]
+
+jobs:
+ formatting:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: axel-op/googlejavaformat-action@v3
+ with:
+ args: "--skip-sorting-imports --replace --aosp"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4788b4b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,113 @@
+# User-specific stuff
+.idea/
+
+*.iml
+*.ipr
+*.iws
+
+# IntelliJ
+out/
+
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+target/
+
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+.flattened-pom.xml
+
+# Common working directory
+run/
diff --git a/action-item/pom.xml b/action-item/pom.xml
new file mode 100644
index 0000000..e17e898
--- /dev/null
+++ b/action-item/pom.xml
@@ -0,0 +1,32 @@
+
+
+ * + * @return Display name of the Action Item + */ + public String getDisplayName(); + + /** + * Gets the lore of the Action Item. + * + *
+ *
+ * @return Lore of the Action Item
+ */
+ public List
+ *
+ * @return Material of the Action Item
+ */
+ public Material getMaterial();
+
+ /**
+ * Gets the {@link EventPriority} for the Action Item.
+ *
+ *
+ *
+ * @return Event priority of the Action Item
+ */
+ public EventPriority getPriority();
+
+ /**
+ * Get the ItemStack of the Action Item.
+ *
+ *
+ *
+ * @return ItemStack of the Action Item
+ */
+ public ItemStack toItemStack();
+
+ /**
+ * Checks if the provided ItemStack is of this Action Item.
+ *
+ *
+ *
+ * @param item ItemStack to check
+ * @return {@code true} if the ItemStack is of this Action Item, else false
+ */
+ public boolean isThis(ItemStack item);
+
+ /**
+ * Register the action to be performed on interact with this item.
+ *
+ *
+ *
+ * @param player Player who performs the action
+ * @param action {@link EnumAction} performed on this item
+ * @param event {@link PlayerInteractEvent} triggered in the action event
+ */
+ public void onActionPerform(Player player, EnumAction action, PlayerInteractEvent event);
+
+ /** Enumeration for actions defined for an item. */
+ public enum EnumAction {
+ LEFT_CLICK,
+ LEFT_CLICK_SNEAKING,
+ LEFT_CLICK_SPRINTING,
+
+ RIGHT_CLICK,
+ RIGHT_CLICK_SNEAKING,
+ RIGHT_CLICK_SPRINTING,
+ ;
+ }
+}
diff --git a/action-item/src/main/java/com/pepedevs/corelib/item/ActionItemBase.java b/action-item/src/main/java/com/pepedevs/corelib/item/ActionItemBase.java
new file mode 100644
index 0000000..c218e53
--- /dev/null
+++ b/action-item/src/main/java/com/pepedevs/corelib/item/ActionItemBase.java
@@ -0,0 +1,97 @@
+package com.pepedevs.corelib.item;
+
+import com.google.common.base.Objects;
+import com.pepedevs.corelib.utils.StringUtils;
+import com.pepedevs.corelib.utils.itemstack.ItemMetaBuilder;
+import com.pepedevs.corelib.utils.itemstack.ItemStackUtils;
+import org.bukkit.Material;
+import org.bukkit.event.EventPriority;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/** Abstract class to be used for creating Action Items. */
+public abstract class ActionItemBase implements ActionItem {
+
+ protected final String display_name;
+ protected final List
+ *
+ * @param display_name Display name of the Action Item
+ * @param lore Lore of the Action Item
+ * @param material Material of the Action Item
+ * @param priority {@link EventPriority} of the Action Item
+ */
+ public ActionItemBase(
+ String display_name,
+ Collection
+ *
+ * @param display_name Display name of the Action Item
+ * @param lore Lore of the Action Item
+ * @param material Material of the Action Item
+ */
+ public ActionItemBase(String display_name, Collection
+ *
+ * @param item Action item to register.
+ * @return true if the provided item is not already registered.
+ */
+ public static boolean register(ActionItem item) {
+ return ACTION_ITEMS.add(item);
+ }
+
+ /**
+ * Unregisters the specified {@link ActionItem}.
+ *
+ *
+ *
+ * @param item Action item to unregister.
+ * @return true if the provided item was unregistered successfully.
+ */
+ public static boolean unregister(ActionItem item) {
+ return ACTION_ITEMS.remove(item);
+ }
+
+ @Override
+ protected boolean isAllowMultipleInstances() {
+ return false;
+ }
+
+ @Override
+ protected boolean isSingleInstanceForAllPlugin() {
+ return false;
+ }
+
+}
diff --git a/bungee-utils/pom.xml b/bungee-utils/pom.xml
new file mode 100644
index 0000000..98ccda8
--- /dev/null
+++ b/bungee-utils/pom.xml
@@ -0,0 +1,19 @@
+
+
+ *
+ * @param plugin Messaging plugin
+ * @param arguments Arguments to send
+ * @throws IOException thrown when an issue occur while writing to message stream
+ * @throws SecurityException thrown by the security manager to indicate a security violation
+ * @throws IllegalArgumentException thrown when a illegal argument is passed
+ */
+ public static void sendPluginMessage(Plugin plugin, Writable... arguments)
+ throws IOException, IllegalArgumentException, SecurityException {
+ /* do not send empty arguments */
+ if (arguments == null || arguments.length == 0) {
+ return;
+ }
+
+ /* make streams and write arguments */
+ final ByteArrayOutputStream array_stream = new ByteArrayOutputStream();
+ final DataOutputStream out_stream = new DataOutputStream(array_stream);
+ for (Writable argument : arguments) {
+ if (argument != null
+ && argument.getObjectToWrite() != null
+ && argument.getWriteType() != null) {
+ argument.writeTo(out_stream);
+ }
+ }
+
+ /* send */
+ Bukkit.getServer().sendPluginMessage(plugin, MESSAGING_CHANNEL, array_stream.toByteArray());
+ }
+
+ /**
+ * Send plugin message using the channel 'BungeeCord'.
+ *
+ *
+ *
+ * @param plugin Messaging plugin
+ * @param written Arguments from {@link Written}
+ * @throws IOException thrown when an issue occur while writing to message stream
+ * @throws SecurityException thrown by the security manager to indicate a security violation
+ * @throws IllegalArgumentException thrown when a illegal argument is passed
+ */
+ public static void sendPluginMessage(Plugin plugin, Written written)
+ throws IOException, IllegalArgumentException, SecurityException {
+ /* donnot send empty arguments */
+ if (written == null || written.getWritables().isEmpty()) {
+ return;
+ }
+
+ /* make streams and write arguments */
+ final ByteArrayOutputStream array_stream = new ByteArrayOutputStream();
+ final DataOutputStream out_stream = new DataOutputStream(array_stream);
+ for (Writable argument : written.getWritables()) {
+ if (argument != null
+ && argument.getObjectToWrite() != null
+ && argument.getWriteType() != null) {
+ argument.writeTo(out_stream);
+ }
+ }
+
+ /* send */
+ Bukkit.getServer().sendPluginMessage(plugin, MESSAGING_CHANNEL, array_stream.toByteArray());
+ }
+
+ /**
+ * Send plugin message using the channel 'BungeeCord' to a specific {@link Player}.
+ *
+ *
+ *
+ * @param plugin Messaging plugin
+ * @param player Player messenger
+ * @param arguments Arguments to send
+ * @throws IOException thrown when an issue occur while writing to message stream
+ * @throws SecurityException thrown by the security manager to indicate a security violation
+ * @throws IllegalArgumentException thrown when a illegal argument is passed
+ */
+ public static void sendPluginMessage(Plugin plugin, Player player, Writable... arguments)
+ throws IOException, IllegalArgumentException, SecurityException {
+ /* donnot send empty arguments */
+ if (player == null || arguments == null || arguments.length == 0) {
+ return;
+ }
+
+ /* make streams and write arguments */
+ final ByteArrayOutputStream array_stream = new ByteArrayOutputStream();
+ final DataOutputStream out_stream = new DataOutputStream(array_stream);
+ for (Writable argument : arguments) {
+ if (argument != null
+ && argument.getObjectToWrite() != null
+ && argument.getWriteType() != null) {
+ argument.writeTo(out_stream);
+ }
+ }
+
+ /* send */
+ player.sendPluginMessage(plugin, MESSAGING_CHANNEL, array_stream.toByteArray());
+ }
+
+ /**
+ * Send plugin message using the channel 'BungeeCord' to a specific {@link Player}.
+ *
+ *
+ *
+ * @param plugin Messaging plugin
+ * @param player Player messenger
+ * @param written Arguments to send
+ * @throws IOException thrown when an issue occur while writing to message stream
+ * @throws SecurityException thrown by the security manager to indicate a security violation
+ * @throws IllegalArgumentException thrown when a illegal argument is passed
+ */
+ public static void sendPluginMessage(Plugin plugin, Player player, Written written)
+ throws IOException, IllegalArgumentException, SecurityException {
+ /* donnot send empty arguments */
+ if (player == null || written == null || written.getWritables().isEmpty()) {
+ return;
+ }
+
+ /* make streams and write arguments */
+ final ByteArrayOutputStream array_stream = new ByteArrayOutputStream();
+ final DataOutputStream out_stream = new DataOutputStream(array_stream);
+ for (Writable argument : written.getWritables()) {
+ if (argument != null
+ && argument.getObjectToWrite() != null
+ && argument.getWriteType() != null) {
+ argument.writeTo(out_stream);
+ }
+ }
+
+ /* send */
+ player.sendPluginMessage(plugin, MESSAGING_CHANNEL, array_stream.toByteArray());
+ }
+}
diff --git a/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/ReadUtils.java b/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/ReadUtils.java
new file mode 100644
index 0000000..77c1b4a
--- /dev/null
+++ b/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/ReadUtils.java
@@ -0,0 +1,123 @@
+package com.pepedevs.corelib.utils.bungeecord;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+
+/** Class for reading responses from BungeeCord channel. */
+public class ReadUtils {
+
+ /**
+ * Read messages from 'BungeeCord' message channel.
+ *
+ *
+ *
+ * @param message Message from bungeecord channel
+ * @return Parsed message
+ */
+ public static Object[] read(byte[] message) {
+ if (message == null || message.length == 0) { // don't read empty messages
+ return new Object[0];
+ }
+
+ try {
+ final DataInputStream in_stream =
+ new DataInputStream(new ByteArrayInputStream(message));
+ final String argument = in_stream.readUTF();
+ final Object[] reponse = readResponseFully(argument, in_stream);
+
+ if (reponse == null || reponse.length == 0) {
+ return new String[0];
+ }
+
+ final Object[] all = new Object[reponse.length + 1];
+ for (int x = 0; x < all.length; x++) {
+ if (x == 0) {
+ all[x] = argument;
+ } else {
+ all[x] = reponse[(x - 1)];
+ }
+ }
+ return all;
+ } catch (Throwable t) {
+ t.printStackTrace();
+ return new String[0];
+ }
+ }
+
+ /**
+ * Read arguments from 'BungeeCord' message channel.
+ *
+ *
+ *
+ * @param message Argument from bungeecord channel
+ * @return Parsed message
+ */
+ public static String readArgument(byte[] message) {
+ final Object[] arg_repo = read(message);
+ if (arg_repo.length > 0) {
+ return (String) arg_repo[0];
+ }
+ return "";
+ }
+
+ /**
+ * Read response from 'BungeeCord' message channel.
+ *
+ *
+ *
+ * @param message Response from bungeecord channel
+ * @return Parsed message
+ */
+ public static Object[] readResponse(byte[] message) {
+ final Object[] arg_repo = read(message);
+ if (arg_repo.length > 1) {
+ Object[] response = new Object[arg_repo.length - 1];
+ System.arraycopy(arg_repo, 1, response, 0, response.length);
+ return response;
+ }
+ return new Object[0];
+ }
+
+ /**
+ * Read response fully from 'BungeeCord' message channel.
+ *
+ *
+ *
+ * @param argument Arguments for the message
+ * @param in {@link DataInputStream}
+ * @return Parsed message
+ */
+ private static Object[] readResponseFully(final String argument, final DataInputStream in) {
+ try {
+ // String fully = ""; /* readed from data input stream */
+ Object[] all = null;
+ switch (argument) {
+ case MessagingUtils.PLAYER_IP_ARGUMENT:
+ case MessagingUtils.SERVER_PLAYER_COUNT_ARGUMENT:
+ all = new Object[] {in.readUTF(), in.readInt()};
+ break;
+ case MessagingUtils.SERVER_PLAYER_LIST_ARGUMENT:
+ String server = in.readUTF();
+ String[] player_list = in.readUTF().split(", ");
+ all = new Object[player_list.length + 1];
+ all[0] = server;
+ System.arraycopy(player_list, 0, all, 1, player_list.length);
+ break;
+ case MessagingUtils.SERVER_NAME_ARGUMENT:
+ all = new Object[] {in.readUTF()};
+ break;
+ case MessagingUtils.SERVERS_NAMES_ARGUMENT:
+ String[] server_list = in.readUTF().split(", ");
+ all = new Object[server_list.length];
+ System.arraycopy(server_list, 0, all, 0, server_list.length);
+ break;
+ case MessagingUtils.SERVER_IP_ARGUMENT:
+ all = new Object[] {in.readUTF(), in.readUTF(), in.readUnsignedShort()};
+ break;
+ }
+ return all;
+ } catch (Throwable t) {
+ return new Object[0];
+ }
+ }
+}
diff --git a/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/Writable.java b/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/Writable.java
new file mode 100644
index 0000000..d2efc6c
--- /dev/null
+++ b/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/Writable.java
@@ -0,0 +1,116 @@
+package com.pepedevs.corelib.utils.bungeecord;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/** Class for writing plugin messages. */
+public class Writable {
+
+ private final Object to_write;
+ private final WriteType type;
+
+ /**
+ * Constructs the writable object.
+ *
+ *
+ *
+ * @param to_write Object to write
+ * @param type Write type
+ */
+ public Writable(Object to_write, WriteType type) {
+ this.to_write = to_write;
+ this.type = type;
+ }
+
+ /**
+ * Writes values to message.
+ *
+ *
+ *
+ * @param to_write Object value to write
+ * @return {@link Writable} object
+ */
+ public static Writable of(Object to_write) {
+ if (to_write instanceof String) {
+ throw new UnsupportedOperationException(
+ "The class of the object to write is not compatible with this operation!");
+ }
+
+ WriteType type = WriteType.of(to_write);
+ if (type != null) {
+ return new Writable(to_write, type);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the object which is to write.
+ *
+ *
+ *
+ * @return Object to write
+ */
+ public Object getObjectToWrite() {
+ return to_write;
+ }
+
+ /**
+ * Returns the write type.
+ *
+ *
+ *
+ * @return {@link WriteType}
+ */
+ public WriteType getWriteType() {
+ return type;
+ }
+
+ /**
+ * Writes to data output stream.
+ *
+ *
+ *
+ * @param data DataOutput to write to
+ * @throws IOException thrown when an issue occur while writing to message stream
+ * @throws SecurityException thrown by the security manager to indicate a security violation
+ * @throws IllegalArgumentException thrown when a illegal argument is passed
+ */
+ public void writeTo(DataOutput data)
+ throws IOException, IllegalArgumentException, SecurityException {
+ /* cannot write when the object to write is null or the write type is null */
+ if (to_write == null || type == null) {
+ throw new IllegalArgumentException("The WriteType or the object to write is null!");
+ }
+
+ /* check instance of object to write */
+ for (int x = 0; x < type.getPrimitiveClasses().length; x++) {
+ Class> type_pr_classes = type.getPrimitiveClasses()[x];
+ if (type_pr_classes.equals(to_write.getClass())) {
+ break;
+ } else if ((x + 1) == type.getPrimitiveClasses().length) {
+ throw new IllegalArgumentException(
+ "The WriteType does not match with the class of the object to write!");
+ }
+ }
+
+ if (type == WriteType.UTF) {
+ data.writeUTF((String) to_write);
+ } else {
+ for (Class> type_pr_classes : type.getPrimitiveClasses()) {
+ try {
+ final String write_method_name =
+ "write"
+ + (type.name().charAt(0)
+ + type.name().toLowerCase().substring(1));
+ final Method write =
+ data.getClass().getMethod(write_method_name, type_pr_classes);
+ write.invoke(data, to_write);
+ break;
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/WriteType.java b/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/WriteType.java
new file mode 100644
index 0000000..24fd02b
--- /dev/null
+++ b/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/WriteType.java
@@ -0,0 +1,61 @@
+package com.pepedevs.corelib.utils.bungeecord;
+
+/** Enumeration of all write types. */
+public enum WriteType {
+ BOOLEAN(boolean.class, Boolean.class),
+ BYTE(byte.class, Byte.class),
+ CHAR(char.class, Character.class),
+ DOUBLE(double.class, Double.class),
+ FLOAT(float.class, Float.class),
+ INT(int.class, Integer.class),
+ LONG(long.class, Long.class),
+ SHORT(short.class, Short.class),
+ BYTES(String.class),
+ CHARS(String.class),
+ UTF(String.class);
+
+ private final Class>[] primitive_classes;
+
+ WriteType(Class>... primitive_classes) {
+ this.primitive_classes = primitive_classes;
+ }
+
+ /**
+ * Returns the write type of the given data.
+ *
+ *
+ *
+ * @param to_write Object to get write type of
+ * @return WriteType of the object
+ */
+ public static WriteType of(Object to_write) {
+ if (to_write == null) {
+ return null;
+ }
+
+ if (to_write instanceof String) {
+ throw new UnsupportedOperationException(
+ "The class of the object to write is not compatible with this operation!");
+ }
+
+ for (WriteType type : values()) {
+ for (Class> type_pr_classes : type.getPrimitiveClasses()) {
+ if (type_pr_classes.equals(to_write.getClass())) {
+ return type;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the primitive class.
+ *
+ *
+ *
+ * @return Primitive classes
+ */
+ public Class>[] getPrimitiveClasses() {
+ return primitive_classes;
+ }
+}
diff --git a/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/Written.java b/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/Written.java
new file mode 100644
index 0000000..45cf153
--- /dev/null
+++ b/bungee-utils/src/main/java/com/pepedevs/corelib/utils/bungeecord/Written.java
@@ -0,0 +1,161 @@
+package com.pepedevs.corelib.utils.bungeecord;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Builder/Wrapper class for writing values. */
+public class Written {
+
+ private final List
+ *
+ * @return List of {@link Writable}
+ */
+ public List
+ *
+ * @param voolean Boolean value
+ * @return This Object, for chaining
+ */
+ public Written writeBoolean(boolean voolean) {
+ return writeOrdinary(voolean);
+ }
+
+ /**
+ * Writes byte value to message.
+ *
+ *
+ *
+ * @param vyte Byte value
+ * @return This Object, for chaining
+ */
+ public Written writeByte(byte vyte) {
+ return writeOrdinary(vyte);
+ }
+
+ /**
+ * Writes character value to message.
+ *
+ *
+ *
+ * @param character Character value
+ * @return This Object, for chaining
+ */
+ public Written writeChar(char character) {
+ return writeOrdinary(character);
+ }
+
+ /**
+ * Writes double value to message.
+ *
+ *
+ *
+ * @param ddouble Double value
+ * @return This Object, for chaining
+ */
+ public Written writeDouble(double ddouble) {
+ return writeOrdinary(ddouble);
+ }
+
+ /**
+ * Writes float value to message.
+ *
+ *
+ *
+ * @param ffloat Float value
+ * @return This Object, for chaining
+ */
+ public Written writeFloat(float ffloat) {
+ return writeOrdinary(ffloat);
+ }
+
+ /**
+ * Writes int value to message.
+ *
+ *
+ *
+ * @param lnt Int value
+ * @return This Object, for chaining
+ */
+ public Written writeInt(int lnt) {
+ return writeOrdinary(lnt);
+ }
+
+ /**
+ * Writes long value to message.
+ *
+ *
+ *
+ * @param iong Long value
+ * @return This Object, for chaining
+ */
+ public Written writeLong(long iong) {
+ return writeOrdinary(iong);
+ }
+
+ /**
+ * Writes short value to message.
+ *
+ *
+ *
+ * @param chort Short value
+ * @return This Object, for chaining
+ */
+ public Written writeShort(short chort) {
+ return writeOrdinary(chort);
+ }
+
+ /**
+ * Writes string value in bytes to message.
+ *
+ *
+ *
+ * @param bytes String value
+ * @return This Object, for chaining
+ */
+ public Written writeBytes(String bytes) {
+ writables.add(new Writable(bytes, WriteType.BYTES));
+ return this;
+ }
+
+ /**
+ * Writes string value in char to message.
+ *
+ *
+ *
+ * @param chars String value
+ * @return This Object, for chaining
+ */
+ public Written writeChars(String chars) {
+ writables.add(new Writable(chars, WriteType.CHARS));
+ return this;
+ }
+
+ /**
+ * Writes String value in UTF8 to message.
+ *
+ *
+ *
+ * @param utf String value
+ * @return This Object, for chaining
+ */
+ public Written writeUTF(String utf) {
+ writables.add(new Writable(utf, WriteType.UTF));
+ return this;
+ }
+
+ private Written writeOrdinary(Object to_write) {
+ writables.add(Writable.of(to_write));
+ return this;
+ }
+}
diff --git a/configuration-utils/pom.xml b/configuration-utils/pom.xml
new file mode 100644
index 0000000..1823f95
--- /dev/null
+++ b/configuration-utils/pom.xml
@@ -0,0 +1,32 @@
+
+
+ *
+ *
+ *
+ * @param section Section where the configuration is located
+ * @return This Object, for chaining
+ */
+ public Loadable load(ConfigurationSection section);
+
+ /**
+ * Load the configuration of every single field in this class from the given {@link
+ * ConfigurationSection} that has the annotation {@link LoadableEntry}/{@link
+ * LoadableCollectionEntry} present.
+ *
+ *
+ *
+ * @param section Section where the configuration is located
+ * @return This Object, for chaining
+ */
+ @SuppressWarnings("unchecked")
+ public default Loadable loadEntries(ConfigurationSection section) {
+ Validate.notNull(section, "The section cannot be null!");
+
+ List The method {@link #saveEntries(ConfigurationSection)} saves the value of any field in this
+ * instance that has the annotation {@link SaveableEntry}.
+ */
+public interface Saveable {
+
+ /**
+ * Save this Object on the provided {@link ConfigurationSection}.
+ *
+ *
+ *
+ * @param section Section to save on.
+ * @return Total done changes in the provided section after saving.
+ */
+ public int save(ConfigurationSection section);
+
+ /**
+ * Save on the given {@link ConfigurationSection}.
+ *
+ * This method saves all the fields that has the annotation {@link SaveableEntry}.
+ *
+ *
+ *
+ * @param section Section in which this will be saved.
+ * @return Total done changes in the provided section after saving.
+ */
+ @SuppressWarnings("unchecked")
+ default int saveEntries(ConfigurationSection section) {
+ Validate.notNull(section, "The section cannot be null!");
+
+ List
+ *
+ * @return Name of the sub-section where this is located.
+ */
+ String subsection();
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/annotations/LoadableEntry.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/annotations/LoadableEntry.java
new file mode 100644
index 0000000..614cfb9
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/annotations/LoadableEntry.java
@@ -0,0 +1,24 @@
+package com.pepedevs.corelib.utils.configuration.annotations;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface LoadableEntry {
+
+ String key();
+
+ /**
+ * Gets the name of the sub-section where this is located.
+ *
+ *
+ *
+ * @return Name of the sub-section where this is located.
+ */
+ String subsection() default StringUtils.EMPTY;
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/annotations/SaveableCollectionEntry.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/annotations/SaveableCollectionEntry.java
new file mode 100644
index 0000000..b5a5edb
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/annotations/SaveableCollectionEntry.java
@@ -0,0 +1,31 @@
+package com.pepedevs.corelib.utils.configuration.annotations;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface SaveableCollectionEntry {
+
+ /**
+ * Gets the name of the sub-section where this will be saved.
+ *
+ *
+ *
+ * @return Name of the sub-section where this will be saved.
+ */
+ String subsection();
+
+ /**
+ * Gets the prefix for the name of sub-section where this will be saved.
+ *
+ *
+ *
+ * @return Prefix for the name of sub-section where this will be saved.
+ */
+ String subsectionprefix() default StringUtils.EMPTY;
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/annotations/SaveableEntry.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/annotations/SaveableEntry.java
new file mode 100644
index 0000000..efbb66f
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/annotations/SaveableEntry.java
@@ -0,0 +1,27 @@
+package com.pepedevs.corelib.utils.configuration.annotations;
+
+import com.pepedevs.corelib.utils.configuration.SaveActionType;
+import org.apache.commons.lang.StringUtils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface SaveableEntry {
+
+ String key();
+
+ SaveActionType action() default SaveActionType.NORMAL;
+
+ /**
+ * Gets the name of the sub-section where this will be saved on.
+ *
+ *
+ *
+ * @return Name of the sub-section where this will be saved.
+ */
+ String subsection() default StringUtils.EMPTY;
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/duration/ConfigurableDuration.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/duration/ConfigurableDuration.java
new file mode 100644
index 0000000..4bbfed5
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/duration/ConfigurableDuration.java
@@ -0,0 +1,83 @@
+package com.pepedevs.corelib.utils.configuration.configurable.duration;
+
+import com.pepedevs.corelib.utils.Duration;
+import com.pepedevs.corelib.utils.configuration.Configurable;
+import com.pepedevs.corelib.utils.configuration.yaml.YamlUtils;
+import com.pepedevs.corelib.utils.reflection.general.EnumReflection;
+import org.bukkit.configuration.ConfigurationSection;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents a {@link Duration} that is 'Configurable' because can be loaded from/saved on a {@link
+ * ConfigurationSection}.
+ */
+public class ConfigurableDuration extends Duration implements Configurable {
+
+ public static final String DURATION_KEY = "duration";
+ public static final String UNIT_KEY = "unit";
+
+ /**
+ * Construct a new uninitialized duration.
+ *
+ * This constructor is used only for {@link ConfigurableDuration}s that will be initialized
+ * after constructing by using the method {@link
+ * ConfigurableDuration#load(ConfigurationSection)}.
+ */
+ public ConfigurableDuration() { // uninitialized
+ this(-1L, null);
+ }
+
+ /**
+ * Construct duration.
+ *
+ *
+ *
+ * @param duration Duration
+ * @param unit Time unit
+ */
+ public ConfigurableDuration(long duration, TimeUnit unit) {
+ super(duration, unit);
+ }
+
+ /**
+ * Construct duration from milliseconds.
+ *
+ *
+ *
+ * @param millis Duration in milliseconds
+ */
+ public ConfigurableDuration(long millis) {
+ this(millis, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Construct duration using the values of another {@link Duration}.
+ *
+ *
+ *
+ * @param copy {@link Duration} to copy
+ */
+ public ConfigurableDuration(Duration copy) {
+ this(copy.getDuration(), copy.getUnit());
+ }
+
+ @Override
+ public ConfigurableDuration load(ConfigurationSection section) {
+ this.duration = section.getLong(DURATION_KEY, -1L);
+ this.unit =
+ EnumReflection.getEnumConstant(TimeUnit.class, section.getString(UNIT_KEY, null));
+ return this;
+ }
+
+ @Override
+ public boolean isValid() {
+ return this.getDuration() > -1L && this.getUnit() != null;
+ }
+
+ @Override
+ public int save(ConfigurationSection section) {
+ return (YamlUtils.setNotEqual(section, DURATION_KEY, getDuration()) ? 1 : 0)
+ + (YamlUtils.setNotEqual(section, UNIT_KEY, getUnit().name()) ? 1 : 0);
+ }
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/enchantment/ConfigurableEnchantment.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/enchantment/ConfigurableEnchantment.java
new file mode 100644
index 0000000..873a32e
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/enchantment/ConfigurableEnchantment.java
@@ -0,0 +1,114 @@
+package com.pepedevs.corelib.utils.configuration.configurable.enchantment;
+
+import com.pepedevs.corelib.utils.configuration.Configurable;
+import com.pepedevs.corelib.utils.configuration.Loadable;
+import com.pepedevs.corelib.utils.configuration.annotations.LoadableEntry;
+import com.pepedevs.corelib.utils.configuration.annotations.SaveableEntry;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.enchantments.Enchantment;
+
+import java.util.Arrays;
+
+/**
+ * Represents a {@link Enchantment} that is 'Configurable' because can be loaded from/saved on a
+ * {@link ConfigurationSection}.
+ */
+public class ConfigurableEnchantment implements Configurable {
+
+ public static final String ENCHANT_KEY = "enchant";
+ public static final String LEVEL_KEY = "level";
+
+ @LoadableEntry(key = ENCHANT_KEY)
+ @SaveableEntry(key = ENCHANT_KEY)
+ public String name;
+
+ @LoadableEntry(key = LEVEL_KEY)
+ @SaveableEntry(key = LEVEL_KEY)
+ public Integer level;
+
+ /** Construct a new uninitialized {@link ConfigurableEnchantment}. */
+ public ConfigurableEnchantment() {
+ /* uninitialized */
+ }
+
+ /**
+ * Constructs the {@link ConfigurableEnchantment}.
+ *
+ *
+ *
+ * @param enchantment_name Enchantment name
+ * @param level Enchantment level
+ */
+ public ConfigurableEnchantment(String enchantment_name, Integer level) {
+ this.name = enchantment_name;
+ this.level = level;
+ }
+
+ /**
+ * Constructs the {@link ConfigurableEnchantment}.
+ *
+ *
+ *
+ * @param enchantment Enchantment
+ * @param level Enchantment level
+ */
+ @SuppressWarnings("deprecation")
+ public ConfigurableEnchantment(Enchantment enchantment, Integer level) {
+ this(enchantment.getName(), level);
+ }
+
+ @Override
+ public Loadable load(ConfigurationSection section) {
+ return this.loadEntries(section);
+ }
+
+ @Override
+ public int save(ConfigurationSection section) {
+ return this.saveEntries(section);
+ }
+
+ /**
+ * Returns the Enchantment name.
+ *
+ *
+ *
+ * @return Enchantment name
+ */
+ public String getEnchantmentName() {
+ return name;
+ }
+
+ /**
+ * Returns the Enchantment level.
+ *
+ *
+ *
+ * @return Enchantment level
+ */
+ public Integer getEnchantmentLevel() {
+ return level;
+ }
+
+ /**
+ * Gets the enchantment bound to the enchantment name.
+ *
+ *
+ *
+ * @return Enchantment {@link Enchantment} bound to the {@link ConfigurableEnchantment#name}
+ */
+ @SuppressWarnings("deprecation")
+ public Enchantment getEnchantment() {
+ return Arrays.stream(Enchantment.values())
+ .filter(enchantment -> enchantment.getName().equalsIgnoreCase(getEnchantmentName()))
+ .findAny()
+ .orElse(null);
+ }
+
+ @Override
+ public boolean isValid() {
+ return getEnchantment() != null
+ && this.getEnchantmentLevel() != null
+ && this.getEnchantmentLevel().intValue() > -1;
+ }
+
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/itemstack/ConfigurableItemStack.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/itemstack/ConfigurableItemStack.java
new file mode 100644
index 0000000..2542e59
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/itemstack/ConfigurableItemStack.java
@@ -0,0 +1,360 @@
+package com.pepedevs.corelib.utils.configuration.configurable.itemstack;
+
+import com.pepedevs.corelib.utils.StringUtils;
+import com.pepedevs.corelib.utils.configuration.Configurable;
+import com.pepedevs.corelib.utils.configuration.annotations.LoadableCollectionEntry;
+import com.pepedevs.corelib.utils.configuration.annotations.SaveableCollectionEntry;
+import com.pepedevs.corelib.utils.configuration.annotations.SaveableEntry;
+import com.pepedevs.corelib.utils.configuration.configurable.enchantment.ConfigurableEnchantment;
+import com.pepedevs.corelib.utils.itemstack.ItemMetaBuilder;
+import com.pepedevs.corelib.utils.material.MaterialUtils;
+import org.apache.commons.lang.Validate;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Represents a {@link ItemStack} that is 'Configurable' because can be loaded from/saved on a
+ * {@link ConfigurationSection}.
+ */
+public class ConfigurableItemStack implements Configurable {
+
+ public static final String TYPE_KEY = "type";
+ public static final String SIZE_KEY = "size";
+ public static final String NAME_KEY = "name";
+ public static final String LORE_KEY = "lore";
+ public static final String DATA_KEY = "data";
+ public static final String ENCHANTS_SECTION = "enchantments";
+ public static final String ENCHANT_SUBSECTION_PREFIX = "enchantment-";
+
+ @LoadableCollectionEntry(subsection = ENCHANTS_SECTION)
+ @SaveableCollectionEntry(
+ subsection = ENCHANTS_SECTION,
+ subsectionprefix = ENCHANT_SUBSECTION_PREFIX)
+ public final Set
+ *
+ * @param stack {@link ItemStack} to get the default values
+ */
+ @SuppressWarnings("deprecation")
+ public ConfigurableItemStack(ItemStack stack) {
+ this(
+ MaterialUtils.getRightMaterial(stack).name(),
+ stack.getAmount(),
+ stack.getItemMeta().getDisplayName(),
+ stack.getItemMeta().getLore(),
+ stack.getDurability());
+ }
+
+ /**
+ * Construct a {@link ConfigurableItemStack} with default values.
+ *
+ *
+ *
+ * @param type Default type
+ * @param size Default size
+ * @param name Default name
+ * @param lore Default lore
+ * @param data Default data
+ */
+ public ConfigurableItemStack(
+ String type, int size, String name, List
+ *
+ * @param type Default type
+ * @param size Default size
+ * @param name Default name
+ * @param lore Default lore
+ * @param data Default data
+ */
+ public ConfigurableItemStack(String type, int size, String name, String[] lore, short data) {
+ this(type, size, name, Arrays.asList(lore), data);
+ }
+
+ /**
+ * Load values from the given {@link ConfigurationSection}.
+ *
+ *
+ *
+ * @param section {@link ConfigurationSection} to load values
+ * @return This Object, for chaining
+ */
+ @Override
+ public ConfigurableItemStack load(ConfigurationSection section) {
+ Validate.notNull(section, "The section cannot be null!");
+ this.type = section.getString(TYPE_KEY, null);
+ this.size = section.getInt(SIZE_KEY, -1);
+ this.name = section.getString(NAME_KEY, null);
+ this.lore = section.isList(LORE_KEY) ? section.getStringList(LORE_KEY) : null;
+ this.data = (short) section.getInt(DATA_KEY, 0);
+ return (ConfigurableItemStack)
+ this.loadEntries(section); // load enchantments by loading entries
+ }
+
+ @Override
+ public int save(ConfigurationSection section) {
+ return this.saveEntries(section);
+ }
+
+ /**
+ * Returns the type string of the ItemStack.
+ *
+ *
+ *
+ * @return Type of ItemStack as string
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Sets the type string of the ItemStack.
+ *
+ *
+ *
+ * @param type Type for ItemStack
+ */
+ public void setType(String type) {
+ this.setType(type, true);
+ }
+
+ /**
+ * Sets the type string of the ItemStack.
+ *
+ *
+ *
+ * @param type Type for ItemStack
+ * @param overwrite whether to overwrite the previous value
+ */
+ public void setType(String type, boolean overwrite) {
+ this.type = overwrite ? type : (this.type == null ? type : this.type);
+ }
+
+ /**
+ * Returns the amount of the ItemStack.
+ *
+ *
+ *
+ * @return Amount of ItemStack
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Sets the amount of the ItemStack.
+ *
+ *
+ *
+ * @param size Amount for ItemStack
+ */
+ public void setSize(int size) {
+ this.setSize(size, true);
+ }
+
+ /**
+ * Sets the amount of the ItemStack.
+ *
+ *
+ *
+ * @param size Amount for ItemStack
+ * @param overwrite whether to overwrite the previous value
+ */
+ public void setSize(int size, boolean overwrite) {
+ this.size = overwrite ? size : (this.size == -1 ? size : this.size);
+ }
+
+ /**
+ * Returns the name of the ItemStack.
+ *
+ *
+ *
+ * @return Name of ItemStack
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of the ItemStack.
+ *
+ *
+ *
+ * @param name Name for ItemStack
+ */
+ public void setName(String name) {
+ this.setName(name, true);
+ }
+
+ /**
+ * Sets the name of the ItemStack.
+ *
+ *
+ *
+ * @param name Name for ItemStack
+ * @param overwrite whether to overwrite the previous value
+ */
+ public void setName(String name, boolean overwrite) {
+ this.name = overwrite ? name : (this.name == null ? name : this.name);
+ }
+
+ /**
+ * Returns the lore of the ItemStack.
+ *
+ *
+ *
+ * @return Lore of ItemStack
+ */
+ public List
+ *
+ * @param lore Lore for ItemStack
+ */
+ public void setLore(List
+ *
+ * @param lore Lore for ItemStack
+ * @param overwrite whether to overwrite the previous value
+ */
+ public void setLore(List
+ *
+ * @return Data of ItemStack
+ */
+ public short getData() {
+ return data;
+ }
+
+ /**
+ * Sets the data of the ItemStack.
+ *
+ *
+ *
+ * @param data Data for ItemStack
+ */
+ public void setData(short data) {
+ this.setData(data, true);
+ }
+
+ /**
+ * Sets the data of the ItemStack.
+ *
+ *
+ *
+ * @param data Data for ItemStack
+ * @param overwrite whether to overwrite the previous value
+ */
+ public void setData(short data, boolean overwrite) {
+ this.data = overwrite ? data : (this.data == -1 ? data : this.data);
+ }
+
+ /**
+ * Returns the enchantments of the ItemStack.
+ *
+ *
+ *
+ * @return {@link ConfigurableEnchantment} of ItemStack
+ */
+ public Set
+ *
+ * @param enchantment {@link ConfigurableEnchantment} for ItemStack
+ */
+ public void addEnchantment(ConfigurableEnchantment enchantment) {
+ Validate.notNull(enchantment, "The enchantment cannot be null!");
+ Validate.isTrue(enchantment.isInvalid(), "The enchantment is invalid!");
+ this.enchants.add(enchantment);
+ }
+
+ /**
+ * Returns the ItemStack.
+ *
+ *
+ *
+ * @return ItemStack
+ */
+ @SuppressWarnings("deprecation")
+ public ItemStack toItemStack() {
+ ItemMetaBuilder builder =
+ new ItemMetaBuilder(MaterialUtils.getRightMaterial(Material.valueOf(getType())));
+ this.getEnchantments().stream()
+ .filter(ConfigurableEnchantment::isValid)
+ .forEach(
+ enchantment ->
+ builder.withEnchantment(
+ enchantment.getEnchantment(),
+ enchantment.getEnchantmentLevel()));
+
+ return builder.withDisplayName(StringUtils.translateAlternateColorCodes(this.getName()))
+ .withLore(StringUtils.translateAlternateColorCodes(this.getLore()))
+ .applyTo(
+ new ItemStack(
+ MaterialUtils.getRightMaterial(Material.valueOf(this.getType())),
+ getSize(),
+ getData()));
+ }
+
+ @Override
+ public boolean isValid() {
+ return getType() != null
+ && Material.valueOf(this.getType()) != null
+ && getName() != null
+ && getSize() > -1;
+ }
+
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/location/ConfigurableLocation.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/location/ConfigurableLocation.java
new file mode 100644
index 0000000..643e6a0
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/configurable/location/ConfigurableLocation.java
@@ -0,0 +1,193 @@
+package com.pepedevs.corelib.utils.configuration.configurable.location;
+
+import com.pepedevs.corelib.utils.Initializable;
+import com.pepedevs.corelib.utils.configuration.Configurable;
+import com.pepedevs.corelib.utils.configuration.yaml.YamlUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.configuration.ConfigurationSection;
+
+import java.util.UUID;
+
+/**
+ * Represents a {@link Location} that is 'Configurable' because can be loaded from/saved on a {@link
+ * ConfigurationSection}.
+ */
+public class ConfigurableLocation extends Location implements Configurable, Initializable {
+
+ public static final String WORLD_UID_KEY = "world-uid";
+ public static final String X_KEY = "x";
+ public static final String Y_KEY = "y";
+ public static final String Z_KEY = "z";
+ public static final String YAW_KEY = "yaw";
+ public static final String PITCH_KEY = "pitch";
+ public static final String[] CONFIGURABLE_LOCATION_KEYS = {
+ WORLD_UID_KEY, X_KEY, Y_KEY, Z_KEY, YAW_KEY, PITCH_KEY
+ };
+ /** Whether {@link #load(ConfigurationSection)} method has been called. */
+ protected boolean initialized;
+
+ /** Constructs a uninitialized {@link ConfigurableLocation}. */
+ public ConfigurableLocation() { // uninitialized
+ super(null, 0, 0, 0, 0, 0);
+ }
+
+ /**
+ * Constructs the {@link ConfigurableLocation}.
+ *
+ *
+ *
+ * @param world World
+ * @param x X-axis
+ * @param y Y-axis
+ * @param z Z-axis
+ * @param yaw Yaw
+ * @param pitch Pitch
+ */
+ public ConfigurableLocation(World world, double x, double y, double z, float yaw, float pitch) {
+ super(world, x, y, z, yaw, pitch);
+ this.initialized = true;
+ }
+
+ /**
+ * Constructs the {@link ConfigurableLocation}.
+ *
+ *
+ *
+ * @param world World
+ * @param x X-axis
+ * @param y Y-axis
+ * @param z Z-axis
+ */
+ public ConfigurableLocation(World world, double x, double y, double z) {
+ this(world, x, y, z, 0.0F, 0.0F);
+ }
+
+ /**
+ * Constructs the {@link ConfigurableLocation}.
+ *
+ *
+ *
+ * @param copy Copy of the location
+ */
+ public ConfigurableLocation(Location copy) {
+ this(
+ copy.getWorld(),
+ copy.getX(),
+ copy.getY(),
+ copy.getZ(),
+ copy.getYaw(),
+ copy.getPitch());
+ }
+
+ /**
+ * Returns a {@link ConfigurableLocation} loaded from the given {@link ConfigurationSection}, or
+ * null if there is no any valid {@link ConfigurableLocation} stored on the given {@link
+ * ConfigurationSection}.
+ *
+ * Note that this method checks the given configuration section calling {@link
+ * #isConfigurableLocation(ConfigurationSection)}.
+ *
+ *
+ *
+ * @param section Section to parse
+ * @return Parsed location
+ */
+ public static ConfigurableLocation of(ConfigurationSection section) {
+ return (isConfigurableLocation(section) ? new ConfigurableLocation().load(section) : null);
+ }
+
+ /**
+ * Return true if and only if there is a valid {@link ConfigurableLocation} stored on the given
+ * {@link ConfigurationSection}
+ *
+ *
+ *
+ * @param section {@link ConfigurationSection} where the supposed {@link ConfigurableLocation}
+ * is stored
+ * @return true if is
+ */
+ public static boolean isConfigurableLocation(ConfigurationSection section) {
+ for (String key : CONFIGURABLE_LOCATION_KEYS) {
+ switch (key) {
+ case WORLD_UID_KEY:
+ break;
+
+ default:
+ if (!(section.get(key) instanceof Number)) {
+ return false;
+ }
+ break;
+ }
+ }
+
+ if (section.isString(WORLD_UID_KEY)) {
+ try {
+ // this will thrown an exception if the UUID is invalid.
+ UUID.fromString(section.getString(WORLD_UID_KEY));
+ return true;
+ } catch (IllegalArgumentException ex) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public ConfigurableLocation load(ConfigurationSection section) {
+ this.setWorld(Bukkit.getWorld(UUID.fromString(section.getString(WORLD_UID_KEY))));
+ this.setX(section.getDouble(X_KEY, 0));
+ this.setY(section.getDouble(Y_KEY, 0));
+ this.setZ(section.getDouble(Z_KEY, 0));
+ this.setYaw((float) section.getDouble(YAW_KEY, 0));
+ this.setPitch((float) section.getDouble(PITCH_KEY, 0));
+ this.initialized = true;
+ return this;
+ }
+
+ @Override
+ public int save(ConfigurationSection section) {
+ return (YamlUtils.setNotEqual(
+ section,
+ WORLD_UID_KEY,
+ (getWorld() != null ? getWorld().getUID().toString() : ""))
+ ? 1
+ : 0)
+ + (YamlUtils.setNotEqual(section, X_KEY, getX()) ? 1 : 0)
+ + (YamlUtils.setNotEqual(section, Y_KEY, getY()) ? 1 : 0)
+ + (YamlUtils.setNotEqual(section, Z_KEY, getZ()) ? 1 : 0)
+ + (YamlUtils.setNotEqual(section, YAW_KEY, getYaw()) ? 1 : 0)
+ + (YamlUtils.setNotEqual(section, PITCH_KEY, getPitch()) ? 1 : 0);
+ }
+
+ /**
+ * Gets a clone of this location with the specified {@link World world}.
+ *
+ *
+ *
+ * @param world New world for the location
+ * @return Clone of this location with the specified {@code world}
+ */
+ public ConfigurableLocation withWorld(World world) {
+ ConfigurableLocation location = clone();
+ location.setWorld(world);
+ return location;
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ @Override
+ public boolean isValid() {
+ return isInitialized() && getWorld() != null;
+ }
+
+ @Override
+ public ConfigurableLocation clone() {
+ return (ConfigurableLocation) super.clone();
+ }
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/yaml/YamlConfigurationComments.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/yaml/YamlConfigurationComments.java
new file mode 100644
index 0000000..bb0c84e
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/yaml/YamlConfigurationComments.java
@@ -0,0 +1,504 @@
+package com.pepedevs.corelib.utils.configuration.yaml;
+
+import org.apache.commons.lang.Validate;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.Configuration;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.configuration.file.YamlConstructor;
+import org.bukkit.configuration.file.YamlRepresenter;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.error.YAMLException;
+import org.yaml.snakeyaml.representer.Representer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Map;
+import java.util.logging.Level;
+
+/**
+ * An implementation of {@link Configuration} which saves all files in Yaml, and allows to comments
+ * the keys by using the method {@link #options()}.comment().
+ *
+ * Note that this implementation is not synchronized.
+ */
+public class YamlConfigurationComments extends YamlConfiguration {
+
+ protected static final String LINE_SEPARATOR = "\n";
+ protected static final String VALUE_INDICATOR = ":";
+ private final DumperOptions yaml_options = new DumperOptions();
+ private final Representer yaml_representer = new YamlRepresenter();
+ private final Yaml yaml = new Yaml(new YamlConstructor(), yaml_representer, yaml_options);
+
+ /**
+ * Creates a new {@link YamlConfigurationComments}, loading from the given file.
+ *
+ * Any errors loading the Configuration will be logged and then ignored. If the specified
+ * input is not a valid config, a blank config will be returned.
+ *
+ * The encoding used may follow the system dependent default.
+ *
+ *
+ *
+ * @param file Input file
+ * @return Resulting configuration
+ * @throws IllegalArgumentException Thrown if file is null
+ */
+ public static YamlConfigurationComments loadConfiguration(File file) {
+ Validate.notNull(file, "File cannot be null");
+
+ YamlConfigurationComments config = new YamlConfigurationComments();
+ try {
+ config.load(file);
+ } catch (FileNotFoundException ignored) {
+ } catch (IOException | InvalidConfigurationException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Cannot load " + file, ex);
+ }
+ return config;
+ }
+
+ /**
+ * Creates a new {@link YamlConfigurationComments}, loading from the given reader.
+ *
+ * Any errors loading the Configuration will be logged and then ignored. If the specified
+ * input is not a valid config, a blank config will be returned.
+ *
+ *
+ *
+ * @param reader Input
+ * @return Resulting configuration
+ * @throws IllegalArgumentException Thrown if stream is null
+ */
+ public static YamlConfigurationComments loadConfiguration(Reader reader) {
+ Validate.notNull(reader, "Stream cannot be null");
+
+ YamlConfigurationComments config = new YamlConfigurationComments();
+ try {
+ config.load(reader);
+ } catch (IOException | InvalidConfigurationException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Cannot load configuration from stream", ex);
+ }
+ return config;
+ }
+
+ @Override
+ public String saveToString() {
+ yaml_options.setIndent(options().indent());
+ yaml_options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
+ yaml_representer.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
+
+ String header = buildHeader();
+ String dump = insertComments(yaml.dump(getValues(false)));
+
+ if (dump.equals(BLANK_CONFIG)) {
+ dump = "";
+ }
+
+ dump = dump.substring(0, dump.lastIndexOf(LINE_SEPARATOR)); // clear
+ // last
+ // line
+ return header + dump;
+ }
+
+ // protected String insertComments ( String dump ) {
+ // StringBuilder result = new StringBuilder ( );
+ // String [ ] lines = dump.split ( "\r?\n" , -1 );
+ //
+ // for ( int i = 0 ; i < lines.length ; i ++ ) {
+ // String line = lines [ i ];
+ //
+ //
+ // }
+ // return dump;
+ // }
+
+ protected String insertComments(String dump) {
+ StringBuilder builder = new StringBuilder();
+ String[] lines = dump.split("\r?\n", -1);
+ String last_node = "";
+
+ for (int i = 0; i < lines.length; i++) {
+ /* calculate line node */
+ String line = lines[i];
+ String line_path = "";
+ int indent = indentCount(line);
+ String node = "";
+
+ // System.out.println ( ">>>>> i = " + i );
+
+ if (i > 0 && indent >= options().indent() && line.indexOf(VALUE_INDICATOR) != -1) {
+ for (int j = i; j >= 0; j--) {
+ String line_back = lines[j];
+ String line_back_path = "";
+ int line_back_indent = indentCount(line_back);
+
+ if ((indent - options().indent()) < 0) {
+ break;
+ }
+
+ if (line_back_indent >= indent) {
+ continue;
+ }
+
+ if (line_back.trim().startsWith(COMMENT_PREFIX)
+ || line_back
+ .trim()
+ .startsWith(
+ YamlConfigurationCommentsOptions.PATH_COMMENT_PREFIX)) {
+ continue;
+ }
+
+ /* father section found */
+ if (line_back_indent == (indent - options().indent())) {
+ line_path = line.trim().substring(0, line.trim().indexOf(VALUE_INDICATOR));
+ line_back_path =
+ line_back
+ .trim()
+ .substring(0, line_back.trim().indexOf(VALUE_INDICATOR));
+ node = line_back_path + options().pathSeparator() + line_path;
+ if (last_node.endsWith(options().pathSeparator() + line_back_path)) {
+ node = last_node + options().pathSeparator() + line_path;
+ }
+
+ last_node = node;
+ break;
+ }
+ }
+ }
+
+ // System.out.println ( ">>>> node = " + node );
+ // System.out.println ( ">>>> line_path = " + line_path );
+
+ // String node_parent_path = node.isEmpty ( ) ? this.getCurrentPath ( )
+ // : node.substring ( 0 , node.lastIndexOf ( options ( ).pathSeparator ( ) ) );
+
+ // System.out.println ( ">>>> node_parent_path = " + node_parent_path );
+
+ ConfigurationSection section =
+ node.isEmpty()
+ ? this
+ : getConfigurationSection(
+ node.substring(0, node.lastIndexOf(options().pathSeparator())));
+
+ // System.out.println ( ">>>> node parent section = " + ( section != null ?
+ // section.getCurrentPath ( ) : "null" ) );
+
+ // System.out.println ( ">>>>>>> node = " + node );
+ // System.out.println ( ">>>>>>> line_path = " + line_path );
+
+ if (section != null) {
+ // System.out.println ( ">>>> 0" );
+ if (line_path.trim().isEmpty() && line.trim().indexOf(VALUE_INDICATOR) != -1) {
+ // System.out.println ( ">>>> 1" );
+ line_path = line.trim().substring(0, line.trim().indexOf(VALUE_INDICATOR));
+ }
+
+ // System.out.println ( ">>>> 2: section path = " + section.getCurrentPath ( ) );
+
+ String comment = options().getComment(section, line_path);
+
+ Map Examples: - getIndent(2) = " " - getIndent(4) = " " - getIndent(6) = " "
+ *
+ *
+ *
+ * @param indent_count Indent count
+ * @return String representation of the given indent
+ */
+ protected String getIndent(int indent_count) {
+ StringBuilder indent = new StringBuilder();
+
+ for (int i = 0; i < indent_count; i++) {
+ indent.append(" ");
+ }
+
+ return indent.toString();
+ }
+
+ @Override
+ public void loadFromString(String contents) throws InvalidConfigurationException {
+ Validate.notNull(contents, "Contents cannot be null");
+
+ Map, ?> input;
+ try {
+ input = (Map, ?>) yaml.load(contents);
+ } catch (YAMLException e) {
+ throw new InvalidConfigurationException(e);
+ } catch (ClassCastException e) {
+ throw new InvalidConfigurationException("Top level is not a Map.");
+ }
+
+ String header = parseHeader(contents);
+ if (header.length() > 0) {
+ options().header(header);
+ }
+
+ if (input != null) {
+ convertMapsToSections(input, this);
+ }
+
+ loadComments(contents);
+ }
+
+ protected void convertMapsToSections(Map, ?> input, ConfigurationSection section) {
+ for (Map.Entry, ?> entry : input.entrySet()) {
+ String key = entry.getKey().toString();
+ Object value = entry.getValue();
+
+ if (value instanceof Map) {
+ convertMapsToSections((Map, ?>) value, section.createSection(key));
+ } else {
+ section.set(key, value);
+ }
+ }
+ }
+
+ protected void loadComments(String input) {
+ String[] lines = input.split("\r?\n", -1);
+ String last_node = "";
+ for (int i = 0; i < lines.length; i++) {
+ /* calculating line node */
+ String line = lines[i];
+ String line_path = "";
+ int indent = indentCount(line);
+ String node = "";
+ if (i > 0 && line.indexOf(VALUE_INDICATOR) != -1) {
+ if (indent >= options().indent()) {
+ for (int j = i - 1; j >= 0; j--) {
+ String line_back = lines[j];
+ String line_back_path = "";
+ int line_back_indent = indentCount(line_back);
+ if (line_back_indent >= indent) {
+ continue;
+ }
+
+ if (line_back.trim().startsWith(COMMENT_PREFIX)
+ || line_back
+ .trim()
+ .startsWith(
+ YamlConfigurationCommentsOptions
+ .PATH_COMMENT_PREFIX)) {
+ continue;
+ }
+
+ /* father section found */
+ if (line_back_indent == (indent - options().indent())) {
+ line_path =
+ line.trim().substring(0, line.trim().indexOf(VALUE_INDICATOR));
+ line_back_path =
+ line_back
+ .trim()
+ .substring(
+ 0, line_back.trim().indexOf(VALUE_INDICATOR));
+ node = line_back_path + options().pathSeparator() + line_path;
+ if (last_node.contains(String.valueOf(options().pathSeparator()))) {
+ if (last_node.endsWith(
+ options().pathSeparator() + line_back_path)) {
+ node = last_node + options().pathSeparator() + line_path;
+ }
+
+ if (last_node
+ .substring(
+ 0, last_node.lastIndexOf(options().pathSeparator()))
+ .endsWith(options().pathSeparator() + line_back_path)) {
+ node =
+ last_node.substring(
+ 0,
+ last_node.lastIndexOf(
+ options().pathSeparator()))
+ + options().pathSeparator()
+ + line_path;
+ }
+ }
+
+ last_node = node;
+ break;
+ }
+ }
+ } else {
+ line_path = line.trim().substring(0, line.trim().indexOf(VALUE_INDICATOR));
+ node = line_path;
+ last_node = node;
+ }
+ }
+
+ if ((i - 1) < 0) {
+ continue;
+ }
+
+ String comment_line = lines[i - 1];
+ if (!comment_line
+ .trim()
+ .startsWith(YamlConfigurationCommentsOptions.PATH_COMMENT_PREFIX)
+ || comment_line.trim().length()
+ == YamlConfigurationCommentsOptions.PATH_COMMENT_PREFIX.length()) {
+ continue;
+ }
+
+ String comment =
+ comment_line
+ .trim()
+ .substring(
+ YamlConfigurationCommentsOptions.PATH_COMMENT_PREFIX.length())
+ .trim();
+ if (node.lastIndexOf(options().pathSeparator()) == -1) { // root
+ // section
+ options().comment(this, line_path, comment);
+ continue;
+ }
+
+ String parent_node = node.substring(0, node.lastIndexOf(options().pathSeparator()));
+ if (isConfigurationSection(parent_node) && !isConfigurationSection(node)) { // sub
+ // section
+ options().comment(getConfigurationSection(parent_node), line_path, comment);
+ }
+ }
+ }
+
+ /**
+ * Counts the amount of indent at the start of a line.
+ *
+ *
+ *
+ * @param line Line to count
+ * @return Amount of indent
+ */
+ protected int indentCount(String line) {
+ int count = 0;
+ while (line.startsWith(" ", count)) {
+ count++;
+ }
+ return count;
+ }
+
+ @Override
+ public YamlConfigurationCommentsOptions options() {
+ return (YamlConfigurationCommentsOptions)
+ (options == null
+ ? (options = new YamlConfigurationCommentsOptions(this))
+ : options);
+ }
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/yaml/YamlConfigurationCommentsOptions.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/yaml/YamlConfigurationCommentsOptions.java
new file mode 100644
index 0000000..fb1ce7b
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/yaml/YamlConfigurationCommentsOptions.java
@@ -0,0 +1,216 @@
+package com.pepedevs.corelib.utils.configuration.yaml;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.Validate;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfigurationOptions;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Various settings for controlling the input and output of a {@link YamlConfigurationComments} */
+public class YamlConfigurationCommentsOptions extends YamlConfigurationOptions {
+
+ public static final String PATH_COMMENT_PREFIX = "## ";
+
+ private final Map This comment will be applied directly at the top of the given path. It is not required a
+ * new line (\n) at the end of the comment as it will automatically be applied, but you may
+ * include one if you wish for extra spacing.
+ *
+ * Null is a valid value which will indicate that no comment is to be applied at the top of
+ * the given path.
+ *
+ *
+ *
+ * @param section Section where the value of the given path is located
+ * @param path Path of the set object
+ * @param comment The comment
+ * @return This object, for chaining
+ */
+ public YamlConfigurationCommentsOptions comment(
+ ConfigurationSection section, String path, String comment) {
+ Validate.notNull(section, "The section cannot be null");
+ Validate.isTrue(!StringUtils.isBlank(path), "Cannot comment an empty path");
+ Validate.isTrue(
+ !path.contains(String.valueOf(pathSeparator())),
+ "The path cannot contains references to sub-sections (path separator '"
+ + pathSeparator()
+ + "' found)!");
+
+ Map This comment will be applied directly at the top of the given path. It is not required a
+ * new line (\n) at the end of the comment as it will automatically be applied, but you may
+ * include one if you wish for extra spacing.
+ *
+ * Null/Empty comment is a valid value which will indicate that no comment is to be applied
+ * at the top of the given path.
+ *
+ *
+ *
+ * @param path Path to comment.
+ * @param comment Text of the comment.
+ * @return Object, for chaining.
+ */
+ public YamlConfigurationCommentsOptions comment(String path, String comment) {
+ Validate.isTrue(!StringUtils.isBlank(path), "Cannot comment an empty path");
+
+ int index = path.lastIndexOf(pathSeparator());
+ if (index == -1) {
+ // we're commenting a section that is at the root.
+ return comment(configuration(), path, comment);
+ } else {
+ // we're commenting something that is within a sub configuration
+ // section.
+ String parent_path = path.substring(0, index);
+ String path_name = path.substring(index + 1);
+
+ return comment(
+ configuration().getConfigurationSection(parent_path), path_name, comment);
+ }
+ }
+
+ /**
+ * Returns true if the given path is commented.
+ *
+ *
+ *
+ * @param section Section where the value of the given path is located.
+ * @param path Path of the set object.
+ * @return true if commented.
+ */
+ public boolean isCommented(ConfigurationSection section, String path) {
+ boolean a = comments.get(section.getCurrentPath()) != null;
+ boolean b = a && comments.get(section.getCurrentPath()).containsKey(path);
+
+ // System.out.println ( ">>>> isCommented: path = " + path );
+ // System.out.println ( ">>>> isCommented: a = " + a );
+ // System.out.println ( ">>>> isCommented: b = " + b );
+ //
+ // if ( a ) {
+ // System.out.println ( ">>>> paths: " );
+ //
+ // for ( String paths : comments.get ( section.getCurrentPath ( ) ).keySet ( ) ) {
+ // System.out.println ( ">>>> - " + paths );
+ // }
+ // }
+
+ return a && b;
+ // return comments.get ( section.getCurrentPath ( ) ) != null
+ // && comments.get ( section.getCurrentPath ( ) ).containsKey ( path );
+ }
+
+ /**
+ * Returns the comment of the given path or null if it is not commented.
+ *
+ *
+ *
+ * @param section Section where the value of the given path is located.
+ * @param path Path of the set object.
+ * @return Comment of the given path or null if not commented.
+ */
+ public String getComment(ConfigurationSection section, String path) {
+ Map The returned map is unmodifiable, so you should not try do modify it!
+ *
+ *
+ *
+ * @return All the comments of all the path of all the {@link ConfigurationSection}.
+ */
+ public Map
+ *
+ * @return true if the comments are enabled.
+ */
+ public boolean copyComments() {
+ return this.copy_comments;
+ }
+
+ /**
+ * Sets whether or not the comments should by copied to the top of the paths when saving the
+ * configuration.
+ *
+ *
+ *
+ * @param copy true to enable the comments.
+ * @return This object, for chaining.
+ */
+ public YamlConfigurationCommentsOptions copyComments(boolean copy) {
+ this.copy_comments = copy;
+ return this;
+ }
+}
diff --git a/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/yaml/YamlUtils.java b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/yaml/YamlUtils.java
new file mode 100644
index 0000000..410cda8
--- /dev/null
+++ b/configuration-utils/src/main/java/com/pepedevs/corelib/utils/configuration/yaml/YamlUtils.java
@@ -0,0 +1,133 @@
+package com.pepedevs.corelib.utils.configuration.yaml;
+
+import org.bukkit.configuration.Configuration;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/** Class for dealing with {@link YamlConfiguration} */
+public class YamlUtils {
+
+ /**
+ * Sets the specified path to the given value if and only if it is not already set.
+ *
+ * If value is null, the entry will be removed. Any existing entry will be replaced,
+ * regardless of what the new value is.
+ *
+ * Some implementations may have limitations on what you may store. See their individual
+ * javadocs for details. No implementations should allow you to store {@link Configuration} or
+ * {@link ConfigurationSection}, please use {@link
+ * ConfigurationSection#createSection(String)} for that.
+ *
+ *
+ *
+ * @param section Configuration section to set
+ * @param path Path of the object to set
+ * @param value Value to set the path to
+ * @return true if set successfully, else false
+ */
+ public static boolean setNotSet(ConfigurationSection section, String path, Object value) {
+ if (section.isSet(path)) {
+ return false;
+ }
+
+ section.set(path, value);
+ return true;
+ }
+
+ /**
+ * Sets the specified path to the given value if and only if it the value at the provided path
+ * is not the same as {@code value}.
+ *
+ * If value is null, the entry will be removed. Any existing entry will be replaced,
+ * regardless of what the new value is.
+ *
+ * Some implementations may have limitations on what you may store. See their individual
+ * javadocs for details. No implementations should allow you to store {@link Configuration} or
+ * {@link ConfigurationSection}, please use {@link
+ * ConfigurationSection#createSection(String)} for that.
+ *
+ *
+ *
+ * @param section Configuration section to set
+ * @param path Path of the object to set
+ * @param value Value to set the path to
+ * @return true if set successfully, else false
+ */
+ public static boolean setNotEqual(ConfigurationSection section, String path, Object value) {
+ if (setNotSet(section, path, value)) {
+ return true;
+ } else if (Objects.equals(value, section.get(path))) {
+ return false;
+ }
+
+ section.set(path, value);
+ return true;
+ }
+
+ /**
+ * Creates an empty {@link ConfigurationSection} within the specified {@link
+ * ConfigurationSection} if and only if there is no another section with the same name as the
+ * provided.
+ *
+ *
+ *
+ * @param section Section at which the new section will be created
+ * @param name Name for the section
+ * @return Newly created section, or the already existing section
+ */
+ public static ConfigurationSection createNotExisting(
+ ConfigurationSection section, String name) {
+ return section.isConfigurationSection(name)
+ ? section.getConfigurationSection(name)
+ : section.createSection(name);
+ }
+
+ /**
+ * Gets the {@link ConfigurationSection} within the desired {@link ConfigurationSection}
+ * (sub-sections of the desired section).
+ *
+ *
+ *
+ * @param section {@link ConfigurationSection} where the sub-sections are stored
+ * @return Sub-sections of the desired section
+ */
+ public static Set
+ *
+ * @param key Key that contains the separator to replace
+ * @param path_separator Path separator to replace
+ * @param alt_char Path separator to use instead the current one
+ * @return Key containing the new path separator
+ */
+ public static String alternatePathSeparator(String key, char path_separator, char alt_char) {
+ return key.replace(path_separator, alt_char);
+ }
+
+ /**
+ * Replaces the common path separator {@literal '.'} by the specified {@code alt_char}.
+ *
+ *
+ *
+ * @param key Key that contains the common separator to replace
+ * @param alt_char Path separator to use instead the common one
+ * @return Key containing the new path separator
+ */
+ public static String alternatePathSeparator(String key, char alt_char) {
+ return alternatePathSeparator(key, '.', alt_char);
+ }
+}
diff --git a/corelib/pom.xml b/corelib/pom.xml
new file mode 100644
index 0000000..9f86f15
--- /dev/null
+++ b/corelib/pom.xml
@@ -0,0 +1,114 @@
+
+
+ *
+ * @param host Database host address
+ * @param port Database port
+ * @param dbname Database name
+ */
+ public MongoDB(String host, int port, String dbname) {
+ super(DatabaseType.MongoDB);
+
+ Validate.isTrue(!StringUtils.isEmpty(host), "Host Address cannot be null or empty!");
+
+ this.ip = host;
+ this.port = port;
+ this.dbname = dbname;
+ }
+
+ /**
+ * Gets whether connected to MongoDB.
+ *
+ *
+ *
+ * @return true if connected.
+ */
+ @Override
+ public boolean isConnected() {
+ return this.client != null && this.db != null;
+ }
+
+ /** Starts the connection with MongoDB. */
+ @Override
+ public synchronized void connect() {
+ this.client = new MongoClient(this.ip, this.port);
+ this.db = client.getDB(this.dbname);
+ }
+
+ /** Closes the connection with MongoDB. */
+ @Override
+ public void disconnect() {
+ this.client.close();
+ this.client = null;
+ this.db = null;
+ }
+
+ /**
+ * Gets Database Collection with given names and adds to database cache.
+ *
+ *
+ *
+ * @param name Names of collection
+ * @return Set of database collections
+ */
+ public Set
+ *
+ * @param name Name of collection
+ * @return Database collections
+ */
+ public DBCollection getCollection(String name) {
+ this.collections.add(db.getCollection(name));
+ return db.getCollection(name);
+ }
+
+ /**
+ * Gets all cached database collection.
+ *
+ *
+ *
+ * @return Set of database collection
+ */
+ public Set
+ *
+ * @return Set of database collection
+ */
+ public Set
+ *
+ * @return Defined database
+ */
+ public DB getDb() {
+ return db;
+ }
+
+ /**
+ * Returns the MongoClient
+ *
+ *
+ *
+ * @return Defined MongoClient
+ */
+ public MongoClient getClient() {
+ return client;
+ }
+}
diff --git a/database/src/main/java/com/pepedevs/corelib/database/mongo/MongoDocument.java b/database/src/main/java/com/pepedevs/corelib/database/mongo/MongoDocument.java
new file mode 100644
index 0000000..d11e10f
--- /dev/null
+++ b/database/src/main/java/com/pepedevs/corelib/database/mongo/MongoDocument.java
@@ -0,0 +1,401 @@
+package com.pepedevs.corelib.database.mongo;
+
+import com.mongodb.*;
+import org.apache.commons.lang.Validate;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Class for interacting with a Mongo database collection {@link DBObject docuemnts}. */
+public class MongoDocument {
+
+ private final MongoClient client;
+ private final DBCollection collection;
+
+ /**
+ * Constructs MongoDocument.
+ *
+ *
+ *
+ * @param client MongoClient object
+ * @param collection Database collection object
+ */
+ public MongoDocument(MongoClient client, DBCollection collection) {
+ Validate.notNull(collection, "Collection cannot be null!");
+
+ this.client = client;
+ this.collection = collection;
+ }
+
+ /**
+ * Constructs MongoDocument.
+ *
+ *
+ *
+ * @param client MongoClient object
+ * @param database Database object
+ * @param collection Database collection name
+ */
+ public MongoDocument(MongoClient client, DB database, String collection) {
+ this(client, database.getCollection(collection));
+ }
+
+ /**
+ * Constructs MongoDocument.
+ *
+ *
+ *
+ * @param host Database Host Address
+ * @param port Database Host port
+ * @param collection Database collection object
+ * @deprecated This initialization may conflict while storing and retrieving data.
+ */
+ @Deprecated
+ public MongoDocument(String host, int port, DBCollection collection) {
+ this(new MongoClient(host, port), collection);
+ }
+
+ /**
+ * Constructs MongoDocument.
+ *
+ *
+ *
+ * @param host Database Host Address
+ * @param port Database Host port
+ * @param database Database object
+ * @param collection Database collection name
+ * @deprecated This initialization may conflict while storing and retrieving data.
+ */
+ @Deprecated
+ public MongoDocument(String host, int port, DB database, String collection) {
+ this(new MongoClient(host, port), database.getCollection(collection));
+ }
+
+ /**
+ * Constructs MongoDocument.
+ *
+ *
+ *
+ * @param collection Database collection object
+ */
+ public MongoDocument(DBCollection collection) {
+ this(null, collection);
+ }
+
+ /**
+ * Constructs MongoDocument.
+ *
+ *
+ *
+ * @param database Database Object
+ * @param collection Database collection name
+ */
+ public MongoDocument(DB database, String collection) {
+ this(null, database.getCollection(collection));
+ }
+
+ /**
+ * Insert new document with the given id and data.
+ *
+ *
+ *
+ * @param id Document id
+ * @param key Key with which the specified value is to be associated
+ * @param value Value to be associated with the specified key
+ */
+ public void insert(Object id, String key, Object value) {
+ BasicDBObject document = new BasicDBObject();
+ document.put("_id", id);
+ document.put(key, value);
+ this.collection.insert(document);
+ }
+
+ /**
+ * Insert new document with the given data.
+ *
+ *
+ *
+ * @param key Key with which the specified value is to be associated
+ * @param value Value to be associated with the specified key
+ */
+ public void insert(String key, Object value) {
+ BasicDBObject document = new BasicDBObject();
+ document.put(key, value);
+ this.collection.insert(document);
+ }
+
+ /**
+ * Insert new document with the given id and data.
+ *
+ *
+ *
+ * @param id Document id
+ * @param values Map of keys and values
+ * @see #insert(Object, String, Object)
+ */
+ public void insert(Object id, Map
+ *
+ * @param values Map of keys and values
+ * @see #insert(String, Object)
+ * @see #insert(Object, Map)
+ */
+ public void insert(Map
+ *
+ * @param queryKey Query key to search with
+ * @param queryValue Query value to search with
+ * @param key Key with which the specified value is to be associated
+ * @param value Value to be associated with the specified key
+ */
+ public void update(String queryKey, Object queryValue, String key, Object value) {
+ BasicDBObject query = new BasicDBObject();
+ query.put(queryKey, queryValue);
+
+ BasicDBObject newDocument = new BasicDBObject();
+ newDocument.put(key, value);
+
+ BasicDBObject updateObject = new BasicDBObject();
+ updateObject.put("$set", newDocument);
+
+ collection.update(query, updateObject);
+ }
+
+ /**
+ * Find documents with the given query set and update/change the values
+ *
+ *
+ *
+ * @param queryKey Query key to search with
+ * @param queryValue Query value to search with
+ * @param values Map of keys and values
+ * @see #update(String, Object, String, Object)
+ */
+ public void update(String queryKey, Object queryValue, Map
+ *
+ * @param id Query document id
+ * @param key Key with which the specified value is to be associated
+ * @param value Value to be associated with the specified key
+ */
+ public void update(Object id, String key, Object value) {
+ BasicDBObject query = new BasicDBObject();
+ query.put("_id", id);
+
+ BasicDBObject newDocument = new BasicDBObject();
+ newDocument.put(key, value);
+
+ BasicDBObject updateObject = new BasicDBObject();
+ updateObject.put("$set", newDocument);
+
+ collection.update(query, updateObject);
+ }
+
+ /**
+ * Find documents with the given query set and update/change the values
+ *
+ *
+ *
+ * @param id Query document id
+ * @param values Map of keys and values
+ * @see #update(Object, String, Object)
+ * @see #update(String, Object, Map)
+ */
+ public void update(Object id, Map
+ *
+ * @param queryKey Query key to search with
+ * @param queryValue Query value to search with
+ * @return Set of {@link DBObject documents}
+ */
+ public Set
+ *
+ * @param query Map of query keys and values
+ * @return Set of {@link DBObject documents}
+ * @see #getDocument(String, Object)
+ */
+ public Set
+ *
+ * @param id Document id to search with
+ * @return {@link DBObject Document}
+ */
+ public DBObject getDocument(Object id) {
+ BasicDBObject searchQuery = new BasicDBObject();
+ searchQuery.put("_id", id);
+ DBCursor cursor = collection.find(searchQuery);
+ return cursor.one();
+ }
+
+ /**
+ * Get the field with the given document id and key.
+ *
+ *
+ *
+ * @param id Document id
+ * @param key Field key to get value from
+ * @return Value of the given field if found else null
+ */
+ public Object getField(Object id, String key) {
+ BasicDBObject searchQuery = new BasicDBObject();
+ searchQuery.put("_id", id);
+ DBCursor cursor = collection.find(searchQuery);
+ try {
+ return cursor.one().get(key);
+ } catch (NullPointerException ex) {
+ throw new NullPointerException("No document can be found with the id!");
+ }
+ }
+
+ /**
+ * Get the field with the given document and key.
+ *
+ *
+ *
+ * @param document {@link DBObject Document}
+ * @param key Field key to get value from
+ * @return Value of the given field if found else null
+ */
+ public Object getField(DBObject document, String key) {
+ return document.get(key);
+ }
+
+ /**
+ * Deletes a document with the given id.
+ *
+ *
+ *
+ * @param id Document id
+ */
+ public void delete(Object id) {
+ BasicDBObject searchQuery = new BasicDBObject();
+ searchQuery.put("_id", id);
+
+ collection.remove(searchQuery);
+ }
+
+ /**
+ * Deletes a document with the given search parameter.
+ *
+ *
+ *
+ * @param key Key to find a document with
+ * @param value Value to find a document with
+ */
+ public void delete(String key, Object value) {
+ BasicDBObject searchQuery = new BasicDBObject();
+ searchQuery.put(key, value);
+
+ collection.remove(searchQuery);
+ }
+
+ /**
+ * Deletes a document with the given search parameter.
+ *
+ *
+ *
+ * @param queries Map of query keys and values
+ */
+ public void delete(Map Note: Database file should end with {@code .db} extension.
+ *
+ *
+ *
+ * @param db Database file
+ * @param reconnect {@code true} to auto reconnect
+ */
+ public H2(File db, boolean reconnect) {
+ super(DatabaseType.H2);
+
+ Validate.notNull(db, "The database file cannot be null!");
+ this.db = db;
+ this.reconnect = reconnect;
+ }
+
+ /**
+ * Constructs the H2 database. Auto-reconnect is set {@code true}.
+ *
+ * Note: Database file should end with {@code .db} extension.
+ *
+ *
+ *
+ * @param db Database file
+ */
+ public H2(File db) {
+ this(db, true);
+ }
+
+ /**
+ * Gets whether connected to H2.
+ *
+ *
+ *
+ * @return true if connected.
+ */
+ @Override
+ public boolean isConnected() {
+ try {
+ return this.connection != null && !this.connection.isClosed();
+ } catch (SQLException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Starts the connection with H2.
+ *
+ *
+ *
+ * @throws IOException when the given database file cannot be created
+ * @throws IllegalStateException if the JDBC drivers is unavailable or the file is not s H2
+ * database file
+ * @throws SQLException if a database access error occurs.
+ * @throws SQLTimeoutException when the driver has determined that the timeout has been exceeded
+ * and has at least tried to cancel the current database connection attempt.
+ */
+ @Override
+ public synchronized void connect()
+ throws IOException, IllegalStateException, SQLException, SQLTimeoutException {
+ if (!this.db.getName().endsWith(".db"))
+ throw new IllegalStateException("The database file should have '.db' extension.");
+
+ if (!this.db.getParentFile().exists()) this.db.getParentFile().mkdirs();
+
+ if (!this.db.exists()) this.db.createNewFile();
+ try {
+ Class.forName(DRIVER_CLASS);
+ } catch (ClassNotFoundException ex) {
+ throw new IllegalStateException(
+ "Could not connect to H2! The H2 driver is unavailable!");
+ }
+ this.connection = DriverManager.getConnection("jdbc:h2:" + this.db.getAbsolutePath());
+ }
+
+ /**
+ * Closes the connection with H2.
+ *
+ *
+ *
+ * @throws IllegalStateException if currently not connected, the connection should be checked
+ * before calling this: {@link #isConnected()}.
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public void disconnect() throws SQLException {
+ if (!isConnected()) {
+ throw new IllegalStateException("Not connected!");
+ }
+
+ this.connection.close();
+ this.connection = null;
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ * @return Connection or null if not connected.
+ * @throws IOException when the given database file cannot be created
+ * @throws SQLTimeoutException when the driver has determined that the timeout value has been
+ * exceeded and has at least tried to cancel the current database connection attempt.
+ * @throws IllegalStateException if the JDBC drivers is unavailable or the file is not s H2
+ * database file
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public Connection getConnection()
+ throws IOException, SQLTimeoutException, IllegalStateException, SQLException {
+ if (!isConnected() && reconnect) {
+ this.lost_connections++;
+ this.connect();
+ }
+ return this.isConnected() ? this.connection : null;
+ }
+
+ /**
+ * The times the connection was lost.
+ *
+ *
+ *
+ * @return Times the connection was lost, or {@code -1} if the
+ * auto-reconnection is disabled.
+ */
+ @Override
+ public int getLostConnections() {
+ return reconnect ? lost_connections : -1;
+ }
+}
diff --git a/database/src/main/java/com/pepedevs/corelib/database/sql/hikaricp/HikariCP.java b/database/src/main/java/com/pepedevs/corelib/database/sql/hikaricp/HikariCP.java
new file mode 100644
index 0000000..706547e
--- /dev/null
+++ b/database/src/main/java/com/pepedevs/corelib/database/sql/hikaricp/HikariCP.java
@@ -0,0 +1,198 @@
+package com.pepedevs.corelib.database.sql.hikaricp;
+
+import com.pepedevs.corelib.database.DatabaseType;
+import com.pepedevs.corelib.database.sql.SQLDatabase;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import org.apache.commons.lang.Validate;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLTimeoutException;
+
+/** Class for interacting with a Hikari database. */
+public class HikariCP extends SQLDatabase {
+
+ /** The JDBC driver class. */
+ private static final String DRIVER_CLASS = "com.mysql.cj.jdbc.Driver";
+
+ private final HikariConfig config;
+ private final HikariDataSource dataSource;
+ private final boolean reconnect;
+
+ private Connection connection;
+ private int lost_connections;
+
+ /**
+ * Constructs the HikariCP database.
+ *
+ *
+ *
+ * @param config HikariConfig for connection
+ * @param dataSource DataSource for given config
+ * @param reconnect {@code true} to auto reconnect
+ */
+ public HikariCP(HikariConfig config, HikariDataSource dataSource, boolean reconnect) {
+ super(DatabaseType.HikariCP);
+
+ Validate.notNull(config, "HikariConfig cannot be null!");
+ Validate.notNull(dataSource, "HikariDataSource cannot be null!");
+
+ this.config = config;
+ this.dataSource = dataSource;
+ this.reconnect = reconnect;
+ }
+
+ /**
+ * Constructs the HikariCP database.
+ *
+ *
+ *
+ * @param config HikariConfig for connection
+ * @param dataSource DataSource for given config
+ */
+ public HikariCP(HikariConfig config, HikariDataSource dataSource) {
+ this(config, dataSource, true);
+ }
+
+ /**
+ * Constructs the HikariCP database.
+ *
+ *
+ *
+ * @param builder {@link HikariClientBuilder}
+ */
+ public HikariCP(HikariClientBuilder builder) {
+ this(builder.getConfig(), new HikariDataSource(builder.getConfig()), builder.isReconnect());
+ }
+
+ /**
+ * Gets whether connected to HikariCP.
+ *
+ *
+ *
+ * @return true if connected.
+ */
+ @Override
+ public boolean isConnected() {
+ try {
+ return this.connection != null && !this.connection.isClosed();
+ } catch (SQLException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Starts the connection with HikariCP.
+ *
+ *
+ *
+ * @throws IllegalStateException if the JDBC drivers is unavailable.
+ * @throws SQLException if a database access error occurs.
+ * @throws SQLTimeoutException when the driver has determined that the timeout has been exceeded
+ * and has at least tried to cancel the current database connection attempt.
+ */
+ @Override
+ public synchronized void connect() throws IllegalStateException, SQLException {
+ try {
+ Class.forName(DRIVER_CLASS);
+ } catch (ClassNotFoundException ex) {
+ throw new IllegalStateException(
+ "Could not connect to HikariCP! The JDBC connection driver is unavailable!");
+ }
+
+ this.connection = dataSource.getConnection();
+ }
+
+ /**
+ * Closes the connection with HikariCP.
+ *
+ *
+ *
+ * @throws IllegalStateException if currently not connected, the connection should be checked
+ * before calling this: {@link #isConnected()}.
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public void disconnect() throws SQLException, IllegalStateException {
+ if (!isConnected()) {
+ throw new IllegalStateException("Not connected!");
+ }
+
+ this.connection.close();
+ this.connection = null;
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ * @return Connection or null if not connected
+ * @throws SQLTimeoutException when the driver has determined that the timeout value has been
+ * exceeded and has at least tried to cancel the current database connection attempt.
+ * @throws IllegalStateException if the JDBC drivers is unavailable
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public Connection getConnection() throws IllegalStateException, SQLException {
+ if (!isConnected() && reconnect) {
+ this.lost_connections++;
+ this.connect();
+ }
+ return this.isConnected() ? this.connection : null;
+ }
+
+ /**
+ * The times the connection was lost.
+ *
+ *
+ *
+ * @return Times the connection was lost, or {@code -1} if the
+ * auto-reconnection is disabled.
+ */
+ @Override
+ public int getLostConnections() {
+ return reconnect ? lost_connections : -1;
+ }
+
+ /**
+ * Get {@link HikariConfig}.
+ *
+ *
+ *
+ * @return Current HikariConfig
+ */
+ public HikariConfig getConfig() {
+ return config;
+ }
+
+ /**
+ * Get {@link HikariDataSource}.
+ *
+ *
+ *
+ * @return Datasource for current HikariConfig
+ */
+ public HikariDataSource getDataSource() {
+ return dataSource;
+ }
+}
diff --git a/database/src/main/java/com/pepedevs/corelib/database/sql/hikaricp/HikariClientBuilder.java b/database/src/main/java/com/pepedevs/corelib/database/sql/hikaricp/HikariClientBuilder.java
new file mode 100644
index 0000000..6bca374
--- /dev/null
+++ b/database/src/main/java/com/pepedevs/corelib/database/sql/hikaricp/HikariClientBuilder.java
@@ -0,0 +1,349 @@
+package com.pepedevs.corelib.database.sql.hikaricp;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.Validate;
+
+import java.util.Properties;
+
+/** Class for creating Hikari client. */
+public class HikariClientBuilder {
+
+ private static final String URL_FORMAT =
+ "jdbc:mysql://"
+ + "%s" // host
+ + ":"
+ + "%d" // port
+ + "/"
+ + "%s" // database
+ + "?autoReconnect="
+ + "%s" // auto reconnect
+ + "&"
+ + "useSSL="
+ + "%s" // use ssl
+ ;
+
+ private final String host;
+ private final int port;
+ private final String database;
+ private final String username;
+ private final String password;
+ private final boolean reconnect;
+ private final boolean ssl;
+
+ private HikariConfig config;
+
+ /**
+ * Constructs the Hikari Client Builder.
+ *
+ *
+ *
+ * @param host Hostname
+ * @param username Username
+ * @param password Password
+ * @param reconnect {@code true} to auto reconnect
+ */
+ public HikariClientBuilder(
+ String host,
+ int port,
+ String database,
+ String username,
+ String password,
+ boolean reconnect,
+ boolean ssl) {
+ this.config = new HikariConfig();
+
+ Validate.isTrue(!StringUtils.isEmpty(host), "The host cannot be null or empty!");
+ Validate.isTrue(!StringUtils.isEmpty(database), "The database cannot be null or empty!");
+ Validate.notNull(username, "Username cannot be null!");
+ Validate.notNull(password, "Password cannot be null!");
+
+ this.host = host;
+ this.port = port;
+ this.database = database;
+ this.username = username;
+ this.password = password;
+ this.reconnect = reconnect;
+ this.ssl = ssl;
+
+ this.config.setJdbcUrl(
+ String.format(
+ URL_FORMAT,
+ this.host,
+ this.port,
+ this.database,
+ this.reconnect,
+ this.ssl));
+ this.config.setUsername(this.username);
+ this.config.setPassword(this.password);
+ }
+
+ /**
+ * Constructs the Hikari Client Builder.
+ *
+ *
+ *
+ * @param host Host name
+ * @param port Port number
+ * @param database Database name
+ * @param username User name
+ * @param password User password
+ * @param reconnect {@code true} to auto reconnect
+ */
+ public HikariClientBuilder(
+ String host,
+ int port,
+ String database,
+ String username,
+ String password,
+ boolean reconnect) {
+ this(host, port, database, username, password, reconnect, true);
+ }
+
+ /**
+ * Add a property (name/value pair) that will be used to configure the DataSource Driver.
+ *
+ *
+ *
+ * @param key The name of property
+ * @param value Value of the property
+ * @return This Object, for chaining
+ * @see HikariConfig#addDataSourceProperty(String, Object)
+ */
+ public HikariClientBuilder addProperty(String key, String value) {
+ this.config.addDataSourceProperty(key, value);
+ return this;
+ }
+
+ /**
+ * Set the fully qualified class name of the JDBC DataSource that will be used create
+ * Connections.
+ *
+ *
+ *
+ * @param className The fully qualified name of the JDBC DataSource class
+ * @return This Object, for chaining
+ * @see HikariConfig#setDataSourceClassName(String)
+ */
+ public HikariClientBuilder setDataSourceClassName(String className) {
+ this.config.setDataSourceClassName(className);
+ return this;
+ }
+
+ /**
+ * Set the provided {@link Properties} to configure the driver.
+ *
+ *
+ *
+ * @param property {@link Properties} to set to the driver
+ * @return This Object, for chaining
+ */
+ public HikariClientBuilder setProperty(Properties property) {
+ this.config.setDataSourceProperties(property);
+ return this;
+ }
+
+ /**
+ * Set the default auto-commit behaviour of connections in the pool.
+ *
+ *
+ *
+ * @param value The desired auto-commit value
+ * @return This Object, for chaining
+ * @see HikariConfig#setAutoCommit(boolean)
+ */
+ public HikariClientBuilder setAutoCommit(boolean value) {
+ this.config.setAutoCommit(value);
+ return this;
+ }
+
+ /**
+ * Set the maximum number of milliseconds that a client will wait for the connection from the
+ * pool.
+ *
+ *
+ *
+ * @param timeout The connection timeout in milliseconds
+ * @return This Object, for chaining
+ * @see HikariConfig#setConnectionTimeout(long)
+ */
+ public HikariClientBuilder setConnectionTimeout(long timeout) {
+ this.config.setConnectionTimeout(timeout);
+ return this;
+ }
+
+ /**
+ * Set the maximum amount a time (in milliseconds) that a connection is allowed to sit idle in
+ * the pool.
+ *
+ *
+ *
+ * @param timeout The idle timeout in milliseconds
+ * @return This Object, for chaining
+ * @see HikariConfig#setIdleTimeout(long)
+ */
+ public HikariClientBuilder setIdleTimeout(long timeout) {
+ this.config.setIdleTimeout(timeout);
+ return this;
+ }
+
+ /**
+ * Set the keep alive timeout for the connection in the pool.
+ *
+ *
+ *
+ * @param time The interval in milliseconds in which the connection will be tested for aliveness
+ * @return This Object, for chaining
+ * @see HikariConfig#setKeepaliveTime(long)
+ */
+ public HikariClientBuilder setKeepAliveTime(long time) {
+ this.config.setKeepaliveTime(time);
+ return this;
+ }
+
+ /**
+ * Set the maximum life of the connection in the pool.
+ *
+ *
+ *
+ * @param time The maximum connection lifetime in the pool
+ * @return This Object, for chaining
+ * @see HikariConfig#setMaxLifetime(long)
+ */
+ public HikariClientBuilder setMaxLifeTime(long time) {
+ this.config.setMaxLifetime(time);
+ return this;
+ }
+
+ /**
+ * Set the SQL query to be executed to test the validity of the connection.
+ *
+ *
+ *
+ * @param query SQL query string
+ * @return This Object, for chaining
+ * @see HikariConfig#setConnectionTestQuery(String)
+ */
+ public HikariClientBuilder setConnectionTestQuery(String query) {
+ this.config.setConnectionTestQuery(query);
+ return this;
+ }
+
+ /**
+ * Set the minimum number of idle connections that will be maintained in the pool.
+ *
+ *
+ *
+ * @param minIdle The minimum number of idle connections
+ * @return This Object, for chaining
+ * @see HikariConfig#setMinimumIdle(int)
+ */
+ public HikariClientBuilder setMinimumIdle(int minIdle) {
+ this.config.setMinimumIdle(minIdle);
+ return this;
+ }
+
+ /**
+ * Set the maximum size that the pool is allowed to reach, including both idle and in-use
+ * connection.
+ *
+ *
+ *
+ * @param size The maximum number of connections
+ * @return This Object, for chaining
+ * @see HikariConfig#setMaximumPoolSize(int)
+ */
+ public HikariClientBuilder setMaximumPoolSize(int size) {
+ this.config.setMaximumPoolSize(size);
+ return this;
+ }
+
+ /**
+ * Set a MetricRegistry instance to use for registration of metrics used by HikariCP.
+ *
+ *
+ *
+ * @param registry The MetricRegistry instance for use
+ * @return This Object, for chaining
+ * @see HikariConfig#setMetricRegistry(Object)
+ */
+ public HikariClientBuilder setMetricRegistry(Object registry) {
+ this.config.setMetricRegistry(registry);
+ return this;
+ }
+
+ /**
+ * Set the HealthRegistry that will be used for registration of health checks by HikariCP.
+ *
+ *
+ *
+ * @param registry The HikariRegistry to be used
+ * @return This Object, for chaining
+ * @see HikariConfig#setHealthCheckRegistry(Object)
+ */
+ public HikariClientBuilder setHealthCheckRegistry(Object registry) {
+ this.config.setHealthCheckRegistry(registry);
+ return this;
+ }
+
+ /**
+ * Set the name of the connection pool.
+ *
+ *
+ *
+ * @param name The name of connection pool to use
+ * @return This Object, for chaining
+ * @see HikariConfig#setPoolName(String)
+ */
+ public HikariClientBuilder setPoolName(String name) {
+ this.config.setPoolName(name);
+ return this;
+ }
+
+ /**
+ * Build the HikariCP client.
+ *
+ *
+ *
+ * @return {@link HikariCP}
+ */
+ public HikariCP build() {
+ return new HikariCP(config, new HikariDataSource(config), reconnect);
+ }
+
+ /**
+ * Gets the {@link HikariConfig}.
+ *
+ *
+ *
+ * @return {@link HikariConfig}
+ */
+ public HikariConfig getConfig() {
+ return config;
+ }
+
+ /**
+ * Sets the {@link HikariConfig}.
+ *
+ *
+ *
+ * @param config The {@link HikariConfig} to set
+ * @return This Object, for chaining
+ */
+ public HikariClientBuilder setConfig(HikariConfig config) {
+ this.config = config;
+ return this;
+ }
+
+ /**
+ * Checks if its auto re-connectable.
+ *
+ *
+ *
+ * @return {@code true} if auto reconnect is enabled, else false
+ */
+ public boolean isReconnect() {
+ return reconnect;
+ }
+}
diff --git a/database/src/main/java/com/pepedevs/corelib/database/sql/mysql/MySQL.java b/database/src/main/java/com/pepedevs/corelib/database/sql/mysql/MySQL.java
new file mode 100644
index 0000000..1d5ce81
--- /dev/null
+++ b/database/src/main/java/com/pepedevs/corelib/database/sql/mysql/MySQL.java
@@ -0,0 +1,223 @@
+package com.pepedevs.corelib.database.sql.mysql;
+
+import com.pepedevs.corelib.database.DatabaseType;
+import com.pepedevs.corelib.database.sql.SQLDatabase;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.Validate;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLTimeoutException;
+
+/** Class for interacting with a MySQL database. */
+public class MySQL extends SQLDatabase {
+
+ /** Connection URL format. */
+ private static final String URL_FORMAT =
+ "jdbc:mysql://"
+ + "%s" // host
+ + ":"
+ + "%d" // port
+ + "/"
+ + "%s" // database
+ + "?autoReconnect="
+ + "%s" // auto reconnect
+ + "&"
+ + "useSSL="
+ + "%s" // use ssl
+ ;
+
+ /** The JDBC driver class. */
+ private static final String DRIVER_CLASS = "com.mysql.cj.jdbc.Driver";
+
+ private final String host;
+ private final int port;
+ private final String database;
+ private final String username;
+ private final String password;
+ private final boolean reconnect;
+ private final boolean ssl;
+
+ private Connection connection;
+ private int lost_connections;
+
+ /**
+ * Constructs the MySQL database.
+ *
+ *
+ *
+ * @param host Host name
+ * @param port Port number
+ * @param database Database name
+ * @param username User name
+ * @param password User password
+ * @param reconnect {@code true} to auto reconnect
+ * @param ssl {@code true} to use SSL
+ */
+ public MySQL(
+ String host,
+ int port,
+ String database,
+ String username,
+ String password,
+ boolean reconnect,
+ boolean ssl) {
+ super(DatabaseType.MYSQL);
+
+ Validate.isTrue(!StringUtils.isEmpty(host), "The host cannot be null or empty!");
+ Validate.isTrue(!StringUtils.isEmpty(database), "The database cannot be null or empty!");
+ Validate.notNull(username, "The username cannot be null!");
+ Validate.notNull(password, "The password cannot be null!");
+
+ this.host = host;
+ this.port = port;
+ this.database = database;
+ this.username = username;
+ this.password = password;
+ this.reconnect = reconnect;
+ this.ssl = ssl;
+ }
+
+ /**
+ * Constructs the MySQL database.
+ *
+ *
+ *
+ * @param host Host name
+ * @param port Port number
+ * @param database Database name
+ * @param username User name
+ * @param password User password
+ * @param reconnect {@code true} to auto reconnect
+ */
+ public MySQL(
+ String host,
+ int port,
+ String database,
+ String username,
+ String password,
+ boolean reconnect) {
+ this(host, port, database, username, password, reconnect, true);
+ }
+
+ /**
+ * Gets whether connected to MySQL.
+ *
+ *
+ *
+ * @return true if connected.
+ */
+ @Override
+ public boolean isConnected() {
+ try {
+ return this.connection != null && !this.connection.isClosed();
+ } catch (SQLException e) {
+ return false;
+ }
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ * @return Connection or null if not connected
+ * @throws SQLTimeoutException when the driver has determined that the timeout value has been
+ * exceeded and has at least tried to cancel the current database connection attempt.
+ * @throws IllegalStateException if the JDBC drivers is unavailable
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public Connection getConnection()
+ throws SQLTimeoutException, IllegalStateException, SQLException {
+ if (!isConnected() && reconnect) {
+ this.lost_connections++;
+ this.connect();
+ }
+ return this.isConnected() ? this.connection : null;
+ }
+
+ /**
+ * The times the connection was lost.
+ *
+ *
+ *
+ * @return Times the connection was lost, or {@code -1} if the
+ * auto-reconnection is disabled.
+ */
+ @Override
+ public int getLostConnections() {
+ return reconnect ? lost_connections : -1;
+ }
+
+ /**
+ * Starts the connection with MySQL.
+ *
+ *
+ *
+ * @throws IllegalStateException if the JDBC drivers is unavailable.
+ * @throws SQLException if a database access error occurs.
+ * @throws SQLTimeoutException when the driver has determined that the timeout has been exceeded
+ * and has at least tried to cancel the current database connection attempt.
+ */
+ @Override
+ public synchronized void connect()
+ throws IllegalStateException, SQLException, SQLTimeoutException {
+ try {
+ Class.forName(DRIVER_CLASS);
+ } catch (ClassNotFoundException ex) {
+ throw new IllegalStateException(
+ "Could not connect to MySQL! The JDBC driver is unavailable!");
+ }
+
+ this.connection =
+ DriverManager.getConnection(
+ String.format(
+ URL_FORMAT,
+ host,
+ port,
+ database,
+ reconnect,
+ ssl),
+ username,
+ password);
+ }
+
+ /**
+ * Closes the connection with MySQL.
+ *
+ *
+ *
+ * @throws IllegalStateException if currently not connected, the connection should be checked
+ * before calling this: {@link #isConnected()}.
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public void disconnect() throws SQLException {
+ if (!isConnected()) {
+ throw new IllegalStateException("Not connected!");
+ }
+
+ this.connection.close();
+ this.connection = null;
+ }
+}
diff --git a/database/src/main/java/com/pepedevs/corelib/database/sql/postgresql/PostGreSQL.java b/database/src/main/java/com/pepedevs/corelib/database/sql/postgresql/PostGreSQL.java
new file mode 100644
index 0000000..f48099e
--- /dev/null
+++ b/database/src/main/java/com/pepedevs/corelib/database/sql/postgresql/PostGreSQL.java
@@ -0,0 +1,221 @@
+package com.pepedevs.corelib.database.sql.postgresql;
+
+import com.pepedevs.corelib.database.DatabaseType;
+import com.pepedevs.corelib.database.sql.SQLDatabase;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.Validate;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLTimeoutException;
+
+public class PostGreSQL extends SQLDatabase {
+
+ /** Connection URL format. */
+ private static final String URL_FORMAT =
+ "jdbc:postgresql://"
+ + "%s" // host
+ + ":"
+ + "%d" // port
+ + "/"
+ + "%s" // database
+ + "?autoReconnect="
+ + "%s" // auto reconnect
+ + "&"
+ + "useSSL="
+ + "%s" // use ssl
+ ;
+
+ private static final String DRIVER_CLASS = "org.postgresql.Driver";
+
+ private final String host;
+ private final int port;
+ private final String database;
+ private final String username;
+ private final String password;
+ private final boolean reconnect;
+ private final boolean ssl;
+
+ private Connection connection;
+ private int lost_connections;
+
+ /**
+ * Constructs the PostGreSQL database.
+ *
+ *
+ *
+ * @param host Host name
+ * @param port Port number
+ * @param database Database name
+ * @param username User name
+ * @param password User password
+ * @param reconnect {@code true} to auto reconnect
+ * @param ssl {@code true} to use SSL
+ */
+ public PostGreSQL(
+ String host,
+ int port,
+ String database,
+ String username,
+ String password,
+ boolean reconnect,
+ boolean ssl) {
+ super(DatabaseType.PostGreSQL);
+
+ Validate.isTrue(!StringUtils.isEmpty(host), "The host cannot be null or empty!");
+ Validate.isTrue(!StringUtils.isEmpty(database), "The database cannot be null or empty!");
+ Validate.notNull(username, "The username cannot be null!");
+ Validate.notNull(password, "The password cannot be null!");
+
+ this.host = host;
+ this.port = port;
+ this.database = database;
+ this.username = username;
+ this.password = password;
+ this.reconnect = reconnect;
+ this.ssl = ssl;
+ }
+
+ /**
+ * Constructs the PostGreSQL database.
+ *
+ *
+ *
+ * @param host Host name
+ * @param port Port number
+ * @param database Database name
+ * @param username User name
+ * @param password User password
+ * @param reconnect {@code true} to auto reconnect
+ */
+ public PostGreSQL(
+ String host,
+ int port,
+ String database,
+ String username,
+ String password,
+ boolean reconnect) {
+ this(host, port, database, username, password, reconnect, true);
+ }
+
+ /**
+ * Gets whether connected to PostGreSQL.
+ *
+ *
+ *
+ * @return true if connected.
+ */
+ @Override
+ public boolean isConnected() {
+ try {
+ return this.connection != null && !this.connection.isClosed();
+ } catch (SQLException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Starts the connection with PostGreSQL.
+ *
+ *
+ *
+ * @throws IllegalStateException if the JDBC drivers is unavailable.
+ * @throws SQLException if a database access error occurs.
+ * @throws SQLTimeoutException when the driver has determined that the timeout has been exceeded
+ * and has at least tried to cancel the current database connection attempt.
+ */
+ @Override
+ public synchronized void connect()
+ throws IllegalStateException, SQLException, SQLTimeoutException {
+ try {
+ Class.forName(DRIVER_CLASS);
+ } catch (ClassNotFoundException ex) {
+ throw new IllegalStateException(
+ "Could not connect to PostGreSQL! The JDBC driver is unavailable!");
+ }
+
+ this.connection =
+ DriverManager.getConnection(
+ String.format(
+ URL_FORMAT,
+ host,
+ port,
+ database,
+ String.valueOf(reconnect),
+ String.valueOf(ssl)),
+ username,
+ password);
+ }
+
+ /**
+ * Closes the connection with PostGreSQL.
+ *
+ *
+ *
+ * @throws IllegalStateException if currently not connected, the connection should be checked
+ * before calling this: {@link #isConnected()}.
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public void disconnect() throws SQLException {
+ if (!this.isConnected()) {
+ throw new IllegalStateException("Not connected!");
+ }
+
+ this.connection.close();
+ this.connection = null;
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ * @return Connection or null if not connected
+ * @throws SQLTimeoutException when the driver has determined that the timeout value has been
+ * exceeded and has at least tried to cancel the current database connection attempt.
+ * @throws IllegalStateException if the JDBC drivers is unavailable
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public Connection getConnection()
+ throws SQLTimeoutException, IllegalStateException, SQLException {
+ if (!isConnected() && reconnect) {
+ this.lost_connections++;
+ this.connect();
+ }
+ return this.isConnected() ? this.connection : null;
+ }
+
+ /**
+ * The times the connection was lost.
+ *
+ *
+ *
+ * @return Times the connection was lost, or {@code -1} if the
+ * auto-reconnection is disabled.
+ */
+ @Override
+ public int getLostConnections() {
+ return reconnect ? lost_connections : -1;
+ }
+}
diff --git a/database/src/main/java/com/pepedevs/corelib/database/sql/sqlite/SQLite.java b/database/src/main/java/com/pepedevs/corelib/database/sql/sqlite/SQLite.java
new file mode 100644
index 0000000..d87e4cd
--- /dev/null
+++ b/database/src/main/java/com/pepedevs/corelib/database/sql/sqlite/SQLite.java
@@ -0,0 +1,175 @@
+package com.pepedevs.corelib.database.sql.sqlite;
+
+import com.pepedevs.corelib.database.DatabaseType;
+import com.pepedevs.corelib.database.sql.SQLDatabase;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLTimeoutException;
+
+/** Class for interacting with a SQLite database. */
+public class SQLite extends SQLDatabase {
+
+ /** The JDBC driver class. */
+ private static final String DRIVER_CLASS = "org.sqlite.JDBC";
+
+ private final File db;
+ private final boolean reconnect;
+
+ private Connection connection;
+ private int lost_connections;
+
+ /**
+ * Constructs the SQLite database.
+ *
+ * Note: Database file should end with {@code .db} extension.
+ *
+ *
+ *
+ * @param db Database file
+ * @param reconnect {@code true} to auto reconnect
+ */
+ public SQLite(File db, boolean reconnect) {
+ super(DatabaseType.SQLite);
+
+ if (db == null)
+ throw new IllegalArgumentException("The database file cannot be null!");
+
+ this.db = db;
+ this.reconnect = reconnect;
+ }
+
+ /**
+ * Constructs the SQLite database. Auto-reconnect is set {@code true}.
+ *
+ * Note: Database file should end with {@code .db} extension.
+ *
+ *
+ *
+ * @param db Database file
+ */
+ public SQLite(File db) {
+ this(db, true);
+ }
+
+ /**
+ * Gets whether connected to SQLite.
+ *
+ *
+ *
+ * @return true if connected.
+ */
+ @Override
+ public boolean isConnected() {
+ try {
+ return this.connection != null && !this.connection.isClosed();
+ } catch (SQLException e) {
+ return false;
+ }
+ }
+
+ /**
+ *
+ *
+ *
+ *
+ * @return Connection or null if not connected.
+ * @throws IOException when the given database file cannot be created
+ * @throws SQLTimeoutException when the driver has determined that the timeout value has been
+ * exceeded and has at least tried to cancel the current database connection attempt.
+ * @throws IllegalStateException if the JDBC drivers is unavailable or the file is not s sqlite
+ * database file
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public Connection getConnection()
+ throws IOException, SQLTimeoutException, IllegalStateException, SQLException {
+ if (!isConnected() && reconnect) {
+ this.lost_connections++;
+ this.connect();
+ }
+ return this.isConnected() ? this.connection : null;
+ }
+
+ /**
+ * The times the connection was lost.
+ *
+ *
+ *
+ * @return Times the connection was lost, or {@code -1} if the
+ * auto-reconnection is disabled.
+ */
+ @Override
+ public int getLostConnections() {
+ return reconnect ? lost_connections : -1;
+ }
+
+ /**
+ * Starts the connection with SQLite.
+ *
+ *
+ *
+ * @throws IOException when the given database file cannot be created
+ * @throws IllegalStateException if the JDBC drivers is unavailable or the file is not s sqlite
+ * database file
+ * @throws SQLException if a database access error occurs.
+ * @throws SQLTimeoutException when the driver has determined that the timeout has been exceeded
+ * and has at least tried to cancel the current database connection attempt.
+ */
+ @Override
+ public synchronized void connect()
+ throws IOException, IllegalStateException, SQLException, SQLTimeoutException {
+ if (!this.db.getName().endsWith(".db"))
+ throw new IllegalStateException("The database file should have '.db' extension.");
+
+ if (!this.db.getParentFile().exists()) this.db.getParentFile().mkdirs();
+
+ if (!this.db.exists()) this.db.createNewFile();
+ try {
+ Class.forName(DRIVER_CLASS);
+ } catch (ClassNotFoundException ex) {
+ throw new IllegalStateException(
+ "Could not connect to SQLite! The JDBC driver is unavailable!");
+ }
+ this.connection = DriverManager.getConnection("jdbc:sqlite:" + this.db.getAbsolutePath());
+ }
+
+ /**
+ * Closes the connection with SQLite.
+ *
+ *
+ *
+ * @throws IllegalStateException if currently not connected, the connection should be checked
+ * before calling this: {@link #isConnected()}.
+ * @throws SQLException if a database access error occurs.
+ */
+ @Override
+ public void disconnect() throws SQLException {
+ if (!isConnected()) {
+ throw new IllegalStateException("Not connected!");
+ }
+
+ this.connection.close();
+ this.connection = null;
+ }
+}
diff --git a/event-utils/pom.xml b/event-utils/pom.xml
new file mode 100644
index 0000000..75d0b6a
--- /dev/null
+++ b/event-utils/pom.xml
@@ -0,0 +1,27 @@
+
+
+ *
+ * @param async true indicates the event will fire asynchronously, false by default from default
+ * constructor
+ */
+ public CustomEvent(boolean async) {
+ super(async);
+ }
+
+ /**
+ * Calls this event.
+ *
+ * This is the same as:
+ *
+ *
+ *
+ * @return This Object, for chaining.
+ * @throws IllegalStateException thrown when an asynchronous event is fired from synchronous
+ * code.
+ * Note: This is best-effort basis, and should not be used to test synchronized state.
+ * This is an indicator for flawed flow logic.
+ */
+ public CustomEvent call() throws IllegalStateException {
+ Bukkit.getPluginManager().callEvent(this);
+ return this;
+ }
+}
diff --git a/event-utils/src/main/java/com/pepedevs/corelib/events/CustomEventCancellable.java b/event-utils/src/main/java/com/pepedevs/corelib/events/CustomEventCancellable.java
new file mode 100644
index 0000000..9cda84d
--- /dev/null
+++ b/event-utils/src/main/java/com/pepedevs/corelib/events/CustomEventCancellable.java
@@ -0,0 +1,21 @@
+package com.pepedevs.corelib.events;
+
+import org.bukkit.event.Cancellable;
+
+/** An event that implements {@link Cancellable}, allowing its cancellation. */
+public abstract class CustomEventCancellable extends CustomEvent implements Cancellable {
+
+ /** Whether this event is cancelled or not. */
+ protected boolean cancelled;
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+}
diff --git a/event-utils/src/main/java/com/pepedevs/corelib/events/EventHandler.java b/event-utils/src/main/java/com/pepedevs/corelib/events/EventHandler.java
new file mode 100644
index 0000000..2befe1c
--- /dev/null
+++ b/event-utils/src/main/java/com/pepedevs/corelib/events/EventHandler.java
@@ -0,0 +1,33 @@
+package com.pepedevs.corelib.events;
+
+import org.bukkit.Bukkit;
+import org.bukkit.event.Event;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.plugin.EventExecutor;
+import org.bukkit.plugin.Plugin;
+
+import java.util.function.Consumer;
+
+public interface EventHandler extends Listener, EventExecutor {
+
+ static
+ *
+ * @param action {@link Action}
+ * @return
+ *
+ * @param action {@link Action}
+ * @return
+ *
+ * @param action {@link Action}
+ * @return
+ *
+ * @param action {@link Action}
+ * @return
+ *
+ * @param pluginName Plugin to check for loading
+ * @param callback Consumer to trigger when the plugin loads
+ */
+ public static void onPluginLoaded(Plugin handlingPlugin, String pluginName, Consumer
+ *
+ * @param player Player involved in this event.
+ * @param async true indicates the event will fire asynchronously, false by default from default
+ * constructor
+ */
+ public CustomPlayerEvent(Player player, boolean async) {
+ super(async);
+ this.player = player;
+ }
+
+ /**
+ * Constructing a synchronous player event.
+ *
+ *
+ *
+ * @param player Player involved in this event.
+ */
+ public CustomPlayerEvent(final Player player) {
+ this(player, false);
+ }
+
+ /**
+ * Gets the player involved in this event.
+ *
+ *
+ *
+ * @return Player who is involved in this event.
+ */
+ public Player getPlayer() {
+ return player;
+ }
+}
diff --git a/event-utils/src/main/java/com/pepedevs/corelib/events/player/CustomPlayerEventCancellable.java b/event-utils/src/main/java/com/pepedevs/corelib/events/player/CustomPlayerEventCancellable.java
new file mode 100644
index 0000000..57b09f8
--- /dev/null
+++ b/event-utils/src/main/java/com/pepedevs/corelib/events/player/CustomPlayerEventCancellable.java
@@ -0,0 +1,46 @@
+package com.pepedevs.corelib.events.player;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+
+/** A player related event that implements {@link Cancellable}, allowing its cancellation. */
+public abstract class CustomPlayerEventCancellable extends CustomPlayerEvent
+ implements Cancellable {
+
+ /** Whether the event is cancelled or not */
+ protected boolean cancelled;
+
+ /**
+ * Constructing a player event.
+ *
+ *
+ *
+ * @param player Player involved in this event.
+ * @param async true indicates the event will fire asynchronously, false by default from default
+ * constructor
+ */
+ public CustomPlayerEventCancellable(Player player, boolean async) {
+ super(player, async);
+ }
+
+ /**
+ * Constructing a synchronous player event.
+ *
+ *
+ *
+ * @param player Player involved in this event.
+ */
+ public CustomPlayerEventCancellable(final Player player) {
+ super(player);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+}
diff --git a/inventory-gui/pom.xml b/inventory-gui/pom.xml
new file mode 100644
index 0000000..aa8e775
--- /dev/null
+++ b/inventory-gui/pom.xml
@@ -0,0 +1,32 @@
+
+
+ *
+ * @param name Name of the AnvilItem
+ * @param icon ItemStack icon for the AnvilItem
+ * @param lore Lore of the AnvilItem
+ */
+ public AnvilItem(String name, ItemStack icon, Collection
+ *
+ * @param name Name of the AnvilItem
+ * @param icon ItemStack icon for the AnvilItem
+ * @param lore Lore of the AnvilItem
+ */
+ public AnvilItem(String name, ItemStack icon, String... lore) {
+ this(name, icon, Arrays.asList(lore));
+ }
+
+ /**
+ * Constructs the AnvilItem.
+ *
+ *
+ *
+ * @param icon ItemStack icon for the AnvilItem
+ */
+ public AnvilItem(ItemStack icon) {
+ this(
+ StringUtils.defaultIfBlank(
+ icon.getItemMeta() != null ? icon.getItemMeta().getDisplayName() : null,
+ "null name"),
+ icon,
+ (String[])
+ ItemStackUtils.extractLore(icon, false)
+ .toArray(
+ new String
+ [ItemStackUtils.extractLore(icon, false).size()]));
+ }
+
+ /**
+ * Returns the name of the AnvilItem.
+ *
+ *
+ *
+ * @return Name of the AnvilItem
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of the AnvilItem
+ *
+ *
+ *
+ * @param name Name for the AnvilItem
+ * @return This Object, for chaining
+ */
+ public AnvilItem setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Returns the icon of the AnvilItem.
+ *
+ *
+ *
+ * @return Icon of the AnvilItem
+ */
+ public ItemStack getIcon() {
+ return icon;
+ }
+
+ /**
+ * Sets the icon of the AnvilItem
+ *
+ *
+ *
+ * @param icon ItemStack icon for the AnvilItem
+ * @return This Object, for chaining
+ */
+ public AnvilItem setIcon(ItemStack icon) {
+ Validate.notNull(icon, "The icon cannot be null!");
+ this.icon = icon;
+ return this;
+ }
+
+ /**
+ * Returns the display icon of the AnvilItem.
+ *
+ *
+ *
+ * @return Display icon of the AnvilItem
+ */
+ public ItemStack getDisplayIcon() {
+ return this.getIcon().getType() == Material.AIR
+ ? this.icon
+ : (new ItemMetaBuilder(MaterialUtils.getRightMaterial(this.getIcon())))
+ .withDisplayName(StringUtils.translateAlternateColorCodes(this.getName()))
+ .withLore(this.getLore())
+ .applyTo(this.getIcon().clone());
+ }
+
+ /**
+ * Returns the lore of the AnvilItem.
+ *
+ *
+ *
+ * @return Lore of the AnvilItem
+ */
+ public List
+ *
+ * @param lore Lore for the AnvilItem
+ * @return This Object, for chaining
+ */
+ public AnvilItem setLore(List Note that this will return null if this item has never been set in a
+ * menu.
+ *
+ *
+ *
+ * @return The menu this item belongs
+ */
+ public AnvilMenu getMenu() {
+ return menu;
+ }
+
+ /**
+ * Click trigger for this AnvilItem.
+ *
+ *
+ *
+ * @param action {@link AnvilItemClickAction} for the AnvilItem
+ */
+ public abstract void onClick(AnvilItemClickAction action);
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/AnvilMenu.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/AnvilMenu.java
new file mode 100644
index 0000000..b873404
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/AnvilMenu.java
@@ -0,0 +1,582 @@
+package com.pepedevs.corelib.gui.anvil;
+
+import com.pepedevs.corelib.gui.anvil.action.AnvilItemClickAction;
+import com.pepedevs.corelib.gui.anvil.action.AnvilMenuClickAction;
+import com.pepedevs.corelib.gui.anvil.handler.AnvilMenuHandler;
+import com.pepedevs.corelib.utils.reflection.bukkit.BukkitReflection;
+import com.pepedevs.corelib.utils.reflection.bukkit.PlayerReflection;
+import com.pepedevs.corelib.utils.reflection.general.ClassReflection;
+import com.pepedevs.corelib.utils.reflection.general.ConstructorReflection;
+import com.pepedevs.corelib.utils.reflection.general.FieldReflection;
+import com.pepedevs.corelib.utils.reflection.general.MethodReflection;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryView;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.plugin.Plugin;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/** Class for creating custom Anvil Inventory menus. */
+public class AnvilMenu {
+
+ private final Set
+ *
+ * @param leftItem Item for the left slot
+ * @param rightItem Item for the right slot
+ */
+ public AnvilMenu(AnvilItem leftItem, AnvilItem rightItem) {
+ this.leftItem = leftItem;
+ this.rightItem = rightItem;
+ if (this.leftItem != null) this.leftItem.menu = this;
+ if (this.rightItem != null) this.rightItem.menu = this;
+ this.openMenus = new HashSet<>();
+ }
+
+ /** Constructs the AnvilMenu. */
+ public AnvilMenu() {
+ this(null, null);
+ }
+
+ /**
+ * Returns the Left slot AnvilItem.
+ *
+ *
+ *
+ * @return Left slot AnvilItem
+ */
+ public AnvilItem getLeftItem() {
+ return leftItem;
+ }
+
+ /**
+ * Sets the Left slot AnvilItem.
+ *
+ *
+ *
+ * @param leftItem AnvilItem to set
+ * @return This Object, for chaining
+ */
+ public AnvilMenu setLeftItem(AnvilItem leftItem) {
+ this.leftItem = leftItem;
+ if (this.leftItem != null) this.leftItem.menu = this;
+ return this;
+ }
+
+ /**
+ * Returns the Right slot AnvilItem.
+ *
+ *
+ *
+ * @return Right slot AnvilItem
+ */
+ public AnvilItem getRightItem() {
+ return rightItem;
+ }
+
+ /**
+ * Sets the Right slot AnvilItem.
+ *
+ *
+ *
+ * @param rightItem AnvilItem to set
+ * @return This Object, for chaining
+ */
+ public AnvilMenu setRightItem(AnvilItem rightItem) {
+ this.rightItem = rightItem;
+ if (this.rightItem != null) this.rightItem.menu = this;
+ return this;
+ }
+
+ /**
+ * Returns the AnvilMenuHandler for this AnvilMenu.
+ *
+ *
+ *
+ * @return {@link AnvilMenuHandler}
+ */
+ public AnvilMenuHandler getHandler() {
+ return handler;
+ }
+
+ /**
+ * Sets the output slot click action.
+ *
+ *
+ *
+ * @param outputAction Click action for output slot of the Anvil
+ * @return This Object, for chaining
+ */
+ public AnvilMenu setOutputAction(ClickAction outputAction) {
+ this.outputAction = outputAction;
+ return this;
+ }
+
+ /**
+ * Return all open menus.
+ *
+ *
+ *
+ * @return All {@link OpenAnvilGui}
+ */
+ public Set
+ *
+ * @param player Player to check for open gui
+ * @return
+ *
+ * @param inventory Inventory to check
+ * @return
+ *
+ * @param plugin The plugin owner of the listener.
+ * @return true if not already registered.
+ */
+ public boolean registerListener(Plugin plugin) {
+ if (this.handler == null) {
+ Bukkit.getPluginManager()
+ .registerEvents(this.handler = new AnvilMenuHandler(this, plugin), plugin);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Stop handling this.
+ *
+ *
+ *
+ * @return false if not already registered
+ */
+ public boolean unregisterListener() {
+ if (this.handler != null) {
+ this.handler.unregisterListener();
+ this.handler = null;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Opens a new {@link Inventory} with the contents of this {@link AnvilMenu}.
+ *
+ *
+ *
+ * @param player The player viewer
+ * @return The opened inventory
+ */
+ public Inventory open(Player player) {
+ Object container = this.newContainerAnvil(player);
+ Inventory inventory = this.toBukkitInventory(container);
+ if (leftItem != null) {
+ inventory.setItem(Slot.INPUT_LEFT, leftItem.getDisplayIcon());
+ }
+ if (rightItem != null) {
+ inventory.setItem(Slot.INPUT_RIGHT, rightItem.getDisplayIcon());
+ }
+
+ int containerId = this.getNextContainer(player);
+ this.sendOpenWindowPacket(player, containerId);
+ this.setActiveContainer(player, container);
+ this.setActiveContainerId(container, containerId);
+ this.addActiveContainerSlotListener(container, player);
+ this.openMenus.add(new OpenAnvilGui(player, inventory, containerId));
+ return inventory;
+ }
+
+ /**
+ * Closes the viewing inventory of the given {@link Player} only if it is equals this.
+ *
+ *
+ *
+ * @param player Player to close inventory
+ * @return true if was closed.
+ */
+ public boolean close(Player player) {
+ if (this.isMenuOpen(player)) {
+ OpenAnvilGui gui = this.getOpenGuiByPlayer(player);
+ this.handleInventoryClose(player);
+ this.setActiveContainerDefault(player);
+ this.sendCloseWindowPacket(player, gui.getContainerId());
+ gui.getInventory().clear();
+ this.openMenus.remove(gui);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Closes the viewing inventory of each online player only if it is equals this.
+ *
+ *
+ *
+ * @return This Object, for chaining
+ */
+ public AnvilMenu closeOnlinePlayers() {
+ Bukkit.getOnlinePlayers().forEach(this::close);
+ return this;
+ }
+
+ /**
+ * {@link AnvilMenuClickAction} for handling click in the menu.
+ *
+ *
+ *
+ * @param action {@link AnvilMenuClickAction} to handle for clicks in the menu
+ * @return This Object, for chaining
+ */
+ public AnvilMenu onClick(AnvilMenuClickAction action) {
+ if (this.getHandler() == null) {
+ throw new UnsupportedOperationException("This menu has never been registered!");
+ } else if (!action.isRightClick() && !action.isLeftClick()) {
+ return this;
+ } else if (action.getRaw_slot() == Slot.OUTPUT) {
+ AnvilItemClickAction clickAction =
+ new AnvilItemClickAction(
+ action.getMenu(),
+ action.getInventoryView(),
+ action.getClickType(),
+ action.getAction(),
+ action.getSlot_type(),
+ action.getRaw_slot(),
+ action.getCurrent(),
+ action.getHotbarKey(),
+ false);
+ this.outputAction.onClick(clickAction);
+ } else if (action.getRaw_slot() == Slot.INPUT_LEFT) {
+ AnvilItemClickAction clickAction =
+ new AnvilItemClickAction(
+ action.getMenu(),
+ action.getInventoryView(),
+ action.getClickType(),
+ action.getAction(),
+ action.getSlot_type(),
+ action.getRaw_slot(),
+ action.getCurrent(),
+ action.getHotbarKey(),
+ false);
+ this.leftItem.onClick(clickAction);
+ } else if (action.getRaw_slot() == Slot.INPUT_RIGHT) {
+ AnvilItemClickAction clickAction =
+ new AnvilItemClickAction(
+ action.getMenu(),
+ action.getInventoryView(),
+ action.getClickType(),
+ action.getAction(),
+ action.getSlot_type(),
+ action.getRaw_slot(),
+ action.getCurrent(),
+ action.getHotbarKey(),
+ false);
+ this.rightItem.onClick(clickAction);
+ }
+ return this;
+ }
+
+ private OpenAnvilGui getOpenGuiByPlayer(Player player) {
+ for (OpenAnvilGui gui : this.openMenus) {
+ if (gui.getPlayer().equals(player)) return gui;
+ }
+
+ return null;
+ }
+
+ private OpenAnvilGui getOpenGuiByInventory(Inventory inventory) {
+ for (OpenAnvilGui gui : this.openMenus) {
+ if (gui.getInventory().equals(inventory)) return gui;
+ }
+
+ return null;
+ }
+
+ private int getNextContainer(Player player) {
+ try {
+ return (int)
+ MethodReflection.invoke(
+ PlayerReflection.getHandle(player), "nextContainerCounter");
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ return -1;
+ }
+
+ private void handleInventoryClose(Player player) {
+ try {
+ Object ePlayer = PlayerReflection.getHandle(player);
+ Class> craftEventFactory =
+ ClassReflection.getCraftClass("CraftEventFactory", "event");
+ Class> entityHuman =
+ ClassReflection.getNmsClass("EntityHuman", "world.entity.player");
+ MethodReflection.get(craftEventFactory, "handleInventoryCloseEvent", entityHuman)
+ .invoke(craftEventFactory, ePlayer);
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void sendOpenWindowPacket(Player player, int containerId) {
+ try {
+ Class> iChatComponent =
+ ClassReflection.getNmsClass("IChatBaseComponent", "network.chat");
+ Class> packetPlayOutOpenWindow =
+ ClassReflection.getNmsClass("PacketPlayOutOpenWindow", "network.protocol.game");
+ Class> chatMessage = ClassReflection.getNmsClass("ChatMessage", "network.chat");
+ Class> block = ClassReflection.getNmsClass("Blocks", "world.level.block");
+ Object blockAnvil = FieldReflection.get(block, "ANVIL").get(null);
+ String blockAnvilA = (String) MethodReflection.invoke(blockAnvil, "a");
+
+ Object packet =
+ ConstructorReflection.newInstance(
+ packetPlayOutOpenWindow,
+ new Class[] {int.class, String.class, iChatComponent},
+ containerId,
+ "minecraft:anvil",
+ ConstructorReflection.newInstance(
+ chatMessage,
+ new Class[] {String.class, Object[].class},
+ blockAnvilA + ".name",
+ new Object[0]));
+
+ BukkitReflection.sendPacket(player, packet);
+ } catch (IllegalAccessException
+ | InvocationTargetException
+ | NoSuchMethodException
+ | InstantiationException
+ | NoSuchFieldException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void sendCloseWindowPacket(Player player, int containerId) {
+ try {
+ Class> packetPlayOutCloseWindow =
+ ClassReflection.getNmsClass(
+ "PacketPlayOutCloseWindow", "network.protocol.game");
+ Object packet =
+ ConstructorReflection.newInstance(
+ packetPlayOutCloseWindow, new Class[] {int.class}, containerId);
+ BukkitReflection.sendPacket(player, packet);
+ } catch (InvocationTargetException
+ | NoSuchMethodException
+ | InstantiationException
+ | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void setActiveContainerDefault(Player player) {
+ try {
+ Object ePlayer = PlayerReflection.getHandle(player);
+ FieldReflection.setValue(
+ ePlayer,
+ "activeContainer",
+ FieldReflection.getValue(ePlayer, "defaultContainer"));
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void setActiveContainer(Player player, Object container) {
+ try {
+ FieldReflection.setValue(
+ PlayerReflection.getHandle(player), "activeContainer", container);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void setActiveContainerId(Object container, int containerId) {
+ try {
+ FieldReflection.setValue(container, "windowId", containerId);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void addActiveContainerSlotListener(Object container, Player player) {
+ try {
+ Class> iCrafting = ClassReflection.getNmsClass("ICrafting", "world.inventory");
+ MethodReflection.invoke(
+ container,
+ "addSlotListener",
+ new Class[] {iCrafting},
+ PlayerReflection.getHandle(player));
+ } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private Inventory toBukkitInventory(Object container) {
+ try {
+ return ((InventoryView) MethodReflection.invoke(container, "getBukkitView"))
+ .getTopInventory();
+ } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private Object newContainerAnvil(Player player) {
+ try {
+ Object ePlayer = PlayerReflection.getHandle(player);
+ Class> containerAnvil =
+ ClassReflection.getNmsClass("ContainerAnvil", "world.inventory");
+ Class> playerInventory =
+ ClassReflection.getNmsClass("PlayerInventory", "world.entity.player");
+ Class> world = ClassReflection.getNmsClass("World", "world.level");
+ Class> blockPosition = ClassReflection.getNmsClass("BlockPosition", "core");
+ Class> entityHuman =
+ ClassReflection.getNmsClass("EntityHuman", "world.entity.player");
+ Object anvil =
+ ConstructorReflection.newInstance(
+ containerAnvil,
+ new Class[] {playerInventory, world, blockPosition, entityHuman},
+ FieldReflection.getValue(ePlayer, "inventory"),
+ FieldReflection.getValue(ePlayer, "world"),
+ ConstructorReflection.newInstance(
+ blockPosition,
+ new Class[] {int.class, int.class, int.class},
+ 0,
+ 0,
+ 0),
+ ePlayer);
+ FieldReflection.setValue(anvil, "checkReachable", false);
+ return anvil;
+ } catch (InvocationTargetException
+ | NoSuchMethodException
+ | InstantiationException
+ | IllegalAccessException
+ | NoSuchFieldException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ } else if (obj == null) {
+ return false;
+ } else if (this.getClass() != obj.getClass()) {
+ return false;
+ } else {
+ AnvilMenu other = (AnvilMenu) obj;
+ if (!Objects.equals(this.leftItem, other.leftItem)
+ || !Objects.equals(this.rightItem, other.rightItem)) {
+ return false;
+ } else {
+ if (this.handler == null) {
+ if (other.handler != null) {
+ return false;
+ }
+ } else if (!this.handler.equals(other.handler)) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+ }
+
+ public static class OpenAnvilGui {
+
+ private Player player;
+ private Inventory inventory;
+ private int containerId;
+
+ protected OpenAnvilGui(Player player, Inventory inventory, int containerId) {
+ this.player = player;
+ this.inventory = inventory;
+ this.containerId = containerId;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public Inventory getInventory() {
+ return inventory;
+ }
+
+ public int getContainerId() {
+ return containerId;
+ }
+ }
+
+ public static class Slot {
+
+ /**
+ * The slot on the far left, where the first input is inserted. An {@link ItemStack} is
+ * always inserted here to be renamed
+ */
+ public static final int INPUT_LEFT = 0;
+ /**
+ * Not used, but in a real anvil you are able to put the second item you want to combine
+ * here
+ */
+ public static final int INPUT_RIGHT = 1;
+ /**
+ * The output slot, where an item is put when two items are combined from {@link
+ * #INPUT_LEFT} and {@link #INPUT_RIGHT} or {@link #INPUT_LEFT} is renamed
+ */
+ public static final int OUTPUT = 2;
+
+ private static final int[] values =
+ new int[] {Slot.INPUT_LEFT, Slot.INPUT_RIGHT, Slot.OUTPUT};
+
+ /**
+ * Get all anvil slot values
+ *
+ * @return The array containing all possible anvil slots
+ */
+ public static int[] values() {
+ return values;
+ }
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/ClickAction.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/ClickAction.java
new file mode 100644
index 0000000..cdfd864
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/ClickAction.java
@@ -0,0 +1,15 @@
+package com.pepedevs.corelib.gui.anvil;
+
+import com.pepedevs.corelib.gui.anvil.action.AnvilItemClickAction;
+
+public interface ClickAction {
+
+ /**
+ * Action triggered when a gui slot is clicked.
+ *
+ *
+ *
+ * @param action {@link AnvilItemClickAction}
+ */
+ void onClick(AnvilItemClickAction action);
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/action/AnvilItemClickAction.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/action/AnvilItemClickAction.java
new file mode 100644
index 0000000..2c48642
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/action/AnvilItemClickAction.java
@@ -0,0 +1,115 @@
+package com.pepedevs.corelib.gui.anvil.action;
+
+import com.pepedevs.corelib.gui.anvil.AnvilMenu;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.event.inventory.InventoryAction;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryView;
+import org.bukkit.inventory.ItemStack;
+
+/** Class for managing AnvilItem click action in menu. */
+public class AnvilItemClickAction {
+
+ protected final AnvilMenu menu;
+ protected final InventoryView inventory_view;
+ protected final ClickType click_type;
+ protected final InventoryAction action;
+ protected final InventoryType.SlotType slot_type;
+ protected final int raw_slot;
+ protected final ItemStack clicked;
+ protected int hotbar_key;
+ protected boolean update;
+
+ /**
+ * Constructs the Item Click Action.
+ *
+ *
+ *
+ * @param menu AnvilItem to bind action
+ * @param event {@link InventoryClickEvent}
+ */
+ public AnvilItemClickAction(AnvilMenu menu, InventoryClickEvent event) {
+ this(
+ menu,
+ event.getView(),
+ event.getClick(),
+ event.getAction(),
+ event.getSlotType(),
+ event.getRawSlot(),
+ event.getCurrentItem(),
+ event.getHotbarButton(),
+ false);
+ }
+
+ public AnvilItemClickAction(
+ AnvilMenu menu,
+ InventoryView view,
+ ClickType click_type,
+ InventoryAction action,
+ InventoryType.SlotType slot_type,
+ int raw_slot,
+ ItemStack clicked,
+ int hotbar_key,
+ boolean update) {
+ this.menu = menu;
+ this.inventory_view = view;
+ this.click_type = click_type;
+ this.action = action;
+ this.slot_type = slot_type;
+ this.raw_slot = raw_slot;
+ this.clicked = clicked;
+ this.hotbar_key = hotbar_key;
+ this.update = update;
+ }
+
+ public AnvilMenu getMenu() {
+ return menu;
+ }
+
+ public Inventory getInventory() {
+ return this.getInventoryView().getTopInventory();
+ }
+
+ public InventoryView getInventoryView() {
+ return this.inventory_view;
+ }
+
+ public Player getPlayer() {
+ return (Player) this.getInventoryView().getPlayer();
+ }
+
+ public ClickType getClickType() {
+ return this.click_type;
+ }
+
+ public InventoryAction getInventoryAction() {
+ return this.action;
+ }
+
+ public InventoryType.SlotType getSlotType() {
+ return this.slot_type;
+ }
+
+ public int getRawSlot() {
+ return this.raw_slot;
+ }
+
+ public ItemStack getClickedItem() {
+ return this.clicked;
+ }
+
+ public int getHotbarKey() {
+ return this.hotbar_key;
+ }
+
+ public boolean isWillUpdate() {
+ return this.update;
+ }
+
+ public void setUpdate(boolean update) {
+ this.update = update;
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/action/AnvilMenuClickAction.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/action/AnvilMenuClickAction.java
new file mode 100644
index 0000000..58c5ff8
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/action/AnvilMenuClickAction.java
@@ -0,0 +1,100 @@
+package com.pepedevs.corelib.gui.anvil.action;
+
+import com.pepedevs.corelib.gui.anvil.AnvilMenu;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.event.inventory.InventoryAction;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.inventory.InventoryView;
+import org.bukkit.inventory.ItemStack;
+
+public class AnvilMenuClickAction {
+
+ protected final AnvilMenu menu;
+ protected final InventoryView inventory_view;
+ protected final ClickType click_type;
+ protected final InventoryAction action;
+ protected final InventoryType.SlotType slot_type;
+ protected final int raw_slot;
+ protected ItemStack current;
+ protected int hotbar_key;
+
+ public AnvilMenuClickAction(AnvilMenu menu, InventoryClickEvent event) {
+ this(
+ menu,
+ event.getView(),
+ event.getClick(),
+ event.getAction(),
+ event.getSlotType(),
+ event.getCurrentItem(),
+ event.getRawSlot(),
+ event.getHotbarButton());
+ }
+
+ public AnvilMenuClickAction(
+ AnvilMenu menu,
+ InventoryView view,
+ ClickType click_type,
+ InventoryAction action,
+ InventoryType.SlotType slot_type,
+ ItemStack current,
+ int raw_slot,
+ int hotbar_key) {
+ this.menu = menu;
+ this.inventory_view = view;
+ this.click_type = click_type;
+ this.action = action;
+ this.slot_type = slot_type;
+ this.current = current;
+ this.raw_slot = raw_slot;
+ this.hotbar_key = hotbar_key;
+ }
+
+ public AnvilMenu getMenu() {
+ return menu;
+ }
+
+ public InventoryView getInventoryView() {
+ return inventory_view;
+ }
+
+ public ClickType getClickType() {
+ return click_type;
+ }
+
+ public InventoryAction getAction() {
+ return action;
+ }
+
+ public InventoryType.SlotType getSlot_type() {
+ return slot_type;
+ }
+
+ public ItemStack getCursor() {
+ return this.getInventoryView().getCursor();
+ }
+
+ public ItemStack getCurrent() {
+ return current;
+ }
+
+ public int getRaw_slot() {
+ return raw_slot;
+ }
+
+ public int getHotbarKey() {
+ return hotbar_key;
+ }
+
+ public boolean isRightClick() {
+ return this.getClickType().isRightClick();
+ }
+
+ public boolean isLeftClick() {
+ return this.getClickType().isLeftClick();
+ }
+
+ public boolean isShiftClick() {
+ return this.getClickType().isShiftClick();
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/handler/AnvilMenuHandler.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/handler/AnvilMenuHandler.java
new file mode 100644
index 0000000..c9b7a94
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/anvil/handler/AnvilMenuHandler.java
@@ -0,0 +1,71 @@
+package com.pepedevs.corelib.gui.anvil.handler;
+
+import com.pepedevs.corelib.gui.anvil.AnvilMenu;
+import com.pepedevs.corelib.gui.anvil.action.AnvilMenuClickAction;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.event.server.PluginDisableEvent;
+import org.bukkit.plugin.Plugin;
+
+public class AnvilMenuHandler implements Listener {
+
+ protected final AnvilMenu menu;
+ protected final Plugin plugin;
+
+ public AnvilMenuHandler(AnvilMenu menu, Plugin plugin) {
+ this.menu = menu;
+ this.plugin = plugin;
+ }
+
+ public Plugin getPlugin() {
+ return plugin;
+ }
+
+ /** Unregisters this listener. */
+ public void unregisterListener() {
+ HandlerList.unregisterAll(this);
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onInventoryClick(InventoryClickEvent event) {
+ if (event.getInventory() != null
+ && event.getInventory().getType() == InventoryType.ANVIL
+ && event.getWhoClicked() instanceof Player
+ && this.menu.isMenuOpen((Player) event.getWhoClicked())
+ && this.menu.isThisMenu(event.getInventory())) {
+ AnvilMenuClickAction action = new AnvilMenuClickAction(this.menu, event);
+ this.menu.onClick(action);
+ event.setCancelled(true);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.LOW)
+ public void onInventoryClose(InventoryCloseEvent event) {
+ if (event.getInventory() != null
+ && event.getInventory().getType() == InventoryType.ANVIL
+ && event.getPlayer() instanceof Player
+ && this.menu.isMenuOpen((Player) event.getPlayer())
+ && this.menu.isThisMenu(event.getInventory())) {
+ for (AnvilMenu.OpenAnvilGui gui : this.menu.getOpenMenus()) {
+ if (gui.getPlayer().equals(event.getPlayer())) {
+ this.menu.getOpenMenus().remove(gui);
+ break;
+ }
+ }
+ event.getInventory().clear();
+ }
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onPluginDisable(PluginDisableEvent event) {
+ if (event.getPlugin().equals(this.plugin)) {
+ this.menu.closeOnlinePlayers();
+ }
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/Item.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/Item.java
new file mode 100644
index 0000000..0ff1ca2
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/Item.java
@@ -0,0 +1,210 @@
+package com.pepedevs.corelib.gui.inventory;
+
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+import com.pepedevs.corelib.utils.StringUtils;
+import com.pepedevs.corelib.utils.itemstack.ItemMetaBuilder;
+import com.pepedevs.corelib.utils.itemstack.ItemStackUtils;
+import com.pepedevs.corelib.utils.material.MaterialUtils;
+import org.apache.commons.lang.Validate;
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/** Class for creating custom Inventory items. */
+public abstract class Item {
+
+ protected String name;
+ protected ItemStack icon;
+ protected List
+ *
+ * @param name Name of the Item
+ * @param icon ItemStack icon for the Item
+ * @param lore Lore of the Item
+ */
+ public Item(String name, ItemStack icon, Collection
+ *
+ * @param name Name of the Item
+ * @param icon ItemStack icon for the Item
+ * @param lore Lore of the Item
+ */
+ public Item(String name, ItemStack icon, String... lore) {
+ this(name, icon, Arrays.asList(lore));
+ }
+
+ /**
+ * Constructs the Item.
+ *
+ *
+ *
+ * @param icon ItemStack icon for the Item
+ */
+ public Item(ItemStack icon) {
+ this(
+ StringUtils.defaultIfBlank(
+ (icon.getItemMeta() != null ? icon.getItemMeta().getDisplayName() : null),
+ "null name"),
+ icon,
+ (ItemStackUtils.extractLore(icon, false)
+ .toArray(new String[ItemStackUtils.extractLore(icon, false).size()])));
+ }
+
+ /**
+ * Returns the name of the Item.
+ *
+ *
+ *
+ * @return Name of the Item
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of the Item
+ *
+ *
+ *
+ * @param name Name for the Item
+ * @return This Object, for chaining
+ */
+ public Item setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Returns the icon of the Item.
+ *
+ *
+ *
+ * @return Icon of the Item
+ */
+ public ItemStack getIcon() {
+ return icon;
+ }
+
+ /**
+ * Sets the icon of the Item
+ *
+ *
+ *
+ * @param icon ItemStack icon for the Item
+ * @return This Object, for chaining
+ */
+ public Item setIcon(ItemStack icon) {
+ Validate.notNull(icon, "The icon cannot be null!");
+ this.icon = icon;
+ return this;
+ }
+
+ /**
+ * Returns the display icon of the Item.
+ *
+ *
+ *
+ * @return Display icon of the Item
+ */
+ public ItemStack getDisplayIcon() {
+ return (getIcon().getType() == Material.AIR
+ ? icon
+ : new ItemMetaBuilder(MaterialUtils.getRightMaterial(getIcon()))
+ .withDisplayName(StringUtils.translateAlternateColorCodes(getName()))
+ .withLore(getLore())
+ .applyTo(getIcon().clone()));
+ }
+
+ /**
+ * Returns the lore of the Item.
+ *
+ *
+ *
+ * @return Lore of the Item
+ */
+ public List
+ *
+ * @param lore Lore for the Item
+ * @return This Object, for chaining
+ */
+ public Item setLore(List Note that this will return null if this item has never been set in a
+ * menu.
+ *
+ *
+ *
+ * @return The menu this item belongs
+ */
+ public ItemMenu getMenu() {
+ return menu;
+ }
+
+ /**
+ * Click trigger for this Item.
+ *
+ *
+ *
+ * @param action {@link ItemClickAction} for the Item
+ */
+ public abstract void onClick(final ItemClickAction action);
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((icon == null) ? 0 : icon.hashCode());
+ result = prime * result + ((lore == null) ? 0 : lore.hashCode());
+ result = prime * result + ((name == null) ? 0 : name.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ Item other = (Item) obj;
+ if (icon == null) {
+ if (other.icon != null) return false;
+ } else if (!icon.equals(other.icon)) return false;
+ if (lore == null) {
+ if (other.lore != null) return false;
+ } else if (!lore.equals(other.lore)) return false;
+ if (name == null) {
+ if (other.name != null) return false;
+ } else if (!name.equals(other.name)) return false;
+ return true;
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/ItemMenu.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/ItemMenu.java
new file mode 100644
index 0000000..5dbabcb
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/ItemMenu.java
@@ -0,0 +1,698 @@
+package com.pepedevs.corelib.gui.inventory;
+
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+import com.pepedevs.corelib.gui.inventory.action.ItemMenuClickAction;
+import com.pepedevs.corelib.gui.inventory.handler.ItemMenuHandler;
+import com.pepedevs.corelib.gui.inventory.holder.ItemMenuHolder;
+import com.pepedevs.corelib.gui.inventory.size.ItemMenuSize;
+import com.pepedevs.corelib.utils.StringUtils;
+import org.apache.commons.lang.Validate;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.plugin.Plugin;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.TreeSet;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/** Class for creating custom Inventory menus. */
+public class ItemMenu {
+
+ /*
+ Default title
+ */
+ public static final String DEFAULT_TITLE = "Empty Title";
+ protected final Item[] contents;
+ protected String title;
+ protected ItemMenuSize size;
+ protected ItemMenu parent;
+ protected ItemMenuHandler handler;
+
+ /**
+ * Constructs the ItemMenu.
+ *
+ *
+ *
+ * @param title Title of the ItemMenu
+ * @param size Size of ItemMenu
+ * @param parent Parent ItemMenu if any, else null
+ * @param contents Contents of the ItemMenu
+ */
+ public ItemMenu(String title, ItemMenuSize size, ItemMenu parent, Item... contents) {
+ Validate.notNull(size, "The size cannot be null!");
+ this.title = StringUtils.defaultIfBlank(title, DEFAULT_TITLE);
+ this.size = size;
+ this.parent = parent;
+ this.contents = new Item[size.getSize()];
+ fill(contents);
+ }
+
+ /**
+ * Returns the title of the ItemMenu.
+ *
+ *
+ *
+ * @return Title of the ItemMenu
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Sets the title of this menu.
+ *
+ * Note that all the Bukkit inventories created from this, must be re-opened to see the
+ * changes.
+ *
+ *
+ *
+ * @param title New title
+ * @return This Object, for chaining
+ */
+ public ItemMenu setTitle(String title) {
+ this.title = StringUtils.defaultIfBlank(title, DEFAULT_TITLE);
+ return this;
+ }
+
+ /**
+ * Returns the size of the ItemMenu.
+ *
+ *
+ *
+ * @return Size of the ItemMenu
+ */
+ public ItemMenuSize getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the contents of the ItemMenu.
+ *
+ *
+ *
+ * @return Contents of the ItemMenu
+ */
+ public Item[] getContents() {
+ return contents;
+ }
+
+ /**
+ * Adds the contents to this menu.
+ *
+ * Note that all the Bukkit inventories created from this, must be re-opened to see the
+ * changes.
+ *
+ *
+ *
+ * @param contents Contents for this menu
+ * @return This Object, for chaining
+ */
+ public ItemMenu setContents(Item[] contents) {
+ fill(contents);
+ return this;
+ }
+
+ /**
+ * Returns the contents of the ItemMenu as stream.
+ *
+ *
+ *
+ * @return Stream of Contents of the ItemMenu
+ */
+ public Stream
+ *
+ * @param predicate_filter Filter for the contents of ItemMenu
+ * @return Filtered contents
+ */
+ public Item[] getContents(Predicate super Item> predicate_filter) {
+ List
+ *
+ * @return Parent ItemMenu for this ItemMenu
+ */
+ public ItemMenu getParent() {
+ return parent;
+ }
+
+ /**
+ * Sets the parent of this menu.
+ *
+ * Note that all the Bukkit inventories created from this, must be re-opened to see the
+ * changes.
+ *
+ *
+ *
+ * @param parent New parent menu
+ * @return This Object, for chaining
+ */
+ public ItemMenu setParent(ItemMenu parent) {
+ this.parent = parent;
+ return this;
+ }
+
+ /**
+ * Checks of this ItemMenu has a parent ItemMenu.
+ *
+ *
+ *
+ * @return {@code true} if it has a parent ItemMenu, else false
+ */
+ public boolean hasParent() {
+ return getParent() != null;
+ }
+
+ /**
+ * Returns the ItemMenuHandler for this ItemMenu.
+ *
+ *
+ *
+ * @return {@link ItemMenuHandler}
+ */
+ public ItemMenuHandler getHandler() {
+ return this.handler;
+ }
+
+ /**
+ * Returns the Item on the given index.
+ *
+ *
+ *
+ * @param index Index of the Item
+ * @return {@link Item} at the given index
+ */
+ public Item getItem(int index) {
+ rangeCheck(index, index);
+ return this.contents[index];
+ }
+
+ /**
+ * Returns an array of index of the Items with the given Predicate filter.
+ *
+ *
+ *
+ * @param predicate_filter Predicate filter for the Items
+ * @return Array of index of the Items
+ */
+ public Integer[] getIndexes(Predicate super Item> predicate_filter) {
+ TreeSet
+ *
+ * @return First empty slot found, or -1 if no empty slots.
+ */
+ public int getFirstEmpty() {
+ return getEmptyIndexes().length > 0 ? getEmptyIndexes()[0] : -1;
+ }
+
+ /**
+ * Returns the empty slot indexes.
+ *
+ *
+ *
+ * @return Empty slot indexes found.
+ */
+ public Integer[] getEmptyIndexes() {
+ return getIndexes(
+ content ->
+ content == null
+ || content.getDisplayIcon() == null
+ || content.getDisplayIcon().getType() == Material.AIR);
+ }
+
+ /**
+ * Checks if the ItemMenu if full or has no empty slots.
+ *
+ *
+ *
+ * @return {@code true} if its full, else false
+ */
+ public boolean isFull() {
+ return getEmptyIndexes().length == 0;
+ }
+
+ /**
+ * Checks if the ItemMenu is empty or has no content.
+ *
+ *
+ *
+ * @return {@code true} if its empty, else false
+ */
+ public boolean isEmpty() {
+ return getEmptyIndexes().length == this.getContents().length;
+ }
+
+ // public ItemMenu setSize(ItemMenuSize size) {
+ // Validate.notNull(size, "The size cannot be null!");
+ // this.size = size;
+ // return this;
+ // }
+
+ /**
+ * Returns true if the opened inventory that the given {@link Player} has is equals this.
+ *
+ *
+ *
+ * @param player Player with the opened inventory
+ * @return true if the opened inventory that the given {@link Player} has is this
+ */
+ public boolean isMenuOpen(Player player) {
+ if (player.getOpenInventory() == null
+ || player.getOpenInventory().getTopInventory() == null
+ || player.getOpenInventory().getTopInventory().getType() != InventoryType.CHEST
+ || !(player.getOpenInventory().getTopInventory().getHolder()
+ instanceof ItemMenuHolder)) {
+ return false;
+ }
+ return this.equals(
+ ((ItemMenuHolder) player.getOpenInventory().getTopInventory().getHolder())
+ .getItemMenu());
+ }
+
+ /**
+ * Gets whether the provided {@link Inventory} is an instance of this {@link ItemMenu}. Note that all the Bukkit inventories created from this, must be re-opened to see the
+ * changes.
+ *
+ *
+ *
+ * @param slot Index of slot to set item to
+ * @param content Item to set to the menu
+ * @return This Object, for chaining
+ */
+ public ItemMenu setItem(int slot, Item content) {
+ setItemIf(slot, content, null);
+ return this;
+ }
+
+ /**
+ * Sets the item on the given slot.
+ *
+ *
+ *
+ * @param slot Index of slot where the given item will be stored
+ * @param item Item to store
+ * @param predicate Your predicate expression
+ * @return true if was set.
+ */
+ public boolean setItemIf(int slot, Item item, Predicate super Item> predicate) {
+ rangeCheck(slot, slot);
+ if (predicate == null || predicate.test(this.getItem(slot))) {
+ this.contents[slot] = item;
+
+ if (item != null) {
+ item.menu = this;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Stores the given {@link Item} only if not full.
+ *
+ *
+ *
+ * @param item Item to add
+ * @return true if could be added
+ */
+ public boolean addItem(Item item) {
+ if (!isFull()) {
+ this.setItem(getFirstEmpty(), item);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Clear the item stored on the given slot.
+ *
+ *
+ *
+ * @param slot Index of slot where the item is stored
+ * @return true if was clear
+ */
+ public boolean clearItem(int slot) {
+ return clearItemIf(slot, null);
+ }
+
+ /**
+ * Clear the item stored on the given slot.
+ *
+ *
+ *
+ * @param slot Index of slot where the item is stored
+ * @param predicate Your predicate expression
+ * @return true if was clear.
+ */
+ public boolean clearItemIf(int slot, Predicate super Item> predicate) {
+ return setItemIf(slot, null, predicate);
+ }
+
+ /**
+ * Fill the items to the menu.
+ *
+ *
+ *
+ * @param contents Items to store
+ * @return This Object, for chaining
+ */
+ public ItemMenu fill(Item... contents) {
+ fill(0, contents);
+ return this;
+ }
+
+ /**
+ * Fill all slots with the given Item.
+ *
+ *
+ *
+ * @param content Item to store
+ * @return This Object, for chaining
+ */
+ public ItemMenu fillToAll(Item content) {
+ return fillToAllIf(content, null);
+ }
+
+ /**
+ * Fill all slots with the given Item according to the predicate.
+ *
+ *
+ *
+ * @param content Item to store
+ * @param predicate Your predicate expression
+ * @return This Object, for chaining
+ */
+ public ItemMenu fillToAllIf(Item content, Predicate super Item> predicate) {
+ for (int i = 0; i < this.getContents().length; i++) {
+ if (predicate == null || predicate.test(this.contents[i])) {
+ this.setItem(i, content);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Fill the Items to the menu from the given index.
+ *
+ *
+ *
+ * @param from_index Index from which to start
+ * @param contents Items to store
+ * @return This Object, for chaining
+ */
+ public ItemMenu fill(int from_index, Item... contents) {
+ rangeCheck(from_index, from_index);
+ for (int i = from_index; i < this.getContents().length; i++) {
+ if (contents == null || i >= contents.length) {
+ break;
+ }
+ this.setItem(i, contents[i]);
+ }
+ return this;
+ }
+
+ /**
+ * Clear all the Items in the menu.
+ *
+ *
+ *
+ * @return This Object, for chaining
+ */
+ public ItemMenu clear() {
+ return fillToAll(null);
+ }
+
+ public Inventory apply(Inventory inventory) {
+ for (int i = 0; i < getContents().length; i++) {
+ if (i >= inventory.getSize()) {
+ break;
+ }
+
+ if (getItem(i) != null) {
+ inventory.setItem(i, getItem(i).getDisplayIcon());
+ }
+ }
+ return inventory;
+ }
+
+ /**
+ * Initializes the {@link ItemMenuHandler} of this.
+ *
+ *
+ *
+ * @param plugin The plugin owner of the listener.
+ * @return true if not already registered.
+ */
+ public boolean registerListener(Plugin plugin) {
+ if (this.handler == null) {
+ Bukkit.getPluginManager()
+ .registerEvents((this.handler = new ItemMenuHandler(this, plugin)), plugin);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Stop handling this.
+ *
+ *
+ *
+ * @return false if not already registered
+ */
+ public boolean unregisterListener() {
+ if (this.handler != null) {
+ this.handler.unregisterListener();
+ this.handler = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Opens a new {@link Inventory} with the contents of this {@link ItemMenu}.
+ *
+ *
+ *
+ * @param player The player viewer
+ * @return The opened inventory
+ */
+ public Inventory open(Player player) {
+ Inventory inventory =
+ apply(
+ Bukkit.createInventory(
+ new ItemMenuHolder(
+ this, Bukkit.createInventory(player, size.getSize())),
+ size.getSize(),
+ StringUtils.limit(
+ StringUtils.translateAlternateColorCodes(getTitle()), 32)));
+ player.closeInventory();
+ player.openInventory(inventory);
+ return inventory;
+ }
+
+ /**
+ * Updates the viewing inventory of the given {@link Player} only if it is equals this.
+ *
+ *
+ *
+ * @param player Player to update inventory view
+ * @return true if was updated
+ */
+ public boolean update(Player player) {
+ if (isMenuOpen(player)) {
+ apply(player.getOpenInventory().getTopInventory());
+ player.updateInventory();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Updates the viewing inventory of each online player only if it is equals this.
+ *
+ *
+ *
+ * @return This Object, for chaining
+ */
+ public ItemMenu updateOnlinePlayers() {
+ Bukkit.getOnlinePlayers().forEach(player -> update(player));
+ return this;
+ }
+
+ /**
+ * Closes the viewing inventory of the given {@link Player} only if it is equals this.
+ *
+ *
+ *
+ * @param player Player to close inventory
+ * @return true if was closed.
+ */
+ public boolean close(Player player) {
+ if (isMenuOpen(player)) {
+ player.closeInventory();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Closes the viewing inventory of each online player only if it is equals this.
+ *
+ *
+ *
+ * @return This Object, for chaining
+ */
+ public ItemMenu closeOnlinePlayers() {
+ Bukkit.getOnlinePlayers().forEach(player -> close(player));
+ return this;
+ }
+
+ /**
+ * {@link ItemMenuClickAction} for handling click in the menu.
+ *
+ *
+ *
+ * @param action {@link ItemMenuClickAction} to handle for clicks in the menu
+ * @return This Object, for chaining
+ */
+ public ItemMenu onClick(final ItemMenuClickAction action) {
+ if (this.getHandler() == null) {
+ throw new UnsupportedOperationException("This menu has never been registered!");
+ }
+
+ if (!action.isRightClick() && !action.isLeftClick()) {
+ return this;
+ }
+
+ if (action.getSlot() < 0
+ || action.getSlot() >= getContents().length
+ || this.getItem(action.getSlot()) == null) {
+ return this;
+ }
+
+ ItemClickAction sub_action =
+ new ItemClickAction(
+ this,
+ action.getInventoryView(),
+ action.getClickType(),
+ action.getInventoryAction(),
+ action.getSlotType(),
+ action.getSlot(),
+ action.getRawSlot(),
+ action.getCurrentItem(),
+ action.getHotbarKey(),
+ false,
+ false,
+ false);
+
+ /* calling click method on the content class */
+ getItem(action.getSlot()).onClick(sub_action);
+
+ /* just update the view */
+ if (sub_action.isWillUpdate()) {
+ getHandler().delayedUpdate(action.getPlayer(), 1);
+ // update(action.getPlayer());
+ } else if (sub_action.isWillClose()
+ || sub_action.isWillGoBack()) { // closing or getting back
+ getHandler().delayedClose(action.getPlayer(), 1);
+
+ /* opening parent */
+ if (sub_action.isWillGoBack() && hasParent()) {
+ getParent().getHandler().delayedOpen(action.getPlayer(), 3);
+ }
+ }
+ return this;
+ }
+
+ protected void rangeCheck(int from, int to) {
+ if (from > to) {
+ throw new IllegalArgumentException("from(" + from + ") > to(" + to + ")!");
+ }
+
+ if (from < 0) {
+ throw new ArrayIndexOutOfBoundsException(from);
+ }
+
+ if (to >= getContents().length) {
+ throw new ArrayIndexOutOfBoundsException(to);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(contents);
+ result = prime * result + ((handler == null) ? 0 : handler.hashCode());
+ result = prime * result + ((parent == null) ? 0 : parent.hashCode());
+ result = prime * result + ((size == null) ? 0 : size.hashCode());
+ result = prime * result + ((title == null) ? 0 : title.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ ItemMenu other = (ItemMenu) obj;
+ if (!Arrays.equals(contents, other.contents)) return false;
+ if (handler == null) {
+ if (other.handler != null) return false;
+ } else if (!handler.equals(other.handler)) return false;
+ if (parent == null) {
+ if (other.parent != null) return false;
+ } else if (!parent.equals(other.parent)) return false;
+ if (size != other.size) return false;
+ if (title == null) {
+ if (other.title != null) return false;
+ } else if (!title.equals(other.title)) return false;
+ return true;
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/action/ItemClickAction.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/action/ItemClickAction.java
new file mode 100644
index 0000000..e30d20d
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/action/ItemClickAction.java
@@ -0,0 +1,296 @@
+package com.pepedevs.corelib.gui.inventory.action;
+
+import com.pepedevs.corelib.gui.inventory.ItemMenu;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.event.inventory.InventoryAction;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryType.SlotType;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryView;
+import org.bukkit.inventory.ItemStack;
+
+/** Class for managing Item click action in menu. */
+public class ItemClickAction {
+
+ protected final ItemMenu menu;
+ protected final InventoryView inventory_view;
+ protected final ClickType click_type;
+ protected final InventoryAction action;
+ protected final SlotType slot_type;
+ protected final int slot;
+ protected final int raw_slot;
+ protected final ItemStack clicked;
+ protected int hotbar_key;
+ protected boolean go_back;
+ protected boolean close;
+ protected boolean update;
+
+ /**
+ * Constructs the Item Click Action.
+ *
+ *
+ *
+ * @param menu Item to bind action
+ * @param event {@link InventoryClickEvent}
+ */
+ public ItemClickAction(ItemMenu menu, InventoryClickEvent event) {
+ this(
+ menu,
+ event.getView(),
+ event.getClick(),
+ event.getAction(),
+ event.getSlotType(),
+ event.getSlot(),
+ event.getRawSlot(),
+ event.getCurrentItem(),
+ event.getHotbarButton(),
+ false,
+ false,
+ false);
+ }
+
+ /**
+ * Constructs the Item Click Action.
+ *
+ *
+ *
+ * @param menu ItemMenu to bind action
+ * @param inventory_view Inventory View of the open inventory
+ * @param click_type Click type
+ * @param action InventoryAction type
+ * @param slot_type {@link SlotType}
+ * @param slot Slot index
+ * @param raw_slot Raw slot index
+ * @param clicked Clicked ItemStack
+ * @param hotbar_key Hotbar Key
+ * @param go_back Go back
+ * @param close Close
+ * @param update Update
+ */
+ public ItemClickAction(
+ ItemMenu menu,
+ InventoryView inventory_view,
+ ClickType click_type,
+ InventoryAction action,
+ SlotType slot_type,
+ int slot,
+ int raw_slot,
+ ItemStack clicked,
+ int hotbar_key,
+ boolean go_back,
+ boolean close,
+ boolean update) {
+ this.menu = menu;
+ this.inventory_view = inventory_view;
+ this.click_type = click_type;
+ this.action = action;
+ this.slot_type = slot_type;
+ this.slot = slot;
+ this.raw_slot = raw_slot;
+ this.clicked = clicked;
+ this.hotbar_key = hotbar_key;
+ this.go_back = go_back;
+ this.close = close;
+ this.update = update;
+ }
+
+ /**
+ * Returns the menu.
+ *
+ *
+ *
+ * @return ItemMenu
+ */
+ public ItemMenu getMenu() {
+ return menu;
+ }
+
+ /**
+ * Returns the Inventory.
+ *
+ *
+ *
+ * @return Inventory
+ */
+ public Inventory getInventory() {
+ return this.getInventoryView().getTopInventory();
+ }
+
+ /**
+ * Returns the InventoryView.
+ *
+ *
+ *
+ * @return InventoryView
+ */
+ public InventoryView getInventoryView() {
+ return inventory_view;
+ }
+
+ /**
+ * Returns the Player who clicked.
+ *
+ *
+ *
+ * @return Player who clicked
+ */
+ public Player getPlayer() {
+ return (Player) this.getInventoryView().getPlayer();
+ }
+
+ /**
+ * Returns the ClickType.
+ *
+ *
+ *
+ * @return ClickType
+ */
+ public ClickType getClickType() {
+ return click_type;
+ }
+
+ /**
+ * Returns the InventoryAction.
+ *
+ *
+ *
+ * @return InventoryAction
+ */
+ public InventoryAction getInventoryAction() {
+ return action;
+ }
+
+ /**
+ * Returns the SlotType.
+ *
+ *
+ *
+ * @return SlotType
+ */
+ public SlotType getSlotType() {
+ return slot_type;
+ }
+
+ /**
+ * Returns the slot index.
+ *
+ *
+ *
+ * @return Slot index
+ */
+ public int getSlot() {
+ return slot;
+ }
+
+ /**
+ * Returns the raw slot index.
+ *
+ *
+ *
+ * @return Raw slot index
+ */
+ public int getRawSlot() {
+ return raw_slot;
+ }
+
+ /**
+ * Returns the clicked ItemStack.
+ *
+ *
+ *
+ * @return Clicked ItemStack
+ */
+ public ItemStack getClickedItem() {
+ return clicked;
+ }
+
+ /**
+ * Returns the Hotbar key.
+ *
+ *
+ *
+ * @return Hotbar Key
+ */
+ public int getHotbarKey() {
+ return hotbar_key;
+ }
+
+ /**
+ * Returns the Go back boolean.
+ *
+ *
+ *
+ * @return if it will go back
+ */
+ public boolean isWillGoBack() {
+ return go_back;
+ }
+
+ /**
+ * Returns the Close boolean.
+ *
+ *
+ *
+ * @return if it will close
+ */
+ public boolean isWillClose() {
+ return close;
+ }
+
+ /**
+ * Returns the Update boolean.
+ *
+ *
+ *
+ * @return if it will update
+ */
+ public boolean isWillUpdate() {
+ return update;
+ }
+
+ /**
+ * Sets the Go back boolean.
+ *
+ *
+ *
+ * @param go_back Boolean value
+ */
+ public void setGoBack(boolean go_back) {
+ this.go_back = go_back;
+ if (go_back) {
+ this.close = false;
+ this.update = false;
+ }
+ }
+
+ /**
+ * Sets the Close boolean.
+ *
+ *
+ *
+ * @param close Boolean value
+ */
+ public void setClose(boolean close) {
+ this.close = close;
+ if (close) {
+ this.go_back = false;
+ this.update = false;
+ }
+ }
+
+ /**
+ * Sets the Update boolean.
+ *
+ *
+ *
+ * @param update Boolean value
+ */
+ public void setUpdate(boolean update) {
+ this.update = update;
+ if (update) {
+ this.go_back = false;
+ this.close = false;
+ }
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/action/ItemMenuClickAction.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/action/ItemMenuClickAction.java
new file mode 100644
index 0000000..e032687
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/action/ItemMenuClickAction.java
@@ -0,0 +1,270 @@
+package com.pepedevs.corelib.gui.inventory.action;
+
+import com.pepedevs.corelib.gui.inventory.ItemMenu;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.event.inventory.InventoryAction;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryType.SlotType;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryView;
+import org.bukkit.inventory.ItemStack;
+
+/** Class for managing ItemMenu click action in menu. */
+public class ItemMenuClickAction {
+
+ protected final ItemMenu menu;
+ protected final InventoryView inventory_view;
+ protected final ClickType click_type;
+ protected final InventoryAction action;
+ protected final SlotType slot_type;
+ protected final int slot;
+ protected final int raw_slot;
+ protected ItemStack current;
+ protected int hotbar_key;
+
+ /**
+ * Constructs the Item Menu Click Action.
+ *
+ *
+ *
+ * @param menu ItemMenu to bind action
+ * @param event {@link InventoryClickEvent}
+ */
+ public ItemMenuClickAction(ItemMenu menu, InventoryClickEvent event) {
+ this(
+ menu,
+ event.getView(),
+ event.getClick(),
+ event.getAction(),
+ event.getSlotType(),
+ event.getSlot(),
+ event.getRawSlot(),
+ event.getCurrentItem(),
+ event.getHotbarButton());
+ }
+
+ /**
+ * Constructs the Item Menu Click Action.
+ *
+ *
+ *
+ * @param menu ItemMenu to bind action
+ * @param inventory_view Inventory View of the open inventory
+ * @param click_type Click type
+ * @param action InventoryAction type
+ * @param slot_type {@link SlotType}
+ * @param slot Slot index
+ * @param raw_slot Raw slot index
+ * @param current Current clicked ItemStack
+ * @param hotbar_key Hotbar Key
+ */
+ public ItemMenuClickAction(
+ ItemMenu menu,
+ InventoryView inventory_view,
+ ClickType click_type,
+ InventoryAction action,
+ SlotType slot_type,
+ int slot,
+ int raw_slot,
+ ItemStack current,
+ int hotbar_key) {
+ this.menu = menu;
+ this.inventory_view = inventory_view;
+ this.click_type = click_type;
+ this.action = action;
+ this.slot_type = slot_type;
+ this.slot = slot;
+ this.raw_slot = raw_slot;
+ this.current = current;
+ this.hotbar_key = hotbar_key;
+ }
+
+ /**
+ * Returns the current menu.
+ *
+ *
+ *
+ * @return ItemMenu
+ */
+ public ItemMenu getMenu() {
+ return menu;
+ }
+
+ /**
+ * Returns the current inventory.
+ *
+ *
+ *
+ * @return Inventory
+ */
+ public Inventory getInventory() {
+ return this.getInventoryView().getTopInventory();
+ }
+
+ /**
+ * Returns the current InventoryView.
+ *
+ *
+ *
+ * @return InventoryView
+ */
+ public InventoryView getInventoryView() {
+ return inventory_view;
+ }
+
+ /**
+ * Returns the Player who clicked.
+ *
+ *
+ *
+ * @return Player who clicked
+ */
+ public Player getPlayer() {
+ return (Player) this.getInventoryView().getPlayer();
+ }
+
+ /**
+ * Returns the ClickType.
+ *
+ *
+ *
+ * @return ClickType
+ */
+ public ClickType getClickType() {
+ return click_type;
+ }
+
+ /**
+ * Returns the {@link InventoryAction}.
+ *
+ *
+ *
+ * @return {@link InventoryAction}
+ */
+ public InventoryAction getInventoryAction() {
+ return action;
+ }
+
+ /**
+ * Returns the {@link SlotType}.
+ *
+ *
+ *
+ * @return {@link SlotType}
+ */
+ public SlotType getSlotType() {
+ return slot_type;
+ }
+
+ /**
+ * Returns the slot index.
+ *
+ *
+ *
+ * @return Slot index
+ */
+ public int getSlot() {
+ return slot;
+ }
+
+ /**
+ * Returns the raw slot index.
+ *
+ *
+ *
+ * @return Raw slot index
+ */
+ public int getRawSlot() {
+ return raw_slot;
+ }
+
+ /**
+ * Returns the current ItemStack on the cursor.
+ *
+ *
+ *
+ * @return Cursor ItemStack
+ */
+ public ItemStack getCursor() {
+ return getInventoryView().getCursor();
+ }
+
+ /**
+ * Sets the item on the cursor.
+ *
+ *
+ *
+ * @param stack New cursor item
+ * @deprecated This changes the ItemStack in their hand before any calculations are applied to
+ * the Inventory, which has a tendency to create inconsistencies between the Player and the
+ * server, and to make unexpected changes in the behavior of the clicked Inventory.
+ */
+ @Deprecated
+ public void setCursor(ItemStack stack) {
+ getInventoryView().setCursor(stack);
+ }
+
+ /**
+ * Returns the ItemStack currently in the clicked slot.
+ *
+ *
+ *
+ * @return Item in the clicked
+ */
+ public ItemStack getCurrentItem() {
+ return current;
+ }
+
+ /**
+ * Sets the ItemStack currently in the clicked slot.
+ *
+ *
+ *
+ * @param current Item to be placed in the current slot
+ */
+ public void setCurrentItem(ItemStack current) {
+ this.current = current;
+ }
+
+ public int getHotbarKey() {
+ return hotbar_key;
+ }
+
+ /**
+ * Returns whether or not the ClickType for this event represents a right click.
+ *
+ *
+ *
+ * @return true if the ClickType uses the right mouse button
+ * @see ClickType#isRightClick()
+ */
+ public boolean isRightClick() {
+ return this.getClickType().isRightClick();
+ }
+
+ /**
+ * Returns whether or not the ClickType for this event represents a left click.
+ *
+ *
+ *
+ * @return true if the ClickType uses the left mouse button
+ * @see ClickType#isLeftClick()
+ */
+ public boolean isLeftClick() {
+ return this.getClickType().isLeftClick();
+ }
+
+ /**
+ * Returns whether the ClickType for this event indicates that the key was pressed down when the
+ * click was made.
+ *
+ *
+ *
+ * @return true if the ClickType uses Shift or Ctrl
+ * @see ClickType#isShiftClick()
+ */
+ public boolean isShiftClick() {
+ return this.getClickType().isShiftClick();
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/book/BookItemMenu.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/book/BookItemMenu.java
new file mode 100644
index 0000000..c6f0122
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/book/BookItemMenu.java
@@ -0,0 +1,580 @@
+package com.pepedevs.corelib.gui.inventory.custom.book;
+
+import com.pepedevs.corelib.gui.inventory.Item;
+import com.pepedevs.corelib.gui.inventory.ItemMenu;
+import com.pepedevs.corelib.gui.inventory.action.ItemMenuClickAction;
+import com.pepedevs.corelib.gui.inventory.size.ItemMenuSize;
+import org.apache.commons.lang.Validate;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.plugin.Plugin;
+
+import java.util.Arrays;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/** Implementation of {@link ItemMenu} interacting with paginated menu. */
+public class BookItemMenu extends ItemMenu {
+
+ /** Default page button bar size. */
+ public static final ItemMenuSize DEFAULT_BUTTONS_BAR_SIZE = ItemMenuSize.ONE_LINE;
+
+ private final ItemMenuSize buttons_bar_size;
+ private final Item[] bar_buttons;
+ private BookPageItemMenu[] pages;
+
+ /**
+ * Constructs the Book menu.
+ *
+ *
+ *
+ * @param title Title of the menu
+ * @param pages_size Page size of the menu
+ * @param buttons_bar_size Page button bar size of menu
+ * @param parent Parent menu if any, else null
+ * @param contents Contents of the menu
+ */
+ public BookItemMenu(
+ String title,
+ ItemMenuSize pages_size,
+ ItemMenuSize buttons_bar_size,
+ ItemMenu parent,
+ Item... contents) {
+ super(title, pages_size, parent, contents);
+ Validate.notNull(pages_size, "The pages size cannot be null!");
+ Validate.notNull(buttons_bar_size, "The buttons bar size cannot be null!");
+ Validate.isTrue(
+ pages_size.isHigherThan(ItemMenuSize.ONE_LINE),
+ "The book pages size must be higher than ItemMenuSize.ONE_LINE!");
+ Validate.isTrue(
+ pages_size.isHigherThan(buttons_bar_size),
+ "The pages size must be higher than the buttons bar size");
+
+ this.buttons_bar_size = buttons_bar_size;
+ this.bar_buttons = new Item[buttons_bar_size.getSize()];
+ int pages_amount = 1;
+ if (contents != null) {
+ int contents_length = contents.length;
+ pages_amount = getPagesAmount(contents_length, pages_size, buttons_bar_size);
+ }
+
+ this.pages = new BookPageItemMenu[pages_amount];
+ for (int i = 0; i < this.pages.length; i++) { // default pages.
+ this.pages[i] = new BookPageItemMenu(this, new Item[pages_size.getSize()]);
+ this.pages[i].setPageNumber(i);
+ }
+
+ this.addItems(contents);
+ }
+
+ /**
+ * Constructs the Book menu.
+ *
+ *
+ *
+ * @param title Title of the menu
+ * @param pages_size Page size of the menu
+ * @param parent Parent menu if any, else null
+ * @param contents Contents of the menu
+ */
+ public BookItemMenu(String title, ItemMenuSize pages_size, ItemMenu parent, Item... contents) {
+ this(title, pages_size, DEFAULT_BUTTONS_BAR_SIZE, parent, contents);
+ }
+
+ /**
+ * Returns the per page menu size.
+ *
+ *
+ *
+ * @return Size of the menu
+ */
+ public ItemMenuSize getPagesSize() {
+ return super.getSize();
+ }
+
+ /**
+ * Returns the button bar size size.
+ *
+ *
+ *
+ * @return Size of button bar
+ */
+ public ItemMenuSize getButtonsBarSize() {
+ return this.buttons_bar_size;
+ }
+
+ /**
+ * Returns the per page menu size.
+ *
+ *
+ *
+ * @return Size of the menu
+ */
+ @Override
+ @Deprecated
+ public final ItemMenuSize getSize() {
+ return super.getSize();
+ }
+
+ /**
+ * Returns the all the pages of the book menu.
+ *
+ *
+ *
+ * @return Array of pages
+ */
+ public BookPageItemMenu[] getPages() {
+ return Arrays.copyOf(pages, pages.length);
+ }
+
+ /**
+ * Returns the page of the menu from index.
+ *
+ *
+ *
+ * @param page_index Index of page
+ * @return Page of menu
+ */
+ public BookPageItemMenu getPage(int page_index) {
+ pagesRangeCheck(page_index, page_index);
+ return pages[page_index];
+ }
+
+ /**
+ * Returns the button-bar buttons of the menu.
+ *
+ *
+ *
+ * @return Array of button-bar buttons.
+ */
+ public Item[] getBarButtons() {
+ return Arrays.copyOf(bar_buttons, bar_buttons.length);
+ }
+
+ /**
+ * Returns the button-bar button at the index.
+ *
+ *
+ *
+ * @param button_index Index of button-bar
+ * @return Button-bar button
+ */
+ public Item getBarButton(int button_index) {
+ buttonsRangeCheck(button_index, button_index);
+ return bar_buttons[button_index];
+ }
+
+ @Override
+ @Deprecated
+ public final Item[] getContents() {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public final Stream
+ *
+ * @param player Player viewer
+ * @return Number of the page of this book that the given player is viewing, or -1 if the player
+ * is not viewing any page of this book
+ */
+ public int getOpenPageNumber(Player player) {
+ for (int i = 0; i < getPages().length; i++) {
+ if (getPage(i).isMenuOpen(player)) {
+ return getPage(i).getPageNumber();
+ }
+ }
+ return -1;
+ }
+
+ // @Override @Deprecated
+ // public final ItemMenu setSize(ItemMenuSize size) {
+ // return null;
+ // }
+
+ @Override
+ @Deprecated
+ public final ItemMenu setContents(Item[] contents) {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public final ItemMenu setItem(int index, Item content) {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public final boolean setItemIf(int index, Item content, Predicate super Item> predicate) {
+ return false;
+ }
+
+ @Override
+ public boolean addItem(Item item) {
+ addItems(item);
+ return true;
+ }
+
+ /**
+ * Stores the given items.
+ *
+ * New pages will be created if required.
+ *
+ *
+ *
+ * @param items Items to add
+ * @return This Object, for chaining
+ */
+ public BookItemMenu addItems(Item... items) {
+ final int count = addItemsNotFull(items);
+ if ((items.length - count) > 0) { // if there is no added items
+ int pages_amount =
+ getPagesAmount((items.length - count), getPagesSize(), getButtonsBarSize());
+ this.pages = Arrays.copyOfRange(this.pages, 0, this.pages.length + pages_amount);
+ for (int i = 0; i < pages.length; i++) {
+ if (pages[i] == null) {
+ pages[i] = new BookPageItemMenu(this);
+ pages[i].setPageNumber(i);
+
+ // here we're registering listener of the new page.
+ if (getHandler() != null) {
+ pages[i].registerListener(getHandler().getPlugin());
+ }
+ }
+ }
+ addItemsNotFull(Arrays.copyOfRange(items, count, items.length));
+ }
+ return this;
+ }
+
+ /**
+ * Stores the given items only if the menu is not full at the moment of storing the current
+ * element of the iteration.
+ *
+ *
+ *
+ * @param items Items to store
+ * @return Amount of items that was stored successfully
+ */
+ public int addItemsNotFull(Item... items) {
+ int added_count = 0;
+ for (int i = 0; i < items.length; i++) {
+ for (int j = 0; j < getPages().length; j++) {
+ if (!getPage(j).isFull()) {
+ getPage(j).addItem(items[i]);
+ added_count++;
+ break;
+ }
+ }
+ }
+ return added_count;
+ }
+
+ /**
+ * Stores the given bar button on the given slot.
+ *
+ *
+ *
+ * @param index Index of Slot
+ * @param button Button to store
+ * @return This Object, for chaining
+ */
+ public BookItemMenu setBarButton(int index, Item button) {
+ this.buttonsRangeCheck(index, index);
+ this.bar_buttons[index] = button;
+ return this;
+ }
+
+ /**
+ * Stores the given bar button item on the next empty slot of the bar.
+ *
+ *
+ *
+ * @param buttons Button items to store
+ * @return true if the button could be stored successfully
+ */
+ public boolean addBarButton(Item... buttons) {
+ for (int i = 0; i < buttons.length; i++) {
+ for (int j = 0; j < this.getButtonsBarSize().getSize(); j++) {
+ if (this.bar_buttons[j] == null) {
+ this.bar_buttons[j] = buttons[i];
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ @Deprecated
+ public final boolean clearItem(int slot) {
+ return false;
+ }
+
+ @Override
+ @Deprecated
+ public final boolean clearItemIf(int slot, Predicate super Item> predicate) {
+ return false;
+ }
+
+ @Override
+ @Deprecated
+ public final ItemMenu fill(Item... contents) {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public final ItemMenu fill(int from_index, Item... contents) {
+ return null;
+ }
+
+ /**
+ * Clears all the items (including button-bar buttons and content) in the book menu.
+ *
+ *
+ *
+ * @return ItemMenu Object
+ * @see #clearContents()
+ * @see #clearBarButtons()
+ */
+ public ItemMenu clear() {
+ clearContents();
+ clearBarButtons();
+ return this;
+ }
+
+ /**
+ * Clears all the content in the book menu.
+ *
+ *
+ *
+ * @return ItemMenu Object
+ */
+ public ItemMenu clearContents() {
+ for (int i = 0; i < this.getPages().length; i++) { // clear
+ BookPageItemMenu page = getPage(i);
+ for (int j = 0; j < page.getContents().length; j++) {
+ page.fillToAll(null);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Clears all the button-bar buttons in the book menu.
+ *
+ *
+ *
+ * @return ItemMenu Object
+ */
+ public ItemMenu clearBarButtons() {
+ for (int i = 0; i < this.getButtonsBarSize().getSize(); i++) { // clear
+ this.bar_buttons[i] = null;
+ }
+ return this;
+ }
+
+ @Override
+ @Deprecated
+ public final Inventory apply(Inventory inventory) {
+ return null;
+ }
+
+ /**
+ * Applies button-bar button to the given inventory.
+ *
+ *
+ *
+ * @param inventory Inventory to apply button-bar
+ * @return Inventory instance
+ */
+ public Inventory applyBarButtons(Inventory inventory) {
+ int from_index = this.getPagesSize().getSize() - this.getButtonsBarSize().getSize();
+ for (int i = 0; i < getButtonsBarSize().getSize(); i++) {
+ Item button = this.bar_buttons[i];
+ if (button != null) {
+ inventory.setItem(from_index + i, button.getDisplayIcon());
+ }
+ }
+ return inventory;
+ }
+
+ @Override
+ public boolean registerListener(Plugin plugin) {
+ for (int i = 0; i < this.getPages().length; i++) {
+ if (!this.getPage(i).registerListener(plugin)) {
+ return false;
+ }
+ }
+ return super.registerListener(plugin);
+ }
+
+ @Override
+ public boolean unregisterListener() {
+ for (int i = 0; i < this.getPages().length; i++) {
+ if (!this.getPage(i).unregisterListener()) {
+ return false;
+ }
+ }
+ return this.unregisterListener();
+ }
+
+ @Override
+ public Inventory open(Player player) {
+ return open(0, player);
+ }
+
+ /**
+ * Opens a new {@link Inventory} with the contents of the given page.
+ *
+ *
+ *
+ * @param page_number the page number.
+ * @param player the player viewer.
+ * @return the opened inventory.
+ */
+ public Inventory open(int page_number, Player player) {
+ this.pagesRangeCheck(page_number, page_number);
+ return getPage(page_number).open(player); // apply page contents and bar buttons
+ }
+
+ @Override
+ public boolean update(Player player) {
+ for (int i = 0; i < this.getPages().length; i++) {
+ BookPageItemMenu page = this.getPage(i);
+ if (page.update(player)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ @Deprecated
+ public ItemMenu onClick(final ItemMenuClickAction action) {
+ return this;
+ }
+
+ protected void bookRangeCheck(int from, int to) {
+ rangeCheck(getPagesSize().getSize() * pages.length, from, to);
+ }
+
+ protected void pagesRangeCheck(int from, int to) {
+ rangeCheck(this.getPages().length, from, to);
+ }
+
+ protected void buttonsRangeCheck(int from, int to) {
+ rangeCheck(this.getButtonsBarSize().getSize(), from, to);
+ }
+
+ protected void rangeCheck(int array_length, int from, int to) {
+ if (from > to) {
+ throw new IllegalArgumentException("from(" + from + ") > to(" + to + ")!");
+ }
+
+ if (from < 0) {
+ throw new ArrayIndexOutOfBoundsException(from);
+ }
+
+ if (to >= array_length) {
+ throw new ArrayIndexOutOfBoundsException(to - 1);
+ }
+ }
+
+ @Override
+ @Deprecated
+ protected final void rangeCheck(int from, int to) {
+ /* do nothing because is deprecated! */
+ }
+
+ protected int getPagesAmount(
+ int contents_length, ItemMenuSize pages_size, ItemMenuSize buttons_bar_size) {
+ Validate.isTrue(
+ pages_size.isHigherThan(ItemMenuSize.ONE_LINE),
+ "The book pages size must be higher than ItemMenuSize.ONE_LINE!");
+ Validate.isTrue(
+ pages_size.isHigherThan(buttons_bar_size),
+ "The pages size must be higher the buttons bar size");
+ int pages_amount = 1;
+ if (contents != null) {
+ int pages_fit_size =
+ pages_size.getSize()
+ - buttons_bar_size
+ .getSize(); // pages_fit_size = the pages size - (buttons bar
+ // size).
+ int length = contents_length;
+ if (length > pages_fit_size) {
+ while (length - pages_fit_size > 0) {
+ length -= pages_fit_size;
+ pages_amount++;
+ }
+ }
+ }
+ return pages_amount;
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/book/BookPageItemMenu.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/book/BookPageItemMenu.java
new file mode 100644
index 0000000..03cfdc5
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/book/BookPageItemMenu.java
@@ -0,0 +1,193 @@
+package com.pepedevs.corelib.gui.inventory.custom.book;
+
+import com.pepedevs.corelib.gui.inventory.Item;
+import com.pepedevs.corelib.gui.inventory.ItemMenu;
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+import com.pepedevs.corelib.gui.inventory.action.ItemMenuClickAction;
+import com.pepedevs.corelib.gui.inventory.size.ItemMenuSize;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+
+import java.util.Arrays;
+
+/** Implementation of {@link ItemMenu} for creating pages of {@link BookItemMenu}. */
+public class BookPageItemMenu extends ItemMenu {
+
+ private int page_number;
+
+ /**
+ * Constructs the Book Page menu.
+ *
+ *
+ *
+ * @param book Book menu of which this page menu is a child of
+ * @param contents Contents of this page menu
+ */
+ public BookPageItemMenu(BookItemMenu book, Item... contents) {
+ super(book.getTitle(), book.getPagesSize(), book, contents);
+ }
+
+ @Override
+ public final String getTitle() {
+ return super.getTitle();
+ }
+
+ @Override
+ public final BookPageItemMenu setTitle(String title) {
+ this.getBookMenu().setTitle(title);
+ return this;
+ }
+
+ public final ItemMenuSize getPageSize() {
+ return this.getBookMenu().getPagesSize();
+ }
+
+ @Override
+ @Deprecated
+ public final ItemMenuSize getSize() {
+ return getPageSize();
+ }
+
+ @Override
+ public final Item[] getContents() { // don't return the buttons
+ return Arrays.copyOfRange(
+ super.getContents(), 0, ItemMenuSize.beforeTo(getPageSize()).getSize());
+ }
+
+ /**
+ * Returns the {@link BookItemMenu} owner of this.
+ *
+ *
+ *
+ * @return Parent of this book menu page
+ */
+ public final BookItemMenu getBookMenu() {
+ return (BookItemMenu) super.getParent();
+ }
+
+ /**
+ * Returns the page next to this page on the book, or null if this is the last page.
+ *
+ *
+ *
+ * @return {@link BookPageItemMenu} next to this page on the book, or null if this is the last
+ * page.
+ */
+ public final BookPageItemMenu getNextBookPage() {
+ return (page_number + 1) < getBookMenu().getPages().length
+ ? getBookMenu().getPage(page_number + 1)
+ : null;
+ }
+
+ /**
+ * Returns the page before this page on the book, or null if this is the first page.
+ *
+ *
+ *
+ * @return {@link BookPageItemMenu} before this page on the book, or null if this is the first
+ * page.
+ */
+ public final BookPageItemMenu getBeforeBookPage() {
+ return (page_number - 1) >= 0 ? getBookMenu().getPage(page_number - 1) : null;
+ }
+
+ /**
+ * Returns the number of this page on the book.
+ *
+ *
+ *
+ * @return Number of this {@link BookPageItemMenu} on the book.
+ */
+ public final int getPageNumber() {
+ return page_number;
+ }
+
+ /**
+ * Sets the page number of the this page menu.
+ *
+ *
+ *
+ * @param number Page number
+ * @return This Object, for chaining
+ */
+ final BookPageItemMenu setPageNumber(int number) {
+ this.page_number = number;
+ return this;
+ }
+
+ /** Returns the {@link BookItemMenu} owner of this. */
+ @Override
+ @Deprecated
+ public final BookItemMenu getParent() {
+ return getBookMenu();
+ }
+
+ // @Override @Deprecated
+ // public final BookPageItemMenu setSize(ItemMenuSize size) {
+ // return this;
+ // }
+
+ @Override
+ public boolean hasParent() {
+ return true;
+ }
+
+ @Override
+ public Inventory open(Player player) {
+ return this.getBookMenu().applyBarButtons(super.open(player));
+ }
+
+ public ItemMenu onClick(final ItemMenuClickAction action) {
+ if (this.getHandler() == null) {
+ throw new UnsupportedOperationException("This menu has never been registered!");
+ }
+
+ if (!action.isRightClick() && !action.isLeftClick()) {
+ return this;
+ }
+
+ if (action.getSlot() < 0) {
+ return this;
+ }
+
+ ItemClickAction sub_action =
+ new ItemClickAction(
+ this,
+ action.getInventoryView(),
+ action.getClickType(),
+ action.getInventoryAction(),
+ action.getSlotType(),
+ action.getSlot(),
+ action.getRawSlot(),
+ action.getCurrentItem(),
+ action.getHotbarKey(),
+ false,
+ false,
+ false);
+
+ /* calling click method of the clicked item */
+ if (action.getSlot() >= getContents().length) {
+ int bar_button_slot = action.getSlot() - getContents().length;
+ if (bar_button_slot < getBookMenu().getBarButtons().length) { // clicking a bar button
+ Item bar_button = this.getBookMenu().getBarButton(bar_button_slot);
+ if (bar_button == null) {
+ return this;
+ }
+ bar_button.onClick(sub_action);
+ }
+ } else {
+ Item item = getItem(action.getSlot());
+
+ if (item != null) {
+ item.onClick(sub_action);
+ }
+ }
+
+ if (sub_action.isWillUpdate()) {
+ update(action.getPlayer());
+ } else if (sub_action.isWillClose() || sub_action.isWillGoBack()) {
+ getHandler().delayedClose(action.getPlayer(), 1);
+ }
+ return this;
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/book/item/AlternateBookPageActionItem.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/book/item/AlternateBookPageActionItem.java
new file mode 100644
index 0000000..855046b
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/book/item/AlternateBookPageActionItem.java
@@ -0,0 +1,96 @@
+package com.pepedevs.corelib.gui.inventory.custom.book.item;
+
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+import com.pepedevs.corelib.gui.inventory.custom.book.BookItemMenu;
+import com.pepedevs.corelib.gui.inventory.custom.book.BookPageItemMenu;
+import com.pepedevs.corelib.gui.inventory.item.action.ActionItem;
+import com.pepedevs.corelib.gui.inventory.item.action.ItemAction;
+import com.pepedevs.corelib.gui.inventory.item.action.ItemActionPriority;
+import org.bukkit.inventory.ItemStack;
+
+/** Implementation of {@link ActionItem} handling page change button item in book menu. */
+public class AlternateBookPageActionItem extends ActionItem {
+
+ protected BookItemMenu book;
+ protected boolean go_next;
+
+ /**
+ * Constructs the book page change action item.
+ *
+ *
+ *
+ * @param name Name of the item
+ * @param icon ItemStack icon of the item
+ * @param lore Lore of the item
+ */
+ public AlternateBookPageActionItem(String name, ItemStack icon, String... lore) {
+ super(name, icon, lore);
+ this.go_next = true;
+ super.addAction(
+ new ItemAction() {
+
+ @Override
+ public ItemActionPriority getPriority() {
+ return ItemActionPriority.LOW;
+ }
+
+ @Override
+ public void onClick(ItemClickAction action) {
+ if (!(action.getMenu() instanceof BookPageItemMenu)) {
+ return;
+ }
+
+ if (!((BookPageItemMenu) action.getMenu()).getBookMenu().equals(book)) {
+ return;
+ }
+
+ BookPageItemMenu current =
+ book.getPage(book.getOpenPageNumber(action.getPlayer()));
+ BookPageItemMenu to =
+ go_next ? current.getNextBookPage() : current.getBeforeBookPage();
+ if (to != null) {
+ book.getHandler().delayedClose(action.getPlayer(), 1);
+ to.getHandler().delayedOpen(action.getPlayer(), 2);
+ }
+ }
+ });
+ }
+
+ /**
+ * Sets the book menu owner of the pages that the player can alternate.
+ *
+ *
+ *
+ * @param book {@link BookItemMenu} owner of the pages that the player can alternate
+ * @return This Object, for chaining
+ */
+ public AlternateBookPageActionItem setBookMenu(BookItemMenu book) {
+ this.book = book;
+ return this;
+ }
+
+ /**
+ * If true: The next page will be opened. If false: The page before the current will be opened.
+ *
+ *
+ *
+ * @param flag true == next page. false == page before
+ * @return This Object, for chaining
+ */
+ public AlternateBookPageActionItem setGoNext(boolean flag) {
+ this.go_next = flag;
+ return this;
+ }
+
+ @Deprecated
+ @Override
+ public final ActionItem addAction(ItemAction action) {
+ return this;
+ }
+
+ @Deprecated
+ @Override
+ public final ActionItem removeAction(ItemAction action) {
+ return this;
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/updating/UpdatingItemMenu.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/updating/UpdatingItemMenu.java
new file mode 100644
index 0000000..8f35026
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/updating/UpdatingItemMenu.java
@@ -0,0 +1,42 @@
+package com.pepedevs.corelib.gui.inventory.custom.updating;
+
+import com.pepedevs.corelib.gui.inventory.Item;
+import com.pepedevs.corelib.gui.inventory.ItemMenu;
+import com.pepedevs.corelib.gui.inventory.custom.updating.handler.UpdatingItemMenuHandler;
+import com.pepedevs.corelib.gui.inventory.size.ItemMenuSize;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.Plugin;
+
+/** Implementation of {@link ItemMenu} handling updatable content item menu. */
+public class UpdatingItemMenu extends ItemMenu {
+
+ /**
+ * Constructs the Updatable menu.
+ *
+ *
+ *
+ * @param title Title of the menu
+ * @param size Size of the menu
+ * @param parent Parent of the menu if any. or null
+ * @param contents Content of the menu
+ */
+ public UpdatingItemMenu(String title, ItemMenuSize size, ItemMenu parent, Item... contents) {
+ super(title, size, parent, contents);
+ }
+
+ @Override
+ public UpdatingItemMenuHandler getHandler() {
+ return (UpdatingItemMenuHandler) this.handler;
+ }
+
+ @Override
+ public boolean registerListener(Plugin plugin) {
+ if (this.handler == null) {
+ Bukkit.getPluginManager()
+ .registerEvents(
+ (this.handler = new UpdatingItemMenuHandler(this, plugin)), plugin);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/updating/handler/UpdatingItemMenuHandler.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/updating/handler/UpdatingItemMenuHandler.java
new file mode 100644
index 0000000..5888182
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/custom/updating/handler/UpdatingItemMenuHandler.java
@@ -0,0 +1,70 @@
+package com.pepedevs.corelib.gui.inventory.custom.updating.handler;
+
+import com.pepedevs.corelib.gui.inventory.ItemMenu;
+import com.pepedevs.corelib.gui.inventory.handler.ItemMenuHandler;
+import com.pepedevs.corelib.utils.scheduler.SchedulerUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitTask;
+
+/** Implementation of {@link ItemMenuHandler} for handling updatable menus. */
+public class UpdatingItemMenuHandler extends ItemMenuHandler {
+
+ protected BukkitTask updater_task;
+
+ /**
+ * Constructs the updating menu handler.
+ *
+ *
+ *
+ * @param menu Updating item menu
+ * @param plugin Plugin instance for the menu
+ */
+ public UpdatingItemMenuHandler(ItemMenu menu, Plugin plugin) {
+ super(menu, plugin);
+ }
+
+ /**
+ * Checks if the menu is updating.
+ *
+ *
+ *
+ * @return true if updating, else false
+ */
+ public boolean isUpdating() {
+ return updater_task != null
+ && Bukkit.getScheduler().isCurrentlyRunning(updater_task.getTaskId());
+ }
+
+ @Override
+ public void unregisterListener() {
+ super.unregisterListener();
+ stopUpdating();
+ }
+
+ /**
+ * Starts updating the menu.
+ *
+ *
+ *
+ * @param start_delay Start delay before updating menu
+ * @param ticks Ticks invterval between updating menu
+ */
+ public void startUpdating(int start_delay, int ticks) {
+ stopUpdating();
+ this.updater_task =
+ SchedulerUtils.runTaskTimer(
+ this.menu::updateOnlinePlayers,
+ start_delay,
+ ticks,
+ plugin);
+ }
+
+ /** Stops updating menu */
+ public void stopUpdating() {
+ if (updater_task != null) {
+ updater_task.cancel();
+ updater_task = null;
+ }
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/handler/ItemMenuHandler.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/handler/ItemMenuHandler.java
new file mode 100644
index 0000000..4d94f60
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/handler/ItemMenuHandler.java
@@ -0,0 +1,132 @@
+package com.pepedevs.corelib.gui.inventory.handler;
+
+import com.pepedevs.corelib.gui.inventory.ItemMenu;
+import com.pepedevs.corelib.gui.inventory.action.ItemMenuClickAction;
+import com.pepedevs.corelib.gui.inventory.holder.ItemMenuHolder;
+import com.pepedevs.corelib.utils.scheduler.SchedulerUtils;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.event.server.PluginDisableEvent;
+import org.bukkit.plugin.Plugin;
+
+/** Handler for handling Item Menu. */
+public class ItemMenuHandler implements Listener {
+
+ protected final ItemMenu menu;
+ protected final Plugin plugin;
+
+ /**
+ * Constructs the ItemMenu Handler.
+ *
+ *
+ *
+ * @param menu ItemMenu
+ * @param plugin Plugin to register handler
+ */
+ public ItemMenuHandler(ItemMenu menu, Plugin plugin) {
+ this.menu = menu;
+ this.plugin = plugin;
+ }
+
+ /**
+ * Returns the plugin instance.
+ *
+ *
+ *
+ * @return Plugin instance
+ */
+ public Plugin getPlugin() {
+ return plugin;
+ }
+
+ /**
+ * Add delay to menu open.
+ *
+ *
+ *
+ * @param player Player to open menu
+ * @param delay Delay in milliseconds
+ */
+ public void delayedOpen(Player player, int delay) {
+ delayed(
+ () -> {
+ menu.open(player);
+ },
+ delay);
+ }
+
+ /**
+ * Add delay to menu update.
+ *
+ *
+ *
+ * @param player Player to open menu
+ * @param delay Delay in milliseconds
+ */
+ public void delayedUpdate(Player player, int delay) {
+ delayed(
+ () -> {
+ menu.update(player);
+ },
+ delay);
+ }
+
+ /**
+ * Add delay to menu close.
+ *
+ *
+ *
+ * @param player Player to open menu
+ * @param delay Delay in milliseconds
+ */
+ public void delayedClose(Player player, int delay) {
+ delayed(
+ () -> {
+ menu.close(player);
+ },
+ delay);
+ }
+
+ /** Unregister the handler. */
+ public void unregisterListener() {
+ HandlerList.unregisterAll(this);
+ }
+
+ protected void delayed(Runnable runnable, int delay) {
+ SchedulerUtils.scheduleSyncDelayedTask(runnable, delay, plugin);
+ }
+
+ @SuppressWarnings("deprecation")
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onInventoryClick(InventoryClickEvent event) {
+ if (event.getInventory() == null
+ || event.getInventory().getType() != InventoryType.CHEST
+ || !(event.getWhoClicked() instanceof Player)
+ || !(event.getInventory().getHolder() instanceof ItemMenuHolder)) {
+ return;
+ }
+
+ ItemMenuHolder holder = (ItemMenuHolder) event.getInventory().getHolder();
+ if (!menu.equals(holder.getItemMenu())) {
+ return;
+ }
+
+ final ItemMenuClickAction action = new ItemMenuClickAction(menu, event);
+ ((ItemMenuHolder) event.getInventory().getHolder()).getItemMenu().onClick(action);
+ event.setCurrentItem(action.getCurrentItem());
+ event.setCursor(action.getCursor());
+ event.setCancelled(true);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onPluginDisable(PluginDisableEvent event) {
+ if (event.getPlugin().equals(plugin)) {
+ this.menu.closeOnlinePlayers();
+ }
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/holder/ItemMenuHolder.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/holder/ItemMenuHolder.java
new file mode 100644
index 0000000..a7ffb5b
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/holder/ItemMenuHolder.java
@@ -0,0 +1,48 @@
+package com.pepedevs.corelib.gui.inventory.holder;
+
+import com.pepedevs.corelib.gui.inventory.ItemMenu;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+
+/** Implementation of {@link InventoryHolder} interacting with ItemMenu. */
+public class ItemMenuHolder implements InventoryHolder {
+
+ private final ItemMenu menu;
+ private final Inventory inventory;
+
+ /**
+ * Constructs the ItemMenuHolder.
+ *
+ *
+ *
+ * @param menu ItemMenu
+ * @param inventory Inventory
+ */
+ public ItemMenuHolder(ItemMenu menu, Inventory inventory) {
+ this.menu = menu;
+ this.inventory = inventory;
+ }
+
+ /**
+ * Returns the ItemMenu.
+ *
+ *
+ *
+ * @return ItemMenu
+ */
+ public ItemMenu getItemMenu() {
+ return menu;
+ }
+
+ /**
+ * Returns the Inventory.
+ *
+ *
+ *
+ * @return Inventory
+ */
+ @Override
+ public Inventory getInventory() {
+ return inventory;
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/ActionItem.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/ActionItem.java
new file mode 100644
index 0000000..0dc9d67
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/ActionItem.java
@@ -0,0 +1,97 @@
+package com.pepedevs.corelib.gui.inventory.item.action;
+
+import com.pepedevs.corelib.gui.inventory.Item;
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+import org.apache.commons.lang.Validate;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Implementation of {@link Item}. */
+public class ActionItem extends Item {
+
+ private final Set
+ *
+ * @param name Name of the Item
+ * @param icon ItemStack icon of the Item
+ * @param lore Lore of the Item
+ */
+ public ActionItem(String name, ItemStack icon, String... lore) {
+ super(name, icon, lore);
+ this.actions = new HashSet<>();
+ }
+
+ /**
+ * Constructs the menu Action Item.
+ *
+ *
+ *
+ * @param name Name of the Item
+ * @param icon ItemStack icon of the Item
+ * @param lore Lore of the Item
+ */
+ public ActionItem(String name, ItemStack icon, List
+ *
+ * @param icon ItemStack icon of the Item
+ */
+ public ActionItem(ItemStack icon) {
+ super(icon);
+ this.actions = new HashSet<>();
+ }
+
+ /**
+ * Add action to the Action Item.
+ *
+ *
+ *
+ * @param action Item Action
+ * @return This Object, for chaining
+ */
+ public ActionItem addAction(ItemAction action) {
+ Validate.notNull(action, "The action cannot be null!");
+ Validate.notNull(action.getPriority(), "The action priority cannot be null!");
+ actions.add(action);
+ return this;
+ }
+
+ /**
+ * Remove action from the Action Item.
+ *
+ *
+ *
+ * @param action Item Action
+ * @return This Object, for chaining
+ */
+ public ActionItem removeAction(ItemAction action) {
+ actions.remove(action);
+ return this;
+ }
+
+ @Override
+ public void onClick(ItemClickAction action) {
+ for (int i = ItemActionPriority.values().length - 1;
+ i >= 0;
+ i--) { // call in order, from high to low
+ ItemActionPriority priority = ItemActionPriority.values()[i];
+ actions.stream()
+ .filter(act -> act != null && priority.equals(act.getPriority()))
+ .forEach(act -> act.onClick(action));
+ }
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/ItemAction.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/ItemAction.java
new file mode 100644
index 0000000..0013833
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/ItemAction.java
@@ -0,0 +1,25 @@
+package com.pepedevs.corelib.gui.inventory.item.action;
+
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+
+/** Class to classify menu item action. */
+public interface ItemAction {
+
+ /**
+ * Returns the priority of the action.
+ *
+ *
+ *
+ * @return {@link ItemActionPriority}
+ */
+ public ItemActionPriority getPriority();
+
+ /**
+ * Click action on the item.
+ *
+ *
+ *
+ * @param action {@link ItemClickAction} on the Item
+ */
+ public void onClick(ItemClickAction action);
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/ItemActionPriority.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/ItemActionPriority.java
new file mode 100644
index 0000000..385423b
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/ItemActionPriority.java
@@ -0,0 +1,13 @@
+package com.pepedevs.corelib.gui.inventory.item.action;
+
+public enum ItemActionPriority {
+
+ /** Should run at last. */
+ LOW,
+
+ /** Should run after the high priority and before low priority. */
+ NORMAL,
+
+ /** Should run first. */
+ HIGH;
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/back/BackActionItem.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/back/BackActionItem.java
new file mode 100644
index 0000000..247b1b6
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/back/BackActionItem.java
@@ -0,0 +1,37 @@
+package com.pepedevs.corelib.gui.inventory.item.action.back;
+
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+import com.pepedevs.corelib.gui.inventory.item.action.ActionItem;
+import com.pepedevs.corelib.gui.inventory.item.action.ItemAction;
+import com.pepedevs.corelib.gui.inventory.item.action.ItemActionPriority;
+import org.bukkit.inventory.ItemStack;
+
+/** Implementation of {@link ActionItem} handling back button. */
+public class BackActionItem extends ActionItem {
+
+ /**
+ * Constructs the Back Action Item.
+ *
+ *
+ *
+ * @param name Name of the Item
+ * @param icon ItemStack icon of the Item
+ * @param lore Lore of the Item
+ */
+ public BackActionItem(String name, ItemStack icon, String[] lore) {
+ super(name, icon, lore);
+ addAction(
+ new ItemAction() {
+
+ @Override
+ public ItemActionPriority getPriority() {
+ return ItemActionPriority.LOW;
+ }
+
+ @Override
+ public void onClick(ItemClickAction action) {
+ action.setGoBack(true);
+ }
+ });
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/close/CloseMenuActionItem.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/close/CloseMenuActionItem.java
new file mode 100644
index 0000000..5b5995c
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/close/CloseMenuActionItem.java
@@ -0,0 +1,72 @@
+package com.pepedevs.corelib.gui.inventory.item.action.close;
+
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+import com.pepedevs.corelib.gui.inventory.item.action.ActionItem;
+import com.pepedevs.corelib.gui.inventory.item.action.ItemAction;
+import com.pepedevs.corelib.gui.inventory.item.action.ItemActionPriority;
+import com.pepedevs.corelib.utils.itemstack.safe.SafeItemStack;
+import com.pepedevs.corelib.utils.itemstack.stainedglass.StainedGlassColor;
+import com.pepedevs.corelib.utils.itemstack.stainedglass.StainedGlassItemStack;
+import com.pepedevs.corelib.utils.reflection.general.EnumReflection;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+
+/** Implementation of {@link ActionItem} handling the close menu option. */
+public class CloseMenuActionItem extends ActionItem {
+
+ @Deprecated // Compatibility with server versions <= 1.8
+ public static final ItemStack DEFAULT_ICON =
+ EnumReflection.getEnumConstant(Material.class, "BARRIER") != null
+ ? new SafeItemStack(Material.BARRIER)
+ : new StainedGlassItemStack(StainedGlassColor.RED, true);
+
+ /**
+ * Constructs the Close Menu Action Item.
+ *
+ *
+ *
+ * @param lore Lore of the Item
+ */
+ public CloseMenuActionItem(String... lore) {
+ this(ChatColor.RED + "Close", lore);
+ }
+
+ /**
+ * Constructs the Close Menu Action Item.
+ *
+ *
+ *
+ * @param name Name of the Item
+ * @param lore Lore of the Item
+ */
+ public CloseMenuActionItem(String name, String... lore) {
+ this(name, DEFAULT_ICON, lore);
+ }
+
+ /**
+ * Constructs the Close Menu Action Item.
+ *
+ *
+ *
+ * @param name Name of the Item
+ * @param icon ItemStack icon of the Item
+ * @param lore Lore of the Item
+ */
+ public CloseMenuActionItem(String name, ItemStack icon, String... lore) {
+ super(name, icon, lore);
+ addAction(
+ new ItemAction() {
+
+ @Override
+ public ItemActionPriority getPriority() {
+ return ItemActionPriority.LOW;
+ }
+
+ @Override
+ public void onClick(ItemClickAction action) {
+ action.setClose(true);
+ }
+ });
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/open/OpenMenuActionItem.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/open/OpenMenuActionItem.java
new file mode 100644
index 0000000..1ad5f4d
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/action/open/OpenMenuActionItem.java
@@ -0,0 +1,71 @@
+package com.pepedevs.corelib.gui.inventory.item.action.open;
+
+import com.pepedevs.corelib.gui.inventory.ItemMenu;
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+import com.pepedevs.corelib.gui.inventory.item.action.ActionItem;
+import com.pepedevs.corelib.gui.inventory.item.action.ItemAction;
+import com.pepedevs.corelib.gui.inventory.item.action.ItemActionPriority;
+import org.bukkit.inventory.ItemStack;
+
+/** Implementation of {@link ActionItem} handling with open menu option. */
+public class OpenMenuActionItem extends ActionItem {
+
+ protected ItemMenu menu;
+
+ /**
+ * Constructs the Open Menu Action Item.
+ *
+ *
+ *
+ * @param name Name of the Item
+ * @param icon ItemStack icon of the Item
+ * @param lore Lore of the Item
+ */
+ public OpenMenuActionItem(String name, ItemStack icon, String... lore) {
+ super(name, icon, lore);
+ addDefaultAction();
+ }
+
+ /**
+ * Constructs the Open Menu Action Item.
+ *
+ *
+ *
+ * @param icon ItemStack icon of the Item
+ */
+ public OpenMenuActionItem(ItemStack icon) {
+ super(icon);
+ addDefaultAction();
+ }
+
+ /** Adds the default action of opening other menu. */
+ protected void addDefaultAction() {
+ addAction(
+ new ItemAction() {
+
+ @Override
+ public ItemActionPriority getPriority() {
+ return ItemActionPriority.LOW;
+ }
+
+ @Override
+ public void onClick(ItemClickAction action) {
+ action.getMenu().getHandler().delayedClose(action.getPlayer(), 1);
+ menu.getHandler().delayedOpen(action.getPlayer(), 3);
+ }
+ });
+ }
+
+ /**
+ * Sets the {@link ItemMenu} that will be opened when clicking this.
+ *
+ *
+ *
+ * @param menu Menu to open when this clicked
+ * @return This Object, for chaining
+ */
+ public OpenMenuActionItem setMenu(ItemMenu menu) {
+ this.menu = menu;
+ return this;
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/voidaction/VoidActionItem.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/voidaction/VoidActionItem.java
new file mode 100644
index 0000000..ed51b02
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/item/voidaction/VoidActionItem.java
@@ -0,0 +1,34 @@
+package com.pepedevs.corelib.gui.inventory.item.voidaction;
+
+import com.pepedevs.corelib.gui.inventory.Item;
+import com.pepedevs.corelib.gui.inventory.action.ItemClickAction;
+import org.bukkit.inventory.ItemStack;
+
+/** Class for Filler/Void Items in ItemMenu. */
+public class VoidActionItem extends Item {
+
+ /**
+ * Constructs the Void Action Item.
+ *
+ *
+ *
+ * @param name Name of the Item
+ * @param icon ItemStack Icon of the Item
+ * @param lore Lore of the Item
+ */
+ public VoidActionItem(String name, ItemStack icon, String... lore) {
+ super(name, icon, lore);
+ }
+
+ /**
+ * Click action for Filler/Void Item.
+ *
+ *
+ *
+ * @param action {@link ItemClickAction} for the Item
+ */
+ @Override
+ public final void onClick(ItemClickAction action) {
+ /* do nothing */
+ }
+}
diff --git a/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/size/ItemMenuSize.java b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/size/ItemMenuSize.java
new file mode 100644
index 0000000..ed921b2
--- /dev/null
+++ b/inventory-gui/src/main/java/com/pepedevs/corelib/gui/inventory/size/ItemMenuSize.java
@@ -0,0 +1,152 @@
+package com.pepedevs.corelib.gui.inventory.size;
+
+import java.util.Arrays;
+
+/** Enumeration for ItemMenu size. */
+public enum ItemMenuSize {
+ ONE_LINE(9),
+ TWO_LINE(18),
+ THREE_LINE(27),
+ FOUR_LINE(36),
+ FIVE_LINE(45),
+ SIX_LINE(54);
+
+ private final int size;
+
+ ItemMenuSize(int size) {
+ this.size = size;
+ }
+
+ /**
+ * Returns the {@link ItemMenuSize} which fits the given size.
+ *
+ *
+ *
+ * @param size Size to fit in ItemMenu
+ * @return ItemMenuSize instance with the given size
+ */
+ public static ItemMenuSize fitOf(int size) {
+ int difference = -1;
+ for (ItemMenuSize constant : ItemMenuSize.values()) {
+ difference =
+ ((difference == -1 || (Math.max(size - constant.getSize(), 0)) < difference)
+ ? (size - constant.getSize())
+ : difference);
+ }
+ return Arrays.stream(ItemMenuSize.values())
+ .filter(constant -> Math.max(size - constant.getSize(), 0) == 0)
+ .findAny()
+ .orElse(SIX_LINE);
+ }
+
+ /**
+ * Returns the size enumeration next to this size.
+ *
+ *
+ *
+ * @param from Size to get next to
+ * @return The next size enumeration
+ */
+ public static ItemMenuSize nextTo(ItemMenuSize from) {
+ return fitOf(from.getSize() + 1);
+ }
+
+ /**
+ * Returns the size enumeration before this size.
+ *
+ *
+ *
+ * @param from Size to get before size
+ * @return The before size enumeration
+ */
+ public static ItemMenuSize beforeTo(ItemMenuSize from) {
+ switch (from) {
+ case ONE_LINE:
+
+ case TWO_LINE:
+ return ONE_LINE;
+
+ case THREE_LINE:
+ return TWO_LINE;
+
+ case FOUR_LINE:
+ return THREE_LINE;
+
+ case FIVE_LINE:
+ return FOUR_LINE;
+
+ case SIX_LINE:
+ return FIVE_LINE;
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns the size of menu.
+ *
+ *
+ *
+ * @return Size of menu
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Checks if the given size is higher than this size.
+ *
+ *
+ *
+ * @param other Other size
+ * @return {@code true} if the given size is higher, else false
+ */
+ public boolean isHigherThan(ItemMenuSize other) {
+ return other != null && getSize() > other.getSize();
+ }
+
+ /**
+ * Checks if the given size is higher than or equal to this size.
+ *
+ *
+ *
+ * @param other Other size
+ * @return {@code true} if the given size is higher or equal, else false
+ */
+ public boolean isHigherEqualsThan(ItemMenuSize other) {
+ return other != null && getSize() >= other.getSize();
+ }
+
+ /**
+ * Checks if the given size is lower than this size.
+ *
+ *
+ *
+ * @param other Other size
+ * @return {@code true} if the given size is lower, else false
+ */
+ public boolean isLowerThan(ItemMenuSize other) {
+ return other != null && getSize() < other.getSize();
+ }
+
+ /**
+ * Checks if the given size is lower than or equal to this size.
+ *
+ *
+ *
+ * @param other Other size
+ * @return {@code true} if the given size is lower or equal, else false
+ */
+ public boolean isLowerEqualsThan(ItemMenuSize other) {
+ return other != null && getSize() <= other.getSize();
+ }
+
+ public int getFirstSlotTest() {
+ return beforeTo(this).getSize() + 1;
+ }
+
+ public int getLastSlotTest() {
+ return getSize() - 1;
+ }
+}
diff --git a/minecraft-version/pom.xml b/minecraft-version/pom.xml
new file mode 100644
index 0000000..0cbad2b
--- /dev/null
+++ b/minecraft-version/pom.xml
@@ -0,0 +1,19 @@
+
+ Note that server versions older than {@link Version#v1_8_R1} are NOT supported.
+ *
+ *
+ *
+ * @return Version of this server
+ */
+ public static Version getServerVersion() {
+ String packaje = Bukkit.getServer().getClass().getPackage().getName();
+ String version = packaje.substring(packaje.lastIndexOf(".") + 1);
+ return valueOf(version);
+ }
+
+ public String getNmsPackage() {
+ if (SERVER_VERSION.isNewerEquals(Version.v1_17_R1))
+ return "net.minecraft";
+ else
+ return String.format(NMS_CLASSES_PACKAGE, SERVER_VERSION.name());
+ }
+
+ public String getObcPackage() {
+ return String.format(CRAFT_CLASSES_PACKAGE, SERVER_VERSION.name());
+ }
+
+ /**
+ * Gets the version's id.
+ *
+ *
+ *
+ * @return Version's id
+ */
+ public int getID() {
+ return this.id;
+ }
+
+ /**
+ * Checks whether this version is older than the provided version.
+ *
+ *
+ *
+ * @param other Other version
+ * @return true if older
+ */
+ public boolean isOlder(Version other) {
+ return (getID() < other.getID());
+ }
+
+ /**
+ * Checks whether this version is older than or equals to the provided version.
+ *
+ *
+ *
+ * @param other Other version
+ * @return true if older or equals
+ */
+ public boolean isOlderEquals(Version other) {
+ return (getID() <= other.getID());
+ }
+
+ /**
+ * Checks whether this version is newer than the provided version.
+ *
+ *
+ *
+ * @param other Other version
+ * @return true if newer
+ */
+ public boolean isNewer(Version other) {
+ return (getID() > other.getID());
+ }
+
+ /**
+ * Checks whether this version is newer than or equals to the provided version.
+ *
+ *
+ *
+ * @param other Other version
+ * @return true if newer or equals
+ */
+ public boolean isNewerEquals(Version other) {
+ return (getID() >= other.getID());
+ }
+
+ /**
+ * Checks whether this has the same version as the provided version.
+ *
+ *
+ *
+ *
+ *
+ * @param other Other version
+ * @return true if has the same version
+ */
+ public boolean equalsVersion(Version other) {
+ String s0 = name().substring(0, name().indexOf("_R"));
+ String s1 = other.name().substring(0, other.name().indexOf("_R"));
+ return s0.equals(s1);
+ }
+
+ /**
+ * Checks whether this has the same revision as the provided version.
+ *
+ *
+ *
+ *
+ *
+ * @param other Other version
+ * @return true if has the same revision
+ */
+ public boolean equalsRevision(Version other) {
+ String s0 = name().substring(name().indexOf("R") + 1);
+ String s1 = other.name().substring(other.name().indexOf("R") + 1);
+ return s0.equals(s1);
+ }
+}
diff --git a/packet-utils/pom.xml b/packet-utils/pom.xml
new file mode 100644
index 0000000..b4f7d5d
--- /dev/null
+++ b/packet-utils/pom.xml
@@ -0,0 +1,33 @@
+
+
+ *
+ * @param packet_name Name of the packets this listener will manage.
+ * @param priority Priority of the listener.
+ * @param listener Listener to run.
+ */
+ public void addPacketListener(
+ final String packet_name, final PacketListener.Priority priority, final PacketListener listener) {
+ Set
+ *
+ * @param listener Listener to unregister.
+ */
+ public void removePacketListener(final PacketListener listener) {
+ Set Also this class injects the players that are already connected to the server when is
+ * constructed.
+ */
+ private static class PlayerInjector implements Listener {
+
+ private PlayerInjector() {
+ Bukkit.getOnlinePlayers().forEach(PlayerInjector::inject);
+ }
+
+ private static void inject(final Player player) {
+ /* unject before injecting */
+ PlayerInjector.unject(player);
+
+ /* injecting */
+ final Channel channel = PlayerReflection.getChannel(player);
+ final PlayerChannelHandler handler = new PlayerChannelHandler(player);
+
+ channel.pipeline().addBefore("packet_handler", "PacketInjector", handler);
+ }
+
+ private static void unject(final Player player) {
+ final Channel channel = PlayerReflection.getChannel(player);
+ if (channel.pipeline().get("PacketInjector") != null) {
+ channel.pipeline().remove("PacketInjector");
+ }
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void handleJoin(final PlayerJoinEvent event) {
+ PlayerInjector.inject(event.getPlayer());
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void handleQuit(final PlayerQuitEvent event) {
+ PlayerInjector.unject(event.getPlayer());
+ }
+
+ }
+
+ /** The duplex channel handler for handling the packets when receiving or sending. */
+ private static class PlayerChannelHandler extends ChannelDuplexHandler {
+
+ private final Player player;
+
+ public PlayerChannelHandler(final Player player) {
+ this.player = player;
+ }
+
+ @Override
+ public void channelRead(final ChannelHandlerContext context, final Object packet)
+ throws Exception {
+ final PacketEvent event = new PacketEvent(player, packet);
+
+ synchronized (PACKET_LISTENERS) {
+ final List
+ *
+ * @param player the player sending-receiving the packet.
+ * @param packet the packet.
+ */
+ public PacketEvent(final Player player, final Object packet) {
+ this.player = player;
+ this.packet = packet;
+ }
+
+ /**
+ * Gets the player sending-receiving the packet.
+ *
+ *
+ *
+ * @return the player.
+ */
+ public Player getPlayer() {
+ return player;
+ }
+
+ /**
+ * The packet, that is an instance of a nms packet like "PacketPlayInSteerVehicle".
+ *
+ *
+ *
+ * @return the packet.
+ */
+ public Object getPacket() {
+ return packet;
+ }
+
+ /**
+ * Returns the name of the packet.
+ *
+ *
+ *
+ * @return Name of the packet
+ */
+ public String getPacketName() {
+ return packet.getClass().getSimpleName();
+ }
+
+ /**
+ * Gets whether this event has been cancelled.
+ *
+ *
+ *
+ * @return true if cancelled.
+ */
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ /**
+ * Canceling this event will avoid the packet to be sent-received.
+ *
+ *
+ *
+ * @param flag true to cancel.
+ */
+ public void setCancelled(boolean flag) {
+ this.cancelled = flag;
+ }
+}
diff --git a/packet-utils/src/main/java/com/pepedevs/corelib/packets/PacketListener.java b/packet-utils/src/main/java/com/pepedevs/corelib/packets/PacketListener.java
new file mode 100644
index 0000000..c47385e
--- /dev/null
+++ b/packet-utils/src/main/java/com/pepedevs/corelib/packets/PacketListener.java
@@ -0,0 +1,59 @@
+package com.pepedevs.corelib.packets;
+
+/** Simple interface called when receiving or sending packets. */
+public interface PacketListener {
+
+ /**
+ * Called when receiving a packet.
+ *
+ *
+ *
+ * @param event the packet event.
+ */
+ void onReceiving(final PacketEvent event);
+
+ /**
+ * Called when sending a packet.
+ *
+ *
+ *
+ * @param event the packet event.
+ */
+ void onSending(final PacketEvent event);
+
+ /** Enumeration for Packet Event priority. */
+ enum Priority {
+
+ /** Very low importance and should be run first. */
+ LOWEST(0),
+
+ /** Low importance. */
+ LOW(1),
+
+ /** Neither important nor unimportant, and may be run normally. */
+ NORMAL(2),
+
+ /** High importance */
+ HIGH(3),
+
+ /** Critical and must have the final say in what happens. */
+ HIGHEST(4),
+
+ /**
+ * Listened to purely for monitoring the outcome of an event.
+ *
+ * No modifications to the packet should be made under this priority
+ */
+ MONITOR(5);
+
+ private final int slot;
+
+ Priority(final int slot) {
+ this.slot = slot;
+ }
+
+ public int getSlot() {
+ return slot;
+ }
+ }
+}
diff --git a/placeholder-utils/pom.xml b/placeholder-utils/pom.xml
new file mode 100644
index 0000000..4c0ffd5
--- /dev/null
+++ b/placeholder-utils/pom.xml
@@ -0,0 +1,54 @@
+
+
+ *
+ * @param player Player that executes the placeholders
+ * @param local Placeholders to add to the default ones
+ * @return Parsed value without placeholders
+ */
+ T resolve(Player player, PlaceholderRegistry local);
+
+ /**
+ * Removes placeholders from the value and parses it.
+ *
+ *
+ *
+ * @param player Player that executes the placeholders
+ * @return Parsed value without placeholders
+ */
+ default T resolve(Player player) {
+ return resolve(player, PlaceholderUtil.getRegistry());
+ }
+
+ /**
+ * @return true only if no real placeholders need to be resolved (a.k.a this is a fake
+ * PlaceholderValue)
+ */
+ default boolean hasPlaceholders() {
+ return true;
+ }
+
+ default Also this methods should return {@code true} if the initialization was
+ * successfully, and {@code false} if not. Returning {@code
+ * false} from this method means that the initialization was unsuccessfully, then the
+ * plugin will be disabled automatically.
+ *
+ *
+ *
+ * @return true if the initialization was successfully.
+ */
+ protected abstract boolean setUp();
+
+ /**
+ * Gets the plugins on which this plugin depends.
+ *
+ * This methods might return an empty array or {@code null} if this plugin
+ * doesn't depend on another.
+ *
+ *
+ *
+ * @return The dependencies or null if this plugin doesn't depend on another.
+ * @see PluginDependence
+ */
+ public abstract PluginDependence[] getDependences();
+
+ /**
+ * This method should setups the configuration.
+ *
+ * Also this should return {@code true} if the configuration was loaded
+ * successfully, and {@code false} if not.
+ *
+ * Note that if this methods returns {@code false}, then the plugin will be
+ * disabled automatically.
+ *
+ * Note that this methods will be called after:
+ *
+ *
+ *
+ * @return whether the configuration was loaded successfully.
+ */
+ protected abstract boolean setUpConfig();
+
+ /**
+ * This method should setups the plugin handlers.
+ *
+ * Also this should return {@code true} if the initialization of the
+ * handlers was successfully, and {@code false} if not.
+ *
+ * Note that if this methods returns {@code false}, then the plugin will be
+ * disabled automatically.
+ *
+ * Note that this methods will be called after:
+ *
+ *
+ *
+ * @return Whether the initialization of the handlers was successfully.
+ */
+ protected abstract boolean setUpHandlers();
+
+ /**
+ * This method should setups the commands of the plugin.
+ *
+ * Also this should return {@code true} if the initialization of the
+ * commands was successfully, and {@code false} if not.
+ *
+ * Note that if this methods returns {@code false}, then the plugin will be
+ * disabled automatically.
+ *
+ * Note that this methods will be called after:
+ *
+ *
+ *
+ * @return Whether the initialization of the commands was successfully.
+ */
+ protected abstract boolean setUpCommands();
+
+ /**
+ * This method should setups the listeners of the plugin.
+ *
+ * Also this should return {@code true} if the initialization of the
+ * listeners was successfully, and {@code false} if not.
+ *
+ * Note that if this methods returns {@code false}, then the plugin will be
+ * disabled automatically.
+ *
+ * Note that this methods will be called after:
+ *
+ * The resource is saved into the desired {@code out_directory}.
+ *
+ *
+ *
+ * @param resource_path The embedded resource path to look for within the plugin's .jar file.
+ * (No preceding slash).
+ * @param out_directory The directory into which the resource will be saved.
+ * @param replace If true, the embedded resource will overwrite the contents of an existing
+ * file.
+ */
+ public void saveResource(String resource_path, File out_directory, boolean replace) {
+ URL url = this.getClassLoader().getResource(resource_path = resource_path.replace('\\', '/'));
+ if (url != null) {
+ File out =
+ new File(
+ out_directory,
+ resource_path.lastIndexOf('/') != -1
+ ? resource_path.substring(resource_path.lastIndexOf('/') + 1)
+ : resource_path);
+
+ if (!out.exists() || replace) {
+ try {
+ FileUtils.copyURLToFile(url, out);
+ } catch (IOException ex) {
+ this.getLogger().severe("Couldn't save resource " + resource_path + " to " + out);
+ ex.printStackTrace();
+ }
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "The embedded resource '"
+ + resource_path
+ + "' cannot be found in "
+ + getFile());
+ }
+ }
+
+ protected String getCompilationId() {
+ if (compilation_id == null) {
+ YamlConfiguration plugin =
+ YamlConfiguration.loadConfiguration(
+ new InputStreamReader(this.getResource("plugin.yml")));
+
+ compilation_id = plugin.getString("compid", String.valueOf(0));
+ }
+
+ return compilation_id;
+ }
+}
diff --git a/plugin-helper/src/main/java/com/pepedevs/corelib/plugin/PluginAdapter.java b/plugin-helper/src/main/java/com/pepedevs/corelib/plugin/PluginAdapter.java
new file mode 100644
index 0000000..192bcf2
--- /dev/null
+++ b/plugin-helper/src/main/java/com/pepedevs/corelib/plugin/PluginAdapter.java
@@ -0,0 +1,33 @@
+package com.pepedevs.corelib.plugin;
+
+/**
+ * An convenience implementation of {@link Plugin}. Derive from this and only override what you
+ * need.
+ */
+public abstract class PluginAdapter extends Plugin {
+
+ @Override
+ public PluginDependence[] getDependences() {
+ return null;
+ }
+
+ @Override
+ protected boolean setUpConfig() {
+ return true;
+ }
+
+ @Override
+ protected boolean setUpHandlers() {
+ return true;
+ }
+
+ @Override
+ protected boolean setUpCommands() {
+ return true;
+ }
+
+ @Override
+ protected boolean setUpListeners() {
+ return true;
+ }
+}
diff --git a/plugin-helper/src/main/java/com/pepedevs/corelib/plugin/PluginDependence.java b/plugin-helper/src/main/java/com/pepedevs/corelib/plugin/PluginDependence.java
new file mode 100644
index 0000000..d794911
--- /dev/null
+++ b/plugin-helper/src/main/java/com/pepedevs/corelib/plugin/PluginDependence.java
@@ -0,0 +1,79 @@
+package com.pepedevs.corelib.plugin;
+
+import org.apache.commons.lang.Validate;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.Plugin;
+
+import java.util.function.Function;
+
+/**
+ * Represents a possible plugin on which a {@link Plugin} may depend.
+ *
+ * This class implements {@link Function} for receiving the plugin on which a {@link Plugin}
+ * depends, and producing a resultant {@link Boolean}, the function might receive {@code
+ * null} that means the plugin has never been loaded by Bukkit plugin manager, in that
+ * case, if the developer return {@code null} from this function, the plugin will
+ * be disabled automatically.
+ *
+ * Also the developer can send messages to the console when checking the received plugin.
+ *
+ *
+ *
+ *
+ *
+ * @param name Name of the depending plugin.
+ */
+ public PluginDependence(final String name) {
+ Validate.notNull(name, "the name cannot be null!");
+ this.name = name;
+ this.enabled = Bukkit.getPluginManager().isPluginEnabled(name);
+ }
+
+ /**
+ * Gets the name of the depending plugin.
+ *
+ *
+ *
+ * @return Name of the depending plugin.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns if the plugin found enable and hooked.
+ *
+ *
+ *
+ * @return {@code true} if the plugin is enabled, false otherwise
+ */
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4813248
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,112 @@
+
+
+ *
+ * @param primitive Primitive class of this data type
+ * @param reference Reference class of this data type
+ */
+ DataType(Class> primitive, Class> reference) {
+ this.primitive = primitive;
+ this.reference = reference;
+ }
+
+ /**
+ * Returns the data type with the given primitive/reference class
+ *
+ *
+ *
+ * @param clazz Primitive/Reference class of the data type
+ * @return The data type
+ */
+ public static DataType fromClass(Class> clazz) {
+ return CLASS_MAP.get(clazz);
+ }
+
+ /**
+ * Returns the primitive class of the data type with the given reference class
+ *
+ *
+ *
+ * @param clazz Reference class of the data type
+ * @return The primitive class
+ */
+ public static Class> getPrimitive(Class> clazz) {
+ DataType type = fromClass(clazz);
+ return type == null ? clazz : type.getPrimitive();
+ }
+
+ /**
+ * Returns the reference class of the data type with the given primitive class
+ *
+ *
+ *
+ * @param clazz Primitive class of the data type
+ * @return The reference class
+ */
+ public static Class> getReference(Class> clazz) {
+ DataType type = fromClass(clazz);
+ return type == null ? clazz : type.getReference();
+ }
+
+ /**
+ * Returns the primitive class array of the given class array
+ *
+ *
+ *
+ * @param classes Given class array
+ * @return The primitive class array
+ */
+ public static Class>[] getPrimitive(Class>[] classes) {
+ int length = classes == null ? 0 : classes.length;
+ Class>[] types = new Class>[length];
+ for (int index = 0; index < length; index++) {
+ types[index] = getPrimitive(classes[index]);
+ }
+ return types;
+ }
+
+ /**
+ * Returns the reference class array of the given class array
+ *
+ *
+ *
+ * @param classes Given class array
+ * @return The reference class array
+ */
+ public static Class>[] getReference(Class>[] classes) {
+ int length = classes == null ? 0 : classes.length;
+ Class>[] types = new Class>[length];
+ for (int index = 0; index < length; index++) {
+ types[index] = getReference(classes[index]);
+ }
+ return types;
+ }
+
+ /**
+ * Returns the primitive class array of the given object array
+ *
+ *
+ *
+ * @param objects Given object array
+ * @return The primitive class array
+ */
+ public static Class>[] getPrimitive(Object[] objects) {
+ int length = objects == null ? 0 : objects.length;
+ Class>[] types = new Class>[length];
+ for (int index = 0; index < length; index++) {
+ types[index] = getPrimitive(objects[index].getClass());
+ }
+ return types;
+ }
+
+ /**
+ * Returns the reference class array of the given object array
+ *
+ *
+ *
+ * @param objects Given object array
+ * @return The reference class array
+ */
+ public static Class>[] getReference(Object[] objects) {
+ int length = objects == null ? 0 : objects.length;
+ Class>[] types = new Class>[length];
+ for (int index = 0; index < length; index++) {
+ types[index] = getReference(objects[index].getClass());
+ }
+ return types;
+ }
+
+ /**
+ * Compares two class arrays on equivalence
+ *
+ *
+ *
+ * @param primary Primary class array
+ * @param secondary Class array which is compared to the primary array
+ * @return Whether these arrays are equal or not
+ */
+ public static boolean compare(Class>[] primary, Class>[] secondary) {
+ if (primary == null || secondary == null || primary.length != secondary.length) {
+ return false;
+ }
+ for (int index = 0; index < primary.length; index++) {
+ Class> primaryClass = primary[index];
+ Class> secondaryClass = secondary[index];
+ if (primaryClass.equals(secondaryClass)
+ || primaryClass.isAssignableFrom(secondaryClass)) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns the primitive class of this data type
+ *
+ *
+ *
+ * @return The primitive class
+ */
+ public Class> getPrimitive() {
+ return primitive;
+ }
+
+ /**
+ * Returns the reference class of this data type
+ *
+ *
+ *
+ * @return The reference class
+ */
+ public Class> getReference() {
+ return reference;
+ }
+
+ /**
+ * Returns true if the given Object is a valid instance of this data type.
+ *
+ *
+ *
+ * @param obj Object to check
+ * @return true if the given Object is a valid instance of this data type
+ */
+ public boolean isInstance(Object obj) {
+ return obj != null
+ && (obj.getClass().equals(getPrimitive()) || obj.getClass().equals(getReference()));
+ }
+
+ /**
+ * Gets whether this represents a number data type.
+ *
+ *
+ *
+ * @return Whether this represents a number data type
+ */
+ public boolean isNumber() {
+ return Number.class.isAssignableFrom(reference);
+ }
+}
diff --git a/reflection-utils/src/main/java/com/pepedevs/corelib/utils/reflection/accessor/FieldAccessor.java b/reflection-utils/src/main/java/com/pepedevs/corelib/utils/reflection/accessor/FieldAccessor.java
new file mode 100644
index 0000000..9177d86
--- /dev/null
+++ b/reflection-utils/src/main/java/com/pepedevs/corelib/utils/reflection/accessor/FieldAccessor.java
@@ -0,0 +1,154 @@
+package com.pepedevs.corelib.utils.reflection.accessor;
+
+import sun.misc.Unsafe;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+public class FieldAccessor {
+
+ private final Field field;
+
+ public FieldAccessor(Field field) {
+ this.field = field;
+ field.setAccessible(true);
+ }
+
+ private static void setField(Object object, Object value, Field foundField) {
+ boolean isStatic = (foundField.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
+ if (isStatic) {
+ setStaticFieldUsingUnsafe(foundField, value);
+ } else {
+ setFieldUsingUnsafe(foundField, object, value);
+ }
+ }
+
+ private static void setStaticFieldUsingUnsafe(final Field field, final Object newValue) {
+ try {
+ field.setAccessible(true);
+ int fieldModifiersMask = field.getModifiers();
+ boolean isFinalModifierPresent =
+ (fieldModifiersMask & Modifier.FINAL) == Modifier.FINAL;
+ if (isFinalModifierPresent) {
+ AccessController.doPrivileged(
+ (PrivilegedActionNote:
+ *
+ * Every single class that implements this interface must to have an empty constructor whose will
+ * act as 'uninitialized constructor', then the method {@link
+ * Loadable#loadEntries(ConfigurationSection)} can create uninitialized instances and initialize it
+ * by using the method {@link Loadable#load(ConfigurationSection)}, that may initialize that
+ * instance correctly.
+ */
+public interface Loadable extends Validable {
+
+ /**
+ * Load the configuration of this from the given {@link ConfigurationSection}.
+ *
+ * Returns:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Returns:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Returns:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Returns:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Returns:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * Bukkit.getPluginManager().callEvent(this);
+ *
true
if right click else false
+ * @see Action
+ */
+ public static boolean isRightClick(Action action) {
+ return action != null && (action.name().contains("RIGHT_"));
+ }
+
+ /**
+ * Check for Left click
+ *
+ * true
if left click else false
+ * @see Action
+ */
+ public static boolean isLeftClick(Action action) {
+ return action != null && (action.name().contains("LEFT_"));
+ }
+
+ /**
+ * Check for Clicking Block
+ *
+ * true
if clicking block else false
+ * @see Action
+ */
+ public static boolean isClickingBlock(Action action) {
+ return action != null && (action.name().contains("_BLOCK"));
+ }
+
+ /**
+ * Check for Clicking Air
+ *
+ * true
if clicking air else false
+ * @see Action
+ */
+ public static boolean isClickingAir(Action action) {
+ return action != null && (action.name().contains("_AIR"));
+ }
+}
diff --git a/event-utils/src/main/java/com/pepedevs/corelib/events/PluginLoadEvent.java b/event-utils/src/main/java/com/pepedevs/corelib/events/PluginLoadEvent.java
new file mode 100644
index 0000000..e4dcc92
--- /dev/null
+++ b/event-utils/src/main/java/com/pepedevs/corelib/events/PluginLoadEvent.java
@@ -0,0 +1,96 @@
+package com.pepedevs.corelib.events;
+
+import com.pepedevs.corelib.utils.Pair;
+import com.pepedevs.corelib.utils.server.ServerLifePhase;
+import org.bukkit.Bukkit;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.server.PluginEnableEvent;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/** Reference to a utility class for plugin. */
+public class PluginLoadEvent {
+
+ private PluginLoadEvent() {}
+
+ /**
+ * Triggers the consumer function on the given plugin loads.
+ *
+ * true
if the gui is open, false otherwise
+ */
+ public boolean isMenuOpen(Player player) {
+ return player.getOpenInventory() != null
+ && player.getOpenInventory().getTopInventory() != null
+ && player.getOpenInventory().getTopInventory().getType() == InventoryType.ANVIL
+ && this.getOpenGuiByPlayer(player) != null;
+ }
+
+ /**
+ * Checks if the given inventory is of this menu.
+ *
+ * true
if the inventory is of this gui, false otherwise
+ */
+ public boolean isThisMenu(Inventory inventory) {
+ return inventory.getType() == InventoryType.ANVIL
+ && this.getOpenGuiByInventory(inventory) != null;
+ }
+
+ /**
+ * Initializes the {@link AnvilMenuHandler} of this.
+ *
+ *
+ *
+ * @param inventory The inventory to check
+ * @return true if the provided {@link Inventory} is an instance of this {@link ItemMenu}
+ */
+ public boolean isThisMenu(Inventory inventory) {
+ if (inventory.getType() == InventoryType.CHEST
+ && inventory.getHolder() instanceof ItemMenuHolder) {
+ return equals(((ItemMenuHolder) inventory.getHolder()).getItemMenu());
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Sets the item to the given slot in this menu.
+ *
+ *
+ *
+ *
+ * Version.1_8_R1.equalsVersion (1_8_R3) = true
+ * Version.1_9_R1.equalsVersion (1_8_R1) = false
+ *
+ *
+ *
+ * Version.1_8_R3.equalsRevision (1_9_R3) = true
+ * Version.1_8_R1.equalsRevision (1_8_R3) = false
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @return Whether the initialization of the listeners was successfully.
+ */
+ protected abstract boolean setUpListeners();
+
+ /**
+ * Saves the raw contents of any resource embedded with this plugin's .jar file assuming it can
+ * be found using {@link #getResource(String)}.
+ *
+ * Implementation example:
+ *
+ *
+ */
+public abstract class PluginDependence implements Function
+ * PluginDependence dependence = new PluginDependence("ProtocolLib") {
+ * {@code @Override}
+ * public Boolean apply (org.bukkit.plugin.Plugin plugin) {
+ * if (plugin == null) {
+ * ConsoleUtil.sendPluginMessage(ChatColor.RED, "ProtocolLib couldn't be found!", MyPlugin.getInstance());
+ * } else {
+ * return true; // returning true will not disable the plugin.
+ * }
+ * return null; // returning null or false will disable the plugin automatically
+ * }
+ * };
+ *