Skip to content

Commit f995320

Browse files
sclassenmcollovatimshabarov
authored
feat: extract interface HierarchicalTreeData from TreeData (#23950)
* feat: extract interface HierarchicalTreeData from TreeData TreeDataProvider currently takes a TreeData as its source. But it relies only on a few methods of TreeData. This change extracts those methods into an interface. It then introduces a super class to TreeDataProvider which accepts a HierarchicalTreeData instead of a TreeData. A client has now the choice to either use the TreeData and the TreeDataProvider, or it can implement the HierarchicalTreeData interface and use the HierarchicalTreeDataProvider. This change introduces a new interface and a class but does not change or break any existing API. Fixes #9808 * feature: introduce second generic param in HierarchicalTreeDataProvider This second gerenic parameter represents the concrete type of the HierarchicalTreeData. Having this as a generic parameter allows for better type safty. The client of the provider can get the underlying data structure in a type safe manner. This eases writing stable code. * refactor: renaming classes and parameters - rename HierarchicalTreeDataProvider.java -> InMemoryHierarchicalDataProvider.java - rename HierarchicalTreeData.java -> HierarchicalData.java - add method InMemoryHierarchicalDataProvider.getHierarchicalData() * feat: remove unnecessary deprecated method --------- Co-authored-by: Marco Collovati <marco@vaadin.com> Co-authored-by: Mikhail Shabarov <61410877+mshabarov@users.noreply.github.com>
1 parent b068eec commit f995320

4 files changed

Lines changed: 309 additions & 190 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2000-2026 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.data.provider.hierarchy;
17+
18+
import java.io.Serializable;
19+
import java.util.List;
20+
21+
/**
22+
* Represents hierarchical data.
23+
* <p>
24+
* Typically used as a backing data source for
25+
* {@link InMemoryHierarchicalDataProvider}.
26+
*
27+
* @author Vaadin Ltd
28+
* @since 25.2
29+
*
30+
* @param <T>
31+
* data type
32+
*/
33+
public interface HierarchicalData<T> extends Serializable {
34+
35+
/**
36+
* Get the immediate child items for the given item.
37+
*
38+
* @param item
39+
* the item for which to retrieve child items for, null to
40+
* retrieve all root items
41+
* @return an unmodifiable list of child items for the given item
42+
*
43+
* @throws IllegalArgumentException
44+
* if the item does not exist in this structure
45+
*/
46+
List<T> getChildren(T item);
47+
48+
/**
49+
* Get the parent item for the given item.
50+
*
51+
* @param item
52+
* the item for which to retrieve the parent item for
53+
* @return parent item for the given item or {@code null} if the item is a
54+
* root item.
55+
* @throws IllegalArgumentException
56+
* if the item does not exist in this structure
57+
*/
58+
T getParent(T item);
59+
60+
/**
61+
* Check whether the given item is in this hierarchy.
62+
*
63+
* @param item
64+
* the item to check
65+
* @return {@code true} if the item is in this hierarchy, {@code false} if
66+
* not
67+
*/
68+
boolean contains(T item);
69+
}
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
/*
2+
* Copyright 2000-2026 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.data.provider.hierarchy;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.Comparator;
21+
import java.util.List;
22+
import java.util.Objects;
23+
import java.util.Optional;
24+
import java.util.Set;
25+
import java.util.stream.Stream;
26+
27+
import com.vaadin.flow.data.provider.InMemoryDataProvider;
28+
import com.vaadin.flow.function.SerializableComparator;
29+
import com.vaadin.flow.function.SerializablePredicate;
30+
31+
/**
32+
* An in-memory data provider for listing components that display hierarchical
33+
* data. Uses an instance of {@link HierarchicalData} as its source of data.
34+
*
35+
* @author Vaadin Ltd
36+
* @since 25.2
37+
*
38+
* @param <T>
39+
* data type
40+
* @param <U>
41+
* concrete type of the {@link HierarchicalData} used in this
42+
* provider.
43+
*/
44+
public class InMemoryHierarchicalDataProvider<T, U extends HierarchicalData<T>>
45+
extends AbstractHierarchicalDataProvider<T, SerializablePredicate<T>>
46+
implements InMemoryDataProvider<T> {
47+
48+
private final U hierarchicalData;
49+
50+
private SerializablePredicate<T> filter = null;
51+
52+
private SerializableComparator<T> sortOrder = null;
53+
54+
private HierarchyFormat hierarchyFormat = HierarchyFormat.NESTED;
55+
56+
/**
57+
* Constructs a new InMemoryHierarchicalDataProvider.
58+
* <p>
59+
* The data provider should be refreshed after making changes to the
60+
* underlying {@link HierarchicalData} instance.
61+
*
62+
* @param hierarchicalData
63+
* the backing {@link HierarchicalData} for this provider, not
64+
* {@code null}
65+
*/
66+
public InMemoryHierarchicalDataProvider(U hierarchicalData) {
67+
this.hierarchicalData = Objects.requireNonNull(hierarchicalData,
68+
"hierarchicalData cannot be null");
69+
}
70+
71+
/**
72+
* Creates a new InMemoryHierarchicalDataProvider and configures it to
73+
* return the hierarchical data in the specified format:
74+
* {@link HierarchyFormat#NESTED} or {@link HierarchyFormat#FLATTENED}.
75+
* <p>
76+
* The data provider should be refreshed after making changes to the
77+
* underlying {@link HierarchicalData} instance.
78+
*
79+
* @param hierarchicalData
80+
* the backing {@link HierarchicalData} for this provider, not
81+
* {@code null}
82+
* @param hierarchyFormat
83+
* the hierarchy format to return data in, not {@code null}
84+
*/
85+
public InMemoryHierarchicalDataProvider(U hierarchicalData,
86+
HierarchyFormat hierarchyFormat) {
87+
this(hierarchicalData);
88+
this.hierarchyFormat = Objects.requireNonNull(hierarchyFormat,
89+
"hierarchyFormat cannot be null");
90+
}
91+
92+
@Override
93+
public HierarchyFormat getHierarchyFormat() {
94+
return hierarchyFormat;
95+
}
96+
97+
/**
98+
* Return the underlying {@link HierarchicalData} of this provider.
99+
*
100+
* @return the underlying data of this provider
101+
*/
102+
public U getHierarchicalData() {
103+
return hierarchicalData;
104+
}
105+
106+
@Override
107+
public boolean hasChildren(T item) {
108+
if (!hierarchicalData.contains(item)) {
109+
// The item might be dropped from the tree already
110+
return false;
111+
}
112+
return !hierarchicalData.getChildren(item).isEmpty();
113+
}
114+
115+
@Override
116+
public T getParent(T item) {
117+
Objects.requireNonNull(item, "Item cannot be null.");
118+
try {
119+
return hierarchicalData.getParent(item);
120+
} catch (IllegalArgumentException e) {
121+
return null;
122+
}
123+
}
124+
125+
@Override
126+
public int getDepth(T item) {
127+
int depth = 0;
128+
while ((item = hierarchicalData.getParent(item)) != null) {
129+
depth++;
130+
}
131+
return depth;
132+
}
133+
134+
@Override
135+
public int getChildCount(
136+
HierarchicalQuery<T, SerializablePredicate<T>> query) {
137+
Optional<SerializablePredicate<T>> combinedFilter = getCombinedFilter(
138+
query.getFilter());
139+
140+
return (int) flatten(query.getParent(), query.getExpandedItemIds(),
141+
combinedFilter, Optional.empty()).stream()
142+
.skip(query.getOffset()).limit(query.getLimit()).count();
143+
}
144+
145+
@Override
146+
public Stream<T> fetchChildren(
147+
HierarchicalQuery<T, SerializablePredicate<T>> query) {
148+
if (!hierarchicalData.contains(query.getParent())) {
149+
throw new IllegalArgumentException("The queried item "
150+
+ query.getParent()
151+
+ " could not be found in the backing HierarchicalData. "
152+
+ "Did you forget to refresh this data provider after item removal?");
153+
}
154+
155+
Optional<SerializablePredicate<T>> combinedFilter = getCombinedFilter(
156+
query.getFilter());
157+
158+
Optional<Comparator<T>> comparator = Stream
159+
.of(query.getInMemorySorting(), sortOrder)
160+
.filter(Objects::nonNull).reduce(Comparator::thenComparing);
161+
162+
return flatten(query.getParent(), query.getExpandedItemIds(),
163+
combinedFilter, comparator).stream().skip(query.getOffset())
164+
.limit(query.getLimit());
165+
}
166+
167+
@Override
168+
public SerializablePredicate<T> getFilter() {
169+
return filter;
170+
}
171+
172+
@Override
173+
public void setFilter(SerializablePredicate<T> filter) {
174+
this.filter = filter;
175+
refreshAll();
176+
}
177+
178+
@Override
179+
public SerializableComparator<T> getSortComparator() {
180+
return sortOrder;
181+
}
182+
183+
@Override
184+
public void setSortComparator(SerializableComparator<T> comparator) {
185+
sortOrder = comparator;
186+
refreshAll();
187+
}
188+
189+
private Optional<SerializablePredicate<T>> getCombinedFilter(
190+
Optional<SerializablePredicate<T>> queryFilter) {
191+
return filter != null
192+
? Optional.of(queryFilter.map(filter::and).orElse(filter))
193+
: queryFilter;
194+
}
195+
196+
private List<T> flatten(T parent, Set<Object> expandedItemIds,
197+
Optional<SerializablePredicate<T>> combinedFilter,
198+
Optional<Comparator<T>> comparator) {
199+
List<T> result = new ArrayList<>();
200+
List<T> children = hierarchicalData.getChildren(parent);
201+
202+
if (comparator.isPresent()) {
203+
children = children.stream().sorted(comparator.get()).toList();
204+
}
205+
206+
for (T child : children) {
207+
boolean isExpanded = expandedItemIds.contains(getId(child));
208+
List<T> descendants = Collections.emptyList();
209+
if (getHierarchyFormat().equals(HierarchyFormat.NESTED)
210+
|| isExpanded || combinedFilter.isPresent()) {
211+
descendants = flatten(child, expandedItemIds, combinedFilter,
212+
comparator);
213+
}
214+
215+
boolean matchesFilter = combinedFilter.map(f -> f.test(child))
216+
.orElse(true) || !descendants.isEmpty();
217+
if (matchesFilter) {
218+
result.add(child);
219+
}
220+
if (matchesFilter
221+
&& getHierarchyFormat().equals(HierarchyFormat.FLATTENED)
222+
&& isExpanded) {
223+
result.addAll(descendants);
224+
}
225+
}
226+
227+
return result;
228+
}
229+
}

flow-data/src/main/java/com/vaadin/flow/data/provider/hierarchy/TreeData.java

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
* @param <T>
4141
* data type
4242
*/
43-
public class TreeData<T> implements Serializable {
43+
public class TreeData<T> implements HierarchicalData<T> {
4444

4545
private static class HierarchyWrapper<T> implements Serializable {
4646
private T parent;
@@ -333,17 +333,7 @@ public List<T> getRootItems() {
333333
return getChildren(null);
334334
}
335335

336-
/**
337-
* Get the immediate child items for the given item.
338-
*
339-
* @param item
340-
* the item for which to retrieve child items for, null to
341-
* retrieve all root items
342-
* @return an unmodifiable list of child items for the given item
343-
*
344-
* @throws IllegalArgumentException
345-
* if the item does not exist in this structure
346-
*/
336+
@Override
347337
public List<T> getChildren(T item) {
348338
if (!contains(item)) {
349339
throw new IllegalArgumentException(
@@ -353,16 +343,7 @@ public List<T> getChildren(T item) {
353343
.unmodifiableList(itemToWrapperMap.get(item).getChildren());
354344
}
355345

356-
/**
357-
* Get the parent item for the given item.
358-
*
359-
* @param item
360-
* the item for which to retrieve the parent item for
361-
* @return parent item for the given item or {@code null} if the item is a
362-
* root item.
363-
* @throws IllegalArgumentException
364-
* if the item does not exist in this structure
365-
*/
346+
@Override
366347
public T getParent(T item) {
367348
if (!contains(item)) {
368349
throw new IllegalArgumentException(
@@ -468,14 +449,7 @@ public void moveAfterSibling(T item, T sibling) {
468449
}
469450
}
470451

471-
/**
472-
* Check whether the given item is in this hierarchy.
473-
*
474-
* @param item
475-
* the item to check
476-
* @return {@code true} if the item is in this hierarchy, {@code false} if
477-
* not
478-
*/
452+
@Override
479453
public boolean contains(T item) {
480454
return itemToWrapperMap.containsKey(item);
481455
}

0 commit comments

Comments
 (0)