Skip to content

Commit 8239577

Browse files
committed
Minimize Minecraft Identifier allocations
1 parent 670fde5 commit 8239577

3 files changed

Lines changed: 320 additions & 114 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
3+
* Copyright (C) 2016-2026 ViaVersion and contributors
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in all
13+
* copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
* SOFTWARE.
22+
*/
23+
package com.viaversion.viaversion.util;
24+
25+
final class CustomKey extends Key {
26+
27+
private final String original;
28+
private final String namespace;
29+
private final String path;
30+
31+
/**
32+
* Non-Minecraft key.
33+
*
34+
* @param original full key including the ':' separator
35+
* @param namespace non-Minecraft namespace
36+
* @param path path
37+
*/
38+
CustomKey(final String original, final String namespace, final String path) {
39+
if (!MinecraftKey.isValidNamespace(namespace)) {
40+
throw new IllegalArgumentException("Invalid namespace: " + namespace);
41+
}
42+
if (!MinecraftKey.isValidPath(path)) {
43+
throw new IllegalArgumentException("Invalid path: " + path);
44+
}
45+
this.original = original;
46+
this.namespace = namespace;
47+
this.path = path;
48+
}
49+
50+
@Override
51+
public String namespace() {
52+
return namespace;
53+
}
54+
55+
@Override
56+
public String path() {
57+
return path;
58+
}
59+
60+
@Override
61+
public String original() {
62+
return original;
63+
}
64+
65+
@Override
66+
public String minimized() {
67+
return this.toString();
68+
}
69+
70+
@Override
71+
public boolean hasMinecraftNamespace() {
72+
return false;
73+
}
74+
75+
@Override
76+
public final boolean equals(final Object o) {
77+
if (this == o) return true;
78+
if (!(o instanceof final CustomKey key)) return false;
79+
return this.original.equals(key.original);
80+
}
81+
82+
@Override
83+
public int hashCode() {
84+
return this.original.hashCode();
85+
}
86+
87+
@Override
88+
public String toString() {
89+
return this.original;
90+
}
91+
}

api/src/main/java/com/viaversion/viaversion/util/Key.java

Lines changed: 60 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,15 @@
2424

2525
import org.checkerframework.checker.nullness.qual.Nullable;
2626

27-
public final class Key {
28-
29-
private static final String MINECRAFT_NAMESPACE = "minecraft";
30-
private static final int MINECRAFT_NAMESPACE_LENGTH = MINECRAFT_NAMESPACE.length();
31-
private final String original;
32-
private final String namespace;
33-
private final String path;
27+
/**
28+
* Represents a Minecraft Identifier/ResourceLocation.
29+
* <p>
30+
* Internally split into {@link CustomKey} and {@link MinecraftKey} to reduce data held
31+
* and keep the runtime/allocation cost of {@link #original()} and {@link #toString()} minimal.
32+
*/
33+
public abstract sealed class Key permits MinecraftKey, CustomKey {
3434

35-
private Key(final String original, final String namespace, final String path) {
36-
if (!isValidNamespace(namespace)) {
37-
throw new IllegalArgumentException("Invalid namespace: " + namespace);
38-
}
39-
if (!isValidPath(path)) {
40-
throw new IllegalArgumentException("Invalid path: " + path);
41-
}
42-
this.original = original; // assume this is correct; also saves whether the namespace was explitly set
43-
this.namespace = namespace;
44-
this.path = path;
45-
}
35+
static final String MINECRAFT_NAMESPACE = "minecraft";
4636

4737
/**
4838
* Creates a new key with the given namespace and path.
@@ -52,7 +42,10 @@ private Key(final String original, final String namespace, final String path) {
5242
* @return a new key with the given namespace and path
5343
*/
5444
public static Key of(final String namespace, final String path) {
55-
return new Key(namespace + ':' + path, namespace, path);
45+
if (namespace.equals(MINECRAFT_NAMESPACE) || namespace.isEmpty()) {
46+
return ofPath(path); // ends up compacting it
47+
}
48+
return new CustomKey(namespace + ':' + path, namespace, path);
5649
}
5750

5851
/**
@@ -62,7 +55,7 @@ public static Key of(final String namespace, final String path) {
6255
* @return a new key with the given path and the default namespace
6356
*/
6457
public static Key ofPath(final String path) {
65-
return new Key(path, MINECRAFT_NAMESPACE, path);
58+
return new MinecraftKey.CompactMinecraftKey(path);
6659
}
6760

6861
/**
@@ -77,9 +70,17 @@ public static Key of(final String identifier) {
7770
return ofPath(identifier);
7871
}
7972

80-
final String namespace = separatorIndex == 0 ? MINECRAFT_NAMESPACE : identifier.substring(0, separatorIndex);
8173
final String path = identifier.substring(separatorIndex + 1);
82-
return new Key(identifier, namespace, path);
74+
if (separatorIndex == 0) {
75+
return new MinecraftKey.ColonPrefixedMinecraftKey(identifier, path);
76+
}
77+
78+
final String namespace = identifier.substring(0, separatorIndex);
79+
if (namespace.equals(MINECRAFT_NAMESPACE)) {
80+
return new MinecraftKey.FullMinecraftKey(identifier, path);
81+
}
82+
83+
return new CustomKey(identifier, namespace, path);
8384
}
8485

8586
/**
@@ -96,6 +97,38 @@ public static Key of(final String identifier) {
9697
}
9798
}
9899

100+
public abstract String namespace();
101+
102+
public abstract String path();
103+
104+
/**
105+
* Returns the unmodified original identifier, possbily without an explicit namespace.
106+
*
107+
* @return the original identifier, possibly without an explicit namespace
108+
*/
109+
public abstract String original();
110+
111+
/**
112+
* Returns the identifier in a minimized form. If the namespace is "minecraft", it will return just the path.
113+
*
114+
* @return the identifier in a minimized form
115+
*/
116+
public abstract String minimized();
117+
118+
public abstract boolean hasMinecraftNamespace();
119+
120+
public Key withNamespace(final String namespace) {
121+
return of(namespace, this.path());
122+
}
123+
124+
public Key withPath(final String path) {
125+
return of(this.namespace(), path);
126+
}
127+
128+
public final boolean equals(final String identifier) {
129+
return this.equals(Key.of(identifier));
130+
}
131+
99132
public static String stripNamespace(final String identifier) {
100133
int index = identifier.indexOf(':');
101134
if (index == -1) {
@@ -106,17 +139,15 @@ public static String stripNamespace(final String identifier) {
106139

107140
public static String namespace(final String identifier) {
108141
final int index = identifier.indexOf(':');
109-
if (index == -1) {
110-
return MINECRAFT_NAMESPACE;
111-
} else if (index == 0) {
142+
if (index == -1 || index == 0) {
112143
return MINECRAFT_NAMESPACE;
113144
}
114145
return identifier.substring(0, index);
115146
}
116147

117148
public static String stripMinecraftNamespace(final String identifier) {
118149
if (identifier.startsWith("minecraft:")) {
119-
return identifier.substring(MINECRAFT_NAMESPACE_LENGTH + 1);
150+
return identifier.substring(MinecraftKey.MINECRAFT_NAMESPACE_LENGTH + 1);
120151
} else if (!identifier.isEmpty() && identifier.charAt(0) == ':') {
121152
return identifier.substring(1);
122153
}
@@ -140,98 +171,13 @@ public static String namespaced(final String identifier) {
140171
public static boolean isValid(final String identifier) {
141172
final int separatorIndex = identifier.indexOf(':');
142173
if (separatorIndex == -1) {
143-
return isValidPath(identifier);
174+
return MinecraftKey.isValidPath(identifier);
144175
} else if (separatorIndex == 0) {
145-
return isValidPath(identifier.substring(1));
176+
return MinecraftKey.isValidPath(identifier.substring(1));
146177
}
147178

148179
final String namespace = identifier.substring(0, separatorIndex);
149180
final String path = identifier.substring(separatorIndex + 1);
150-
return isValidNamespace(namespace) && isValidPath(path);
151-
}
152-
153-
public String namespace() {
154-
return this.namespace;
155-
}
156-
157-
public String path() {
158-
return this.path;
159-
}
160-
161-
/**
162-
* Returns the unmodified original identifier, possbily without an explicit namespace.
163-
*
164-
* @return the original identifier, possibly without an explicit namespace
165-
*/
166-
public String original() {
167-
return this.original;
168-
}
169-
170-
/**
171-
* Returns the identifier in a minimized form. If the namespace is "minecraft", it will return just the path.
172-
*
173-
* @return the identifier in a minimized form
174-
*/
175-
public String minimized() {
176-
return this.hasMinecraftNamespace() ? this.path : this.toString();
177-
}
178-
179-
public boolean hasMinecraftNamespace() {
180-
return this.namespace.equals(MINECRAFT_NAMESPACE);
181-
}
182-
183-
public Key withNamespace(final String namespace) {
184-
return of(namespace, this.path);
185-
}
186-
187-
public Key withPath(final String path) {
188-
return of(this.namespace, path);
189-
}
190-
191-
@Override
192-
public final boolean equals(final Object o) {
193-
if (this == o) return true;
194-
if (!(o instanceof final Key key)) return false;
195-
return this.namespace.equals(key.namespace) && this.path.equals(key.path);
196-
}
197-
198-
public final boolean equals(final String identifier) {
199-
return this.equals(Key.of(identifier));
200-
}
201-
202-
@Override
203-
public int hashCode() {
204-
int result = this.namespace.hashCode();
205-
result = 31 * result + this.path.hashCode();
206-
return result;
207-
}
208-
209-
@Override
210-
public String toString() {
211-
return this.namespace + ':' + this.path;
212-
}
213-
214-
private static boolean isValidNamespace(final String namespace) {
215-
//noinspection StringEquality - a quick way out
216-
if (namespace == MINECRAFT_NAMESPACE) {
217-
return true;
218-
}
219-
for (int i = 0, length = namespace.length(); i < length; i++) {
220-
final char c = namespace.charAt(i);
221-
if (!(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '_' || c == '-' || c == '.')) {
222-
return false;
223-
}
224-
}
225-
return true;
226-
}
227-
228-
private static boolean isValidPath(final String path) {
229-
for (int i = 0, length = path.length(); i < length; i++) {
230-
final char c = path.charAt(i);
231-
if (!(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '_' || c == '-' || c == '.' || c == '/')) {
232-
return false;
233-
}
234-
}
235-
return true;
181+
return MinecraftKey.isValidNamespace(namespace) && MinecraftKey.isValidPath(path);
236182
}
237183
}

0 commit comments

Comments
 (0)