Skip to content

Commit de25c8c

Browse files
authored
Added support for including multiple (same) EntryData within a single EntryContainer (#8035)
* added support for single entry data to be included multiple times within a section node * added test for entry data being included multiple times within a single entry container * added missing constructors * made list returned by EntryContainer#getAll unmodifiable * fixed incorrect package name in the test * Update src/main/java/ch/njol/skript/test/runner/StructTestEntryContainer.java * fixed the test * fixed the test
1 parent dad3605 commit de25c8c

File tree

9 files changed

+216
-90
lines changed

9 files changed

+216
-90
lines changed

src/main/java/ch/njol/skript/test/runner/StructTestEntryContainer.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,20 @@ public class StructTestEntryContainer extends Structure {
2323
Skript.registerStructure(StructTestEntryContainer.class,
2424
EntryValidator.builder()
2525
.addSection("has entry", true)
26+
.addSection("has multiple entries", true, true)
2627
.build(),
2728
"test entry container");
2829
}
2930

3031
private EntryContainer entryContainer;
3132

3233
@Override
33-
public boolean init(Literal<?>[] args, int matchedPattern, ParseResult parseResult, @Nullable EntryContainer entryContainer) {
34+
public boolean init(
35+
Literal<?>[] args, int matchedPattern, ParseResult parseResult, @Nullable EntryContainer entryContainer
36+
) {
3437
assert entryContainer != null;
3538
this.entryContainer = entryContainer;
36-
if (entryContainer.hasEntry("has entry")) {
39+
if (entryContainer.hasEntry("has entry") && entryContainer.hasEntry("has multiple entries")) {
3740
return true;
3841
}
3942
assert false;
@@ -47,6 +50,16 @@ public boolean load() {
4750
Script script = getParser().getCurrentScript();
4851
Trigger trigger = new Trigger(script, "entry container test", null, triggerItems);
4952
trigger.execute(new SkriptTestEvent());
53+
54+
List<SectionNode> multipleSections = entryContainer.getAll(
55+
"has multiple entries", SectionNode.class, false
56+
);
57+
for (SectionNode multipleSection : multipleSections) {
58+
triggerItems = ScriptLoader.loadItems(multipleSection);
59+
trigger = new Trigger(script, "entry container test", null, triggerItems);
60+
trigger.execute(new SkriptTestEvent());
61+
}
62+
5063
return true;
5164
}
5265

src/main/java/org/skriptlang/skript/lang/entry/ContainerEntryData.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ public ContainerEntryData(String key, boolean optional, EntryValidatorBuilder va
2525
this.entryValidator = validatorBuilder.build();
2626
}
2727

28+
public ContainerEntryData(String key, boolean optional, boolean multiple, EntryValidator entryValidator) {
29+
super(key, null, optional, multiple);
30+
this.entryValidator = entryValidator;
31+
}
32+
33+
public ContainerEntryData(
34+
String key, boolean optional, boolean multiple, EntryValidatorBuilder validatorBuilder
35+
) {
36+
super(key, null, optional, multiple);
37+
this.entryValidator = validatorBuilder.build();
38+
}
39+
2840
public EntryValidator getEntryValidator() {
2941
return entryValidator;
3042
}
@@ -44,8 +56,7 @@ public boolean canCreateWith(Node node) {
4456
key = ScriptLoader.replaceOptions(key);
4557
if (!getKey().equalsIgnoreCase(key))
4658
return false;
47-
EntryContainer container = entryValidator.validate(sectionNode);
48-
entryContainer = container;
59+
entryContainer = entryValidator.validate(sectionNode);
4960
return true;
5061
}
5162

src/main/java/org/skriptlang/skript/lang/entry/EntryContainer.java

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,23 @@
55
import ch.njol.skript.lang.parser.ParserInstance;
66
import org.jetbrains.annotations.NotNull;
77
import org.jetbrains.annotations.Nullable;
8+
import org.jetbrains.annotations.Unmodifiable;
89

9-
import java.util.ArrayList;
10-
import java.util.List;
11-
import java.util.Map;
10+
import java.util.*;
1211

1312
/**
1413
* An EntryContainer is a data container for obtaining the values of the entries of a {@link SectionNode}.
1514
*/
1615
public class EntryContainer {
1716

1817
private final SectionNode source;
19-
@Nullable
20-
private final EntryValidator entryValidator;
21-
@Nullable
22-
private final Map<String, Node> handledNodes;
18+
private final @Nullable EntryValidator entryValidator;
19+
private final @Nullable Map<String, Collection<Node>> handledNodes;
2320
private final List<Node> unhandledNodes;
2421

2522
EntryContainer(
26-
SectionNode source, @Nullable EntryValidator entryValidator, @Nullable Map<String, Node> handledNodes, List<Node> unhandledNodes
23+
SectionNode source, @Nullable EntryValidator entryValidator,
24+
@Nullable Map<String, Collection<Node>> handledNodes, List<Node> unhandledNodes
2725
) {
2826
this.source = source;
2927
this.entryValidator = entryValidator;
@@ -60,11 +58,72 @@ public List<Node> getUnhandledNodes() {
6058
return unhandledNodes;
6159
}
6260

61+
/**
62+
* A method for obtaining a typed entry values.
63+
* @param key The key associated with the entry.
64+
* @param expectedType The class representing the expected type of the entry's values.
65+
* @param useDefaultValue Whether the default value should be used if parsing failed.
66+
* @return The entry's values. May be empty list if the entry is missing or a parsing error occurred.
67+
* @throws RuntimeException If the entry's value is not of the expected type.
68+
*/
69+
@SuppressWarnings("unchecked")
70+
public <E, R extends E> @Unmodifiable List<R> getAll(String key, Class<E> expectedType, boolean useDefaultValue) {
71+
List<?> parsed = getAll(key, useDefaultValue);
72+
for (Object object : parsed) {
73+
if (!expectedType.isInstance(object))
74+
throw new RuntimeException("Expected entry with key '" + key + "' to be '" +
75+
expectedType + "', but got '" + object.getClass() + "'");
76+
}
77+
return (List<R>) parsed;
78+
}
79+
80+
/**
81+
* A method for obtaining an entry values with an unknown type.
82+
* @param key The key associated with the entry.
83+
* @param useDefaultValue Whether the default value should be used if parsing failed.
84+
* @return The entry's values. May be empty list if the entry is missing or a parsing error occurred.
85+
*/
86+
public @Unmodifiable List<Object> getAll(String key, boolean useDefaultValue) {
87+
if (entryValidator == null || handledNodes == null)
88+
return Collections.emptyList();
89+
90+
EntryData<?> entryData = entryValidator.getEntryData().stream()
91+
.filter(data -> data.getKey().equals(key))
92+
.findFirst()
93+
.orElse(null);
94+
if (entryData == null)
95+
return Collections.emptyList();
96+
97+
Collection<Node> nodes = handledNodes.get(key);
98+
if (nodes == null || nodes.isEmpty()) {
99+
Object defaultValue = entryData.getDefaultValue();
100+
return defaultValue != null
101+
? Collections.singletonList(defaultValue)
102+
: Collections.emptyList();
103+
}
104+
105+
List<Object> values = new LinkedList<>();
106+
ParserInstance parser = ParserInstance.get();
107+
Node oldNode = parser.getNode();
108+
for (Node node : nodes) {
109+
parser.setNode(node);
110+
Object value = entryData.getValue(node);
111+
if (value == null && useDefaultValue)
112+
value = entryData.getDefaultValue();
113+
if (value != null)
114+
values.add(value);
115+
}
116+
parser.setNode(oldNode);
117+
118+
return Collections.unmodifiableList(values);
119+
}
120+
63121
/**
64122
* A method for obtaining a non-null, typed entry value.
65123
* This method should ONLY be called if there is no way the entry could return null.
66-
* In general, this means that the entry has a default value (and 'useDefaultValue' is true). This is because even
67-
* though an entry may be required, parsing errors may occur that mean no value can be returned.
124+
* In general, this means that the entry has a default value (and 'useDefaultValue' is true).
125+
* This is because even though an entry may be required, parsing errors may occur that
126+
* mean no value can be returned.
68127
* It can also mean that the entry data is simple enough such that it will never return a null value.
69128
* @param key The key associated with the entry.
70129
* @param expectedType The class representing the expected type of the entry's value.
@@ -73,28 +132,29 @@ public List<Node> getUnhandledNodes() {
73132
* @throws RuntimeException If the entry's value is null, or if it is not of the expected type.
74133
*/
75134
public <E, R extends E> R get(String key, Class<E> expectedType, boolean useDefaultValue) {
76-
R value = getOptional(key, expectedType, useDefaultValue);
77-
if (value == null)
135+
List<R> all = getAll(key, expectedType, useDefaultValue);
136+
if (all.isEmpty())
78137
throw new RuntimeException("Null value for asserted non-null value");
79-
return value;
138+
return all.get(0); // always present
80139
}
81140

82141
/**
83142
* A method for obtaining a non-null entry value with an unknown type.
84143
* This method should ONLY be called if there is no way the entry could return null.
85-
* In general, this means that the entry has a default value (and 'useDefaultValue' is true). This is because even
86-
* though an entry may be required, parsing errors may occur that mean no value can be returned.
144+
* In general, this means that the entry has a default value (and 'useDefaultValue' is true).
145+
* This is because even though an entry may be required, parsing errors may occur that
146+
* mean no value can be returned.
87147
* It can also mean that the entry data is simple enough such that it will never return a null value.
88148
* @param key The key associated with the entry.
89149
* @param useDefaultValue Whether the default value should be used if parsing failed.
90150
* @return The entry's value.
91151
* @throws RuntimeException If the entry's value is null.
92152
*/
93153
public Object get(String key, boolean useDefaultValue) {
94-
Object parsed = getOptional(key, useDefaultValue);
95-
if (parsed == null)
154+
List<Object> all = getAll(key, useDefaultValue);
155+
if (all.isEmpty())
96156
throw new RuntimeException("Null value for asserted non-null value");
97-
return parsed;
157+
return all.get(0); // always present
98158
}
99159

100160
/**
@@ -105,15 +165,9 @@ public Object get(String key, boolean useDefaultValue) {
105165
* @return The entry's value. May be null if the entry is missing or a parsing error occurred.
106166
* @throws RuntimeException If the entry's value is not of the expected type.
107167
*/
108-
@Nullable
109-
@SuppressWarnings("unchecked")
110-
public <E, R extends E> R getOptional(String key, Class<E> expectedType, boolean useDefaultValue) {
111-
Object parsed = getOptional(key, useDefaultValue);
112-
if (parsed == null)
113-
return null;
114-
if (!expectedType.isInstance(parsed))
115-
throw new RuntimeException("Expected entry with key '" + key + "' to be '" + expectedType + "', but got '" + parsed.getClass() + "'");
116-
return (R) parsed;
168+
public <E, R extends E> @Nullable R getOptional(String key, Class<E> expectedType, boolean useDefaultValue) {
169+
List<R> all = getAll(key, expectedType, useDefaultValue);
170+
return all.isEmpty() ? null : all.get(0);
117171
}
118172

119173
/**
@@ -122,35 +176,9 @@ public <E, R extends E> R getOptional(String key, Class<E> expectedType, boolean
122176
* @param useDefaultValue Whether the default value should be used if parsing failed.
123177
* @return The entry's value. May be null if the entry is missing or a parsing error occurred.
124178
*/
125-
@Nullable
126-
public Object getOptional(String key, boolean useDefaultValue) {
127-
if (entryValidator == null || handledNodes == null)
128-
return null;
129-
130-
EntryData<?> entryData = null;
131-
for (EntryData<?> data : entryValidator.getEntryData()) {
132-
if (data.getKey().equals(key)) {
133-
entryData = data;
134-
break;
135-
}
136-
}
137-
if (entryData == null)
138-
return null;
139-
140-
Node node = handledNodes.get(key);
141-
if (node == null)
142-
return entryData.getDefaultValue();
143-
144-
// Update ParserInstance node for parsing
145-
ParserInstance parser = ParserInstance.get();
146-
Node oldNode = parser.getNode();
147-
parser.setNode(node);
148-
Object value = entryData.getValue(node);
149-
if (value == null && useDefaultValue)
150-
value = entryData.getDefaultValue();
151-
parser.setNode(oldNode);
152-
153-
return value;
179+
public @Nullable Object getOptional(String key, boolean useDefaultValue) {
180+
List<Object> all = getAll(key, useDefaultValue);
181+
return all.isEmpty() ? null : all.get(0);
154182
}
155183

156184
/**
@@ -159,7 +187,7 @@ public Object getOptional(String key, boolean useDefaultValue) {
159187
* @return true if an entry data with the matching key was used.
160188
*/
161189
public boolean hasEntry(@NotNull String key) {
162-
return handledNodes.containsKey(key);
190+
return handledNodes != null && handledNodes.containsKey(key);
163191
}
164192

165193
}

src/main/java/org/skriptlang/skript/lang/entry/EntryData.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,17 @@ public abstract class EntryData<T> {
3030
private final String key;
3131
private final @Nullable T defaultValue;
3232
private final boolean optional;
33+
private final boolean multiple;
3334

3435
public EntryData(String key, @Nullable T defaultValue, boolean optional) {
36+
this(key, defaultValue, optional, false);
37+
}
38+
39+
public EntryData(String key, @Nullable T defaultValue, boolean optional, boolean multiple) {
3540
this.key = key;
3641
this.defaultValue = defaultValue;
3742
this.optional = optional;
43+
this.multiple = multiple;
3844
}
3945

4046
/**
@@ -59,6 +65,13 @@ public boolean isOptional() {
5965
return optional;
6066
}
6167

68+
/**
69+
* @return Whether this entry data can be included repeatedly within a {@link SectionNode}.
70+
*/
71+
public boolean supportsMultiple() {
72+
return multiple;
73+
}
74+
6275
/**
6376
* Obtains a value from the provided node using the methods of this entry data.
6477
* @param node The node to obtain a value from.

0 commit comments

Comments
 (0)