aboutsummaryrefslogtreecommitdiffstats
path: root/bukkit/src
diff options
context:
space:
mode:
Diffstat (limited to 'bukkit/src')
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminItemsCommandHandler.java2
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java2
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/PaginatedQMenu.java2
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/CommandTaskType.java3
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingTaskType.java3
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoBossesKillingTaskType.java3
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoMobsKillingTaskType.java3
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/MythicMobsKillingTaskType.java3
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/StringUtils.java128
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/TaskUtils.java75
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/lang3/CharSequenceUtils.java55
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/lang3/StringUtils.java81
12 files changed, 220 insertions, 140 deletions
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminItemsCommandHandler.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminItemsCommandHandler.java
index 6eb6b36f..32558673 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminItemsCommandHandler.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminItemsCommandHandler.java
@@ -3,7 +3,7 @@ package com.leonardobishop.quests.bukkit.command;
import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
import com.leonardobishop.quests.bukkit.item.ParsedQuestItem;
import com.leonardobishop.quests.bukkit.item.QuestItem;
-import com.leonardobishop.quests.bukkit.util.StringUtils;
+import com.leonardobishop.quests.bukkit.util.lang3.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java
index 5a16d283..8f867673 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java
@@ -11,7 +11,7 @@ import com.leonardobishop.quests.bukkit.item.QuestItemRegistry;
import com.leonardobishop.quests.bukkit.item.SlimefunQuestItem;
import com.leonardobishop.quests.bukkit.menu.itemstack.QItemStack;
import com.leonardobishop.quests.bukkit.menu.itemstack.QItemStackRegistry;
-import com.leonardobishop.quests.bukkit.util.StringUtils;
+import com.leonardobishop.quests.bukkit.util.lang3.StringUtils;
import com.leonardobishop.quests.bukkit.util.chat.Chat;
import com.leonardobishop.quests.common.config.ConfigProblem;
import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/PaginatedQMenu.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/PaginatedQMenu.java
index 1947c738..39adf59a 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/PaginatedQMenu.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/PaginatedQMenu.java
@@ -11,7 +11,7 @@ import com.leonardobishop.quests.bukkit.menu.element.PageNextMenuElement;
import com.leonardobishop.quests.bukkit.menu.element.PagePrevMenuElement;
import com.leonardobishop.quests.bukkit.menu.element.SpacerMenuElement;
import com.leonardobishop.quests.bukkit.util.MenuUtils;
-import com.leonardobishop.quests.bukkit.util.StringUtils;
+import com.leonardobishop.quests.bukkit.util.lang3.StringUtils;
import com.leonardobishop.quests.common.player.QPlayer;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/CommandTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/CommandTaskType.java
index 62162833..0c49559a 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/CommandTaskType.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/CommandTaskType.java
@@ -23,6 +23,7 @@ public final class CommandTaskType extends BukkitTaskType {
super.addConfigValidator(TaskUtils.useRequiredConfigValidator(this, "command"));
super.addConfigValidator(TaskUtils.useBooleanConfigValidator(this, "ignore-case"));
+ super.addConfigValidator(TaskUtils.useEnumConfigValidator(this, TaskUtils.StringMatchMode.class, "command-match-mode"));
}
@EventHandler(priority = EventPriority.MONITOR)
@@ -51,7 +52,7 @@ public final class CommandTaskType extends BukkitTaskType {
boolean ignoreCase = TaskUtils.getConfigBoolean(task, "ignore-case");
- if (!TaskUtils.matchString(this, pendingTask, message, player.getUniqueId(), "command", "commands", false, ignoreCase)) {
+ if (!TaskUtils.matchString(this, pendingTask, message,player.getUniqueId(), "command", "commands", false, "command-match-mode", ignoreCase)) {
super.debug("Continuing...", quest.getId(), task.getId(), player.getUniqueId());
continue;
}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingTaskType.java
index e0d85ba1..28f78554 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingTaskType.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingTaskType.java
@@ -39,6 +39,7 @@ public final class MobkillingTaskType extends BukkitTaskType {
super.addConfigValidator(TaskUtils.useItemStackConfigValidator(this, "item"));
super.addConfigValidator(TaskUtils.useIntegerConfigValidator(this, "data"));
super.addConfigValidator(TaskUtils.useBooleanConfigValidator(this, "exact-match"));
+ super.addConfigValidator(TaskUtils.useEnumConfigValidator(this, TaskUtils.StringMatchMode.class, "name-match-mode"));
if (plugin.getQuestsConfig().getBoolean("options.mobkilling-use-wildstacker-hook", true)) {
try {
@@ -132,7 +133,7 @@ public final class MobkillingTaskType extends BukkitTaskType {
continue;
}
- if (!TaskUtils.matchString(this, pendingTask, customName, player.getUniqueId(), "name", "names", true, false)) {
+ if (!TaskUtils.matchString(this, pendingTask, customName, player.getUniqueId(), "name", "names", true, "name-match-mode", false)) {
super.debug("Continuing...", quest.getId(), task.getId(), player.getUniqueId());
continue;
}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoBossesKillingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoBossesKillingTaskType.java
index a420c853..1f833e5a 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoBossesKillingTaskType.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoBossesKillingTaskType.java
@@ -26,6 +26,7 @@ public final class EcoBossesKillingTaskType extends BukkitTaskType {
super.addConfigValidator(TaskUtils.useRequiredConfigValidator(this, "id", "ids"));
super.addConfigValidator(TaskUtils.useRequiredConfigValidator(this, "amount"));
super.addConfigValidator(TaskUtils.useIntegerConfigValidator(this, "amount"));
+ super.addConfigValidator(TaskUtils.useEnumConfigValidator(this, TaskUtils.StringMatchMode.class, "id-match-mode"));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@@ -50,7 +51,7 @@ public final class EcoBossesKillingTaskType extends BukkitTaskType {
super.debug("Player killed EcoBosses boss '" + ecoBoss.getDisplayName() + "' (id = " + ecoBoss.getID() + ")", quest.getId(), task.getId(), player.getUniqueId());
- if (!TaskUtils.matchString(this, pendingTask, ecoBoss.getID(), player.getUniqueId(), "id", "ids", false, false)) {
+ if (!TaskUtils.matchString(this, pendingTask, ecoBoss.getID(), player.getUniqueId(), "id", "ids", false, "id-match-mode", false)) {
super.debug("Continuing...", quest.getId(), task.getId(), player.getUniqueId());
continue;
}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoMobsKillingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoMobsKillingTaskType.java
index c10aca6b..e9aa5030 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoMobsKillingTaskType.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EcoMobsKillingTaskType.java
@@ -26,6 +26,7 @@ public final class EcoMobsKillingTaskType extends BukkitTaskType {
super.addConfigValidator(TaskUtils.useRequiredConfigValidator(this, "id", "ids"));
super.addConfigValidator(TaskUtils.useRequiredConfigValidator(this, "amount"));
super.addConfigValidator(TaskUtils.useIntegerConfigValidator(this, "amount"));
+ super.addConfigValidator(TaskUtils.useEnumConfigValidator(this, TaskUtils.StringMatchMode.class, "id-match-mode"));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@@ -50,7 +51,7 @@ public final class EcoMobsKillingTaskType extends BukkitTaskType {
super.debug("Player killed EcoMobs mob '" + mob.getDisplayName() + "' (id = " + mob.getMob().getID() + ")", quest.getId(), task.getId(), player.getUniqueId());
- if (!TaskUtils.matchString(this, pendingTask, ecoMob.getID(), player.getUniqueId(), "id", "ids", false, false)) {
+ if (!TaskUtils.matchString(this, pendingTask, ecoMob.getID(), player.getUniqueId(), "id", "ids", false, "id-match-mode", false)) {
super.debug("Continuing...", quest.getId(), task.getId(), player.getUniqueId());
continue;
}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/MythicMobsKillingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/MythicMobsKillingTaskType.java
index eaf64292..f481a5ac 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/MythicMobsKillingTaskType.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/MythicMobsKillingTaskType.java
@@ -31,6 +31,7 @@ public final class MythicMobsKillingTaskType extends BukkitTaskType {
super.addConfigValidator(TaskUtils.useIntegerConfigValidator(this, "amount"));
super.addConfigValidator(TaskUtils.useIntegerConfigValidator(this, "level"));
super.addConfigValidator(TaskUtils.useIntegerConfigValidator(this, "min-level"));
+ super.addConfigValidator(TaskUtils.useEnumConfigValidator(this, TaskUtils.StringMatchMode.class, "name-match-mode"));
// MythicMobs 4
try {
@@ -103,7 +104,7 @@ public final class MythicMobsKillingTaskType extends BukkitTaskType {
super.debug("Player killed mythic mob '" + mobName + "' (level = " + level + ")", quest.getId(), task.getId(), player.getUniqueId());
- if (!TaskUtils.matchString(this, pendingTask, mobName, player.getUniqueId(), "name", "names", false, false)) {
+ if (!TaskUtils.matchString(this, pendingTask, mobName, player.getUniqueId(), "name", "names", false, "name-match-mode", false)) {
super.debug("Continuing...", quest.getId(), task.getId(), player.getUniqueId());
continue;
}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/StringUtils.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/StringUtils.java
deleted file mode 100644
index ea44fef6..00000000
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/StringUtils.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.leonardobishop.quests.bukkit.util;
-
-import java.lang.reflect.Array;
-
-/*
- * From Apache Commons Lang
- * https://github.com/apache/commons-lang/blob/master/LICENSE.txt
- */
-public class StringUtils {
- public static boolean isEmpty(final CharSequence cs) {
- return cs == null || cs.length() == 0;
- }
-
- public static boolean isAlphanumeric(final CharSequence cs) {
- if (isEmpty(cs)) {
- return false;
- }
-
- final int sz = cs.length();
- for (int i = 0; i < sz; i++) {
- final char c = cs.charAt(i);
- if (!(Character.isLetterOrDigit(c) || c == '_' || c == '-')) {
- return false;
- }
- }
-
- return true;
- }
-
- public static boolean isNumeric(final CharSequence cs) {
- if (isEmpty(cs)) {
- return false;
- }
- final int sz = cs.length();
- for (int i = 0; i < sz; i++) {
- if (!Character.isDigit(cs.charAt(i))) {
- return false;
- }
- }
- return true;
- }
-
- public static boolean equals(final CharSequence cs1, final CharSequence cs2, final boolean ignoreCase) {
- if (cs1 == cs2) {
- return true;
- }
- if (cs1 == null || cs2 == null) {
- return false;
- }
- if (cs1.length() != cs2.length()) {
- return false;
- }
- return regionMatches(cs1, ignoreCase, 0, cs2, 0, cs1.length());
- }
-
- static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
- final CharSequence substring, final int start, final int length) {
- if (cs instanceof String && substring instanceof String) {
- return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
- }
- int index1 = thisStart;
- int index2 = start;
- int tmpLen = length;
-
- // Extract these first so we detect NPEs the same as the java.lang.String version
- final int srcLen = cs.length() - thisStart;
- final int otherLen = substring.length() - start;
-
- // Check for invalid parameters
- if (thisStart < 0 || start < 0 || length < 0) {
- return false;
- }
-
- // Check that the regions are long enough
- if (srcLen < length || otherLen < length) {
- return false;
- }
-
- while (tmpLen-- > 0) {
- final char c1 = cs.charAt(index1++);
- final char c2 = substring.charAt(index2++);
-
- if (c1 == c2) {
- continue;
- }
-
- if (!ignoreCase) {
- return false;
- }
-
- // The real same check as in String.regionMatches():
- final char u1 = Character.toUpperCase(c1);
- final char u2 = Character.toUpperCase(c2);
- if (u1 != u2 && Character.toLowerCase(u1) != Character.toLowerCase(u2)) {
- return false;
- }
- }
-
- return true;
- }
-
- public static boolean equalsAny(final CharSequence string, final CharSequence[] searchStrings, final boolean ignoreCase) {
- if (isNotEmpty(searchStrings)) {
- for (final CharSequence next : searchStrings) {
- if (equals(string, next, ignoreCase)) {
- return true;
- }
- }
- }
- return false;
- }
-
- public static <T> boolean isNotEmpty(final T[] array) {
- return !isEmpty(array);
- }
-
- public static boolean isEmpty(final Object[] array) {
- return isArrayEmpty(array);
- }
-
- private static boolean isArrayEmpty(final Object array) {
- return getLength(array) == 0;
- }
-
- public static int getLength(final Object array) {
- return array != null ? Array.getLength(array) : 0;
- }
-}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/TaskUtils.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/TaskUtils.java
index ec966576..dfcbdcef 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/TaskUtils.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/TaskUtils.java
@@ -8,6 +8,7 @@ import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
import com.leonardobishop.quests.bukkit.util.chat.Chat;
import com.leonardobishop.quests.bukkit.util.constraint.TaskConstraint;
import com.leonardobishop.quests.bukkit.util.constraint.TaskConstraintSet;
+import com.leonardobishop.quests.bukkit.util.lang3.StringUtils;
import com.leonardobishop.quests.common.config.ConfigProblem;
import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
import com.leonardobishop.quests.common.player.QPlayer;
@@ -483,7 +484,30 @@ public class TaskUtils {
return false;
}
- public static boolean matchString(@NotNull BukkitTaskType type, @NotNull PendingTask pendingTask, @Nullable String string, @NotNull UUID player, final @NotNull String stringKey, final @NotNull String listKey, boolean legacyColor, boolean ignoreCase) {
+ public enum StringMatchMode {
+ EQUALS {
+ @Override
+ public boolean matches(@NotNull String str1, @NotNull String str2, boolean ignoreCase) {
+ return StringUtils.equals(str1, str2, ignoreCase);
+ }
+ },
+ STARTS_WITH {
+ @Override
+ public boolean matches(@NotNull String str, @NotNull String prefix, boolean ignoreCase) {
+ return StringUtils.startsWith(str, prefix, ignoreCase);
+ }
+ },
+ ENDS_WITH {
+ @Override
+ public boolean matches(@NotNull String str, @NotNull String suffix, boolean ignoreCase) {
+ return StringUtils.endsWith(str, suffix, ignoreCase);
+ }
+ };
+
+ public abstract boolean matches(@NotNull String str1, @NotNull String str2, boolean ignoreCase);
+ }
+
+ public static boolean matchString(@NotNull BukkitTaskType type, @NotNull PendingTask pendingTask, @Nullable String string, @NotNull UUID player, @NotNull String stringKey, @NotNull String listKey, boolean legacyColor, @NotNull String matchModeKey, boolean ignoreCase) {
Task task = pendingTask.task;
List<String> checkNames = TaskUtils.getConfigStringList(task, task.getConfigValues().containsKey(stringKey) ? stringKey : listKey);
@@ -501,10 +525,21 @@ public class TaskUtils {
string = Chat.legacyColor(string);
}
+ StringMatchMode matchMode;
+
+ String matchModeString = (String) task.getConfigValue(matchModeKey);
+ if (matchModeString != null) {
+ matchMode = StringMatchMode.valueOf(matchModeString);
+ } else {
+ matchMode = StringMatchMode.EQUALS;
+ }
+
+ type.debug("Utilising " + matchMode + " mode for checking", pendingTask.quest.getId(), task.getId(), player);
+
for (String name : checkNames) {
- type.debug("Checking against name " + name, pendingTask.quest.getId(), task.getId(), player);
+ type.debug("Checking against name " + string, pendingTask.quest.getId(), task.getId(), player);
- if (StringUtils.equals(name, string, ignoreCase)) {
+ if (matchMode.matches(string, name, ignoreCase)) {
type.debug("Name match", pendingTask.quest.getId(), task.getId(), player);
return true;
} else {
@@ -535,10 +570,21 @@ public class TaskUtils {
}
}
+ StringMatchMode matchMode;
+
+ String matchModeString = (String) task.getConfigValue(matchModeKey);
+ if (matchModeString != null) {
+ matchMode = StringMatchMode.valueOf(matchModeString);
+ } else {
+ matchMode = StringMatchMode.EQUALS;
+ }
+
+ type.debug("Utilising " + matchMode + " mode for checking", pendingTask.quest.getId(), task.getId(), player);
+
for (String name : checkNames) {
type.debug("Checking against name " + name, pendingTask.quest.getId(), task.getId(), player);
- if (StringUtils.equalsAny(name, strings, ignoreCase)) {
+ if (matchMode.matchesAny(strings, name, ignoreCase)) {
type.debug("Name match", pendingTask.quest.getId(), task.getId(), player);
return true;
} else {
@@ -1004,6 +1050,27 @@ public class TaskUtils {
/**
* Returns a config validator which checks if at least one value in the given
+ * paths is present in the enum.
+ *
+ * Should be used for small enums only as it lists possible values in config
+ * problem extended description.
+ *
+ * @param clazz the enum class
+ * @param paths a list of valid paths for task
+ * @return config validator
+ */
+ public static <T extends Enum<T>> TaskType.ConfigValidator useEnumConfigValidator(TaskType type, Class<T> clazz, String... paths) {
+ List<String> acceptedValues = new ArrayList<>();
+ T[] constants = clazz.getEnumConstants();
+ for (T constant : constants) {
+ String acceptedValue = constant.name();
+ acceptedValues.add(acceptedValue);
+ }
+ return useAcceptedValuesConfigValidator(type, acceptedValues, paths);
+ }
+
+ /**
+ * Returns a config validator which checks if at least one value in the given
* paths is a value in the list of accepted values.
*
* @param acceptedValues a list of accepted values
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/lang3/CharSequenceUtils.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/lang3/CharSequenceUtils.java
new file mode 100644
index 00000000..5728e2de
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/lang3/CharSequenceUtils.java
@@ -0,0 +1,55 @@
+package com.leonardobishop.quests.bukkit.util.lang3;
+
+/*
+ * From Apache Commons Lang
+ * https://github.com/apache/commons-lang/blob/master/LICENSE.txt
+ */
+public final class CharSequenceUtils {
+
+ // https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java#L294-L338
+ public static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, // package -> public
+ final CharSequence substring, final int start, final int length) {
+ if (cs instanceof String && substring instanceof String) {
+ return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
+ }
+ int index1 = thisStart;
+ int index2 = start;
+ int tmpLen = length;
+
+ // Extract these first so we detect NPEs the same as the java.lang.String version
+ final int srcLen = cs.length() - thisStart;
+ final int otherLen = substring.length() - start;
+
+ // Check for invalid parameters
+ if (thisStart < 0 || start < 0 || length < 0) {
+ return false;
+ }
+
+ // Check that the regions are long enough
+ if (srcLen < length || otherLen < length) {
+ return false;
+ }
+
+ while (tmpLen-- > 0) {
+ final char c1 = cs.charAt(index1++);
+ final char c2 = substring.charAt(index2++);
+
+ if (c1 == c2) {
+ continue;
+ }
+
+ if (!ignoreCase) {
+ return false;
+ }
+
+ // The real same check as in String.regionMatches():
+ final char u1 = Character.toUpperCase(c1);
+ final char u2 = Character.toUpperCase(c2);
+ if (u1 != u2 && Character.toLowerCase(u1) != Character.toLowerCase(u2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/lang3/StringUtils.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/lang3/StringUtils.java
new file mode 100644
index 00000000..5bf93e56
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/lang3/StringUtils.java
@@ -0,0 +1,81 @@
+package com.leonardobishop.quests.bukkit.util.lang3;
+
+/*
+ * From Apache Commons Lang
+ * https://github.com/apache/commons-lang/blob/master/LICENSE.txt
+ */
+@SuppressWarnings({"BooleanMethodIsAlwaysInverted", "SizeReplaceableByIsEmpty"})
+public final class StringUtils {
+
+ // https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java#L3602-L3604
+ public static boolean isEmpty(final CharSequence cs) {
+ return cs == null || cs.length() == 0;
+ }
+
+ // https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java#L3361-L3372
+ public static boolean isAlphanumeric(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isLetterOrDigit(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java#L3774-L3785
+ public static boolean isNumeric(final CharSequence cs) {
+ if (isEmpty(cs)) {
+ return false;
+ }
+ final int sz = cs.length();
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isDigit(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java#L1899-L1910
+ public static boolean equals(final CharSequence cs1, final CharSequence cs2, final boolean ignoreCase) { // make ignore case a parameter
+ if (cs1 == cs2) {
+ return true;
+ }
+ if (cs1 == null || cs2 == null) {
+ return false;
+ }
+ if (cs1.length() != cs2.length()) {
+ return false;
+ }
+ return CharSequenceUtils.regionMatches(cs1, ignoreCase, 0, cs2, 0, cs1.length()); // make ignore case a parameter
+ }
+
+ // https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java#L7990-L8000
+ public static boolean startsWith(final CharSequence str, final CharSequence prefix, final boolean ignoreCase) { // private -> public
+ if (str == null || prefix == null) {
+ return str == prefix;
+ }
+ // Get length once instead of twice in the unlikely case that it changes.
+ final int preLen = prefix.length();
+ if (preLen > str.length()) {
+ return false;
+ }
+ return CharSequenceUtils.regionMatches(str, ignoreCase, 0, prefix, 0, preLen);
+ }
+
+ // https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java#L1702-L1711
+ public static boolean endsWith(final CharSequence str, final CharSequence suffix, final boolean ignoreCase) { // private -> public
+ if (str == null || suffix == null) {
+ return str == suffix;
+ }
+ if (suffix.length() > str.length()) {
+ return false;
+ }
+ final int strOffset = str.length() - suffix.length();
+ return CharSequenceUtils.regionMatches(str, ignoreCase, strOffset, suffix, 0, suffix.length());
+ }
+}