diff --git a/src/main/java/com/minersstudios/mscore/MSCore.java b/src/main/java/com/minersstudios/mscore/MSCore.java index b19ddc97..4a65c55f 100644 --- a/src/main/java/com/minersstudios/mscore/MSCore.java +++ b/src/main/java/com/minersstudios/mscore/MSCore.java @@ -1,5 +1,6 @@ package com.minersstudios.mscore; +import com.minersstudios.mscore.annotation.Namespace; import com.minersstudios.mscore.plugin.MSLogger; import com.minersstudios.mscore.plugin.MSPlugin; import com.minersstudios.mscore.utility.CoreProtectUtils; @@ -22,7 +23,8 @@ public final class MSCore extends MSPlugin { private static MSCore singleton; - public static final String NAMESPACE = "mscore"; + /** The namespace of the plugin */ + public static final @Namespace String NAMESPACE = "mscore"; // private static final String KEY_CONNECTION_THROTTLE = "messages.kick.connection-throttle"; diff --git a/src/main/java/com/minersstudios/mscore/annotation/Key.java b/src/main/java/com/minersstudios/mscore/annotation/Key.java new file mode 100644 index 00000000..adcd9f99 --- /dev/null +++ b/src/main/java/com/minersstudios/mscore/annotation/Key.java @@ -0,0 +1,97 @@ +package com.minersstudios.mscore.annotation; + +import com.minersstudios.mscore.throwable.InvalidRegexException; +import org.intellij.lang.annotations.RegExp; +import org.intellij.lang.annotations.Subst; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.regex.Pattern; + +import static java.lang.annotation.ElementType.*; + +/** + * Annotation used to mark the key. + *
+ * The key must match the {@link #REGEX regex} pattern. + * + * @see Key.Validator + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ + FIELD, + LOCAL_VARIABLE, + METHOD, + PARAMETER +}) +@org.intellij.lang.annotations.Pattern(Key.REGEX) +public @interface Key { + /** The regex pattern that a valid key must match */ + @RegExp String REGEX = "[a-z0-9/._-]*"; + + /** The compiled Pattern of the {@link #REGEX regex} string */ + Pattern PATTERN = Pattern.compile(REGEX); + + /** + * Validator class for the {@link Key} annotation to check whether the + * key matches the {@link #REGEX regex} + * + * @see #matches(String) + * @see #validate(String) + */ + final class Validator { + + @Contract(" -> fail") + private Validator() throws AssertionError { + throw new AssertionError("Utility class"); + } + + /** + * Checks whether the key matches the {@link #REGEX regex} + * + * @param key The key + * @return Whether the key matches the {@link #REGEX regex} + */ + public static boolean matches(final @Subst("key") @Key @Nullable String key) { + if (key == null) { + return true; + } + + for(int i = 0; i < key.length(); ++i) { + final char character = key.charAt(i); + + switch (character) { + case '_', '-', '.', '/' -> {} + default -> { + if (character < 'a' || character > 'z') { + if (character < '0' || character > '9') { + return false; + } + } + } + } + } + + return true; + } + + /** + * Validates the key + * + * @param key The key + * @throws InvalidRegexException If the key does not match the + * {@link #REGEX regex} + * @see #matches(String) + */ + public static void validate(final @Subst("key") @Key @Nullable String key) throws InvalidRegexException { + if (!matches(key)) { + throw new InvalidRegexException("Key must match regex: " + REGEX); + } + } + } +} diff --git a/src/main/java/com/minersstudios/mscore/annotation/Namespace.java b/src/main/java/com/minersstudios/mscore/annotation/Namespace.java new file mode 100644 index 00000000..79f5738c --- /dev/null +++ b/src/main/java/com/minersstudios/mscore/annotation/Namespace.java @@ -0,0 +1,97 @@ +package com.minersstudios.mscore.annotation; + +import com.minersstudios.mscore.throwable.InvalidRegexException; +import org.intellij.lang.annotations.RegExp; +import org.intellij.lang.annotations.Subst; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.regex.Pattern; + +import static java.lang.annotation.ElementType.*; + +/** + * Annotation used to mark the namespace. + *
+ * The namespace must match the {@link #REGEX regex} pattern. + * + * @see Namespace.Validator + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ + FIELD, + LOCAL_VARIABLE, + METHOD, + PARAMETER +}) +@org.intellij.lang.annotations.Pattern(Namespace.REGEX) +public @interface Namespace { + /** The regex pattern that a valid namespace must match */ + @RegExp String REGEX = "[a-z0-9._-]*"; + + /** The compiled Pattern of the {@link #REGEX regex} string */ + Pattern PATTERN = Pattern.compile(REGEX); + + /** + * Validator class for the {@link Namespace} annotation to check whether the + * namespace matches the {@link #REGEX regex} + * + * @see #matches(String) + * @see #validate(String) + */ + final class Validator { + + @Contract(" -> fail") + private Validator() throws AssertionError { + throw new AssertionError("Utility class"); + } + + /** + * Checks whether the namespace matches the {@link #REGEX regex} + * + * @param namespace The namespace + * @return Whether the namespace matches the {@link #REGEX regex} + */ + public static boolean matches(final @Subst("namespace") @Namespace @Nullable String namespace) { + if (namespace == null) { + return false; + } + + for(int i = 0; i < namespace.length(); ++i) { + final char character = namespace.charAt(i); + + switch (character) { + case '_', '-', '.' -> {} + default -> { + if (character < 'a' || character > 'z') { + if (character < '0' || character > '9') { + return false; + } + } + } + } + } + + return true; + } + + /** + * Validates the namespace + * + * @param namespace The namespace + * @throws InvalidRegexException If the namespace does not match the + * {@link #REGEX regex} + * @see #matches(String) + */ + public static void validate(final @Subst("namespace") @Namespace @Nullable String namespace) throws InvalidRegexException { + if (!matches(namespace)) { + throw new InvalidRegexException("Namespace must match regex: " + REGEX); + } + } + } +} diff --git a/src/main/java/com/minersstudios/mscore/annotation/ResourceKey.java b/src/main/java/com/minersstudios/mscore/annotation/ResourceKey.java new file mode 100644 index 00000000..ef65accc --- /dev/null +++ b/src/main/java/com/minersstudios/mscore/annotation/ResourceKey.java @@ -0,0 +1,97 @@ +package com.minersstudios.mscore.annotation; + +import com.minersstudios.mscore.throwable.InvalidRegexException; +import org.bukkit.NamespacedKey; +import org.intellij.lang.annotations.RegExp; +import org.intellij.lang.annotations.Subst; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.regex.Pattern; + +import static java.lang.annotation.ElementType.*; + +/** + * Annotation used to mark the {@link NamespacedKey namespaced-key}. + *
+ * The namespaced-key must match the {@link #REGEX regex} pattern. + * + * @see ResourceKey.Validator + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ + FIELD, + LOCAL_VARIABLE, + METHOD, + PARAMETER +}) +@org.intellij.lang.annotations.Pattern(ResourceKey.REGEX) +public @interface ResourceKey { + /** The regex pattern that a valid namespaced-key must match */ + @RegExp String REGEX = "(" + Namespace.REGEX + ")(:(" + Key.REGEX + "))?"; + + /** The compiled Pattern of the {@link #REGEX regex} string */ + Pattern PATTERN = Pattern.compile(REGEX); + + /** + * Validator class for the {@link ResourceKey} annotation to check whether + * the namespaced-key matches the {@link #REGEX regex} + * + * @see #matches(String) + * @see #validate(String) + */ + final class Validator { + + @Contract(" -> fail") + private Validator() throws AssertionError { + throw new AssertionError("Utility class"); + } + + /** + * Checks whether the namespaced-key matches the {@link #REGEX regex} + * + * @param namespacedKey The namespaced-key + * @return Whether the namespaced-key matches the {@link #REGEX regex} + */ + public static boolean matches(final @Subst("namespace:key") @ResourceKey @Nullable String namespacedKey) { + if (namespacedKey == null) { + return true; + } + + final int colonIndex = namespacedKey.indexOf(':'); + + @Subst("namespace") String namespace = ""; + @Subst("key") String key = namespacedKey; + + if (colonIndex >= 0) { + key = namespacedKey.substring(colonIndex + 1); + + if (colonIndex >= 1) { + namespace = namespacedKey.substring(0, colonIndex); + } + } + + return Namespace.Validator.matches(namespace) + && Key.Validator.matches(key); + } + + /** + * Validates the namespaced-key + * + * @param namespacedKey The namespaced-key + * @throws InvalidRegexException If the namespaced-key does not match + * the {@link #REGEX regex} + * @see #matches(String) + */ + public static void validate(final @Subst("namespace:key") @ResourceKey @Nullable String namespacedKey) throws InvalidRegexException { + if (!matches(namespacedKey)) { + throw new InvalidRegexException("NamespacedKey must match regex: " + REGEX); + } + } + } +} diff --git a/src/main/java/com/minersstudios/mscore/inventory/recipe/choice/CustomChoice.java b/src/main/java/com/minersstudios/mscore/inventory/recipe/choice/CustomChoice.java index ca9dcc1a..422e78fd 100644 --- a/src/main/java/com/minersstudios/mscore/inventory/recipe/choice/CustomChoice.java +++ b/src/main/java/com/minersstudios/mscore/inventory/recipe/choice/CustomChoice.java @@ -1,7 +1,9 @@ package com.minersstudios.mscore.inventory.recipe.choice; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.minersstudios.mscore.utility.ChatUtils; +import com.minersstudios.mscore.annotation.ResourceKey; +import com.minersstudios.mscore.throwable.InvalidRegexException; import com.minersstudios.mscustoms.utility.MSCustomUtils; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -14,8 +16,10 @@ import org.jetbrains.annotations.Unmodifiable; import javax.annotation.concurrent.Immutable; -import java.util.*; -import java.util.regex.Pattern; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; /** * Represents a choice that will be valid only one of the stacks is exactly @@ -29,54 +33,47 @@ public final class CustomChoice implements RecipeChoice { private Object2ObjectMap choiceMap; - private static final String REGEX = "[a-z0-9/._-]+:[a-z0-9/._-]+"; - private static final Pattern PATTERN = Pattern.compile(REGEX); - /** * Constructs a new custom choice with the specified namespaced keys * * @param namespacedKey The namespaced key to use for the choice - * @throws IllegalArgumentException If the namespaced key is invalid + * @throws InvalidRegexException If the namespaced key is invalid */ - public CustomChoice(final @NotNull String namespacedKey) throws IllegalArgumentException { + public CustomChoice(final @ResourceKey @NotNull String namespacedKey) throws InvalidRegexException { this(Collections.singletonList(namespacedKey)); } /** * Constructs a new custom choice with the specified namespaced keys * - * @param namespacedKeys The namespaced keys to use for the choice - * @throws IllegalArgumentException If the namespaced keys are empty, or if - * any of the namespaced keys are invalid + * @param first The first namespaced key to use for the choice + * @param rest The rest of the namespaced keys to use for the choice + * @throws InvalidRegexException If any of the namespaced keys are invalid */ - public CustomChoice(final String @NotNull ... namespacedKeys) throws IllegalArgumentException { - this(Arrays.asList(namespacedKeys)); + public CustomChoice( + final @ResourceKey @NotNull String first, + final String @NotNull ... rest + ) throws InvalidRegexException { + this(Lists.asList(first, rest)); } /** * Constructs a new custom choice with the specified namespaced keys * * @param namespacedKeys The namespaced keys to use for the choice - * @throws IllegalArgumentException If the namespaced keys are empty, or if - * any of the namespaced keys are null or + * @throws IllegalArgumentException If the namespaced keys are empty + * @throws InvalidRegexException If any of the namespaced keys are * invalid */ - public CustomChoice(final @NotNull Collection namespacedKeys) throws IllegalArgumentException { + public CustomChoice(final @NotNull Collection namespacedKeys) throws IllegalArgumentException, InvalidRegexException { if (namespacedKeys.isEmpty()) { throw new IllegalArgumentException("Must have at least one namespacedKey"); } this.choiceMap = new Object2ObjectOpenHashMap<>(namespacedKeys.size()); - for (final var namespacedKey : namespacedKeys) { - if (ChatUtils.isBlank(namespacedKey)) { - throw new IllegalArgumentException("Cannot have a blank namespacedKey"); - } - - if (!PATTERN.matcher(namespacedKey).matches()) { - throw new IllegalArgumentException("Invalid namespacedKey : " + namespacedKey); - } - + for (final @ResourceKey var namespacedKey : namespacedKeys) { + ResourceKey.Validator.validate(namespacedKey); MSCustomUtils.getItemStack(namespacedKey) .ifPresent(itemStack -> this.choiceMap.put(namespacedKey, itemStack)); } @@ -101,7 +98,7 @@ public CustomChoice(final @NotNull Collection namespacedKeys) throws Ill * @param namespacedKey The namespaced key to get the item stack for * @return A clone of the item stack for the specified namespaced key */ - public @NotNull ItemStack getItemStack(final @NotNull String namespacedKey) { + public @NotNull ItemStack getItemStack(final @ResourceKey @NotNull String namespacedKey) { return this.choiceMap.get(namespacedKey).clone(); } @@ -169,7 +166,7 @@ public boolean equals(final @Nullable Object obj) { * @param namespacedKey The namespaced key to test * @return True if the namespaced key is present in the choices */ - public boolean test(final @NotNull String namespacedKey) { + public boolean test(final @ResourceKey @NotNull String namespacedKey) { return this.choiceMap.containsKey(namespacedKey); } diff --git a/src/main/java/com/minersstudios/mscore/status/Status.java b/src/main/java/com/minersstudios/mscore/status/Status.java index 38baae21..d603b91d 100644 --- a/src/main/java/com/minersstudios/mscore/status/Status.java +++ b/src/main/java/com/minersstudios/mscore/status/Status.java @@ -1,5 +1,6 @@ package com.minersstudios.mscore.status; +import com.minersstudios.mscore.throwable.InvalidRegexException; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -106,12 +107,12 @@ U apply( * * @param key Key of the status * @return A new status with the specified key and low priority - * @throws IllegalArgumentException If the key does not match the - * {@link StatusKey#REGEX regex} + * @throws InvalidRegexException If the key does not match the + * {@link StatusKey#REGEX regex} * @see #success(String, Priority) */ @Contract("_ -> new") - static @NotNull SuccessStatus successLow(final @StatusKey @NotNull String key) throws IllegalArgumentException { + static @NotNull SuccessStatus successLow(final @StatusKey @NotNull String key) throws InvalidRegexException { return success(key, Priority.LOW); } @@ -123,9 +124,9 @@ U apply( * @param failureStatus The status that will be set if this status is not * successful, or null if there is no failure status * @return A new status with the given key and low priority - * @throws IllegalArgumentException If the key does not match the + * @throws InvalidRegexException If the key does not match the * {@link StatusKey#REGEX regex} - * @throws IllegalStateException If the failure status has a different + * @throws IllegalArgumentException If the failure status has a different * priority than the specified priority * @see #success(String, Priority, FailureStatus) */ @@ -133,7 +134,7 @@ U apply( static @NotNull SuccessStatus successLow( final @StatusKey @NotNull String key, final @Nullable FailureStatus failureStatus - ) throws IllegalArgumentException, IllegalStateException { + ) throws InvalidRegexException, IllegalArgumentException { return success(key, Priority.LOW, failureStatus); } @@ -142,12 +143,12 @@ U apply( * * @param key Key of the status * @return A new status with the specified key and high priority - * @throws IllegalArgumentException If the key does not match the - * {@link StatusKey#REGEX regex} + * @throws InvalidRegexException If the key does not match the + * {@link StatusKey#REGEX regex} * @see #success(String, Priority) */ @Contract("_ -> new") - static @NotNull SuccessStatus successHigh(final @StatusKey @NotNull String key) throws IllegalArgumentException { + static @NotNull SuccessStatus successHigh(final @StatusKey @NotNull String key) throws InvalidRegexException { return success(key, Priority.HIGH); } @@ -160,9 +161,9 @@ U apply( * successful, or null if there is no failure status * @return A new status with the specified key, failure status, and high * priority - * @throws IllegalArgumentException If the key does not match the + * @throws InvalidRegexException If the key does not match the * {@link StatusKey#REGEX regex} - * @throws IllegalStateException If the failure status has a different + * @throws IllegalArgumentException If the failure status has a different * priority than the specified priority * @see #success(String, Priority, FailureStatus) */ @@ -170,7 +171,7 @@ U apply( static @NotNull SuccessStatus successHigh( final @StatusKey @NotNull String key, final @Nullable FailureStatus failureStatus - ) throws IllegalArgumentException, IllegalStateException { + ) throws InvalidRegexException, IllegalArgumentException { return success(key, Priority.HIGH, failureStatus); } @@ -180,15 +181,15 @@ U apply( * @param key Key of the status * @param priority Priority of the status * @return A new status with the specified key and priority - * @throws IllegalArgumentException If the key does not match the - * {@link StatusKey#REGEX regex} + * @throws InvalidRegexException If the key does not match the + * {@link StatusKey#REGEX regex} * @see StatusKey.Validator#validate(String) */ @Contract("_, _ -> new") static @NotNull SuccessStatus success( final @StatusKey @NotNull String key, final @NotNull Priority priority - ) throws IllegalArgumentException { + ) throws InvalidRegexException { validate(key); return new SuccessStatus(key, priority, null); @@ -202,9 +203,9 @@ U apply( * @param failureStatus The status that will be set if this status is not * successful, or null if there is no failure status * @return A new status with the specified key, priority, and failure status - * @throws IllegalArgumentException If the key does not match the + * @throws InvalidRegexException If the key does not match the * {@link StatusKey#REGEX regex} - * @throws IllegalStateException If the failure status has a different + * @throws IllegalArgumentException If the failure status has a different * priority than the specified priority * @see StatusKey.Validator#validate(String) */ @@ -213,7 +214,7 @@ U apply( final @StatusKey @NotNull String key, final @NotNull Priority priority, final @Nullable FailureStatus failureStatus - ) throws IllegalArgumentException, IllegalStateException { + ) throws InvalidRegexException, IllegalArgumentException { validate(key); if ( @@ -233,12 +234,12 @@ U apply( * * @param key Key of the failure status * @return A new failure status with the specified key and low priority - * @throws IllegalArgumentException If the key does not match the - * {@link StatusKey#REGEX regex} + * @throws InvalidRegexException If the key does not match the + * {@link StatusKey#REGEX regex} * @see #failure(String, Priority) */ @Contract("_ -> new") - static @NotNull FailureStatus failureLow(final @StatusKey @NotNull String key) throws IllegalArgumentException { + static @NotNull FailureStatus failureLow(final @StatusKey @NotNull String key) throws InvalidRegexException { return failure(key, Priority.LOW); } @@ -247,12 +248,12 @@ U apply( * * @param key Key of the failure status * @return A new failure status with the specified key and medium priority - * @throws IllegalArgumentException If the key does not match the - * {@link StatusKey#REGEX regex} + * @throws InvalidRegexException If the key does not match the + * {@link StatusKey#REGEX regex} * @see #failure(String, Priority) */ @Contract("_ -> new") - static @NotNull FailureStatus failureHigh(final @StatusKey @NotNull String key) throws IllegalArgumentException { + static @NotNull FailureStatus failureHigh(final @StatusKey @NotNull String key) throws InvalidRegexException { return failure(key, Priority.HIGH); } @@ -262,15 +263,15 @@ U apply( * @param key Key of the failure status * @param priority Priority of the failure status * @return A new failure status with the specified key and priority - * @throws IllegalArgumentException If the key does not match the - * {@link StatusKey#REGEX regex} + * @throws InvalidRegexException If the key does not match the + * {@link StatusKey#REGEX regex} * @see StatusKey.Validator#validate(String) */ @Contract("_, _ -> new") static @NotNull FailureStatus failure( final @StatusKey @NotNull String key, final @NotNull Priority priority - ) throws IllegalArgumentException { + ) throws InvalidRegexException { validate(key); return new FailureStatus(key, priority); diff --git a/src/main/java/com/minersstudios/mscore/status/StatusKey.java b/src/main/java/com/minersstudios/mscore/status/StatusKey.java index 8cc55aec..61ad70f2 100644 --- a/src/main/java/com/minersstudios/mscore/status/StatusKey.java +++ b/src/main/java/com/minersstudios/mscore/status/StatusKey.java @@ -1,10 +1,15 @@ package com.minersstudios.mscore.status; +import com.minersstudios.mscore.throwable.InvalidRegexException; import org.intellij.lang.annotations.RegExp; +import org.intellij.lang.annotations.Subst; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.regex.Pattern; import static java.lang.annotation.ElementType.*; @@ -52,21 +57,49 @@ private Validator() throws AssertionError { * @param key Key of the status * @return Whether the key matches the {@link #REGEX regex} */ - public static boolean matches(final @StatusKey @NotNull String key) { - return PATTERN.matcher(key).matches(); + public static boolean matches(final @Subst("STATUS_KEY") @StatusKey @Nullable String key) { + if (key == null) { + return false; + } + + final int length = key.length(); + + if (length == 0) { + return false; + } + + final char first = key.charAt(0); + + if (first < 'A' || first > 'Z') { + return false; + } + + for (int i = 1; i < length; ++i) { + final char character = key.charAt(i); + + if ( + (character < 'A' || character > 'Z') + && (character < '0' || character > '9') + && character != '_' + ) { + return false; + } + } + + return true; } /** * Validates the key of the status * * @param key Key of the status - * @throws IllegalArgumentException If the key does not match the - * {@link #REGEX regex} + * @throws InvalidRegexException If the key does not match the + * {@link #REGEX regex} * @see #matches(String) */ - public static void validate(final @StatusKey @NotNull String key) throws IllegalArgumentException { + public static void validate(final @Subst("STATUS_KEY") @StatusKey @Nullable String key) throws InvalidRegexException { if (!matches(key)) { - throw new IllegalArgumentException("Key must match regex: " + REGEX); + throw new InvalidRegexException("Status key must match regex: " + REGEX); } } } diff --git a/src/main/java/com/minersstudios/mscore/throwable/InvalidRegexException.java b/src/main/java/com/minersstudios/mscore/throwable/InvalidRegexException.java new file mode 100644 index 00000000..0fb83382 --- /dev/null +++ b/src/main/java/com/minersstudios/mscore/throwable/InvalidRegexException.java @@ -0,0 +1,49 @@ +package com.minersstudios.mscore.throwable; + +import org.jetbrains.annotations.Nullable; + +/** + * Signals that when checking a string against a regex pattern, the string was + * invalid + */ +public class InvalidRegexException extends RuntimeException { + + /** + * Constructs a new exception with no detail message + */ + public InvalidRegexException() { + super(); + } + + /** + * Constructs a new exception with the specified detail message + * + * @param message The detail message + */ + public InvalidRegexException(final @Nullable String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause + * + * @param message The detail message + * @param cause The cause + */ + public InvalidRegexException( + final @Nullable String message, + final @Nullable Throwable cause + ) { + super(message, cause); + } + + /** + * Constructs a new exception with the specified cause and a detail message + * of cause + * + * @param cause The cause + */ + public InvalidRegexException(final @Nullable Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/minersstudios/mscore/utility/ChatUtils.java b/src/main/java/com/minersstudios/mscore/utility/ChatUtils.java index 761b0b76..a720252d 100644 --- a/src/main/java/com/minersstudios/mscore/utility/ChatUtils.java +++ b/src/main/java/com/minersstudios/mscore/utility/ChatUtils.java @@ -14,7 +14,6 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; -import java.util.regex.Pattern; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.format.NamedTextColor.WHITE; @@ -61,8 +60,6 @@ public final class ChatUtils { STRIKETHROUGH.withState(false), UNDERLINED.withState(false) ); - public static final String KEY_REGEX = "[a-z0-9./_-]+"; - public static final Pattern KEY_PATTERN = Pattern.compile(KEY_REGEX); private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build(); private static final PlainTextComponentSerializer PLAIN_SERIALIZER = PlainTextComponentSerializer.plainText(); @@ -286,7 +283,7 @@ private ChatUtils() throws AssertionError { * @param rest Other strings * @return List of components */ - public static @NotNull List convertStringsToComponents( + public static @NotNull List convertStringsToComponents( final @NotNull String first, final String @NotNull ... rest ) { @@ -347,25 +344,4 @@ public static boolean isBlank(final @Nullable String string) { public static boolean isNotBlank(final @Nullable String string) { return !isBlank(string); } - - /** - * @param string String to be checked - * @return True if string matches {@link #KEY_REGEX} - */ - @Contract("null -> false") - public static boolean matchesKey(final @Nullable String string) { - return isNotBlank(string) - && KEY_PATTERN.matcher(string).matches(); - } - - /** - * @param string String to be checked - * @throws IllegalArgumentException If string doesn't match {@link #KEY_REGEX} - */ - @Contract("null -> fail") - public static void validateKey(final @Nullable String string) { - if (!matchesKey(string)) { - throw new IllegalArgumentException("Key '" + string + "' does not match regex " + ChatUtils.KEY_REGEX); - } - } } diff --git a/src/main/java/com/minersstudios/mscore/utility/SharedConstants.java b/src/main/java/com/minersstudios/mscore/utility/SharedConstants.java index d49160df..4f8cb7c5 100644 --- a/src/main/java/com/minersstudios/mscore/utility/SharedConstants.java +++ b/src/main/java/com/minersstudios/mscore/utility/SharedConstants.java @@ -1,5 +1,6 @@ package com.minersstudios.mscore.utility; +import com.minersstudios.mscore.annotation.Namespace; import net.kyori.adventure.translation.Translator; import org.jetbrains.annotations.Contract; @@ -26,10 +27,10 @@ public final class SharedConstants { public static final String CONSOLE_NICKNAME = "$Console"; public static final String INVISIBLE_ITEM_FRAME_TAG = "invisibleItemFrame"; public static final String HIDE_TAGS_TEAM_NAME = "hide_tags"; - public static final String WHOMINE_NAMESPACE = "whomine"; - public static final String MSBLOCK_NAMESPACE = "msblock"; - public static final String MSITEMS_NAMESPACE = "msitems"; - public static final String MSDECOR_NAMESPACE = "msdecor"; + public static final @Namespace String WHOMINE_NAMESPACE = "whomine"; + public static final @Namespace String MSBLOCK_NAMESPACE = "msblock"; + public static final @Namespace String MSITEMS_NAMESPACE = "msitems"; + public static final @Namespace String MSDECOR_NAMESPACE = "msdecor"; public static final Locale DEFAULT_LOCALE = Objects.requireNonNull(Translator.parseLocale(DEFAULT_LANGUAGE_CODE), "Not found default locale for " + DEFAULT_LANGUAGE_CODE); public static final int SIT_RANGE = 9; public static final int FINAL_DESTROY_STAGE = 9; diff --git a/src/main/java/com/minersstudios/mscustoms/MSCustoms.java b/src/main/java/com/minersstudios/mscustoms/MSCustoms.java index 07263292..43d435d8 100644 --- a/src/main/java/com/minersstudios/mscustoms/MSCustoms.java +++ b/src/main/java/com/minersstudios/mscustoms/MSCustoms.java @@ -1,5 +1,6 @@ package com.minersstudios.mscustoms; +import com.minersstudios.mscore.annotation.Namespace; import com.minersstudios.mscore.plugin.MSPlugin; import com.minersstudios.mscore.status.FailureStatus; import com.minersstudios.mscore.status.SuccessStatus; @@ -32,6 +33,9 @@ public class MSCustoms extends MSPlugin { private Cache cache; private Config config; + /** The namespace of the plugin */ + public static final @Namespace String NAMESPACE = "mscustoms"; + // public static final FailureStatus FAILED_LOAD_BLOCKS = failureLow("FAILED_LOAD_BLOCKS"); public static final FailureStatus FAILED_LOAD_RENAMEABLES = failureLow("FAILED_LOAD_RENAMEABLES"); @@ -46,9 +50,6 @@ public class MSCustoms extends MSPlugin { public static final SuccessStatus LOADED_RENAMEABLES = successLow("LOADED_RENAMEABLES", FAILED_LOAD_RENAMEABLES); // - /** The namespace of the plugin */ - public static final String NAMESPACE = "mscustoms"; - static { initClass(SoundGroup.class); initClass(SoundAdapter.class); diff --git a/src/main/java/com/minersstudios/mscustoms/custom/block/file/adapter/NamespacedKeyAdapter.java b/src/main/java/com/minersstudios/mscustoms/custom/block/file/adapter/NamespacedKeyAdapter.java index f292ebbf..2b995276 100644 --- a/src/main/java/com/minersstudios/mscustoms/custom/block/file/adapter/NamespacedKeyAdapter.java +++ b/src/main/java/com/minersstudios/mscustoms/custom/block/file/adapter/NamespacedKeyAdapter.java @@ -1,8 +1,10 @@ package com.minersstudios.mscustoms.custom.block.file.adapter; import com.google.gson.*; +import com.minersstudios.mscore.annotation.ResourceKey; import org.bukkit.NamespacedKey; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Type; @@ -19,7 +21,7 @@ public class NamespacedKeyAdapter implements JsonSerializer, JsonDeserializer { private final String namespace; - public NamespacedKeyAdapter(final String namespace) { + public NamespacedKeyAdapter(final @ResourceKey @NotNull String namespace) { this.namespace = namespace; } diff --git a/src/main/java/com/minersstudios/mscustoms/custom/decor/CustomDecorDataImpl.java b/src/main/java/com/minersstudios/mscustoms/custom/decor/CustomDecorDataImpl.java index 23c4ce36..636d7271 100644 --- a/src/main/java/com/minersstudios/mscustoms/custom/decor/CustomDecorDataImpl.java +++ b/src/main/java/com/minersstudios/mscustoms/custom/decor/CustomDecorDataImpl.java @@ -1,9 +1,11 @@ package com.minersstudios.mscustoms.custom.decor; +import com.minersstudios.mscore.annotation.Key; import com.minersstudios.mscore.inventory.recipe.entry.RecipeEntry; import com.minersstudios.mscore.location.MSBoundingBox; import com.minersstudios.mscore.location.MSPosition; import com.minersstudios.mscore.location.MSVector; +import com.minersstudios.mscore.throwable.InvalidRegexException; import com.minersstudios.mscore.utility.*; import com.minersstudios.mscustoms.custom.decor.action.DecorBreakAction; import com.minersstudios.mscustoms.custom.decor.action.DecorClickAction; @@ -39,6 +41,7 @@ import org.bukkit.inventory.meta.LeatherArmorMeta; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; +import org.intellij.lang.annotations.Subst; import org.jetbrains.annotations.*; import javax.annotation.concurrent.Immutable; @@ -1374,8 +1377,8 @@ public Builder() { return this.namespacedKey; } - public @NotNull Builder key(final @NotNull String key) throws IllegalArgumentException { - ChatUtils.validateKey(key); + public @NotNull Builder key(final @Key @NotNull String key) throws InvalidRegexException { + Key.Validator.validate(key); this.namespacedKey = new NamespacedKey(SharedConstants.MSDECOR_NAMESPACE, key); @@ -1919,10 +1922,10 @@ protected final class Type implements CustomDecorData.Type { public Type( final @NotNull Builder builder, - final @NotNull String key, + final @Key @NotNull String key, final @NotNull ItemStack itemStack - ) { - ChatUtils.validateKey(key); + ) throws InvalidRegexException { + Key.Validator.validate(key); final String typedKey = builder.namespacedKey.getKey() + ".type." + key; this.namespacedKey = new NamespacedKey(SharedConstants.MSDECOR_NAMESPACE, typedKey); @@ -1972,8 +1975,10 @@ public boolean equals(final @Nullable Object obj) { @SuppressWarnings("unchecked") @Override public @NotNull D buildData() { + final @Subst("key") String key = this.namespacedKey.getKey(); + return (D) CustomDecorDataImpl.this.builder() - .key(this.namespacedKey.getKey()) + .key(key) .itemStack(this.itemStack) .preBuild() .build(); diff --git a/src/main/java/com/minersstudios/mscustoms/custom/decor/CustomDecorType.java b/src/main/java/com/minersstudios/mscustoms/custom/decor/CustomDecorType.java index f8d9a639..78ac1560 100644 --- a/src/main/java/com/minersstudios/mscustoms/custom/decor/CustomDecorType.java +++ b/src/main/java/com/minersstudios/mscustoms/custom/decor/CustomDecorType.java @@ -1,5 +1,6 @@ package com.minersstudios.mscustoms.custom.decor; +import com.minersstudios.mscore.annotation.Key; import com.minersstudios.mscore.plugin.MSPlugin; import com.minersstudios.mscore.status.StatusWatcher; import com.minersstudios.mscore.utility.ChatUtils; @@ -187,7 +188,7 @@ public enum CustomDecorType { public static final String TYPE_TAG_NAME = "type"; public static final NamespacedKey TYPE_NAMESPACED_KEY = new NamespacedKey(SharedConstants.MSDECOR_NAMESPACE, TYPE_TAG_NAME); - public static final String TYPED_KEY_REGEX = "(" + ChatUtils.KEY_REGEX + ")\\.type\\.(" + ChatUtils.KEY_REGEX + ")"; + public static final String TYPED_KEY_REGEX = "(" + Key.REGEX + ")\\.type\\.(" + Key.REGEX + ")"; public static final Pattern TYPED_KEY_PATTERN = Pattern.compile(TYPED_KEY_REGEX); private static final CustomDecorType[] VALUES = values(); diff --git a/src/main/java/com/minersstudios/mscustoms/custom/item/CustomItemImpl.java b/src/main/java/com/minersstudios/mscustoms/custom/item/CustomItemImpl.java index e40951c7..fa1af0d7 100644 --- a/src/main/java/com/minersstudios/mscustoms/custom/item/CustomItemImpl.java +++ b/src/main/java/com/minersstudios/mscustoms/custom/item/CustomItemImpl.java @@ -1,7 +1,8 @@ package com.minersstudios.mscustoms.custom.item; +import com.minersstudios.mscore.annotation.Key; import com.minersstudios.mscore.inventory.recipe.entry.RecipeEntry; -import com.minersstudios.mscore.utility.ChatUtils; +import com.minersstudios.mscore.throwable.InvalidRegexException; import com.minersstudios.mscore.utility.SharedConstants; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.bukkit.Keyed; @@ -41,13 +42,13 @@ public abstract class CustomItemImpl implements CustomItem, Cloneable { * @param itemStack The {@link ItemStack} representing the custom item * @throws IllegalArgumentException If the key format is invalid or the item * stack type is air - * @see ChatUtils#matchesKey(String) + * @see Key.Validator#matches(String) */ protected CustomItemImpl( - final @NotNull String key, + final @Key @NotNull String key, final @NotNull ItemStack itemStack - ) throws IllegalArgumentException { - ChatUtils.validateKey(key); + ) throws InvalidRegexException, IllegalArgumentException { + Key.Validator.validate(key); if (itemStack.isEmpty()) { throw new IllegalArgumentException("Item type cannot be empty! Check " + key); diff --git a/src/main/java/com/minersstudios/mscustoms/utility/MSBlockUtils.java b/src/main/java/com/minersstudios/mscustoms/utility/MSBlockUtils.java index 96bab4ee..3254aa47 100644 --- a/src/main/java/com/minersstudios/mscustoms/utility/MSBlockUtils.java +++ b/src/main/java/com/minersstudios/mscustoms/utility/MSBlockUtils.java @@ -1,5 +1,6 @@ package com.minersstudios.mscustoms.utility; +import com.minersstudios.mscore.annotation.Key; import com.minersstudios.mscore.utility.ChatUtils; import com.minersstudios.mscore.utility.SharedConstants; import com.minersstudios.mscustoms.custom.block.CustomBlockData; @@ -16,7 +17,7 @@ * Utility class for {@link CustomBlockData} */ public final class MSBlockUtils { - public static final String NAMESPACED_KEY_REGEX = '(' + SharedConstants.MSBLOCK_NAMESPACE + "):(" + ChatUtils.KEY_REGEX + ")"; + public static final String NAMESPACED_KEY_REGEX = '(' + SharedConstants.MSBLOCK_NAMESPACE + "):(" + Key.REGEX + ")"; public static final Pattern NAMESPACED_KEY_PATTERN = Pattern.compile(NAMESPACED_KEY_REGEX); @Contract(" -> fail") diff --git a/src/main/java/com/minersstudios/mscustoms/utility/MSCustomUtils.java b/src/main/java/com/minersstudios/mscustoms/utility/MSCustomUtils.java index eaefee9e..eb49df35 100644 --- a/src/main/java/com/minersstudios/mscustoms/utility/MSCustomUtils.java +++ b/src/main/java/com/minersstudios/mscustoms/utility/MSCustomUtils.java @@ -1,5 +1,8 @@ package com.minersstudios.mscustoms.utility; +import com.minersstudios.mscore.annotation.Key; +import com.minersstudios.mscore.annotation.Namespace; +import com.minersstudios.mscore.annotation.ResourceKey; import com.minersstudios.mscore.utility.ChatUtils; import com.minersstudios.mscustoms.custom.block.CustomBlockData; import com.minersstudios.mscustoms.custom.block.CustomBlockRegistry; @@ -46,7 +49,7 @@ private MSCustomUtils() throws AssertionError { * or empty optional if not found * @see #getItemStack(String, String) */ - public static @NotNull Optional getItemStack(final @Nullable String namespacedKeyStr) { + public static @NotNull Optional getItemStack(final @ResourceKey @Nullable String namespacedKeyStr) { if ( ChatUtils.isBlank(namespacedKeyStr) || !namespacedKeyStr.contains(":") @@ -93,8 +96,8 @@ private MSCustomUtils() throws AssertionError { * @see MSItemUtils#getItemStack(String) */ public static @NotNull Optional getItemStack( - final @Nullable String namespace, - final @Nullable String key + final @Namespace @Nullable String namespace, + final @Key @Nullable String key ) { return namespace == null || key == null ? Optional.empty() @@ -147,8 +150,11 @@ private MSCustomUtils() throws AssertionError { * or {@link CustomItem} or empty optional if not found * @see #getCustom(String, String) */ - public static @NotNull Optional getCustom(final @Nullable String namespacedKeyStr) { - if (ChatUtils.isBlank(namespacedKeyStr)) { + public static @NotNull Optional getCustom(final @ResourceKey @Nullable String namespacedKeyStr) { + if ( + ChatUtils.isBlank(namespacedKeyStr) + || !namespacedKeyStr.contains(":") + ) { return Optional.empty(); } diff --git a/src/main/java/com/minersstudios/mscustoms/utility/MSDecorUtils.java b/src/main/java/com/minersstudios/mscustoms/utility/MSDecorUtils.java index 9884491f..3988ca42 100644 --- a/src/main/java/com/minersstudios/mscustoms/utility/MSDecorUtils.java +++ b/src/main/java/com/minersstudios/mscustoms/utility/MSDecorUtils.java @@ -1,5 +1,6 @@ package com.minersstudios.mscustoms.utility; +import com.minersstudios.mscore.annotation.Key; import com.minersstudios.mscore.location.MSBoundingBox; import com.minersstudios.mscore.location.MSPosition; import com.minersstudios.mscore.utility.ChatUtils; @@ -24,7 +25,7 @@ * Utility class for {@link CustomDecorData} */ public final class MSDecorUtils { - public static final String NAMESPACED_KEY_REGEX = '(' + SharedConstants.MSDECOR_NAMESPACE + "):(" + ChatUtils.KEY_REGEX + ")"; + public static final String NAMESPACED_KEY_REGEX = '(' + SharedConstants.MSDECOR_NAMESPACE + "):(" + Key.REGEX + ")"; public static final Pattern NAMESPACED_KEY_PATTERN = Pattern.compile(NAMESPACED_KEY_REGEX); @Contract(" -> fail") diff --git a/src/main/java/com/minersstudios/mscustoms/utility/MSItemUtils.java b/src/main/java/com/minersstudios/mscustoms/utility/MSItemUtils.java index 43d41641..5af33ffa 100644 --- a/src/main/java/com/minersstudios/mscustoms/utility/MSItemUtils.java +++ b/src/main/java/com/minersstudios/mscustoms/utility/MSItemUtils.java @@ -1,5 +1,6 @@ package com.minersstudios.mscustoms.utility; +import com.minersstudios.mscore.annotation.Key; import com.minersstudios.mscore.utility.ChatUtils; import com.minersstudios.mscore.utility.SharedConstants; import com.minersstudios.mscustoms.custom.item.CustomItem; @@ -16,7 +17,7 @@ * Utility class for {@link CustomItem} */ public final class MSItemUtils { - public static final String NAMESPACED_KEY_REGEX = '(' + SharedConstants.MSITEMS_NAMESPACE + "):(" + ChatUtils.KEY_REGEX + ")"; + public static final String NAMESPACED_KEY_REGEX = '(' + SharedConstants.MSITEMS_NAMESPACE + "):(" + Key.REGEX + ")"; public static final Pattern NAMESPACED_KEY_PATTERN = Pattern.compile(NAMESPACED_KEY_REGEX); @Contract(" -> fail") diff --git a/src/main/java/com/minersstudios/msessentials/MSEssentials.java b/src/main/java/com/minersstudios/msessentials/MSEssentials.java index 5880358d..c45b0145 100644 --- a/src/main/java/com/minersstudios/msessentials/MSEssentials.java +++ b/src/main/java/com/minersstudios/msessentials/MSEssentials.java @@ -1,5 +1,6 @@ package com.minersstudios.msessentials; +import com.minersstudios.mscore.annotation.Namespace; import com.minersstudios.mscore.plugin.MSPlugin; import com.minersstudios.mscore.status.FailureStatus; import com.minersstudios.mscore.status.SuccessStatus; @@ -41,7 +42,8 @@ public final class MSEssentials extends MSPlugin { private Scoreboard scoreboardHideTags; private Team scoreboardHideTagsTeam; - public static final String NAMESPACE = "msessentials"; + /** The namespace of the plugin */ + public static final @Namespace String NAMESPACE = "msessentials"; // public static final FailureStatus FAILED_LOAD_RESOURCE_PACKS = failureLow("FAILED_LOAD_RESOURCE_PACKS"); diff --git a/src/test/java/com/minersstudios/Main.java b/src/test/java/com/minersstudios/Main.java index 729b49e7..80a3061d 100644 --- a/src/test/java/com/minersstudios/Main.java +++ b/src/test/java/com/minersstudios/Main.java @@ -1,8 +1,24 @@ package com.minersstudios; +import com.minersstudios.mscore.annotation.Key; + public final class Main { public static void main(final String[] args) { System.out.println("Hello, PackmanDude!"); + + final String statusKey = "status_key"; + + System.out.println(Key.Validator.matches(statusKey)); + + long startTime = System.currentTimeMillis(); + + for (int i = 0; i < 1000000; ++i) { + Key.Validator.matches(statusKey); + } + + long endTime = System.currentTimeMillis(); + + System.out.println("Elapsed time: " + (endTime - startTime) + "ms"); } }