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.util.function.UnaryOperator; import net.minecraft.ResourceLocationException; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import org.jetbrains.annotations.Nullable; /** * An immutable location of a resource, in terms of a path and namespace. *

* This is used as an identifier for a resource, usually for those housed in a {@link net.minecraft.core.Registry}, such as blocks and items. *

* {@code minecraft} is always taken as the default namespace for a resource location when none is explicitly stated. When using this for registering objects, this namespace should only be used for resources added by Minecraft itself. *

* Generally, and by the implementation of {@link #toString()}, the string representation of this class is expressed in the form {@code namespace:path}. The colon is also used as the default separator for parsing strings as a {@code ResourceLocation}. * @see net.minecraft.resources.ResourceKey */ public final class ResourceLocation implements Comparable { public static final Codec CODEC = Codec.STRING.comapFlatMap(ResourceLocation::read, ResourceLocation::toString).stable(); public static final StreamCodec STREAM_CODEC = ByteBufCodecs.STRING_UTF8.map(ResourceLocation::parse, ResourceLocation::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"; private final String namespace; private final String path; private ResourceLocation(String namespace, String path) { assert isValidNamespace(namespace); assert isValidPath(path); this.namespace = namespace; this.path = path; } private static ResourceLocation createUntrusted(String namespace, String path) { return new ResourceLocation(assertValidNamespace(namespace, path), assertValidPath(namespace, path)); } public static ResourceLocation fromNamespaceAndPath(String namespace, String path) { return createUntrusted(namespace, path); } public static ResourceLocation parse(String location) { return bySeparator(location, ':'); } public static ResourceLocation withDefaultNamespace(String location) { return new ResourceLocation("minecraft", assertValidPath("minecraft", location)); } /** * Attempts to parse the specified {@code location} as a {@code ResourceLocation} by splitting it into a * namespace and path by a colon. *

* If no colon is present in the {@code location}, the namespace defaults to {@code minecraft}, taking the {@code location} as the path. * @return the parsed resource location; otherwise {@code null} if there is a non {@code [a-z0-9_.-]} character in the decomposed namespace or a non {@code [a-z0-9/._-]} character in the decomposed path * @see #of(String, char) * * @param location the location string to try to parse as a {@code ResourceLocation} */ @Nullable public static ResourceLocation tryParse(String location) { return tryBySeparator(location, ':'); } @Nullable public static ResourceLocation tryBuild(String namespace, String path) { return isValidNamespace(namespace) && isValidPath(path) ? new ResourceLocation(namespace, path) : null; } public static ResourceLocation bySeparator(String location, char seperator) { int i = location.indexOf(seperator); if (i >= 0) { String string = location.substring(i + 1); if (i != 0) { String string2 = location.substring(0, i); return createUntrusted(string2, string); } else { return withDefaultNamespace(string); } } else { return withDefaultNamespace(location); } } @Nullable public static ResourceLocation tryBySeparator(String location, char seperator) { int i = location.indexOf(seperator); if (i >= 0) { String string = location.substring(i + 1); if (!isValidPath(string)) { return null; } else if (i != 0) { String string2 = location.substring(0, i); return isValidNamespace(string2) ? new ResourceLocation(string2, string) : null; } else { return new ResourceLocation("minecraft", string); } } else { return isValidPath(location) ? new ResourceLocation("minecraft", location) : null; } } public static DataResult read(String location) { try { return DataResult.success(parse(location)); } catch (ResourceLocationException var2) { return DataResult.error(() -> "Not a valid resource location: " + location + " " + var2.getMessage()); } } public String getPath() { return this.path; } public String getNamespace() { return this.namespace; } public ResourceLocation withPath(String path) { return new ResourceLocation(this.namespace, assertValidPath(this.namespace, path)); } public ResourceLocation withPath(UnaryOperator pathOperator) { return this.withPath((String)pathOperator.apply(this.path)); } public ResourceLocation withPrefix(String pathPrefix) { return this.withPath(pathPrefix + this.path); } public ResourceLocation withSuffix(String pathSuffix) { return this.withPath(this.path + pathSuffix); } public String toString() { return this.namespace + ":" + this.path; } public boolean equals(Object object) { if (this == object) { return true; } else { return !(object instanceof ResourceLocation resourceLocation) ? false : this.namespace.equals(resourceLocation.namespace) && this.path.equals(resourceLocation.path); } } public int hashCode() { return 31 * this.namespace.hashCode() + this.path.hashCode(); } public int compareTo(ResourceLocation other) { int i = this.path.compareTo(other.path); if (i == 0) { i = this.namespace.compareTo(other.namespace); } return i; } 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 toLanguageKey(String type) { return type + "." + this.toLanguageKey(); } public String toLanguageKey(String type, String key) { return type + "." + this.toLanguageKey() + "." + key; } private static String readGreedy(StringReader reader) { int i = reader.getCursor(); while (reader.canRead() && isAllowedInResourceLocation(reader.peek())) { reader.skip(); } return reader.getString().substring(i, reader.getCursor()); } public static ResourceLocation read(StringReader reader) throws CommandSyntaxException { int i = reader.getCursor(); String string = readGreedy(reader); try { return parse(string); } catch (ResourceLocationException var4) { reader.setCursor(i); throw ERROR_INVALID.createWithContext(reader); } } public static ResourceLocation readNonEmpty(StringReader reader) throws CommandSyntaxException { int i = reader.getCursor(); String string = readGreedy(reader); if (string.isEmpty()) { throw ERROR_INVALID.createWithContext(reader); } else { try { return parse(string); } catch (ResourceLocationException var4) { reader.setCursor(i); throw ERROR_INVALID.createWithContext(reader); } } } public static boolean isAllowedInResourceLocation(char character) { return character >= '0' && character <= '9' || character >= 'a' && character <= 'z' || character == '_' || character == ':' || character == '/' || character == '.' || character == '-'; } /** * @return {@code true} if the specified {@code path} is valid: consists only of {@code [a-z0-9/._-]} characters */ public static boolean isValidPath(String path) { for (int i = 0; i < path.length(); i++) { if (!validPathChar(path.charAt(i))) { return false; } } return true; } /** * @return {@code true} if the specified {@code namespace} is valid: consists only of {@code [a-z0-9_.-]} characters */ public static boolean isValidNamespace(String namespace) { for (int i = 0; i < namespace.length(); i++) { if (!validNamespaceChar(namespace.charAt(i))) { return false; } } return true; } private static String assertValidNamespace(String namespace, String path) { if (!isValidNamespace(namespace)) { throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + namespace + ":" + path); } else { return namespace; } } public static boolean validPathChar(char pathChar) { return pathChar == '_' || pathChar == '-' || pathChar >= 'a' && pathChar <= 'z' || pathChar >= '0' && pathChar <= '9' || pathChar == '/' || pathChar == '.'; } private static boolean validNamespaceChar(char namespaceChar) { return namespaceChar == '_' || namespaceChar == '-' || namespaceChar >= 'a' && namespaceChar <= 'z' || namespaceChar >= '0' && namespaceChar <= '9' || namespaceChar == '.'; } private static String assertValidPath(String namespace, String path) { if (!isValidPath(path)) { throw new ResourceLocationException("Non [a-z0-9/._-] character in path of location: " + namespace + ":" + path); } else { return path; } } }