package net.minecraft.resources; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import io.netty.buffer.ByteBuf; import java.nio.file.Path; import java.util.Locale; import java.util.function.UnaryOperator; import net.minecraft.IdentifierException; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import org.jspecify.annotations.Nullable; public final class Identifier implements Comparable { public static final Codec CODEC = Codec.STRING.comapFlatMap(Identifier::read, Identifier::toString).stable(); public static final StreamCodec STREAM_CODEC = ByteBufCodecs.STRING_UTF8.map(Identifier::parse, Identifier::toString); public static final SimpleCommandExceptionType ERROR_INVALID = new SimpleCommandExceptionType(Component.translatable("argument.id.invalid")); public static final char NAMESPACE_SEPARATOR = ':'; public static final String DEFAULT_NAMESPACE = "minecraft"; public static final String REALMS_NAMESPACE = "realms"; public static final String ALLOWED_NAMESPACE_CHARACTERS = "[a-z0-9_.-]"; private final String namespace; private final String path; private Identifier(final String namespace, final String path) { assert isValidNamespace(namespace); assert isValidPath(path); this.namespace = namespace; this.path = path; } private static Identifier createUntrusted(final String namespace, final String path) { return new Identifier(assertValidNamespace(namespace, path), assertValidPath(namespace, path)); } public static Identifier fromNamespaceAndPath(final String namespace, final String path) { return createUntrusted(namespace, path); } public static Identifier parse(final String identifier) { return bySeparator(identifier, ':'); } public static Identifier withDefaultNamespace(final String path) { return new Identifier("minecraft", assertValidPath("minecraft", path)); } @Nullable public static Identifier tryParse(final String identifier) { return tryBySeparator(identifier, ':'); } @Nullable public static Identifier tryBuild(final String namespace, final String path) { return isValidNamespace(namespace) && isValidPath(path) ? new Identifier(namespace, path) : null; } public static Identifier bySeparator(final String identifier, final char separator) { int separatorIndex = identifier.indexOf(separator); if (separatorIndex >= 0) { String path = identifier.substring(separatorIndex + 1); if (separatorIndex != 0) { String namespace = identifier.substring(0, separatorIndex); return createUntrusted(namespace, path); } else { return withDefaultNamespace(path); } } else { return withDefaultNamespace(identifier); } } @Nullable public static Identifier tryBySeparator(final String identifier, final char separator) { int separatorIndex = identifier.indexOf(separator); if (separatorIndex >= 0) { String path = identifier.substring(separatorIndex + 1); if (!isValidPath(path)) { return null; } else if (separatorIndex != 0) { String namespace = identifier.substring(0, separatorIndex); return isValidNamespace(namespace) ? new Identifier(namespace, path) : null; } else { return new Identifier("minecraft", path); } } else { return isValidPath(identifier) ? new Identifier("minecraft", identifier) : null; } } public static DataResult read(final String input) { try { return DataResult.success(parse(input)); } catch (IdentifierException var2) { return DataResult.error(() -> "Not a valid resource location: " + input + " " + var2.getMessage()); } } public String getPath() { return this.path; } public String getNamespace() { return this.namespace; } public Identifier withPath(final String newPath) { return new Identifier(this.namespace, assertValidPath(this.namespace, newPath)); } public Identifier withPath(final UnaryOperator modifier) { return this.withPath((String)modifier.apply(this.path)); } public Identifier withPrefix(final String prefix) { return this.withPath(prefix + this.path); } public Identifier withSuffix(final String suffix) { return this.withPath(this.path + suffix); } public String toString() { return this.namespace + ":" + this.path; } public boolean equals(final Object o) { if (this == o) { return true; } else { return !(o instanceof Identifier that) ? false : this.namespace.equals(that.namespace) && this.path.equals(that.path); } } public int hashCode() { return 31 * this.namespace.hashCode() + this.path.hashCode(); } public int compareTo(final Identifier o) { int result = this.path.compareTo(o.path); if (result == 0) { result = this.namespace.compareTo(o.namespace); } return result; } public Path resolveAgainst(final Path root) { Path resultingPath = root.resolve(this.getNamespace(), this.getPath()); Path normalizedPath = resultingPath.normalize(); Path normalizedRoot = root.normalize(); if (!normalizedPath.startsWith(normalizedRoot)) { throw new IllegalStateException( String.format(Locale.ROOT, "Identifier \"%s\" tried to access path \"%s\" from root \"%s\"", this, normalizedPath, normalizedRoot) ); } else { return resultingPath; } } public String toDebugFileName() { return this.toString().replace('/', '_').replace(':', '_'); } public String toLanguageKey() { return this.namespace + "." + this.path; } public String toShortLanguageKey() { return this.namespace.equals("minecraft") ? this.path : this.toLanguageKey(); } public String toShortString() { return this.namespace.equals("minecraft") ? this.path : this.toString(); } public String toLanguageKey(final String prefix) { return prefix + "." + this.toLanguageKey(); } public String toLanguageKey(final String prefix, final String suffix) { return prefix + "." + this.toLanguageKey() + "." + suffix; } private static String readGreedy(final StringReader reader) { int start = reader.getCursor(); while (reader.canRead() && isAllowedInIdentifier(reader.peek())) { reader.skip(); } return reader.getString().substring(start, reader.getCursor()); } public static Identifier read(final StringReader reader) throws CommandSyntaxException { int start = reader.getCursor(); String raw = readGreedy(reader); try { return parse(raw); } catch (IdentifierException var4) { reader.setCursor(start); throw ERROR_INVALID.createWithContext(reader); } } public static Identifier readNonEmpty(final StringReader reader) throws CommandSyntaxException { int start = reader.getCursor(); String raw = readGreedy(reader); if (raw.isEmpty()) { throw ERROR_INVALID.createWithContext(reader); } else { try { return parse(raw); } catch (IdentifierException var4) { reader.setCursor(start); throw ERROR_INVALID.createWithContext(reader); } } } public static boolean isAllowedInIdentifier(final char c) { return c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c == '_' || c == ':' || c == '/' || c == '.' || c == '-'; } public static boolean isValidPath(final String path) { for (int i = 0; i < path.length(); i++) { if (!validPathChar(path.charAt(i))) { return false; } } return true; } public static boolean isValidNamespace(final String namespace) { if (namespace.equals("..")) { return false; } else { for (int i = 0; i < namespace.length(); i++) { if (!validNamespaceChar(namespace.charAt(i))) { return false; } } return true; } } private static String assertValidNamespace(final String namespace, final String path) { if (!isValidNamespace(namespace)) { throw new IdentifierException("Non [a-z0-9_.-] character in namespace of identifier: " + namespace + ":" + path); } else { return namespace; } } public static boolean validPathChar(final char c) { return c == '_' || c == '-' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '/' || c == '.'; } private static boolean validNamespaceChar(final char c) { return c == '_' || c == '-' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '.'; } private static String assertValidPath(final String namespace, final String path) { if (!isValidPath(path)) { throw new IdentifierException("Non [a-z0-9/._-] character in path of location: " + namespace + ":" + path); } else { return path; } } }