1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.juneau;
18
19 import static org.apache.juneau.commons.utils.AssertionUtils.*;
20 import static org.apache.juneau.commons.utils.CollectionUtils.*;
21 import static org.apache.juneau.commons.utils.Utils.*;
22
23 import java.text.*;
24 import java.util.*;
25 import java.util.function.*;
26
27 import org.apache.juneau.collections.*;
28 import org.apache.juneau.commons.collections.FluentMap;
29 import org.apache.juneau.commons.utils.*;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 public class BeanTraverseSession extends BeanSession {
51
52
53
54 public static abstract class Builder extends BeanSession.Builder {
55
56 private BeanTraverseContext ctx;
57 private int initialDepth;
58
59
60
61
62
63
64
65 protected Builder(BeanTraverseContext ctx) {
66 super(assertArgNotNull("ctx", ctx).getBeanContext());
67 this.ctx = ctx;
68 initialDepth = ctx.getInitialDepth();
69 }
70
71 @Override
72 public <T> Builder apply(Class<T> type, Consumer<T> apply) {
73 super.apply(type, apply);
74 return this;
75 }
76
77 @Override
78 public Builder debug(Boolean value) {
79 super.debug(value);
80 return this;
81 }
82
83 @Override
84 public Builder locale(Locale value) {
85 super.locale(value);
86 return this;
87 }
88
89
90 @Override
91 public Builder mediaType(MediaType value) {
92 super.mediaType(value);
93 return this;
94 }
95
96 @Override
97 public Builder mediaTypeDefault(MediaType value) {
98 super.mediaTypeDefault(value);
99 return this;
100 }
101
102 @Override
103 public Builder properties(Map<String,Object> value) {
104 super.properties(value);
105 return this;
106 }
107
108 @Override
109 public Builder property(String key, Object value) {
110 super.property(key, value);
111 return this;
112 }
113
114 @Override
115 public Builder timeZone(TimeZone value) {
116 super.timeZone(value);
117 return this;
118 }
119
120 @Override
121 public Builder timeZoneDefault(TimeZone value) {
122 super.timeZoneDefault(value);
123 return this;
124 }
125
126 @Override
127 public Builder unmodifiable() {
128 super.unmodifiable();
129 return this;
130 }
131 }
132
133 private class StackElement {
134 final int depth;
135 final String name;
136 final Object o;
137 final ClassMeta<?> aType;
138
139 StackElement(int depth, String name, Object o, ClassMeta<?> aType) {
140 this.depth = depth;
141 this.name = name;
142 this.o = o;
143 this.aType = aType;
144 }
145
146 String toString(boolean simple) {
147 var sb = new StringBuilder().append('[').append(depth).append(']').append(' ');
148 sb.append(e(name) ? "<noname>" : name).append(':');
149 sb.append(aType.toString(simple));
150 if (aType != aType.getSerializedClassMeta(BeanTraverseSession.this))
151 sb.append('/').append(aType.getSerializedClassMeta(BeanTraverseSession.this).toString(simple));
152 return sb.toString();
153 }
154 }
155
156 private final BeanTraverseContext ctx;
157 private final LinkedList<StackElement> stack = new LinkedList<>();
158 private final Map<Object,Object> set;
159 private BeanPropertyMeta currentProperty;
160 private ClassMeta<?> currentClass;
161 private boolean isBottom;
162
163 public int indent;
164 private int depth;
165
166
167
168
169
170
171 protected BeanTraverseSession(Builder builder) {
172 super(builder);
173 ctx = builder.ctx;
174 indent = builder.initialDepth;
175 if (isDetectRecursions() || isDebug()) {
176 set = new IdentityHashMap<>();
177 } else {
178 set = mape();
179 }
180 }
181
182
183
184
185
186
187
188
189 public final int getInitialDepth() { return ctx.getInitialDepth(); }
190
191
192
193
194
195
196 public final JsonMap getLastLocation() {
197 Predicate<Object> nn = Utils::nn;
198 Predicate<Collection<?>> nec = Utils::ne;
199
200 return JsonMap
201 .create()
202 .appendIf(nn, "currentClass", currentClass)
203 .appendIf(nn, "currentProperty", currentProperty)
204 .appendIf(nec, "stack", stack);
205
206 }
207
208
209
210
211
212
213
214
215
216 public final int getMaxDepth() { return ctx.getMaxDepth(); }
217
218
219
220
221
222
223
224
225 public final boolean isDetectRecursions() { return ctx.isDetectRecursions(); }
226
227
228
229
230
231
232
233
234
235 public final boolean isIgnoreRecursions() { return ctx.isIgnoreRecursions(); }
236
237
238
239
240
241
242
243 protected final ClassMeta<?> getOptionalType(ClassMeta<?> cm) {
244 if (cm.isOptional())
245 return getOptionalType(cm.getElementType());
246 return cm;
247 }
248
249
250
251
252
253
254
255 protected final Object getOptionalValue(Object o) {
256 if (o == null)
257 return null;
258 if (o instanceof Optional<?> o2)
259 return getOptionalValue(o2.orElse(null));
260 return o;
261 }
262
263
264
265
266
267
268
269
270 protected String getStack(boolean full) {
271 var sb = new StringBuilder();
272 stack.forEach(x -> {
273 if (full) {
274 sb.append("\n\t");
275 for (var i = 1; i < x.depth; i++)
276 sb.append(" ");
277 if (x.depth > 0)
278 sb.append("->");
279 sb.append(x.toString(false));
280 } else {
281 sb.append(" > ").append(x.toString(true));
282 }
283 });
284 return sb.toString();
285 }
286
287
288
289
290
291
292
293 protected final static boolean isOptional(ClassMeta<?> cm) {
294 return (nn(cm) && cm.isOptional());
295 }
296
297
298
299
300
301
302
303
304
305 protected final boolean isRoot() { return depth == 1; }
306
307
308
309
310
311
312
313
314 protected void onError(Throwable t, String msg, Object...args) {
315 super.addWarning(msg, args);
316 }
317
318
319
320
321 protected final void pop() {
322 indent--;
323 depth--;
324 if ((isDetectRecursions() || isDebug()) && ! isBottom) {
325 Object o = stack.removeLast().o;
326 Object o2 = set.remove(o);
327 if (o2 == null)
328 onError(null, "Couldn't remove object of type ''{0}'' on attribute ''{1}'' from object stack.", cn(o), stack);
329 }
330 isBottom = false;
331 }
332
333 @Override
334 protected FluentMap<String,Object> properties() {
335 return super.properties()
336 .a("indent", indent)
337 .a("depth", depth);
338 }
339
340
341
342
343
344
345
346
347
348
349
350
351 protected final ClassMeta<?> push(String attrName, Object o, ClassMeta<?> eType) throws BeanRecursionException {
352 indent++;
353 depth++;
354 isBottom = true;
355 if (o == null)
356 return null;
357 var c = o.getClass();
358 var cm = (nn(eType) && c == eType.inner()) ? eType : ((o instanceof ClassMeta) ? (ClassMeta<?>)o : getClassMeta(c));
359 if (cm.isCharSequence() || cm.isNumber() || cm.isBoolean())
360 return cm;
361 if (depth > getMaxDepth())
362 return null;
363 if (isDetectRecursions() || isDebug()) {
364 if (willRecurse(attrName, o, cm))
365 return null;
366 isBottom = false;
367 stack.add(new StackElement(stack.size(), attrName, o, cm));
368 set.put(o, o);
369 }
370 return cm;
371 }
372
373
374
375
376
377
378 protected final void setCurrentClass(ClassMeta<?> currentClass) { this.currentClass = currentClass; }
379
380
381
382
383
384
385 protected final void setCurrentProperty(BeanPropertyMeta currentProperty) { this.currentProperty = currentProperty; }
386
387
388
389
390
391
392 protected final boolean willExceedDepth() {
393 return (depth >= getMaxDepth());
394 }
395
396
397
398
399
400
401
402
403
404
405
406 protected final boolean willRecurse(String attrName, Object o, ClassMeta<?> cm) throws BeanRecursionException {
407 if (! (isDetectRecursions() || isDebug()) || ! set.containsKey(o))
408 return false;
409 if (isIgnoreRecursions() && ! isDebug())
410 return true;
411
412 stack.add(new StackElement(stack.size(), attrName, o, cm));
413 throw new BeanRecursionException("Recursion occurred, stack={0}", getStack(true));
414 }
415 }