Skip to content

Commit

Permalink
feat(key): #773 Expose methods to check if a Key can be parsed
Browse files Browse the repository at this point in the history
  • Loading branch information
kashike committed Jun 2, 2022
1 parent 15a2939 commit b24f4fc
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 35 deletions.
83 changes: 82 additions & 1 deletion key/src/main/java/net/kyori/adventure/key/Key.java
Expand Up @@ -29,6 +29,8 @@
import net.kyori.examination.ExaminableProperty;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;

/**
* An identifying object used to fetch and/or store unique objects.
Expand Down Expand Up @@ -62,6 +64,12 @@ public interface Key extends Comparable<Key>, Examinable, Namespaced {
* @since 4.0.0
*/
String MINECRAFT_NAMESPACE = "minecraft";
/**
* The default namespace and value separator.
*
* @since 4.12.0
*/
char DEFAULT_SEPARATOR = ':';

/**
* Creates a key.
Expand All @@ -78,7 +86,7 @@ public interface Key extends Comparable<Key>, Examinable, Namespaced {
* @since 4.0.0
*/
static @NotNull Key key(final @NotNull @Pattern("(" + KeyImpl.NAMESPACE_PATTERN + ":)?" + KeyImpl.VALUE_PATTERN) String string) {
return key(string, ':');
return key(string, DEFAULT_SEPARATOR);
}

/**
Expand Down Expand Up @@ -142,6 +150,79 @@ public interface Key extends Comparable<Key>, Examinable, Namespaced {
return KeyImpl.COMPARATOR;
}

/**
* Checks if {@code string} can be parsed into a {@link Key}.
*
* @param string the input string
* @return {@code true} if {@code string} can be parsed into a {@link Key}, {@code false} otherwise
* @since 4.12.0
*/
static boolean parseable(final @Nullable String string) {
if (string == null) {
return false;
}
final int index = string.indexOf(DEFAULT_SEPARATOR);
final String namespace = index >= 1 ? string.substring(0, index) : MINECRAFT_NAMESPACE;
final String value = index >= 0 ? string.substring(index + 1) : string;
return parseableNamespace(namespace) && parseableValue(value);
}

/**
* Checks if {@code value} is a valid namespace.
*
* @param namespace the string to check
* @return {@code true} if {@code value} is a valid namespace, {@code false} otherwise
* @since 4.12.0
*/
@VisibleForTesting
static boolean parseableNamespace(final @NotNull String namespace) {
for (int i = 0, length = namespace.length(); i < length; i++) {
if (!allowedInNamespace(namespace.charAt(i))) {
return false;
}
}
return true;
}

/**
* Checks if {@code value} is a valid value.
*
* @param value the string to check
* @return {@code true} if {@code value} is a valid value, {@code false} otherwise
* @since 4.12.0
*/
@VisibleForTesting
static boolean parseableValue(final @NotNull String value) {
for (int i = 0, length = value.length(); i < length; i++) {
if (!allowedInValue(value.charAt(i))) {
return false;
}
}
return true;
}

/**
* Checks if {@code value} is a valid character in a namespace.
*
* @param character the character to check
* @return {@code true} if {@code value} is a valid character in a namespace, {@code false} otherwise
* @since 4.12.0
*/
static boolean allowedInNamespace(final char character) {
return KeyImpl.allowedInNamespace(character);
}

/**
* Checks if {@code value} is a valid character in a value.
*
* @param character the character to check
* @return {@code true} if {@code value} is a valid character in a value, {@code false} otherwise
* @since 4.12.0
*/
static boolean allowedInValue(final char character) {
return KeyImpl.allowedInValue(character);
}

/**
* Gets the namespace.
*
Expand Down
33 changes: 6 additions & 27 deletions key/src/main/java/net/kyori/adventure/key/KeyImpl.java
Expand Up @@ -28,7 +28,6 @@
import java.util.stream.Stream;
import net.kyori.examination.ExaminableProperty;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.VisibleForTesting;

import static java.util.Objects.requireNonNull;

Expand All @@ -42,38 +41,18 @@ final class KeyImpl implements Key {
private final String value;

KeyImpl(final @NotNull String namespace, final @NotNull String value) {
if (!namespaceValid(namespace)) throw new InvalidKeyException(namespace, value, String.format("Non [a-z0-9_.-] character in namespace of Key[%s]", asString(namespace, value)));
if (!valueValid(value)) throw new InvalidKeyException(namespace, value, String.format("Non [a-z0-9/._-] character in value of Key[%s]", asString(namespace, value)));
if (!Key.parseableNamespace(namespace)) throw new InvalidKeyException(namespace, value, String.format("Non [a-z0-9_.-] character in namespace of Key[%s]", asString(namespace, value)));
if (!Key.parseableValue(value)) throw new InvalidKeyException(namespace, value, String.format("Non [a-z0-9/._-] character in value of Key[%s]", asString(namespace, value)));
this.namespace = requireNonNull(namespace, "namespace");
this.value = requireNonNull(value, "value");
}

@VisibleForTesting
static boolean namespaceValid(final @NotNull String namespace) {
for (int i = 0, length = namespace.length(); i < length; i++) {
if (!validNamespaceChar(namespace.charAt(i))) {
return false;
}
}
return true;
static boolean allowedInNamespace(final char character) {
return character == '_' || character == '-' || (character >= 'a' && character <= 'z') || (character >= '0' && character <= '9') || character == '.';
}

@VisibleForTesting
static boolean valueValid(final @NotNull String value) {
for (int i = 0, length = value.length(); i < length; i++) {
if (!validValueChar(value.charAt(i))) {
return false;
}
}
return true;
}

private static boolean validNamespaceChar(final int value) {
return value == '_' || value == '-' || (value >= 'a' && value <= 'z') || (value >= '0' && value <= '9') || value == '.';
}

private static boolean validValueChar(final int value) {
return value == '_' || value == '-' || (value >= 'a' && value <= 'z') || (value >= '0' && value <= '9') || value == '/' || value == '.';
static boolean allowedInValue(final char character) {
return character == '_' || character == '-' || (character >= 'a' && character <= 'z') || (character >= '0' && character <= '9') || character == '.' || character == '/';
}

@Override
Expand Down
20 changes: 13 additions & 7 deletions key/src/test/java/net/kyori/adventure/key/KeyTest.java
Expand Up @@ -90,15 +90,21 @@ void testCompare() {
}

@Test
void testNamespaceValid() {
assertTrue(KeyImpl.namespaceValid(Key.MINECRAFT_NAMESPACE));
assertTrue(KeyImpl.namespaceValid("realms"));
assertFalse(KeyImpl.namespaceValid("some/path"));
void testParseable() {
assertTrue(Key.parseable("minecraft:empty"));
assertFalse(Key.parseable("minecraft:Empty"));
}

@Test
void testValueValid() {
assertTrue(KeyImpl.valueValid("empty"));
assertTrue(KeyImpl.valueValid("some/path"));
void testParseableNamespace() {
assertTrue(Key.parseableNamespace(Key.MINECRAFT_NAMESPACE));
assertTrue(Key.parseableNamespace("realms"));
assertFalse(Key.parseableNamespace("some/path"));
}

@Test
void testParseableValue() {
assertTrue(Key.parseableValue("empty"));
assertTrue(Key.parseableValue("some/path"));
}
}

0 comments on commit b24f4fc

Please sign in to comment.