• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 277

27 Nov 2025 01:37PM UTC coverage: 78.778% (+0.03%) from 78.749%
277

push

github

adangel
[java] UseArraysAsList: skip when if-statements (#6228)

18419 of 24233 branches covered (76.01%)

Branch coverage included in aggregate %.

40090 of 50038 relevant lines covered (80.12%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

34.07
/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/NodeStream.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.ast;
6

7
import java.util.ArrayList;
8
import java.util.Arrays;
9
import java.util.List;
10
import java.util.NoSuchElementException;
11
import java.util.Objects;
12
import java.util.Optional;
13
import java.util.function.BiFunction;
14
import java.util.function.Consumer;
15
import java.util.function.Function;
16
import java.util.function.Predicate;
17
import java.util.function.ToIntFunction;
18
import java.util.stream.Collector;
19
import java.util.stream.Collectors;
20
import java.util.stream.Stream;
21

22
import org.checkerframework.checker.nullness.qual.NonNull;
23
import org.checkerframework.checker.nullness.qual.Nullable;
24

25
import net.sourceforge.pmd.lang.ast.internal.StreamImpl;
26

27

28
/**
29
 * A sequence of AST nodes. Conceptually similar to a {@link Stream},
30
 * and exposes a specialized API to navigate abstract syntax trees.
31
 * This API can make it as easy as a XPath expression to navigate the tree.
32
 *
33
 * <h2>API usage</h2>
34
 *
35
 * <p>The {@link Node} interface exposes methods like {@link Node#children()}
36
 * or {@link Node#asStream()} to obtain new NodeStreams. Null-safe construction
37
 * methods are available here, see {@link #of(Node)}, {@link #of(Node[])},
38
 * {@link #fromIterable(Iterable)}.
39
 *
40
 * <p>Most functions have an equivalent in the {@link Stream} interface
41
 * and their behaviour is similar. One important departure from the
42
 * {@link Stream} contract is the absence of requirement on the laziness
43
 * of pipeline operations. More on that in the details section below.
44
 *
45
 * <p>Some additional functions are provided to iterate the axes of the
46
 * tree: {@link #children()}, {@link #descendants()}, {@link #descendantsOrSelf()},
47
 * {@link #parents()}, {@link #ancestors()}, {@link #ancestorsOrSelf()},
48
 * {@link #precedingSiblings()}, {@link #followingSiblings()}.
49
 * Filtering and mapping nodes by type is possible through {@link #filterIs(Class)},
50
 * and the specialized {@link #children(Class)}, {@link #descendants(Class)},
51
 * and {@link #ancestors(Class)}.
52
 *
53
 * <p>Many complex predicates about nodes can be expressed by testing
54
 * the emptiness of a node stream. E.g. the following tests if the node
55
 * is a variable declarator id initialized to the value {@code 0}:
56
 * <pre>
57
 *     {@linkplain #of(Node) NodeStream.of}(someNode)                    <i>// the stream here is empty if the node is null</i>
58
 *               {@linkplain #filterIs(Class) .filterIs}(ASTVariableId.class)   <i>// the stream here is empty if the node was not a variable declarator id</i>
59
 *               {@linkplain #followingSiblings() .followingSiblings}()             <i>// the stream here contains only the siblings, not the original node</i>
60
 *               {@linkplain #take(int) .take}(1)                         <i>// the stream here contains only the first sibling, if it exists</i>
61
 *               {@linkplain #filterIs(Class) .filterIs}(ASTNumericLiteral.class)
62
 *               {@linkplain #filter(Predicate) .filter}(it -&gt; !it.isFloatingPoint() &amp;&amp; it.getValueAsInt() == 0)
63
 *               {@linkplain #nonEmpty() .nonEmpty}(); <i>// If the stream is non empty here, then all the pipeline matched</i>
64
 * </pre>
65
 *
66
 * <p>Many existing operations from the node interface can be written with streams too:
67
 * <ul>
68
 * <li>Traverse the children to find the first instance of type childType: <code>node.{@link Node#children(Class) children(t)}.{@link #first()}</code></li>
69
 * <li>Traverse down the tree to find the first descendant instance of type descendantType without crossing find boundaries: <code>node.{@link Node#descendants(Class) descendants(t)}.{@link #first()}</code></li>
70
 * <li>Traverse up the tree to find the first parent instance of type parentType or one of its subclasses: <code>node.{@link Node#ancestors(Class) ancestors(t)}.{@link #first()}</code></li>
71
 * <li>Traverse the children to find all the instances of type childType or one of its subclasses: <code>node.{@link Node#descendants(Class) children(t)}.{@link #toList()}</code></li>
72
 * <li>Traverse down the tree to find all the descendant instances of type descendantType without crossing find boundaries: <code>node.{@link Node#descendants(Class) descendants(t)}.{@link #toList()}</code></li>
73
 * <li>Traverse up the tree to find all of the parent instances of type parentType or one of its subclasses, ordering the nodes deepest first: <code>node.{@link Node#descendants(Class) ancestors(t)}.{@link #toList()}</code></li>
74
 * <li>Get the n-th parent or null if there are less than {@code n} ancestors: <code>node.{@link Node#ancestors() ancestors()}.{@link #get(int) get(n - 1)}</code></li>
75
 * <li>Find if this node contains a descendant of the given type without crossing find boundaries: <code>node.{@link Node#descendants(Class) descendants(t)}.{@link #nonEmpty()}</code></li>
76
 * <li><code>node.getFirstParentOfAnyType(c1, c2) ===  node.{@link Node#ancestors() ancestors()}.{@link #firstNonNull(Function) firstNonNull}({@link #asInstanceOf(Class, Class[]) asInstanceOf(c1, c2)})</code></li>
77
 * <li><code>node.hasDescendantOfAnyType(c1, c2) ===  node.{@link Node#descendants() descendants()}.{@link #map(Function) map}({@link #asInstanceOf(Class, Class[]) asInstanceOf(c1, c2)}).{@link #nonEmpty()}</code></li>
78
 * </ul>
79
 * The new way to write those is as efficient as the old way.
80
 *
81
 * <p>Unlike {@link Stream}s, NodeStreams can be iterated multiple times. That means, that the operations
82
 * that are <i>terminal</i> in the Stream interface (i.e. consume the stream) don't consume NodeStreams.
83
 * Be aware though, that node streams don't cache their results by default, so e.g. calling {@link #count()}
84
 * followed by {@link #toList()} will execute the whole pipeline twice. The elements of a stream can
85
 * however be {@linkplain #cached() cached} at an arbitrary point in the pipeline to evaluate the
86
 * upstream only once. Some construction methods allow building a node stream from an external data
87
 * source, e.g. {@link #fromIterable(Iterable) fromIterable}.
88
 * Depending on how the data source is implemented, the built node streams may be iterable only once.
89
 *
90
 * <p>Node streams may contain duplicates, which can be pruned with {@link #distinct()}.
91
 *
92
 * <h2>Details</h2>
93
 *
94
 * <p>NodeStreams are not necessarily implemented with {@link Stream}, but
95
 * when a method has an equivalent in the {@link Stream} API, their
96
 * contract is similar. The only difference, is that node streams are not
97
 * necessarily lazy, ie, a pipeline operation may be evaluated eagerly
98
 * to improve performance. For this reason, relying on side-effects
99
 * produced in the middle of the pipeline is a bad idea. {@link Stream}
100
 * gives the same guideline about statefulness, but not for the same reason.
101
 * Their justification is parallelism and operation reordering, once
102
 * the pipeline is fully known.
103
 *
104
 * <p>Node streams are meant to be sequential streams, so there is no
105
 * equivalent to {@link Stream#findAny()}. The method {@link #first()}
106
 * is an equivalent to {@link Stream#findFirst()}. There is however a
107
 * {@link #last()} method, which may be implemented efficiently on some
108
 * streams (eg {@link #children()}).
109
 *
110
 * <p>Node streams are most of the time ordered in document order (w.r.t. the XPath specification),
111
 * a.k.a. prefix order. Some operations which explicitly manipulate the order of nodes, like
112
 * {@link #union(NodeStream[]) union} or {@link #append(NodeStream) append}, may not preserve that ordering.
113
 * {@link #map(Function) map} and {@link #flatMap(Function) flatMap} operations may not preserve the ordering
114
 * if the stream has more than one element, since the mapping is applied in order to each element
115
 * of the receiver stream. This extends to methods defined in terms of map or flatMap, e.g.
116
 * {@link #descendants()} or {@link #children()}.
117
 *
118
 * @param <T> Type of nodes this stream contains. This parameter is
119
 *           covariant, which means for maximum flexibility, methods
120
 *           taking a node stream argument should declare it with an
121
 *           "extends" wildcard.
122
 *
123
 * @author Clément Fournier
124
 * @implNote Choosing to wrap a stream instead of extending the interface is to
125
 * allow the functions to return NodeStreams, and to avoid the code bloat
126
 * induced by delegation.
127
 *
128
 * <p>The default implementation relies on the iterator method. From benchmarking,
129
 * that appears more efficient than streams.
130
 *
131
 * @since 7.0.0
132
 */
133
public interface NodeStream<@NonNull T extends Node> extends Iterable<@NonNull T> {
134

135
    /**
136
     * Returns a node stream consisting of the results of replacing each
137
     * node of this stream with the contents of a stream produced by the
138
     * given mapping function. If a mapped stream is null, it is discarded.
139
     *
140
     * <p>If you want to flatMap this node stream to a {@link Stream} with
141
     * arbitrary elements (ie not nodes), use {@link #toStream()} then
142
     * {@link Stream#flatMap(Function)}.
143
     *
144
     * @param mapper A function mapping the elements of this stream to another stream
145
     * @param <R>    Type of nodes contained in the returned stream
146
     *
147
     * @return A flat mapped stream
148
     *
149
     * @see Stream#flatMap(Function)
150
     */
151
    <R extends Node> NodeStream<R> flatMap(Function<? super T, ? extends @Nullable NodeStream<? extends R>> mapper);
152

153
    // lazy pipeline transformations
154

155

156
    /**
157
     * Returns a node stream consisting of the results of applying the given
158
     * mapping function to the node of this stream. If the mapping function
159
     * returns null, the elements are not included.
160
     *
161
     * <p>If you want to map this node stream to a {@link Stream} with
162
     * arbitrary elements (ie not nodes), use {@link #toStream()} then
163
     * {@link Stream#map(Function)}.
164
     *
165
     * @param mapper A function mapping the elements of this stream to another node type
166
     * @param <R>    The node type of the new stream
167
     *
168
     * @return A mapped stream
169
     *
170
     * @see Stream#map(Function)
171
     */
172
    <R extends Node> NodeStream<R> map(Function<? super T, ? extends @Nullable R> mapper);
173

174

175
    /**
176
     * Returns a node stream consisting of the nodes of this stream that match
177
     * the given predicate.
178
     *
179
     * @param predicate A predicate to apply to each node to determine if
180
     *                  it should be included
181
     *
182
     * @return A filtered node stream
183
     *
184
     * @see Stream#filter(Predicate)
185
     * @see #filterNot(Predicate)
186
     * @see #filterIs(Class)
187
     * @see #filterMatching(Function, Object)
188
     */
189
    NodeStream<T> filter(Predicate<? super @NonNull T> predicate);
190

191

192
    /**
193
     * Returns a stream consisting of the elements of this stream, additionally
194
     * performing the provided action on each element as elements are consumed
195
     * from the resulting stream. Note that terminal operations such as {@link #count()}
196
     * don't necessarily execute the action.
197
     *
198
     * @param action an action to perform on the elements as they are consumed
199
     *               from the stream
200
     *
201
     * @return A new stream
202
     */
203
    NodeStream<T> peek(Consumer<? super @NonNull T> action);
204

205

206

207
    /**
208
     * Returns a new node stream that contains all the elements of this stream, then
209
     * all the elements of the given stream.
210
     *
211
     * @param right Other stream
212
     *
213
     * @return A concatenated stream
214
     *
215
     * @see #union(NodeStream[])
216
     */
217
    NodeStream<T> append(NodeStream<? extends T> right);
218

219

220
    /**
221
     * Returns a new node stream that contains all the elements of the given stream,
222
     * then all the elements of this stream.
223
     *
224
     * @param right Other stream
225
     *
226
     * @return A concatenated stream
227
     *
228
     * @see #union(NodeStream[])
229
     */
230
    NodeStream<T> prepend(NodeStream<? extends T> right);
231

232

233
    /**
234
     * Returns a node stream containing all the elements of this node stream,
235
     * but which will evaluate the upstream pipeline only once. The returned
236
     * stream is not necessarily lazy, which means it may evaluate the upstream
237
     * pipeline as soon as the call to this method is made.
238
     *
239
     * <p>This is useful e.g. if you want to call several terminal operations
240
     * without executing the pipeline several times. For example,
241
     *
242
     * <pre>
243
     *
244
     *     NodeStream&lt;T&gt; stream = NodeStream.of(...)
245
     *                                      <i>// long pipeline</i>
246
     *                                      <i>// ...</i>
247
     *                                      .cached()
248
     *                                      <i>// downstream</i>
249
     *                                      <i>// ...</i>
250
     *                                      ;
251
     *
252
     *     stream.forEach(this::addViolation); <i>// both up- and downstream will be evaluated</i>
253
     *     curViolations += stream.count();    <i>// only downstream is evaluated</i>
254
     * </pre>
255
     *
256
     * @return A cached node stream
257
     */
258
    NodeStream<T> cached();
259

260

261
    /**
262
     * Returns a stream consisting of the elements of this stream,
263
     * truncated to be no longer than maxSize in length.
264
     *
265
     * @param maxSize Maximum size of the returned stream
266
     *
267
     * @return A new node stream
268
     *
269
     * @throws IllegalArgumentException if n is negative
270
     * @see Stream#limit(long)
271
     * @see #drop(int)
272
     */
273
    NodeStream<T> take(int maxSize);
274

275

276
    /**
277
     * Returns a stream consisting of the remaining elements of this
278
     * stream after discarding the first n elements of the stream. If
279
     * this stream contains fewer than n elements then an empty stream
280
     * will be returned.
281
     *
282
     * @param n the number of leading elements to skip
283
     *
284
     * @return A new node stream
285
     *
286
     * @throws IllegalArgumentException if n is negative
287
     * @see Stream#skip(long)
288
     * @see #take(int)
289
     * @see #dropLast(int)
290
     */
291
    NodeStream<T> drop(int n);
292

293
    /**
294
     * Returns a stream consisting of the elements of this stream except
295
     * the n tail elements. If n is greater than the number of elements
296
     * of this stream, returns an empty stream. This requires a lookahead
297
     * buffer in general.
298
     *
299
     * @param n the number of trailing elements to skip
300
     *
301
     * @return A new node stream
302
     *
303
     * @throws IllegalArgumentException if n is negative
304
     * @see #drop(int)
305
     */
306
    NodeStream<T> dropLast(int n);
307

308

309
    /**
310
     * Returns the longest prefix of elements that satisfy the given predicate.
311
     *
312
     * @param predicate The predicate used to test elements.
313
     *
314
     * @return the longest prefix of this stream whose elements all satisfy
315
     *     the predicate.
316
     */
317
    NodeStream<T> takeWhile(Predicate<? super T> predicate);
318

319

320
    /**
321
     * Returns a stream consisting of the distinct elements (w.r.t
322
     * {@link Object#equals(Object)}) of this stream.
323
     *
324
     * @return a stream consisting of the distinct elements of this stream
325
     */
326
    NodeStream<T> distinct();
327

328
    // tree navigation
329

330

331
    /**
332
     * Returns a node stream containing all the ancestors of the nodes
333
     * contained in this stream. The returned stream doesn't preserve document
334
     * order, since ancestors are yielded in innermost to outermost order.
335
     *
336
     * <p>This is equivalent to {@code flatMap(Node::ancestors)}.
337
     *
338
     * @return A stream of ancestors
339
     *
340
     * @see Node#ancestors()
341
     * @see #ancestorsOrSelf()
342
     * @see #ancestors(Class)
343
     */
344
    default NodeStream<Node> ancestors() {
345
        return flatMap(Node::ancestors);
1✔
346
    }
347

348

349
    /**
350
     * Returns a node stream containing the nodes contained in this stream and their ancestors.
351
     * The nodes of the returned stream are yielded in a depth-first fashion.
352
     *
353
     * <p>This is equivalent to {@code flatMap(Node::ancestorsOrSelf)}.
354
     *
355
     * @return A stream of ancestors
356
     *
357
     * @see #ancestors()
358
     */
359
    default NodeStream<Node> ancestorsOrSelf() {
360
        return flatMap(Node::ancestorsOrSelf);
1✔
361
    }
362

363

364
    /**
365
     * Returns a node stream containing all the (first-degree) parents of the nodes
366
     * contained in this stream.
367
     *
368
     * <p>This is equivalent to {@code map(Node::getParent)}.
369
     *
370
     * @return A stream of parents
371
     *
372
     * @see #ancestors()
373
     * @see #ancestorsOrSelf()
374
     */
375
    default NodeStream<Node> parents() {
376
        return map(Node::getParent);
×
377
    }
378

379

380
    /**
381
     * Returns a node stream containing all the children of the nodes
382
     * contained in this stream.
383
     *
384
     * <p>This is equivalent to {@code flatMap(Node::children)}.
385
     *
386
     * @return A stream of children
387
     *
388
     * @see Node#children()
389
     * @see #children(Class)
390
     */
391
    default NodeStream<Node> children() {
392
        return flatMap(Node::children);
1✔
393
    }
394

395

396
    /**
397
     * Returns a node stream containing all the strict descendants of the nodes
398
     * contained in this stream. See {@link DescendantNodeStream} for details.
399
     *
400
     * <p>This is equivalent to {@code flatMap(Node::descendants)}, except
401
     * the returned stream is a {@link DescendantNodeStream}.
402
     *
403
     * @return A stream of descendants
404
     *
405
     * @see Node#descendants()
406
     * @see #descendants(Class)
407
     * @see #descendantsOrSelf()
408
     */
409
    DescendantNodeStream<Node> descendants();
410

411

412
    /**
413
     * Returns a node stream containing the nodes contained in this stream and their descendants.
414
     * See {@link DescendantNodeStream} for details.
415
     *
416
     * <p>This is equivalent to {@code flatMap(Node::descendantsOrSelf)}, except
417
     * the returned stream is a {@link DescendantNodeStream}.
418
     *
419
     * @return A stream of descendants
420
     *
421
     * @see Node#descendantsOrSelf()
422
     * @see #descendants()
423
     */
424
    DescendantNodeStream<Node> descendantsOrSelf();
425

426

427
    /**
428
     * Returns a node stream containing all the following siblings of the nodes contained
429
     * in this stream.
430
     *
431
     * @return A stream of siblings
432
     */
433
    default NodeStream<Node> followingSiblings() {
434
        return flatMap(StreamImpl::followingSiblings);
1✔
435
    }
436

437

438
    /**
439
     * Returns a node stream containing all the preceding siblings of the nodes contained
440
     * in this stream. The nodes are yielded from left to right, i.e. in document order.
441
     *
442
     * @return A stream of siblings
443
     */
444
    default NodeStream<Node> precedingSiblings() {
445
        return flatMap(StreamImpl::precedingSiblings);
1✔
446
    }
447

448

449
    /**
450
     * Returns the {@linkplain #children() children stream} of each node
451
     * in this stream, filtered by the given node type.
452
     *
453
     * <p>This is equivalent to {@code children().filterIs(rClass)}.
454
     *
455
     * @param rClass Type of node the returned stream should contain
456
     * @param <R>    Type of node the returned stream should contain
457
     *
458
     * @return A new node stream
459
     *
460
     * @see #filterIs(Class)
461
     * @see Node#children(Class)
462
     */
463
    default <R extends Node> NodeStream<R> children(Class<? extends R> rClass) {
464
        return flatMap(it -> it.children(rClass));
1✔
465
    }
466

467
    /**
468
     * Returns a stream containing the first child of each of the nodes
469
     * in this stream that has the given type.
470
     *
471
     * <p>This is equivalent to {@code flatMap(it -> it.children(rClass).take(1))}.
472
     *
473
     * @param rClass Type of node the returned stream should contain
474
     * @param <R>    Type of node the returned stream should contain
475
     *
476
     * @return A new node stream
477
     *
478
     * @see Node#children(Class)
479
     */
480
    default <R extends Node> NodeStream<R> firstChild(Class<? extends R> rClass) {
481
        return flatMap(it -> it.children(rClass).take(1));
1✔
482
    }
483

484
    /**
485
     * Returns the {@linkplain #descendants() descendant stream} of each node
486
     * in this stream, filtered by the given node type. See {@link DescendantNodeStream}
487
     * for details.
488
     *
489
     * <p>This is equivalent to {@code descendants().filterIs(rClass)}, except
490
     * the returned stream is a {@link DescendantNodeStream}.
491
     *
492
     * @param rClass Type of node the returned stream should contain
493
     * @param <R>    Type of node the returned stream should contain
494
     *
495
     * @return A new node stream
496
     *
497
     * @see #filterIs(Class)
498
     * @see Node#descendants(Class)
499
     */
500
    <R extends Node> DescendantNodeStream<R> descendants(Class<? extends R> rClass);
501

502

503
    /**
504
     * Returns the {@linkplain #ancestors() ancestor stream} of each node
505
     * in this stream, filtered by the given node type.
506
     *
507
     * <p>This is equivalent to {@code ancestors().filterIs(rClass)}.
508
     *
509
     * @param rClass Type of node the returned stream should contain
510
     * @param <R>    Type of node the returned stream should contain
511
     *
512
     * @return A new node stream
513
     *
514
     * @see #filterIs(Class)
515
     * @see Node#ancestors(Class)
516
     */
517
    default <R extends Node> NodeStream<R> ancestors(Class<? extends R> rClass) {
518
        return flatMap(it -> it.ancestors(rClass));
1✔
519
    }
520

521

522
    /**
523
     * Filters the node of this stream using the negation of the given predicate.
524
     *
525
     * <p>This is equivalent to {@code filter(predicate.negate())}
526
     *
527
     * @param predicate A predicate to apply to each node to determine if
528
     *                  it should be included
529
     *
530
     * @return A filtered node stream
531
     *
532
     * @see #filter(Predicate)
533
     */
534
    default NodeStream<T> filterNot(Predicate<? super @NonNull T> predicate) {
535
        return filter(predicate.negate());
×
536
    }
537

538
    // these are shorthands defined relative to filter
539

540

541
    /**
542
     * Filters the nodes of this stream by comparing a value extracted from the nodes
543
     * with the given constant. This takes care of null value by calling
544
     * {@link Objects#equals(Object, Object)}. E.g. to filter nodes that have
545
     * the {@linkplain Node#getImage() image} {@code "a"}, use {@code filterMatching(Node::getImage, "a")}.
546
     *
547
     * <p>This is equivalent to {@code filter(t -> Objects.equals(extractor.apply(t), comparand))}.
548
     *
549
     * @param extractor Function extracting a value from the nodes of this stream
550
     * @param comparand Value to which the extracted value will be compared
551
     * @param <U>       Type of value to compare
552
     *
553
     * @return A filtered node stream
554
     *
555
     * @see #filter(Predicate)
556
     * @see #filterNotMatching(Function, Object)
557
     */
558
    default <U> NodeStream<T> filterMatching(Function<? super @NonNull T, ? extends @Nullable U> extractor, U comparand) {
559
        return filter(t -> Objects.equals(extractor.apply(t), comparand));
×
560
    }
561

562

563
    /**
564
     * Filters the nodes of this stream that are a subtype of the given class.
565
     *
566
     * <p>This is equivalent to {@code filter(rClass::isInstance).map(rClass::cast)}.
567
     *
568
     * @param rClass The type of the nodes of the returned stream
569
     * @param <R>    The type of the nodes of the returned stream
570
     *
571
     * @return A filtered node stream
572
     *
573
     * @see #filter(Predicate)
574
     * @see #asInstanceOf(Class, Class[])
575
     */
576
    @SuppressWarnings("unchecked")
577
    default <R extends Node> NodeStream<R> filterIs(Class<? extends R> rClass) {
578
        return (NodeStream<R>) filter(rClass::isInstance);
×
579
    }
580

581

582
    /**
583
     * Inverse of {@link #filterMatching(Function, Object)}.
584
     *
585
     * @param extractor Function extracting a value from the nodes of this stream
586
     * @param comparand Value to which the extracted value will be compared
587
     * @param <U>       Type of value to compare
588
     *
589
     * @return A filtered node stream
590
     *
591
     * @see #filter(Predicate)
592
     * @see #filterMatching(Function, Object)
593
     */
594
    default <U> NodeStream<T> filterNotMatching(Function<? super @NonNull T, ? extends @Nullable U> extractor, U comparand) {
595
        return filter(t -> !Objects.equals(extractor.apply(t), comparand));
×
596
    }
597

598

599
    // "terminal" operations
600

601

602
    @Override
603
    void forEach(Consumer<? super @NonNull T> action);
604

605

606
    /**
607
     * Reduce the elements of this stream sequentially.
608
     *
609
     * @param identity   Identity element
610
     * @param accumulate Combine an intermediate result with a new node from this stream,
611
     *                   returns the next intermediate result
612
     * @param <R>        Result type
613
     *
614
     * @return The last intermediate result (identity if this stream is empty)
615
     */
616
    default <R> R reduce(R identity, BiFunction<? super R, ? super T, ? extends R> accumulate) {
617
        R result = identity;
×
618
        for (T node : this) {
×
619
            result = accumulate.apply(result, node);
×
620
        }
×
621
        return result;
×
622
    }
623

624
    /**
625
     * Sum the elements of this stream by associating them to an integer.
626
     *
627
     * @param toInt Map an element to an integer, which will be added
628
     *              to the running sum
629
     *              returns the next intermediate result
630
     *
631
     * @return The sum, zero if the stream is empty.
632
     */
633
    default int sumBy(ToIntFunction<? super T> toInt) {
634
        int result = 0;
×
635
        for (T node : this) {
×
636
            result += toInt.applyAsInt(node);
×
637
        }
×
638
        return result;
×
639
    }
640

641

642
    /**
643
     * Returns the number of nodes in this stream.
644
     *
645
     * @return the number of nodes in this stream
646
     */
647
    // ASTs are not so big as to warrant using a 'long' here
648
    int count();
649

650
    /**
651
     * Returns the sum of the value of the function applied to all
652
     * elements of this stream.
653
     *
654
     * @param intMapper Mapping function
655
     *
656
     * @return The sum
657
     */
658
    default int sumByInt(ToIntFunction<? super T> intMapper) {
659
        int sum = 0;
×
660
        for (T item : this) {
×
661
            sum += intMapper.applyAsInt(item);
×
662
        }
×
663
        return sum;
×
664
    }
665

666

667
    /**
668
     * Returns 'true' if the stream has at least one element.
669
     *
670
     * @return 'true' if the stream has at least one element.
671
     *
672
     * @see #isEmpty()
673
     */
674
    boolean nonEmpty();
675

676

677
    /**
678
     * Returns 'true' if the stream has no elements.
679
     *
680
     * @return 'true' if the stream has no elements.
681
     *
682
     * @see #nonEmpty()
683
     */
684
    default boolean isEmpty() {
685
        return !nonEmpty();
1✔
686
    }
687

688

689
    /**
690
     * Returns whether any elements of this stream match the provided predicate.
691
     * If the stream is empty then false is returned and the predicate is not evaluated.
692
     *
693
     * @param predicate The predicate that one element should match for this method to return true
694
     *
695
     * @return true if any elements of the stream match the provided predicate, otherwise false
696
     *
697
     * @see #all(Predicate)
698
     * @see #none(Predicate)
699
     */
700
    boolean any(Predicate<? super T> predicate);
701

702

703
    /**
704
     * Returns whether no elements of this stream match the provided predicate.
705
     * If the stream is empty then true is returned and the predicate is not evaluated.
706
     *
707
     * @param predicate The predicate that no element should match for this method to return true
708
     *
709
     * @return true if either no elements of the stream match the provided predicate or the stream is empty, otherwise false
710
     *
711
     * @see #any(Predicate)
712
     * @see #all(Predicate)
713
     */
714
    boolean none(Predicate<? super T> predicate);
715

716

717
    /**
718
     * Returns whether all elements of this stream match the provided predicate.
719
     * If the stream is empty then true is returned and the predicate is not evaluated.
720
     *
721
     * @param predicate The predicate that all elements should match for this method to return true
722
     *
723
     * @return true if either all elements of the stream match the provided predicate or the stream is empty, otherwise false
724
     *
725
     * @see #any(Predicate)
726
     * @see #none(Predicate)
727
     */
728
    boolean all(Predicate<? super T> predicate);
729

730

731
    /**
732
     * Returns the element at index n in this stream.
733
     * If no such element exists, {@code null} is returned.
734
     *
735
     * <p>This is equivalent to <code>{@link #drop(int) drop(n)}.{@link #first()}</code>.
736
     *
737
     * <p>If you'd rather continue processing the nth element as a node stream,
738
     * you can use <code>{@link #drop(int) drop(n)}.{@link #take(int) take(1)}</code>.
739
     *
740
     * @param n Index of the element to find
741
     *
742
     * @return The nth element of this stream, or {@code null} if it doesn't exist
743
     *
744
     * @throws IllegalArgumentException if n is negative
745
     */
746
    default @Nullable T get(int n) {
747
        return drop(n).first();
×
748
    }
749

750

751
    /**
752
     * Returns the first element of this stream, or {@code null} if the
753
     * stream is empty.
754
     *
755
     * <p>If you'd rather continue processing the first element as a node
756
     * stream, you can use {@link #take(int) take(1)}.
757
     *
758
     * <p>This is equivalent to {@link #get(int) get(0)}.
759
     *
760
     * @return the first element of this stream, or {@code null} if it doesn't exist
761
     *
762
     * @see #first(Predicate)
763
     * @see #first(Class)
764
     * @see #firstOpt()
765
     */
766
    @Nullable T first();
767

768

769
    /**
770
     * Returns the first element of this stream, or throws a {@link NoSuchElementException}
771
     * if the stream is empty.
772
     *
773
     * @return the first element of this stream
774
     *
775
     * @see #first(Predicate)
776
     * @see #first(Class)
777
     * @see #firstOpt()
778
     */
779
    default @NonNull T firstOrThrow() {
780
        T first = first();
×
781
        if (first == null) {
×
782
            throw new NoSuchElementException("Empty node stream");
×
783
        }
784
        return first;
×
785
    }
786

787

788
    /**
789
     * Returns an optional containing the first element of this stream,
790
     * or an empty optional if the stream is empty.
791
     *
792
     * <p>This is equivalent to {@code Optional.ofNullable(first())}.
793
     *
794
     * @return the first element of this stream, or an empty optional if it doesn't exist
795
     *
796
     * @see #first(Predicate)
797
     * @see #first(Class)
798
     * @see #first()
799
     */
800
    default Optional<T> firstOpt() {
801
        return Optional.ofNullable(first());
×
802
    }
803

804

805
    /**
806
     * Returns the first element of this stream that matches the given
807
     * predicate, or {@code null} if there is none.
808
     *
809
     * @param predicate The predicate that one element should match for
810
     *                  this method to return it
811
     *
812
     * @return the first element of this stream that matches the given
813
     * predicate, or {@code null} if it doesn't exist
814
     *
815
     * @see #first()
816
     * @see #first(Class)
817
     */
818
    default @Nullable T first(Predicate<? super T> predicate) {
819
        return filter(predicate).first();
×
820
    }
821

822

823
    /**
824
     * Returns the first element of this stream of the given type, or
825
     * {@code null} if there is none.
826
     *
827
     * @param rClass The type of node to find
828
     * @param <R>    The type of node to find
829
     *
830
     * @return the first element of this stream of the given type, or {@code null} if it doesn't exist
831
     *
832
     * @see #first()
833
     * @see #first(Predicate)
834
     */
835
    default <R extends Node> @Nullable R first(Class<? extends R> rClass) {
836
        return filterIs(rClass).first();
×
837
    }
838

839

840
    /**
841
     * Returns the first element of this stream for which the mapping function
842
     * returns a non-null result. Returns null if there is no such element.
843
     * This is a convenience method to use with {@link #asInstanceOf(Class, Class[])},
844
     * because using just {@link #map(Function) map} followed by {@link #first()}
845
     * will lose the type information and mentioning explicit type arguments
846
     * would be needed.
847
     *
848
     * @param nullableFun Mapper function
849
     * @param <R>         Result type
850
     *
851
     * @return A node, or null
852
     *
853
     * @see #asInstanceOf(Class, Class[])
854
     */
855
    default <R extends Node> @Nullable R firstNonNull(Function<? super @NonNull T, ? extends @Nullable R> nullableFun) {
856
        return map(nullableFun).first();
×
857
    }
858

859

860
    /**
861
     * Returns the last element of this stream, or {@code null} if the
862
     * stream is empty. This may or may not require traversing all the
863
     * elements of the stream.
864
     *
865
     * @return the last element of this stream, or {@code null} if it doesn't exist
866
     */
867
    @Nullable T last();
868

869

870
    /**
871
     * Returns the last element of this stream of the given type, or
872
     * {@code null} if there is none.
873
     *
874
     * @param rClass The type of node to find
875
     * @param <R>    The type of node to find
876
     *
877
     * @return the last element of this stream of the given type, or {@code null} if it doesn't exist
878
     *
879
     * @see #last()
880
     */
881
    default <R extends Node> @Nullable R last(Class<? extends R> rClass) {
882
        return filterIs(rClass).last();
×
883
    }
884

885

886
    /**
887
     * Collects the elements of this node stream using the specified {@link Collector}.
888
     * This is equivalent to {@link #toStream()} followed by {@link Stream#collect(Collector)}.
889
     *
890
     * @param <R>       the type of the result
891
     * @param <A>       the intermediate accumulation type of the {@code Collector}
892
     * @param collector the {@code Collector} describing the reduction
893
     *
894
     * @return the result of the reduction
895
     *
896
     * @see Stream#collect(Collector)
897
     * @see java.util.stream.Collectors
898
     * @see #toList()
899
     * @see #toList(Function)
900
     */
901
    <R, A> R collect(Collector<? super T, A, R> collector);
902

903

904
    /**
905
     * Returns a new stream of Ts having the pipeline of operations
906
     * defined by this node stream. This can be called multiple times.
907
     *
908
     * @return A stream containing the same elements as this node stream
909
     */
910
    Stream<@NonNull T> toStream();
911

912

913
    /**
914
     * Collects the elements of this node stream into a list. Just like
915
     * for {@link Collectors#toList()}, there are no guarantees on the
916
     * type, mutability, serializability, or thread-safety of the returned
917
     * list.
918
     *
919
     * <p>This is equivalent to {@code collect(Collectors.toList())}.
920
     *
921
     * @return a list containing the elements of this stream
922
     *
923
     * @see Collectors#toList()
924
     * @see #collect(Collector)
925
     */
926
    default List<T> toList() {
927
        return collect(Collectors.toList());
×
928
    }
929

930

931
    /**
932
     * Maps the elements of this node stream using the given mapping
933
     * and collects the results into a list.
934
     *
935
     * <p>This is equivalent to {@code collect(Collectors.mapping(mapper, Collectors.toList()))}.
936
     *
937
     * @param mapper Mapping function
938
     * @param <R>    Return type of the mapper, and element type of the returned list
939
     *
940
     * @return a list containing the elements of this stream
941
     *
942
     * @see Collectors#mapping(Function, Collector)
943
     * @see #collect(Collector)
944
     */
945
    default <R> List<R> toList(Function<? super T, ? extends R> mapper) {
946
        return collect(Collectors.mapping(mapper, Collectors.toList()));
1✔
947
    }
948

949

950
    /**
951
     * Returns a node stream containing zero or one node,
952
     * depending on whether the argument is null or not.
953
     *
954
     * <p>If you know the node is not null, you can also
955
     * call <code>node.{@link Node#asStream() asStream()}</code>.
956
     *
957
     * @param node The node to contain
958
     * @param <T>  Element type of the returned stream
959
     *
960
     * @return A new node stream
961
     *
962
     * @see Node#asStream()
963
     */
964
    static <T extends Node> NodeStream<T> of(@Nullable T node) {
965
        // overload the varargs to avoid useless array creation
966
        return node == null ? empty() : StreamImpl.singleton(node);
1✔
967
    }
968

969

970
    // construction
971
    // we ensure here that no node stream may contain null values
972

973

974
    /**
975
     * Returns a new node stream that contains the same elements as the given
976
     * iterable. Null items are filtered out of the resulting stream.
977
     *
978
     * <p>It's possible to map an iterator to a node stream by calling
979
     * {@code fromIterable(() -> iterator)}, but then the returned node stream
980
     * would only be iterable once.
981
     *
982
     * @param iterable Source of nodes
983
     * @param <T>      Type of nodes in the returned node stream
984
     *
985
     * @return A new node stream
986
     */
987
    static <T extends Node> NodeStream<T> fromIterable(Iterable<? extends @Nullable T> iterable) {
988
        return StreamImpl.fromIterable(iterable);
1✔
989
    }
990

991

992
    /**
993
     * Returns a node stream containing zero or one node,
994
     * depending on whether the optional is empty or not.
995
     *
996
     * @param optNode The node to contain
997
     * @param <T>     Element type of the returned stream
998
     *
999
     * @return A new node stream
1000
     *
1001
     * @see #of(Node)
1002
     */
1003
    static <T extends Node> NodeStream<T> ofOptional(Optional<? extends T> optNode) {
1004
        return optNode.map(StreamImpl::<T>singleton).orElseGet(StreamImpl::empty);
1✔
1005
    }
1006

1007

1008
    /**
1009
     * Returns a node stream whose elements are the given nodes
1010
     * in order. Null elements are not part of the resulting node
1011
     * stream.
1012
     *
1013
     * @param nodes The elements of the new stream
1014
     * @param <T>   Element type of the returned stream
1015
     *
1016
     * @return A new node stream
1017
     */
1018
    @SafeVarargs
1019
    static <T extends Node> NodeStream<T> of(T... nodes) {
1020
        return fromIterable(Arrays.asList(nodes));
1✔
1021
    }
1022

1023

1024
    /**
1025
     * Returns a node stream containing all the elements of the given streams,
1026
     * one after the other.
1027
     *
1028
     * @param <T>     The type of stream elements
1029
     * @param streams the streams to flatten
1030
     *
1031
     * @return the concatenation of the input streams
1032
     */
1033
    @SafeVarargs
1034
    static <T extends Node> NodeStream<T> union(NodeStream<? extends T>... streams) {
1035
        return union(Arrays.asList(streams));
1✔
1036
    }
1037

1038

1039
    /**
1040
     * Returns a node stream containing all the elements of the given streams,
1041
     * one after the other.
1042
     *
1043
     * @param <T>     The type of stream elements
1044
     * @param streams the streams to flatten
1045
     *
1046
     * @return the concatenation of the input streams
1047
     */
1048
    static <T extends Node> NodeStream<T> union(Iterable<? extends NodeStream<? extends T>> streams) {
1049
        return StreamImpl.union(streams);
1✔
1050
    }
1051

1052

1053
    /**
1054
     * Returns an empty node stream.
1055
     *
1056
     * @param <T> Expected type of nodes.
1057
     *
1058
     * @return An empty node stream
1059
     */
1060
    static <T extends Node> NodeStream<T> empty() {
1061
        return StreamImpl.empty();
1✔
1062
    }
1063

1064

1065
    /**
1066
     * Applies the given mapping functions to the given upstream in order and merges the
1067
     * results into a new node stream. This allows exploring several paths at once on the
1068
     * same stream. The method is lazy and won't evaluate the upstream pipeline several times.
1069
     *
1070
     * @param upstream Source of the stream
1071
     * @param fst      First mapper
1072
     * @param snd      Second mapper
1073
     * @param rest     Rest of the mappers
1074
     * @param <R>      Common supertype for the element type of the streams returned by the mapping functions
1075
     *
1076
     * @return A merged node stream
1077
     */
1078
    @SafeVarargs // this method is static because of the generic varargs
1079
    static <T extends Node, R extends Node> NodeStream<R> forkJoin(NodeStream<? extends T> upstream,
1080
                                                                   Function<? super @NonNull T, ? extends NodeStream<? extends R>> fst,
1081
                                                                   Function<? super @NonNull T, ? extends NodeStream<? extends R>> snd,
1082
                                                                   Function<? super @NonNull T, ? extends NodeStream<? extends R>>... rest) {
1083
        Objects.requireNonNull(fst);
1✔
1084
        Objects.requireNonNull(snd);
1✔
1085

1086
        List<Function<? super T, ? extends NodeStream<? extends R>>> mappers = new ArrayList<>(rest.length + 2);
1✔
1087
        mappers.add(fst);
1✔
1088
        mappers.add(snd);
1✔
1089
        mappers.addAll(Arrays.asList(rest));
1✔
1090

1091
        Function<? super T, NodeStream<R>> aggregate =
1✔
1092
            t -> NodeStream.<R>union(mappers.stream().map(f -> f.apply(t)).collect(Collectors.toList()));
1✔
1093

1094
        // with forkJoin we know that the stream will be iterated more than twice so we cache the values
1095
        return upstream.cached().flatMap(aggregate);
1✔
1096
    }
1097

1098

1099
    /**
1100
     * Returns a map function, that checks whether the parameter is an
1101
     * instance of any of the given classes. If so, it returns the parameter,
1102
     * otherwise it returns null.
1103
     *
1104
     * <p>This may be used to filter a node stream to those specific
1105
     * classes, for example:
1106
     *
1107
     * <pre>{@code
1108
     *     NodeStream<ASTExpression> exprs = someStream.map(asInstanceOf(ASTInfixExpression.class, ASTCastExpression.class));
1109
     * }</pre>
1110
     *
1111
     * Using this in the middle of a call chain might require passing
1112
     * explicit type arguments:
1113
     *
1114
     * <pre>{@code
1115
     *    ASTTypeDeclaration ts =
1116
     *       node.ancestors()
1117
     *           .<ASTTypeDeclaration>map(asInstanceOf(ASTClassDeclaration.class, ASTEnumDeclaration.class))
1118
     *           .first(); // would not compile without the explicit type arguments
1119
     * }</pre>
1120
     *
1121
     * <p>For this use case the {@link #firstNonNull(Function)} method
1122
     * may be used, which reduces the above to
1123
     *
1124
     * <pre>{@code
1125
     *    ASTTypeDeclaration ts =
1126
     *       node.ancestors().firstNonNull(asInstanceOf(ASTClassDeclaration.class, ASTEnumDeclaration.class));
1127
     * }</pre>
1128
     *
1129
     * @param c1   First type to test
1130
     * @param rest Other types to test
1131
     * @param <O>  Output type
1132
     *
1133
     * @see #firstNonNull(Function)
1134
     */
1135
    @SafeVarargs // this method is static because of the generic varargs
1136
    @SuppressWarnings("unchecked")
1137
    static <O> Function<@Nullable Object, @Nullable O> asInstanceOf(Class<? extends O> c1, Class<? extends O>... rest) {
1138
        if (rest.length == 0) {
×
1139
            return obj -> c1.isInstance(obj) ? (O) obj : null;
×
1140
        }
1141
        return obj -> {
×
1142
            if (c1.isInstance(obj)) {
×
1143
                return (O) obj;
×
1144
            }
1145

1146
            for (Class<? extends O> aClass : rest) {
×
1147
                if (aClass.isInstance(obj)) {
×
1148
                    return (O) obj;
×
1149
                }
1150
            }
1151
            return null;
×
1152
        };
1153
    }
1154

1155

1156
    /**
1157
     * A specialization of {@link NodeStream} that allows configuring
1158
     * tree traversal behaviour when traversing the descendants of a node.
1159
     * Such a stream is returned by methods such as {@link Node#descendants()}.
1160
     * When those methods are called on a stream containing more than one
1161
     * element (eg {@link NodeStream#descendants()}), the configuration
1162
     * applies to each individual traversal.
1163
     *
1164
     * <p>By default, traversal is performed depth-first (prefix order). Eg
1165
     * <pre>{@code
1166
     * A
1167
     * + B
1168
     *   + C
1169
     *   + D
1170
     * + E
1171
     *   + F
1172
     * }</pre>
1173
     * is traversed in the order {@code A, B, C, D, E, F}.
1174
     *
1175
     * <p>By default, traversal also does not cross {@linkplain #crossFindBoundaries(boolean) find boundaries}.
1176
     *
1177
     * @param <T> Type of node this stream contains
1178
     */
1179
    interface DescendantNodeStream<T extends Node> extends NodeStream<T> {
1180

1181
        // TODO stop recursion on an arbitrary boundary
1182
        // TODO breadth-first traversal
1183

1184

1185
        /**
1186
         * Returns a node stream that will not stop the tree traversal
1187
         * when encountering a find boundary. Find boundaries are node
1188
         * that by default stop tree traversals, like class declarations.
1189
         * They are identified via {@link Node#isFindBoundary()}.
1190
         *
1191
         * <p>For example, supposing you have the AST node for the following
1192
         * method:
1193
         * <pre>{@code
1194
         *  void method() {
1195
         *    String outer = "before";
1196
         *
1197
         *    class Local {
1198
         *      void localMethod() {
1199
         *        String local = "local";
1200
         *      }
1201
         *    }
1202
         *
1203
         *    String after = "after";
1204
         *  }
1205
         * }</pre>
1206
         * Then the stream {@code method.descendants(ASTStringLiteral.class)}
1207
         * will only yield the literals {@code "before"} and {@code "after"},
1208
         * because the traversal doesn't go below the local class.
1209
         *
1210
         * <p>Note that traversal is stopped only for the subtree of the
1211
         * find boundary, but continues on the siblings. This is why
1212
         * {@code "after"} is yielded. This is also why {@link #takeWhile(Predicate)}
1213
         * is not a substitute for this method: {@code method.descendants(ASTStringLiteral.class).takeWhile(it -> !it.isFindBoundary)}
1214
         * would yield only {@code "before"}.
1215
         *
1216
         * <p>This behaviour can be opted out of with this method. In the
1217
         * example, the stream {@code method.descendants(ASTStringLiteral.class).crossFindBoundaries()}
1218
         * will yield {@code "before"}, {@code "local"} and {@code "after"}
1219
         * literals.
1220
         *
1221
         * @param cross If true, boundaries will be crossed.
1222
         *
1223
         * @return A new node stream
1224
         */
1225
        DescendantNodeStream<T> crossFindBoundaries(boolean cross);
1226

1227

1228
        /**
1229
         * An alias for {@link #crossFindBoundaries(boolean) crossFindBoundaries(true)}.
1230
         *
1231
         * @return A new node stream
1232
         */
1233
        default DescendantNodeStream<T> crossFindBoundaries() {
1234
            return crossFindBoundaries(true);
1✔
1235
        }
1236

1237
    }
1238

1239

1240
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc