23
23
*/
24
24
package net .kyori .adventure .text .flattener ;
25
25
26
+ import java .util .ArrayDeque ;
27
+ import java .util .Deque ;
28
+ import java .util .List ;
26
29
import java .util .function .BiConsumer ;
27
30
import java .util .function .Consumer ;
28
31
import java .util .function .Function ;
36
39
import net .kyori .adventure .util .InheritanceAwareMap ;
37
40
import org .jetbrains .annotations .NotNull ;
38
41
import org .jetbrains .annotations .Nullable ;
42
+ import org .jetbrains .annotations .Range ;
39
43
40
44
import static java .util .Objects .requireNonNull ;
41
45
@@ -64,94 +68,136 @@ final class ComponentFlattenerImpl implements ComponentFlattener {
64
68
65
69
private final InheritanceAwareMap <Component , Handler > flatteners ;
66
70
private final Function <Component , String > unknownHandler ;
71
+ private final int maxNestedDepth ;
67
72
68
- ComponentFlattenerImpl (final InheritanceAwareMap <Component , Handler > flatteners , final @ Nullable Function <Component , String > unknownHandler ) {
73
+ ComponentFlattenerImpl (final InheritanceAwareMap <Component , Handler > flatteners , final @ Nullable Function <Component , String > unknownHandler , final int maxNestedDepth ) {
69
74
this .flatteners = flatteners ;
70
75
this .unknownHandler = unknownHandler ;
76
+ this .maxNestedDepth = maxNestedDepth ;
77
+ }
78
+
79
+ private static final class StackEntry {
80
+ final Component component ;
81
+ final int depth ;
82
+
83
+ StackEntry (final Component component , final int depth ) {
84
+ this .component = component ;
85
+ this .depth = depth ;
86
+ }
71
87
}
72
88
73
89
@ Override
74
90
public void flatten (final @ NotNull Component input , final @ NotNull FlattenerListener listener ) {
75
- this .flatten0 (input , listener , 0 );
91
+ this .flatten0 (input , listener , 0 , 0 );
76
92
}
77
93
78
- private void flatten0 (final @ NotNull Component input , final @ NotNull FlattenerListener listener , final int depth ) {
94
+ private void flatten0 (final @ NotNull Component input , final @ NotNull FlattenerListener listener , final int depth , final int nestedDepth ) {
79
95
requireNonNull (input , "input" );
80
96
requireNonNull (listener , "listener" );
81
97
if (input == Component .empty ()) return ;
82
- if (depth > MAX_DEPTH ) {
83
- throw new IllegalStateException ("Exceeded maximum depth of " + MAX_DEPTH + " while attempting to flatten components!" );
98
+
99
+ if (this .maxNestedDepth != ComponentFlattener .NO_NESTING_LIMIT && nestedDepth > this .maxNestedDepth ) {
100
+ throw new IllegalStateException ("Exceeded maximum nesting depth of " + this .maxNestedDepth + " while attempting to flatten components!" );
84
101
}
85
102
86
- final @ Nullable Handler flattener = this .flattener (input );
87
- final Style inputStyle = input .style ();
103
+ final Deque <StackEntry > componentStack = new ArrayDeque <>();
104
+ final Deque <Style > styleStack = new ArrayDeque <>();
105
+
106
+ // Push the starting component.
107
+ componentStack .push (new StackEntry (input , depth ));
108
+
109
+ while (!componentStack .isEmpty ()) {
110
+ final StackEntry entry = componentStack .pop ();
111
+ final int currentDepth = entry .depth ;
112
+
113
+ if (currentDepth > MAX_DEPTH ) {
114
+ throw new IllegalStateException ("Exceeded maximum depth of " + MAX_DEPTH + " while attempting to flatten components!" );
115
+ }
116
+
117
+ final Component component = entry .component ;
118
+ final @ Nullable Handler flattener = this .flattener (component );
119
+ final Style componentStyle = component .style ();
120
+
121
+ // Push the style to both the listener and the stack (so we can pop later).
122
+ listener .pushStyle (componentStyle );
123
+ styleStack .push (componentStyle );
88
124
89
- listener .pushStyle (inputStyle );
90
- try {
91
125
if (flattener != null ) {
92
- flattener .handle (this , input , listener , depth + 1 );
126
+ flattener .handle (this , component , listener , currentDepth , nestedDepth );
93
127
}
94
128
95
- if (!input .children ().isEmpty () && listener .shouldContinue ()) {
96
- for (final Component child : input .children ()) {
97
- this .flatten0 (child , listener , depth + 1 );
129
+ if (!component .children ().isEmpty () && listener .shouldContinue ()) {
130
+ // Push any children onto the stack in reverse order so they are popped in the right order.
131
+ final List <Component > children = component .children ();
132
+ for (int i = children .size () - 1 ; i >= 0 ; i --) {
133
+ componentStack .push (new StackEntry (children .get (i ), currentDepth + 1 ));
98
134
}
135
+ } else {
136
+ // If there are no children, we pop the latest style to go back "up" the tree.
137
+ final Style style = styleStack .pop ();
138
+ listener .popStyle (style );
99
139
}
100
- } finally {
101
- listener .popStyle (inputStyle );
140
+ }
141
+
142
+ // Pop any remaining styles at the end.
143
+ while (!styleStack .isEmpty ()) {
144
+ final Style style = styleStack .pop ();
145
+ listener .popStyle (style );
102
146
}
103
147
}
104
148
105
149
private <T extends Component > @ Nullable Handler flattener (final T test ) {
106
150
final Handler flattener = this .flatteners .get (test .getClass ());
107
151
108
152
if (flattener == null && this .unknownHandler != null ) {
109
- return (self , component , listener , depth ) -> listener .component (this .unknownHandler .apply (component ));
153
+ return (self , component , listener , depth , nestedDepth ) -> listener .component (this .unknownHandler .apply (component ));
110
154
} else {
111
155
return flattener ;
112
156
}
113
157
}
114
158
115
159
@ Override
116
160
public ComponentFlattener .@ NotNull Builder toBuilder () {
117
- return new BuilderImpl (this .flatteners , this .unknownHandler );
161
+ return new BuilderImpl (this .flatteners , this .unknownHandler , this . maxNestedDepth );
118
162
}
119
163
120
164
// A function that allows nesting other flatten operations
121
165
@ FunctionalInterface
122
166
interface Handler {
123
- void handle (final ComponentFlattenerImpl self , final Component input , final FlattenerListener listener , final int depth );
167
+ void handle (final ComponentFlattenerImpl self , final Component input , final FlattenerListener listener , final int depth , final int nestedDepth );
124
168
}
125
169
126
170
static final class BuilderImpl implements Builder {
127
171
private final InheritanceAwareMap .Builder <Component , Handler > flatteners ;
128
172
private @ Nullable Function <Component , String > unknownHandler ;
173
+ private int maxNestedDepth = ComponentFlattener .NO_NESTING_LIMIT ;
129
174
130
175
BuilderImpl () {
131
176
this .flatteners = InheritanceAwareMap .<Component , Handler >builder ().strict (true );
132
177
}
133
178
134
- BuilderImpl (final InheritanceAwareMap <Component , Handler > flatteners , final @ Nullable Function <Component , String > unknownHandler ) {
179
+ BuilderImpl (final InheritanceAwareMap <Component , Handler > flatteners , final @ Nullable Function <Component , String > unknownHandler , final int maxNestedDepth ) {
135
180
this .flatteners = InheritanceAwareMap .builder (flatteners ).strict (true );
136
181
this .unknownHandler = unknownHandler ;
182
+ this .maxNestedDepth = maxNestedDepth ;
137
183
}
138
184
139
185
@ Override
140
186
public @ NotNull ComponentFlattener build () {
141
- return new ComponentFlattenerImpl (this .flatteners .build (), this .unknownHandler );
187
+ return new ComponentFlattenerImpl (this .flatteners .build (), this .unknownHandler , this . maxNestedDepth );
142
188
}
143
189
144
190
@ Override
145
191
@ SuppressWarnings ("unchecked" )
146
192
public <T extends Component > ComponentFlattener .@ NotNull Builder mapper (final @ NotNull Class <T > type , final @ NotNull Function <T , String > converter ) {
147
- this .flatteners .put (type , (self , component , listener , depth ) -> listener .component (converter .apply ((T ) component )));
193
+ this .flatteners .put (type , (self , component , listener , depth , nestedDepth ) -> listener .component (converter .apply ((T ) component )));
148
194
return this ;
149
195
}
150
196
151
197
@ Override
152
198
@ SuppressWarnings ("unchecked" )
153
199
public <T extends Component > ComponentFlattener .@ NotNull Builder complexMapper (final @ NotNull Class <T > type , final @ NotNull BiConsumer <T , Consumer <Component >> converter ) {
154
- this .flatteners .put (type , (self , component , listener , depth ) -> converter .accept ((T ) component , c -> self .flatten0 (c , listener , depth )));
200
+ this .flatteners .put (type , (self , component , listener , depth , nestedDepth ) -> converter .accept ((T ) component , c -> self .flatten0 (c , listener , depth , nestedDepth + 1 )));
155
201
return this ;
156
202
}
157
203
@@ -160,5 +206,13 @@ static final class BuilderImpl implements Builder {
160
206
this .unknownHandler = converter ;
161
207
return this ;
162
208
}
209
+
210
+ @ Override
211
+ public @ NotNull Builder nestingLimit (final @ Range (from = 1 , to = Integer .MAX_VALUE ) int limit ) {
212
+ // noinspection ConstantValue (the Range annotation tells IDEA this will never happen, but API users could ignore that)
213
+ if (limit != ComponentFlattener .NO_NESTING_LIMIT && limit < 1 ) throw new IllegalArgumentException ("limit must be positive or ComponentFlattener.NO_NESTING_LIMIT" );
214
+ this .maxNestedDepth = limit ;
215
+ return this ;
216
+ }
163
217
}
164
218
}
0 commit comments