Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(key): #773 Expose methods to check if a Key can be parsed #774

Merged
merged 1 commit into from Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
80 changes: 79 additions & 1 deletion key/src/main/java/net/kyori/adventure/key/Key.java
Expand Up @@ -29,6 +29,7 @@
import net.kyori.examination.ExaminableProperty;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* An identifying object used to fetch and/or store unique objects.
Expand Down Expand Up @@ -62,6 +63,12 @@ public interface Key extends Comparable<Key>, Examinable, Namespaced, Keyed {
* @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 +85,7 @@ public interface Key extends Comparable<Key>, Examinable, Namespaced, Keyed {
* @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 +149,77 @@ public interface Key extends Comparable<Key>, Examinable, Namespaced, Keyed {
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
*/
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
*/
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;
}

/**
zml2008 marked this conversation as resolved.
Show resolved Hide resolved
* 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"));
}
}