Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 25 additions & 15 deletions key/src/main/java/net/kyori/adventure/key/Key.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,30 @@ public interface Key extends Comparable<Key>, Examinable, Namespaced, Keyed {
* @throws InvalidKeyException if the namespace or value contains an invalid character
* @since 4.0.0
*/
@SuppressWarnings("PatternValidation") // impossible to validate since the character is variable
static @NotNull Key key(final @NotNull String string, final char character) {
Objects.requireNonNull(string, "string");
final int index = string.indexOf(character);
final String namespace = index >= 1 ? string.substring(0, index) : MINECRAFT_NAMESPACE;
return key(string, character, Key.MINECRAFT_NAMESPACE);
}

/**
* Creates a key.
*
* <p>This will parse {@code string} as a key, using {@code character} as a separator between the namespace and the value.</p>
*
* <p>The namespace is optional. If you do not provide one (for example, if you provide {@code player} or {@code character + "player"}
* as the string) then {@code fallbackNamespace} will be used as a namespace and {@code string} will be used as the value,
* removing the provided separator character if necessary.</p>
*
* @param string the string
* @param separator the character that separates the namespace from the value
* @param fallbackNamespace the namespace used, if there is none provided in {@code string}
* @return the key
* @throws InvalidKeyException if the namespace or value contains an invalid character
* @since 4.24.0
*/
@SuppressWarnings("PatternValidation") // impossible to validate since the character is variable
static @NotNull Key key(final @NotNull String string, final char separator, @KeyPattern.Namespace final @NotNull String fallbackNamespace) {
final int index = string.indexOf(separator);
final String namespace = index >= 1 ? string.substring(0, index) : fallbackNamespace;
final String value = index >= 0 ? string.substring(index + 1) : string;
return key(namespace, value);
}
Expand Down Expand Up @@ -159,13 +178,7 @@ public interface Key extends Comparable<Key>, Examinable, Namespaced, Keyed {
* @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);
return KeyFormat.minecraft().parseable(string);
}

/**
Expand Down Expand Up @@ -282,10 +295,7 @@ static boolean allowedInValue(final char character) {
* @since 4.15.0
*/
default @NotNull String asMinimalString() {
if (this.namespace().equals(MINECRAFT_NAMESPACE)) {
return this.value();
}
return this.asString();
return KeyFormat.minecraft().asMinimalString(this);
}

@Override
Expand Down
285 changes: 285 additions & 0 deletions key/src/main/java/net/kyori/adventure/key/KeyFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2025 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.adventure.key;

import java.util.stream.Stream;
import net.kyori.examination.Examinable;
import net.kyori.examination.ExaminableProperty;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static java.util.Objects.requireNonNull;

/**
* A factory to create {@link Key}s using configured defaults.
*
* <p>The configuration options include:</p>
* <dl>
* <dt>namespace</dt>
* <dd>The namespace to be used as fallback, when no other namespace is specified</dd>
* <dt>separator</dt>
* <dd>The character separating namespace and value</dd>
* </dl>
*
* <p>Example usage of this class:</p>
* <pre>{@code
* KeyFormat format = keyFormat("adventure", ':');
* Key key = format.key("translations");
* format.asString(key); // -> "adventure:translations"
* }</pre>
*
* @see Key#key(String)
* @since 4.24.0
*/
public interface KeyFormat extends Namespaced, Examinable {
/**
* Gets the default minecraft key format instance.
*
* <p>This uses {@link Key#MINECRAFT_NAMESPACE} and {@link Key#DEFAULT_SEPARATOR}.</p>
*
* @return the instance
* @since 4.24.0
*/
static @NotNull KeyFormat minecraft() {
return KeyFormatImpl.MINECRAFT_INSTANCE;
}

/**
* Creates a key format with a fallback {@code namespace} from a {@link Namespaced} instance.
*
* @param namespaced the namespaced instance to get the namespace from
* @return the key format
* @throws IllegalArgumentException if the namespace contains an invalid character
* @see #namespace(String)
* @since 4.24.0
*/
static KeyFormat namespace(final @NotNull Namespaced namespaced) {
return namespace(requireNonNull(namespaced, "namespaced").namespace());
}

/**
* Creates a key format with a fallback {@code namespace} and the {@link Key#DEFAULT_SEPARATOR}.
*
* @param namespace the namespace to be used as fallback in {@link #parse(String)}
* @return the key format
* @throws IllegalArgumentException if the namespace contains an invalid character
* @since 4.24.0
*/
static KeyFormat namespace(@KeyPattern.Namespace final @NotNull String namespace) {
return keyFormat(namespace, Key.DEFAULT_SEPARATOR);
}

/**
* Creates a key format.
*
* @param namespaced the namespaced instance to get the namespace from
* @param separator the separator to bse used when parsing keys
* @return the key format
* @throws IllegalArgumentException if the namespace contains an invalid character
* @see #keyFormat(String, char)
* @since 4.24.0
*/
static KeyFormat keyFormat(final @NotNull Namespaced namespaced, final char separator) {
return keyFormat(requireNonNull(namespaced, "namespaced").namespace(), separator);
}

/**
* Creates a key format.
*
* @param namespace the namespace to be used as fallback in {@link #parse(String)}
* @param separator the separator to be used when parsing keys using {@link #parse(String)}
* @return the key format
* @throws IllegalArgumentException if the namespace contains an invalid character
* @since 4.24.0
*/
static KeyFormat keyFormat(@KeyPattern.Namespace final @NotNull String namespace, final char separator) {
return keyFormat()
.namespace(namespace)
.separator(separator)
.build();
}

/**
* Creates a key format builder.
*
* @return the builder
* @since 4.24.0
*/
static KeyFormat.@NotNull Builder keyFormat() {
return new KeyFormatImpl.BuilderImpl();
}

/**
* Gets the separator of this key format.
*
* @return the separator
* @since 4.24.0
*/
char separator();

/**
* Gets the fallback namespace of this key format.
*
* @return the namespace
* @since 4.24.0
*/
@KeyPattern.Namespace
@Override
@NotNull String namespace();

/**
* Creates a key from this format.
*
* <p>This will create a key with {@link #namespace()} and {@code value}.</p>
*
* @param value the value of the key
* @return the key
* @throws InvalidKeyException if the namespace or value contains an invalid character
* @since 4.24.0
*/
default @NotNull Key key(@KeyPattern.Value final @NotNull String value) {
return Key.key(this.namespace(), value);
}

/**
* Creates a key from this format.
*
* <p>This will parse {@code string} as a key, using {@link #separator()} as a separator between namespace and value.</p>
*
* <p>The namespace is optional. If you do not provide one (for example, if you provide just {@code player} or {@code separator + "player"}
* as the string) then {@link #namespace()} will be used as a namespace and {@code string} will be used as the value,
* removing the separator if necessary.</p>
*
* @param string the string
* @return the key
* @throws InvalidKeyException if the namespace or value contains an invalid character
* @since 4.24.0
*/
default @NotNull Key parse(final @NotNull String string) {
return Key.key(string, this.separator(), this.namespace());
}

/**
* Checks if {@code string} can be parsed into a {@link Key} using this formats {@link #separator()}.
* If no namespace is specified in {@code string}, the fallback namespace {@link #namespace()} is used.
*
* @param string the string
* @return {@code true} if {@code string} can be parsed into a key using this format, {@code false} otherwise
* @since 4.24.0
*/
boolean parseable(final @Nullable String string);

/**
* Turns a {@link Key} into its string representation using the configuration of this format.
*
* @param key the key
* @return the string representation of the key
* @since 4.24.0
*/
default @NotNull String asString(final @NotNull Key key) {
return key.namespace() + this.separator() + key.value();
}

/**
* Turns a {@link Key} into its string representation in minimal form using the configuration of this format.
*
* <p>If the {@link Key#namespace()} of the key is {@link #namespace()}, only the {@link Key#value()} will be returned.</p>
*
* @param key the key
* @return the minimal string representation of the key
* @since 4.24.0
*/
default @NotNull String asMinimalString(final @NotNull Key key) {
if (key.namespace().equals(this.namespace()))
return key.value();
return this.asString(key);
}

@Override
default @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
return Stream.of(
ExaminableProperty.of("namespace", this.namespace()),
ExaminableProperty.of("separator", this.separator())
);
}

/**
* A builder for a key format.
*
* @since 4.24.0
*/
interface Builder {
/**
* Sets the fallback namespace of this builder from a {@link Namespaced} instance.
*
* @param namespaced the namespaced instance
* @return this builder
* @see #namespace(String)
* @since 4.24.0
*/
@Contract("_ -> this")
default @NotNull Builder namespace(final @NotNull Namespaced namespaced) {
return this.namespace(requireNonNull(namespaced, "namespaced").namespace());
}

/**
* Sets the fallback namespace of this builder.
*
* <p>This namespace is used in {@link #parse(String)} when the input string doesn't specify a namespace.</p>
*
* <p>Defaults to {@link Key#MINECRAFT_NAMESPACE}</p>
*
* @param namespace the namespace
* @return this builder
* @since 4.24.0
*/
@Contract("_ -> this")
@NotNull Builder namespace(@KeyPattern.Namespace final @NotNull String namespace);

/**
* Sets the separator of this builder.
*
* <p>The separator is used to separate namespace and value in {@link #parse(String)}.</p>
*
* <p>Defaults to {@link Key#DEFAULT_SEPARATOR}</p>
*
* @param separator the separator
* @return this builder
* @since 4.24.0
*/
@Contract("_ -> this")
@NotNull Builder separator(final char separator);

/**
* Builds the key format.
*
* @return the key format
* @throws IllegalArgumentException if the namespace contains an invalid character
* @since 4.24.0
*/
@Contract(value = "-> new", pure = true)
@NotNull KeyFormat build();
}
}
Loading
Loading