On Thu, 9 Apr 2026 03:27:24 GMT, Charlie Schlosser <[email protected]> wrote:

>> Fixes https://bugs.openjdk.org/browse/JDK-8181411 and 
>> https://bugs.openjdk.org/browse/JDK-8380308
>> 
>> `TreeUtil.getItem` is used pervasively throughout `TreeView` and 
>> `TreeTableView`, specifically `getTreeItem`. This performs a 
>> depth-first-search (DFS) of the tree, and caches the target item. This is ok 
>> for random access, but is slow for sequential access with a cold cache, as 
>> each subsequent call to `getTreeItem` starts a new DFS from the root of the 
>> tree. For `selectAll`, this has O(N^2) time complexity. 
>> 
>> This PR adds a stateful iterator that resumes the DFS from the previously 
>> retrieved item, and caches each item during the traversal. This reduces the 
>> time complexity `selectAll` case to O(N). 
>> 
>> This change revealed an otherwise harmless bug in the order of event 
>> handling in the `invalidated()` method of the root property.
>> 
>> Current behavior:
>> 1. `expandedItemCountChangeEvent` fires
>> 2. Focus model listener fires
>> 3. Focus model calls `getTreeItem`, `expandedItemCountDirty` is false, cache 
>> miss, correct item returned
>> 5. `rootEvent` fires, sets `expandedItemCountDirty = true`
>> 
>> The correct behavior would be to set `expandedItemCountDirty = true` before 
>> the focus model calls getTreeItem. It works anyway because any item that is 
>> not in the cache triggers a full DFS from the root, which is always valid. 
>> 
>> This PR:
>> 1. `expandedItemCountChangeEvent` fires
>> 2. `rootEvent` fires, sets `expandedItemCountDirty = true`
>> 3. Focus model listener fires
>> 4. Focus model calls `getTreeItem`, `expandedItemCountDirty` is true, 
>> iterator resets, correct item returned
>> 
>> This is fixed by using `expandedItemCountChangeEvent` instead of the super 
>> type `treeNotificationEvent`. The subclass event types are processed before 
>> their super counterparts, regardless of registration order. Now that they 
>> are the same type, the order is correct.
>> 
>> This behavior is already covered (and is motivated) by `TreeTableViewTest > 
>> test_rt17522_focusShouldMoveWhenItemRemovedBeforeFocusIndex_1()` and 
>> `TreeTableViewTest > 
>> test_rt17522_focusShouldMoveWhenItemAddedAtFocusIndex_1()`. 
>> 
>> 
>>   
>> ┌─────────┬───────────────────┬──────────────────┬────────────────────────┬───────────────────────┐
>>   │  Items  │ TreeView (before) │ TreeView (after) │ TreeTableView (before) 
>> │ TreeTableView (after) │
>>   
>> ├─────────┼───────────────────┼──────────────────┼────────────────────────┼───────────────────────┤
>>   │      64 │               1ms │              1ms │               ...
>
> Charlie Schlosser has updated the pull request incrementally with one 
> additional commit since the last revision:
> 
>   copyright update

The testing (on macOS 26.3.1) looks good - a major performance improvement!  
There seems to be no negative impact selecting and deselecting individual items 
after select-all.

I agree with @Maran23 about adding a few tests.

modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java 
line 766:

> 764:     // See JDK-8125681 for the justification and performance gains.
> 765:     private Map<Integer, SoftReference<TreeItem<S>>> treeItemCacheMap = 
> new HashMap<>();
> 766:     // Persistent DFS iterator so that sequential access is O(N) instead 
> of O(N^2).

please expand "DFS"

modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java 
line 807:

> 805:         // this forces layoutChildren at the next pulse, and therefore
> 806:         // updates the item count if necessary
> 807:         expandedItemCountDirty = true;

"if necessary" no longer applies.

modules/javafx.controls/src/main/java/javafx/scene/control/TreeUtil.java line 
171:

> 169:             }
> 170: 
> 171:             @Override public boolean hasNext() {

please put `@Override` annotations on its own line in the new code

modules/javafx.controls/src/main/java/javafx/scene/control/TreeView.java line 
410:

> 408:     // See JDK-8125681 for the justification and performance gains.
> 409:     private Map<Integer, SoftReference<TreeItem<T>>> treeItemCacheMap = 
> new HashMap<>();
> 410:     // Persistent DFS iterator so that sequential access is O(N) instead 
> of O(N^2).

please expand "DFS"

-------------

PR Review: https://git.openjdk.org/jfx/pull/2142#pullrequestreview-4083392045
PR Review Comment: https://git.openjdk.org/jfx/pull/2142#discussion_r3058819814
PR Review Comment: https://git.openjdk.org/jfx/pull/2142#discussion_r3058822507
PR Review Comment: https://git.openjdk.org/jfx/pull/2142#discussion_r3058859239
PR Review Comment: https://git.openjdk.org/jfx/pull/2142#discussion_r3058867862

Reply via email to