Skip to content

Commit a0da360

Browse files
committed
rewrite the entire thing ig
1 parent afe508e commit a0da360

File tree

3 files changed

+97
-28
lines changed

3 files changed

+97
-28
lines changed

api/src/main/java/net/kyori/adventure/text/flattener/ComponentFlattener.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ public interface ComponentFlattener extends Buildable<ComponentFlattener, Compon
9090
* @since 4.7.0
9191
*/
9292
interface Builder extends AbstractBuilder<ComponentFlattener>, Buildable.Builder<ComponentFlattener> {
93+
/**
94+
* A constant value for {@link #maximumComplexity(int)} that indicates that the maximum complexity is unlimited.
95+
*
96+
* @see #maximumComplexity(int)
97+
* @since 4.22.0
98+
*/
99+
int UNLIMITED_COMPLEXITY = -1;
100+
93101
/**
94102
* Register a type of component to be handled.
95103
*
@@ -125,12 +133,16 @@ interface Builder extends AbstractBuilder<ComponentFlattener>, Buildable.Builder
125133
@NotNull Builder unknownMapper(final @Nullable Function<Component, String> converter);
126134

127135
/**
128-
* Sets the maximum depth of the flattening.
136+
* Sets the maximum complexity of the flattening.
137+
*
138+
* <p>Complexity is defined as the number of recursive flatten operations that take place.
139+
* This includes both depth and breadth as an additional count.</p>
129140
*
130-
* @param maximumDepth the maximum depth
141+
* @param maximumComplexity the maximum complexity, defaulting to {@link #UNLIMITED_COMPLEXITY}
131142
* @return this builder
143+
* @see #UNLIMITED_COMPLEXITY
132144
* @since 4.22.0
133145
*/
134-
@NotNull Builder maximumDepth(final int maximumDepth);
146+
@NotNull Builder maximumComplexity(final int maximumComplexity);
135147
}
136148
}

api/src/main/java/net/kyori/adventure/text/flattener/ComponentFlattenerImpl.java

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,27 +60,32 @@ final class ComponentFlattenerImpl implements ComponentFlattener {
6060
.mapper(TextComponent.class, TextComponent::content)
6161
.build();
6262

63+
private static final int MAX_DEPTH = 512;
64+
6365
private final InheritanceAwareMap<Component, Handler> flatteners;
6466
private final Function<Component, String> unknownHandler;
65-
private final int maximumDepth;
67+
private final int maximumComplexity;
6668

67-
ComponentFlattenerImpl(final InheritanceAwareMap<Component, Handler> flatteners, final @Nullable Function<Component, String> unknownHandler, final int maximumDepth) {
69+
ComponentFlattenerImpl(final InheritanceAwareMap<Component, Handler> flatteners, final @Nullable Function<Component, String> unknownHandler, final int maximumComplexity) {
6870
this.flatteners = flatteners;
6971
this.unknownHandler = unknownHandler;
70-
this.maximumDepth = maximumDepth;
72+
this.maximumComplexity = maximumComplexity;
7173
}
7274

7375
@Override
7476
public void flatten(final @NotNull Component input, final @NotNull FlattenerListener listener) {
75-
this.flatten0(input, listener, 0);
77+
this.flatten0(input, listener, new State(), 0);
7678
}
7779

78-
private void flatten0(final @NotNull Component input, final @NotNull FlattenerListener listener, final int depth) {
80+
private void flatten0(final @NotNull Component input, final @NotNull FlattenerListener listener, final @NotNull State state, final int depth) {
7981
requireNonNull(input, "input");
8082
requireNonNull(listener, "listener");
8183
if (input == Component.empty()) return;
82-
if (depth > this.maximumDepth) {
83-
throw new IllegalStateException("Exceeded maximum depth of " + this.maximumDepth + " while attempting to flatten components!");
84+
if (this.maximumComplexity != Builder.UNLIMITED_COMPLEXITY && state.complexity > this.maximumComplexity) {
85+
throw new IllegalStateException("Exceeded maximum complexity of " + this.maximumComplexity + " while attempting to flatten components!");
86+
}
87+
if (depth >= MAX_DEPTH) {
88+
throw new IllegalStateException("Exceeded maximum depth of " + MAX_DEPTH + " while attempting to flatten components!");
8489
}
8590

8691
final @Nullable Handler flattener = this.flattener(input);
@@ -89,12 +94,14 @@ private void flatten0(final @NotNull Component input, final @NotNull FlattenerLi
8994
listener.pushStyle(inputStyle);
9095
try {
9196
if (flattener != null) {
92-
flattener.handle(this, input, listener, depth + 1);
97+
state.complexity++;
98+
flattener.handle(this, input, listener, state, depth + 1);
9399
}
94100

95101
if (!input.children().isEmpty() && listener.shouldContinue()) {
96102
for (final Component child : input.children()) {
97-
this.flatten0(child, listener, depth + 1);
103+
state.complexity++;
104+
this.flatten0(child, listener, state, depth + 1);
98105
}
99106
}
100107
} finally {
@@ -106,57 +113,59 @@ private void flatten0(final @NotNull Component input, final @NotNull FlattenerLi
106113
final Handler flattener = this.flatteners.get(test.getClass());
107114

108115
if (flattener == null && this.unknownHandler != null) {
109-
return (self, component, listener, depth) -> listener.component(this.unknownHandler.apply(component));
116+
return (self, component, listener, state, depth) -> listener.component(this.unknownHandler.apply(component));
110117
} else {
111118
return flattener;
112119
}
113120
}
114121

115122
@Override
116123
public ComponentFlattener.@NotNull Builder toBuilder() {
117-
return new BuilderImpl(this.flatteners, this.unknownHandler, this.maximumDepth);
124+
return new BuilderImpl(this.flatteners, this.unknownHandler, this.maximumComplexity);
125+
}
126+
127+
static final class State {
128+
int complexity = 0;
118129
}
119130

120131
// A function that allows nesting other flatten operations
121132
@FunctionalInterface
122133
interface Handler {
123-
void handle(final ComponentFlattenerImpl self, final Component input, final FlattenerListener listener, final int depth);
134+
void handle(final ComponentFlattenerImpl self, final Component input, final FlattenerListener listener, final State state, final int depth);
124135
}
125136

126137
static final class BuilderImpl implements Builder {
127-
private static final int DEFAULT_MAX_DEPTH = 512;
128-
129138
private final InheritanceAwareMap.Builder<Component, Handler> flatteners;
130139
private @Nullable Function<Component, String> unknownHandler;
131-
private int maximumDepth;
140+
private int maximumComplexity;
132141

133142
BuilderImpl() {
134143
this.flatteners = InheritanceAwareMap.<Component, Handler>builder().strict(true);
135-
this.maximumDepth = DEFAULT_MAX_DEPTH;
144+
this.maximumComplexity = UNLIMITED_COMPLEXITY;
136145
}
137146

138-
BuilderImpl(final InheritanceAwareMap<Component, Handler> flatteners, final @Nullable Function<Component, String> unknownHandler, final int maximumDepth) {
147+
BuilderImpl(final InheritanceAwareMap<Component, Handler> flatteners, final @Nullable Function<Component, String> unknownHandler, final int maximumComplexity) {
139148
this.flatteners = InheritanceAwareMap.builder(flatteners).strict(true);
140149
this.unknownHandler = unknownHandler;
141-
this.maximumDepth = maximumDepth;
150+
this.maximumComplexity = maximumComplexity;
142151
}
143152

144153
@Override
145154
public @NotNull ComponentFlattener build() {
146-
return new ComponentFlattenerImpl(this.flatteners.build(), this.unknownHandler, this.maximumDepth);
155+
return new ComponentFlattenerImpl(this.flatteners.build(), this.unknownHandler, this.maximumComplexity);
147156
}
148157

149158
@Override
150159
@SuppressWarnings("unchecked")
151160
public <T extends Component> ComponentFlattener.@NotNull Builder mapper(final @NotNull Class<T> type, final @NotNull Function<T, String> converter) {
152-
this.flatteners.put(type, (self, component, listener, depth) -> listener.component(converter.apply((T) component)));
161+
this.flatteners.put(type, (self, component, listener, state, depth) -> listener.component(converter.apply((T) component)));
153162
return this;
154163
}
155164

156165
@Override
157166
@SuppressWarnings("unchecked")
158167
public <T extends Component> ComponentFlattener.@NotNull Builder complexMapper(final @NotNull Class<T> type, final @NotNull BiConsumer<T, Consumer<Component>> converter) {
159-
this.flatteners.put(type, (self, component, listener, depth) -> converter.accept((T) component, c -> self.flatten0(c, listener, depth)));
168+
this.flatteners.put(type, (self, component, listener, state, depth) -> converter.accept((T) component, c -> self.flatten0(c, listener, state, depth)));
160169
return this;
161170
}
162171

@@ -167,9 +176,11 @@ static final class BuilderImpl implements Builder {
167176
}
168177

169178
@Override
170-
public @NotNull Builder maximumDepth(final int maximumDepth) {
171-
if (maximumDepth <= 0) throw new IllegalArgumentException("maxDepth must be greater than 0, was " + maximumDepth);
172-
this.maximumDepth = maximumDepth;
179+
public @NotNull Builder maximumComplexity(final int maximumComplexity) {
180+
if (maximumComplexity != UNLIMITED_COMPLEXITY && maximumComplexity <= 0) {
181+
throw new IllegalArgumentException("maximumComplexity must be greater than 0, was " + maximumComplexity);
182+
}
183+
this.maximumComplexity = maximumComplexity;
173184
return this;
174185
}
175186
}

api/src/test/java/net/kyori/adventure/text/flattener/ComponentFlattenerTest.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,18 @@
2727
import java.util.Arrays;
2828
import java.util.List;
2929
import java.util.Locale;
30+
import java.util.regex.Matcher;
31+
import java.util.regex.Pattern;
3032
import net.kyori.adventure.text.BlockNBTComponent;
3133
import net.kyori.adventure.text.Component;
3234
import net.kyori.adventure.text.NBTComponent;
3335
import net.kyori.adventure.text.TranslatableComponent;
36+
import net.kyori.adventure.text.TranslationArgument;
3437
import net.kyori.adventure.text.format.NamedTextColor;
3538
import net.kyori.adventure.text.format.Style;
3639
import net.kyori.adventure.text.format.TextDecoration;
3740
import org.jetbrains.annotations.NotNull;
41+
import org.jetbrains.annotations.Nullable;
3842
import org.junit.jupiter.api.Assertions;
3943
import org.junit.jupiter.api.Test;
4044

@@ -289,6 +293,48 @@ void testEarlyExit() {
289293
.assertContents("Hello", "How are you?", "Not great");
290294
}
291295

296+
private static final Pattern PAPERS_WEIRD_LOCALIZATION_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?s");
297+
public static final ComponentFlattener PAPERS_WEIRD_FLATTENER = ComponentFlattener.basic().toBuilder()
298+
.complexMapper(TranslatableComponent.class, (translatable, consumer) -> {
299+
final String key = translatable.key();
300+
final Matcher matcher = PAPERS_WEIRD_LOCALIZATION_PATTERN.matcher(key);
301+
final List<TranslationArgument> args = translatable.arguments();
302+
int argPosition = 0;
303+
int lastIdx = 0;
304+
while (matcher.find()) {
305+
// append prior
306+
if (lastIdx < matcher.start()) {
307+
consumer.accept(Component.text(key.substring(lastIdx, matcher.start())));
308+
}
309+
lastIdx = matcher.end();
310+
311+
final @Nullable String argIdx = matcher.group(1);
312+
// calculate argument position
313+
if (argIdx != null) {
314+
try {
315+
final int idx = Integer.parseInt(argIdx) - 1;
316+
if (idx < args.size()) {
317+
consumer.accept(args.get(idx).asComponent());
318+
}
319+
} catch (final NumberFormatException ex) {
320+
// ignore, drop the format placeholder
321+
}
322+
} else {
323+
final int idx = argPosition++;
324+
if (idx < args.size()) {
325+
consumer.accept(args.get(idx).asComponent());
326+
}
327+
}
328+
}
329+
330+
// append tail
331+
if (lastIdx < key.length()) {
332+
consumer.accept(Component.text(key.substring(lastIdx)));
333+
}
334+
})
335+
.maximumComplexity(32)
336+
.build();
337+
292338
public static Component createNestedComponent(final int depth, final String finalText) {
293339
Component component = Component.text(finalText);
294340

@@ -303,6 +349,6 @@ public static Component createNestedComponent(final int depth, final String fina
303349
void testGiantComponent() {
304350
final Component component = createNestedComponent(34, "only 34?!");
305351
final StringBuilder sb = new StringBuilder();
306-
ComponentFlattener.basic().flatten(component, sb::append);
352+
assertThrows(IllegalStateException.class, () -> PAPERS_WEIRD_FLATTENER.flatten(component, sb::append));
307353
}
308354
}

0 commit comments

Comments
 (0)