1717
1818import java .util .Arrays ;
1919import java .util .Collection ;
20+ import java .util .Collections ;
2021import java .util .HashSet ;
2122import java .util .LinkedList ;
2223import java .util .List ;
3233import com .vaadin .flow .data .provider .DataGenerator ;
3334import com .vaadin .flow .data .provider .DataProvider ;
3435import com .vaadin .flow .data .provider .KeyMapper ;
36+ import com .vaadin .flow .data .provider .hierarchy .HierarchicalDataProvider .HierarchyFormat ;
3537import com .vaadin .flow .function .SerializableConsumer ;
3638import com .vaadin .flow .function .SerializableSupplier ;
3739import com .vaadin .flow .function .ValueProvider ;
4547import elemental .json .JsonValue ;
4648
4749/**
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>
4854 * {@link HierarchicalDataCommunicator} is a middleware layer between
4955 * {@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}.
5364 * <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
5569 * is represented by a {@link Cache} object, and the root by {@link RootCache}.
5670 * <p>
5771 * Before sending data to the client, the visible range is flattened into a
6074 * method should be used by the component to get an item's depth and apply
6175 * indentation or other visual styling based on hierarchy level.
6276 * <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>
6388 * For each item in the visible range, the communicator generates a client-side
6489 * key using {@link KeyMapper}. This key is used to identify the item on the
6590 * server when the client sends updates or interaction events for that item such
6691 * 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.
7192 *
7293 * @param <T>
7394 * the bean type
@@ -137,15 +158,15 @@ private void requestFlush() {
137158
138159 /**
139160 * 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
141162 * the root level.
142163 * <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.
149170 */
150171 @ Override
151172 public void reset () {
@@ -175,26 +196,38 @@ public void refresh(T item) {
175196
176197 /**
177198 * 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.
181202 * <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.
188209 *
189210 * @since 25.0
190211 * @param item
191212 * the item to refresh
192213 * @param refreshChildren
193214 * 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}
194218 */
195219 public void refresh (T item , boolean refreshChildren ) {
196220 Objects .requireNonNull (item , "Item cannot be null" );
197221
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+
198231 getKeyMapper ().refresh (item );
199232 dataGenerator .refreshData (item );
200233
@@ -311,6 +344,11 @@ public Collection<T> collapse(Collection<T> items) {
311344 if (rootCache != null ) {
312345 rootCache .removeDescendantCacheIf (
313346 (cache ) -> !isExpanded (cache .getParentItem ()));
347+ }
348+
349+ if (getHierarchyFormat ().equals (HierarchyFormat .FLATTENED )) {
350+ reset ();
351+ } else {
314352 requestFlush ();
315353 }
316354
@@ -345,7 +383,11 @@ public Collection<T> expand(Collection<T> items) {
345383 return expandedItemIds .add (getDataProvider ().getId (item ));
346384 }).toList ();
347385
348- requestFlush ();
386+ if (getHierarchyFormat ().equals (HierarchyFormat .FLATTENED )) {
387+ reset ();
388+ } else {
389+ requestFlush ();
390+ }
349391
350392 return expandedItems ;
351393 }
@@ -384,6 +426,10 @@ public boolean isExpanded(T item) {
384426 public int getDepth (T item ) {
385427 Objects .requireNonNull (item , "Item cannot be null" );
386428
429+ if (getHierarchyFormat ().equals (HierarchyFormat .FLATTENED )) {
430+ return getDataProvider ().getDepth (item );
431+ }
432+
387433 if (rootCache == null ) {
388434 return -1 ;
389435 }
@@ -447,8 +493,14 @@ private void resolveIndexPath(Cache<T> cache, int... path) {
447493 preloadRange (cache , index , 1 );
448494 }
449495
496+ if (restPath .length == 0 ) {
497+ // If there is no rest path, we are at the target item
498+ return ;
499+ }
500+
450501 var item = cache .getItem (index );
451- if (restPath .length > 0 && isExpanded (item )) {
502+ if (getHierarchyFormat ().equals (HierarchyFormat .NESTED )
503+ && isExpanded (item )) {
452504 var subCache = cache .ensureSubCache (index ,
453505 () -> getDataProviderChildCount (item ));
454506 resolveIndexPath (subCache , restPath );
@@ -507,7 +559,8 @@ protected List<T> preloadFlatRangeBackward(int start, int length) {
507559 // Checking result.size() > 0 ensures that the start item
508560 // won't be expanded and its descendants won't be included
509561 // in the result.
510- if (isExpanded (item ) && !cache .hasSubCache (index )
562+ if (getHierarchyFormat ().equals (HierarchyFormat .NESTED )
563+ && isExpanded (item ) && !cache .hasSubCache (index )
511564 && result .size () > 0 ) {
512565 var subCache = cache .ensureSubCache (index ,
513566 () -> getDataProviderChildCount (item ));
@@ -556,7 +609,8 @@ protected List<T> preloadFlatRangeForward(int start, int length) {
556609 }
557610
558611 var item = cache .getItem (index );
559- if (isExpanded (item )) {
612+ if (getHierarchyFormat ().equals (HierarchyFormat .NESTED )
613+ && isExpanded (item )) {
560614 cache .ensureSubCache (index ,
561615 () -> getDataProviderChildCount (item ));
562616 }
@@ -604,10 +658,21 @@ private void flush(ExecutionContext context) {
604658 update .commit (nextUpdateId ++);
605659 }
606660
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+
607671 @ SuppressWarnings ("unchecked" )
608672 private Stream <T > fetchDataProviderChildren (T parent , Range range ) {
609673 var query = new HierarchicalQuery <>(range .getStart (), range .length (),
610- getBackEndSorting (), getInMemorySorting (), getFilter (), parent );
674+ getBackEndSorting (), getInMemorySorting (), getFilter (),
675+ getExpandedItemIds (), parent );
611676
612677 return ((HierarchicalDataProvider <T , Object >) getDataProvider ())
613678 .fetchChildren (query ).peek ((item ) -> {
@@ -620,7 +685,8 @@ private Stream<T> fetchDataProviderChildren(T parent, Range range) {
620685
621686 @ SuppressWarnings ("unchecked" )
622687 private int getDataProviderChildCount (T parent ) {
623- var query = new HierarchicalQuery <>(getFilter (), parent );
688+ var query = new HierarchicalQuery <>(getFilter (), getExpandedItemIds (),
689+ parent );
624690
625691 var count = ((HierarchicalDataProvider <T , Object >) getDataProvider ())
626692 .getChildCount (query );
0 commit comments