17
17
18
18
import java .util .Arrays ;
19
19
import java .util .Collection ;
20
+ import java .util .Collections ;
20
21
import java .util .HashSet ;
21
22
import java .util .LinkedList ;
22
23
import java .util .List ;
32
33
import com .vaadin .flow .data .provider .DataGenerator ;
33
34
import com .vaadin .flow .data .provider .DataProvider ;
34
35
import com .vaadin .flow .data .provider .KeyMapper ;
36
+ import com .vaadin .flow .data .provider .hierarchy .HierarchicalDataProvider .HierarchyFormat ;
35
37
import com .vaadin .flow .function .SerializableConsumer ;
36
38
import com .vaadin .flow .function .SerializableSupplier ;
37
39
import com .vaadin .flow .function .ValueProvider ;
45
47
import elemental .json .JsonValue ;
46
48
47
49
/**
50
+ * WARNING: Direct use of this class in application code is not recommended and
51
+ * may result in unexpected behavior. Use the API provided by the component
52
+ * instead.
53
+ * <p>
48
54
* {@link HierarchicalDataCommunicator} is a middleware layer between
49
55
* {@link HierarchicalDataProvider} and the client-side. It handles the loading
50
- * and caching of hierarchical data from the data provider, tracks expanded and
51
- * collapsed items, and delivers data to the client based on the
52
- * {@link #setViewportRange(int, int) requested viewport range}.
56
+ * and caching of hierarchical data from the data provider based on its
57
+ * hierarchy format, tracks expanded and collapsed items, and delivers data to
58
+ * the client based on the {@link #setViewportRange(int, int) requested viewport
59
+ * range}.
60
+ * <p>
61
+ * The communicator supports data providers that implement in one of the
62
+ * following formats: {@link HierarchyFormat#NESTED} or
63
+ * {@link HierarchyFormat#FLATTENED}.
53
64
* <p>
54
- * Internally, it stores data in a hierarchical cache structure where each level
65
+ * <strong>Nested Hierarchy Format</strong>
66
+ * <p>
67
+ * When using data providers with {@link HierarchyFormat#NESTED}, the
68
+ * communicator stores data in a hierarchical cache structure where each level
55
69
* is represented by a {@link Cache} object, and the root by {@link RootCache}.
56
70
* <p>
57
71
* Before sending data to the client, the visible range is flattened into a
60
74
* method should be used by the component to get an item's depth and apply
61
75
* indentation or other visual styling based on hierarchy level.
62
76
* <p>
77
+ * <strong>Flattened Hierarchy Format</strong>
78
+ * <p>
79
+ * When using data providers whose format is {@link HierarchyFormat#FLATTENED},
80
+ * the communicator maintains all items in a single flat list, managed through
81
+ * {@link RootCache}, which is directly suitable for client-side rendering
82
+ * without any additional processing. The {@link #getDepth(Object)} method uses
83
+ * the data provider's implementation to determine the depth of an item in the
84
+ * hierarchy.
85
+ * <p>
86
+ * <strong>KeyMapper</strong>
87
+ * <p>
63
88
* For each item in the visible range, the communicator generates a client-side
64
89
* key using {@link KeyMapper}. This key is used to identify the item on the
65
90
* server when the client sends updates or interaction events for that item such
66
91
* as selection, expansion, etc.
67
- * <p>
68
- * WARNING: It's not recommended to rely on this class directly in application
69
- * code. Instead, the API provided by the component should be used. Direct use
70
- * may lead to unexpected behavior and isn't guaranteed to be stable.
71
92
*
72
93
* @param <T>
73
94
* the bean type
@@ -137,15 +158,15 @@ private void requestFlush() {
137
158
138
159
/**
139
160
* Clears all cached data and recursively re-fetches items from hierarchy
140
- * levels that happen to be within the current viewport range, starting from
161
+ * levels that are still within the current viewport range, starting from
141
162
* the root level.
142
163
* <p>
143
- * WARNING: This method performs a full hierarchy reset which discards
144
- * information about previously visited expanded items and their positions
145
- * in the hierarchy. As a result, the viewport's start index may become
146
- * pointing to a different item if there were visited expanded items before
147
- * the start index, which can cause a shift in the currently displayed
148
- * items.
164
+ * WARNING: For data providers that use {@link HierarchyFormat#NESTED}, this
165
+ * method will clear all cached hierarchy state, discarding any potential
166
+ * information about the positions of expanded items in the hierarchy. As a
167
+ * result, the viewport's start index may become pointing to a different
168
+ * item if there were cached expanded items before the start index, causing
169
+ * a shift in the currently displayed items.
149
170
*/
150
171
@ Override
151
172
public void reset () {
@@ -175,26 +196,38 @@ public void refresh(T item) {
175
196
176
197
/**
177
198
* Replaces the cached item with a new instance and schedules a client
178
- * update to re-render this item. If {@code refreshChildren} is true, the
179
- * item's children are cleared from the cache and forced to be re-fetched
180
- * from the data provider when visible.
199
+ * update to re-render this item. When {@code refreshChildren} is true, the
200
+ * item's sub-hierarchy is cleared from the cache and scheduled to be
201
+ * re-fetched from the data provider once visible.
181
202
* <p>
182
- * WARNING: When {@code refreshChildren} is true, the method resets the
183
- * item's hierarchy, which may in turn cause visible range shifts if the
184
- * refreshed item contains expanded children . In such cases, their
185
- * descendants might not be re-fetched immediately, which can affect the
186
- * flattened hierarchy size and result in the viewport range pointing to a
187
- * different set of items than before the refresh.
203
+ * WARNING: This method is only supported with data providers that use
204
+ * {@link HierarchyFormat#NESTED} and may cause visible range shift if the
205
+ * refreshed item contains <i> expanded</i> descendants . In such cases, they
206
+ * might not be re-fetched immediately if they are not visible. This can
207
+ * affect the flattened hierarchy size and result in the viewport range
208
+ * pointing to a different set of items than before the refresh.
188
209
*
189
210
* @since 25.0
190
211
* @param item
191
212
* the item to refresh
192
213
* @param refreshChildren
193
214
* whether or not to refresh child items
215
+ * @throws UnsupportedOperationException
216
+ * if {@code refreshChildren} is true and the data provider's
217
+ * hierarchy format is not {@link HierarchyFormat#NESTED}
194
218
*/
195
219
public void refresh (T item , boolean refreshChildren ) {
196
220
Objects .requireNonNull (item , "Item cannot be null" );
197
221
222
+ if (!getHierarchyFormat ().equals (HierarchyFormat .NESTED )
223
+ && refreshChildren ) {
224
+ throw new UnsupportedOperationException (
225
+ """
226
+ Refreshing children of an item is only supported when the data provider \
227
+ uses HierarchyFormat#NESTED. For other formats, use reset() instead.
228
+ """ );
229
+ }
230
+
198
231
getKeyMapper ().refresh (item );
199
232
dataGenerator .refreshData (item );
200
233
@@ -311,6 +344,11 @@ public Collection<T> collapse(Collection<T> items) {
311
344
if (rootCache != null ) {
312
345
rootCache .removeDescendantCacheIf (
313
346
(cache ) -> !isExpanded (cache .getParentItem ()));
347
+ }
348
+
349
+ if (getHierarchyFormat ().equals (HierarchyFormat .FLATTENED )) {
350
+ reset ();
351
+ } else {
314
352
requestFlush ();
315
353
}
316
354
@@ -345,7 +383,11 @@ public Collection<T> expand(Collection<T> items) {
345
383
return expandedItemIds .add (getDataProvider ().getId (item ));
346
384
}).toList ();
347
385
348
- requestFlush ();
386
+ if (getHierarchyFormat ().equals (HierarchyFormat .FLATTENED )) {
387
+ reset ();
388
+ } else {
389
+ requestFlush ();
390
+ }
349
391
350
392
return expandedItems ;
351
393
}
@@ -384,6 +426,10 @@ public boolean isExpanded(T item) {
384
426
public int getDepth (T item ) {
385
427
Objects .requireNonNull (item , "Item cannot be null" );
386
428
429
+ if (getHierarchyFormat ().equals (HierarchyFormat .FLATTENED )) {
430
+ return getDataProvider ().getDepth (item );
431
+ }
432
+
387
433
if (rootCache == null ) {
388
434
return -1 ;
389
435
}
@@ -447,8 +493,14 @@ private void resolveIndexPath(Cache<T> cache, int... path) {
447
493
preloadRange (cache , index , 1 );
448
494
}
449
495
496
+ if (restPath .length == 0 ) {
497
+ // If there is no rest path, we are at the target item
498
+ return ;
499
+ }
500
+
450
501
var item = cache .getItem (index );
451
- if (restPath .length > 0 && isExpanded (item )) {
502
+ if (getHierarchyFormat ().equals (HierarchyFormat .NESTED )
503
+ && isExpanded (item )) {
452
504
var subCache = cache .ensureSubCache (index ,
453
505
() -> getDataProviderChildCount (item ));
454
506
resolveIndexPath (subCache , restPath );
@@ -507,7 +559,8 @@ protected List<T> preloadFlatRangeBackward(int start, int length) {
507
559
// Checking result.size() > 0 ensures that the start item
508
560
// won't be expanded and its descendants won't be included
509
561
// in the result.
510
- if (isExpanded (item ) && !cache .hasSubCache (index )
562
+ if (getHierarchyFormat ().equals (HierarchyFormat .NESTED )
563
+ && isExpanded (item ) && !cache .hasSubCache (index )
511
564
&& result .size () > 0 ) {
512
565
var subCache = cache .ensureSubCache (index ,
513
566
() -> getDataProviderChildCount (item ));
@@ -556,7 +609,8 @@ protected List<T> preloadFlatRangeForward(int start, int length) {
556
609
}
557
610
558
611
var item = cache .getItem (index );
559
- if (isExpanded (item )) {
612
+ if (getHierarchyFormat ().equals (HierarchyFormat .NESTED )
613
+ && isExpanded (item )) {
560
614
cache .ensureSubCache (index ,
561
615
() -> getDataProviderChildCount (item ));
562
616
}
@@ -604,10 +658,21 @@ private void flush(ExecutionContext context) {
604
658
update .commit (nextUpdateId ++);
605
659
}
606
660
661
+ private HierarchyFormat getHierarchyFormat () {
662
+ return getDataProvider ().getHierarchyFormat ();
663
+ }
664
+
665
+ private Set <Object > getExpandedItemIds () {
666
+ return getHierarchyFormat ().equals (HierarchyFormat .FLATTENED )
667
+ ? Collections .unmodifiableSet (this .expandedItemIds )
668
+ : Collections .emptySet ();
669
+ }
670
+
607
671
@ SuppressWarnings ("unchecked" )
608
672
private Stream <T > fetchDataProviderChildren (T parent , Range range ) {
609
673
var query = new HierarchicalQuery <>(range .getStart (), range .length (),
610
- getBackEndSorting (), getInMemorySorting (), getFilter (), parent );
674
+ getBackEndSorting (), getInMemorySorting (), getFilter (),
675
+ getExpandedItemIds (), parent );
611
676
612
677
return ((HierarchicalDataProvider <T , Object >) getDataProvider ())
613
678
.fetchChildren (query ).peek ((item ) -> {
@@ -620,7 +685,8 @@ private Stream<T> fetchDataProviderChildren(T parent, Range range) {
620
685
621
686
@ SuppressWarnings ("unchecked" )
622
687
private int getDataProviderChildCount (T parent ) {
623
- var query = new HierarchicalQuery <>(getFilter (), parent );
688
+ var query = new HierarchicalQuery <>(getFilter (), getExpandedItemIds (),
689
+ parent );
624
690
625
691
var count = ((HierarchicalDataProvider <T , Object >) getDataProvider ())
626
692
.getChildCount (query );
0 commit comments