Skip to content

Commit 6bebdc9

Browse files
committed
validate created identifiers
1 parent e5513d7 commit 6bebdc9

File tree

4 files changed

+191
-9
lines changed

4 files changed

+191
-9
lines changed

libraries/core/src/main/java/net/ornithemc/osl/core/api/util/NamespacedIdentifier.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,12 @@
44

55
public final class NamespacedIdentifier {
66

7-
public static NamespacedIdentifier fromMinecraft(String identifier) {
8-
return new NamespacedIdentifier("minecraft", identifier);
9-
}
10-
11-
public static NamespacedIdentifier from(String namespace, String identifier) {
12-
return new NamespacedIdentifier(namespace, identifier);
13-
}
7+
public static final char SEPARATOR = ':';
148

159
private final String namespace;
1610
private final String identifier;
1711

18-
private NamespacedIdentifier(String namespace, String identifier) {
12+
NamespacedIdentifier(String namespace, String identifier) {
1913
this.namespace = namespace;
2014
this.identifier = identifier;
2115
}
@@ -39,7 +33,7 @@ public int hashCode() {
3933

4034
@Override
4135
public String toString() {
42-
return namespace + ":" + identifier;
36+
return namespace + SEPARATOR + identifier;
4337
}
4438

4539
public String getNamespace() {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package net.ornithemc.osl.core.api.util;
2+
3+
import net.ornithemc.osl.core.impl.util.NamespacedIdentifierException;
4+
import net.ornithemc.osl.core.impl.util.NamespacedIdentifierParseException;
5+
6+
/**
7+
* Utility methods for creating and validating {@link NamespacedIdentifier}s.
8+
*/
9+
public final class NamespacedIdentifiers {
10+
11+
/**
12+
* The {@code minecraft} namespace is used for Vanilla resources and ids.
13+
*/
14+
public static final String MINECRAFT_NAMESPACE = "minecraft";
15+
/**
16+
* The default namespace of {@code NamespacedIdentifier}s.
17+
* It is recommended to use a custom namespace for your own identifiers.
18+
*/
19+
public static final String DEFAULT_NAMESPACE = MINECRAFT_NAMESPACE;
20+
21+
/**
22+
* The maximum length of a {@code NamespacedIdentifier}'s namespace string.
23+
*/
24+
public static final int MAX_LENGTH_NAMESPACE = Integer.MAX_VALUE;
25+
/**
26+
* The maximum length of a {@code NamespacedIdentifier} identifier string.
27+
*/
28+
public static final int MAX_LENGTH_IDENTIFIER = Integer.MAX_VALUE;
29+
30+
/**
31+
* Construct a {@code NamespacedIdentifier} without validating it.
32+
*
33+
* @deprecated use {@link #from(String, String)} instead
34+
*/
35+
public static NamespacedIdentifier of(String namespace, String identifier) {
36+
return new NamespacedIdentifier(namespace, identifier);
37+
}
38+
39+
/**
40+
* Construct and validate a {@code NamespacedIdentifier} with the default namespace and the given identifier.
41+
*
42+
* @return a {@code NamespacedIdentifier} with the default namespace and the given identifier.
43+
* @throws NamespacedIdentifierException
44+
* if the given identifier is invalid.
45+
*/
46+
public static NamespacedIdentifier from(String identifier) {
47+
return from(DEFAULT_NAMESPACE, identifier);
48+
}
49+
50+
/**
51+
* Construct and validate a {@code NamespacedIdentifier} from the given namespace and identifier.
52+
*
53+
* @return a {@code NamespacedIdentifier} with the given namespace and identifier.
54+
* @throws NamespacedIdentifierException
55+
* if the given namespace or identifier is invalid.
56+
*/
57+
public static NamespacedIdentifier from(String namespace, String identifier) {
58+
return new NamespacedIdentifier(
59+
validateNamespace(namespace),
60+
validateIdentifier(identifier)
61+
);
62+
}
63+
64+
/**
65+
* Parse a {@code NamespacedIdentifier} from the given {@code String}.
66+
* The returned identifier is always valid. If no valid identifier can
67+
* be parsed from the given string, an exception is thrown.
68+
*
69+
* @return the {@code NamespacedIdentifier}} represented by the {@code String}.
70+
* @throws NamespacedIdentifierParseException
71+
* if no valid {@code NamespacedIdentifier} can be parsed from the given {@code String}.
72+
*/
73+
public static NamespacedIdentifier parse(String s) {
74+
int i = s.indexOf(NamespacedIdentifier.SEPARATOR);
75+
76+
try {
77+
if (i < 0) {
78+
return from(s.substring(i + 1));
79+
} else if (i > 0) {
80+
return from(s.substring(0, i), s.substring(i + 1));
81+
} else {
82+
throw NamespacedIdentifierParseException.invalid(s, "badly formatted");
83+
}
84+
} catch (NamespacedIdentifierException e) {
85+
throw NamespacedIdentifierParseException.invalid(s, e);
86+
}
87+
}
88+
89+
/**
90+
* Check whether the given {@code NamespacedIdentifier} is valid, or throw an exception.
91+
*/
92+
public static NamespacedIdentifier validate(NamespacedIdentifier id) {
93+
try {
94+
validateNamespace(id.getNamespace());
95+
validateIdentifier(id.getIdentifier());
96+
97+
return id;
98+
} catch (NamespacedIdentifierException e) {
99+
throw NamespacedIdentifierException.invalid(id, e);
100+
}
101+
}
102+
103+
/**
104+
* Check that the given namespace is valid for a {@code NamespacedIdentifier}.
105+
*/
106+
public static String validateNamespace(String namespace) {
107+
if (namespace == null || namespace.isEmpty()) {
108+
throw NamespacedIdentifierException.invalidNamespace(namespace, "null or empty");
109+
}
110+
if (namespace.length() > MAX_LENGTH_NAMESPACE) {
111+
throw NamespacedIdentifierException.invalidNamespace(namespace, "length " + namespace.length() + " is greater than maximum allowed " + MAX_LENGTH_NAMESPACE);
112+
}
113+
if (!namespace.chars().allMatch(chr -> chr == '-' || chr == '.' || chr == '_' || (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9'))) {
114+
throw NamespacedIdentifierException.invalidNamespace(namespace, "contains illegal characters - only [a-zA-Z0-9-._] are allowed");
115+
}
116+
117+
return namespace;
118+
}
119+
120+
/**
121+
* Check that the given identifier is valid for a {@code NamespacedIdentifier}.
122+
*/
123+
public static String validateIdentifier(String identifier) {
124+
if (identifier == null || identifier.isEmpty()) {
125+
throw NamespacedIdentifierException.invalidIdentifier(identifier, "null or empty");
126+
}
127+
if (identifier.length() > MAX_LENGTH_IDENTIFIER) {
128+
throw NamespacedIdentifierException.invalidIdentifier(identifier, "length " + identifier.length() + " is greater than maximum allowed " + MAX_LENGTH_IDENTIFIER);
129+
}
130+
if (!identifier.chars().allMatch(chr -> chr == '-' || chr == '.' || chr == '_' || chr == '/' || (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9'))) {
131+
throw NamespacedIdentifierException.invalidIdentifier(identifier, "contains illegal characters - only [a-zA-Z0-9-._/] are allowed");
132+
}
133+
134+
return identifier;
135+
}
136+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package net.ornithemc.osl.core.impl.util;
2+
3+
import net.ornithemc.osl.core.api.util.NamespacedIdentifier;
4+
5+
@SuppressWarnings("serial")
6+
public class NamespacedIdentifierException extends RuntimeException {
7+
8+
private NamespacedIdentifierException(String message) {
9+
super(message);
10+
}
11+
12+
private NamespacedIdentifierException(String message, Throwable cause) {
13+
super(message, cause);
14+
}
15+
16+
public static NamespacedIdentifierException invalid(NamespacedIdentifier id, Throwable cause) {
17+
return new NamespacedIdentifierException("\'" + id + "\' is not a valid namespaced identifier", cause);
18+
}
19+
20+
public static NamespacedIdentifierException invalid(NamespacedIdentifier id, String reason) {
21+
return new NamespacedIdentifierException("\'" + id + "\' is not a valid namespaced identifier: " + reason);
22+
}
23+
24+
public static NamespacedIdentifierException invalidNamespace(String namespace, String reason) {
25+
return new NamespacedIdentifierException("\'" + namespace + "\' is not a valid namespace: " + reason);
26+
}
27+
28+
public static NamespacedIdentifierException invalidIdentifier(String identifier, String reason) {
29+
return new NamespacedIdentifierException("\'" + identifier + "\' is not a valid identifier: " + reason);
30+
}
31+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package net.ornithemc.osl.core.impl.util;
2+
3+
@SuppressWarnings("serial")
4+
public class NamespacedIdentifierParseException extends RuntimeException {
5+
6+
private NamespacedIdentifierParseException(String message) {
7+
super(message);
8+
}
9+
10+
private NamespacedIdentifierParseException(String message, Throwable cause) {
11+
super(message, cause);
12+
}
13+
14+
public static NamespacedIdentifierParseException invalid(String s, Throwable cause) {
15+
return new NamespacedIdentifierParseException("unable to parse namespaced identifier from \'" + s + "\'", cause);
16+
}
17+
18+
public static NamespacedIdentifierParseException invalid(String s, String reason) {
19+
return new NamespacedIdentifierParseException("unable to parse namespaced identifier from \'" + s + "\': " + reason);
20+
}
21+
}

0 commit comments

Comments
 (0)