Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -28,7 +28,6 @@
import javafx.collections.ObservableList;
import javafx.collections.ObservableListBase;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
Expand All @@ -38,7 +37,7 @@ public abstract class SelectedItemsReadOnlyObservableList<E> extends ObservableL
// This is the actual observable list of selected indices used in the selection model
private final ObservableList<Integer> selectedIndices;
private final Supplier<Integer> modelSizeSupplier;
private final List<WeakReference<E>> itemsRefList;
private final List<WeakReferenceWrapper<E>> itemsRefList;

public SelectedItemsReadOnlyObservableList(ObservableList<Integer> selectedIndices, Supplier<Integer> modelSizeSupplier) {
this.modelSizeSupplier = modelSizeSupplier;
Expand Down Expand Up @@ -85,7 +84,7 @@ public SelectedItemsReadOnlyObservableList(ObservableList<Integer> selectedIndic
// FIXME we could make this more efficient by only making the reported changes to the list
itemsRefList.clear();
for (int selectedIndex : selectedIndices) {
itemsRefList.add(new WeakReference<>(getModelItem(selectedIndex)));
itemsRefList.add(new WeakReferenceWrapper<>(getModelItem(selectedIndex)));
}

endChange();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package com.sun.javafx.scene.control;

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

/**
* Wrapper class that holds a reference to an object with or without identity.
* If the object is non-null and has identity, a weak reference is created and
* stored; otherwise it holds a reference to the object itself.
* <p>
* In the case of a value object, the referent is never collected, so it is only
* suitable for uses that do not rely on the object being placed onto a reference
* queue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might suggest to include the JBS number for when we need to undo / redo things due to inevitable change in the value objects JEP (and possibly a link to the JEP itself).

Perhaps also say a couple of words about the fact that this class should not exist had they decided to make the WeakReference implementation handle this case transparently.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no JBS issue or JEP that will eliminate the need for this. The restriction is intentional with no current plan to change it.

I could add a comment that this class might become unnecessary in the future, if a there is a change in the way WeakReference deals with value objects, but it is uncertain if or when that might be.

*
* @param <T> the type of the referent
*/
public class WeakReferenceWrapper<T> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An application or a library should not be forced to do this, in my opinion.

WeakReference(T) should work as before - in case of a value object it should hold the reference indefinitely because it's the same behavior as before. It may work differently with the WeakReference(T,ReferenceQueue) constructor which is ok because it would affect a much smaller space of use cases.

It is probably ok to do it right now, to avoid javafx breaking with the value objects preview enabled.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WeakReference(T) should work as before

But that isn't the case.


private static final Method hasIdentityMethod;
private final T obj;
private final WeakReference<T> ref;

static {
Method meth;
try {
meth = Objects.class.getMethod("hasIdentity", Object.class);
} catch (NoSuchMethodException ex) {
meth = null;
}
hasIdentityMethod = meth;
}

/**
* Helper method that reflectively calls Objects.hasIdentity to determine
* whether we should create and hold a weak reference to the given object.
* If {@code obj} is null, treat it as having no identity (matching JDK 28's
* behavior with or without {@code --enable-preview}) and return false.
* If {@code obj} is not null, check whether {@code Objects.hasIdentity} exists:
* if the method doesn't exist or cannot be invoked, return true; otherwise,
* call {@code Objects.hasIdentity(obj)} reflectively and return its value.
*/
private static boolean useWeakRef(Object obj) {
if (obj == null) {
return false;
} else if (hasIdentityMethod == null) {
return true;
} else {
try {
return (Boolean)hasIdentityMethod.invoke(null, obj);
} catch (IllegalAccessException | InvocationTargetException ex) {
return true;
}
}
}

/**
* Creates a new reference that refers to the given object.
* If the object is non-null and has identity, a weak reference is created and
* stored; otherwise it holds a reference to the object itself.
*
* @param obj the object this reference will refer to
*/
public WeakReferenceWrapper(T obj) {
if (useWeakRef(obj)) {
this.obj = null;
this.ref = new WeakReference<>(obj);
} else {
this.obj = obj;
this.ref = null;
}
}

/**
* Returns this reference object's referent. If the referent is held in a weak reference,
* and this reference object has been cleared by the garbage collector, then this method
* returns null.
*
* @return the object to which this reference refers, or null if this reference object has been cleared
*/
public T get() {
return ref != null ? ref.get() : obj;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,6 +25,7 @@

package javafx.scene.control;

import com.sun.javafx.scene.control.WeakReferenceWrapper;
import javafx.css.PseudoClass;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
Expand Down Expand Up @@ -631,7 +632,7 @@ private boolean isInCellSelectionMode() {

private boolean isFirstRun = true;

private WeakReference<S> oldRowItemRef;
private WeakReferenceWrapper<S> oldRowItemRef;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it is ok to apply this workaround (WeakReferenceWrapper) here, this kind of change in the WeakReference behavior is just awful: we should never force the application developers (r a third party library developers) to make a change like this.


/*
* This is called when we think that the data within this TableCell may have
Expand Down Expand Up @@ -704,7 +705,7 @@ private void updateItem(int oldIndex) {
updateItem(newValue, false);
}

oldRowItemRef = new WeakReference<>(rowItem);
oldRowItemRef = new WeakReferenceWrapper<>(rowItem);

if (currentObservableValue == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -26,7 +26,6 @@
package javafx.scene.control;

import java.lang.ref.WeakReference;
import java.util.List;

import javafx.beans.NamedArg;

Expand Down Expand Up @@ -70,10 +69,6 @@ public TablePosition(@NamedArg("tableView") TableView<S> tableView, @NamedArg("r
super(row, tableColumn);
this.controlRef = new WeakReference<>(tableView);

List<S> items = tableView != null ? tableView.getItems() : null;
this.itemRef = new WeakReference<>(
items != null && row >= 0 && row < items.size() ? items.get(row) : null);

nonFixedColumnIndex = tableView == null || tableColumn == null ? -1 : tableView.getVisibleLeafIndex(tableColumn);
}

Expand All @@ -86,7 +81,6 @@ public TablePosition(@NamedArg("tableView") TableView<S> tableView, @NamedArg("r
**************************************************************************/

private final WeakReference<TableView<S>> controlRef;
private final WeakReference<S> itemRef;
int fixedColumnIndex = -1;
private final int nonFixedColumnIndex;

Expand Down Expand Up @@ -123,14 +117,6 @@ public final TableView<S> getTableView() {
return super.getTableColumn();
}

/**
* Returns the item that backs the {@link #getRow()} row}, at the point
* in time when this TablePosition was created.
*/
final S getItem() {
return itemRef == null ? null : itemRef.get();
}

/**
* Returns a string representation of this {@code TablePosition} object.
* @return a string representation of this {@code TablePosition} object.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,6 +25,7 @@

package javafx.scene.control;

import com.sun.javafx.scene.control.WeakReferenceWrapper;
import javafx.css.PseudoClass;
import javafx.scene.control.skin.TreeTableCellSkin;
import javafx.beans.InvalidationListener;
Expand Down Expand Up @@ -626,7 +627,7 @@ private boolean isInCellSelectionMode() {

private boolean isFirstRun = true;

private WeakReference<S> oldRowItemRef;
private WeakReferenceWrapper<S> oldRowItemRef;

/*
* This is called when we think that the data within this TreeTableCell may have
Expand Down Expand Up @@ -700,7 +701,7 @@ private void updateItem(int oldIndex) {
updateItem(newValue, false);
}

oldRowItemRef = new WeakReference<>(rowItem);
oldRowItemRef = new WeakReferenceWrapper<>(rowItem);

if (currentObservableValue == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package test.com.sun.javafx.scene.control;

import com.sun.javafx.scene.control.WeakReferenceWrapper;
import java.lang.ref.WeakReference;
import org.junit.jupiter.api.Test;
import test.util.memory.JMemoryBuddy;

import static org.junit.jupiter.api.Assertions.*;

/*
* Test WeakReferenceWrapper utility.
*/
public class WeakReferenceWrapperTest {

// POJO with identity
static class POJO {
final int i;

@Override
public int hashCode() {
return i;
}

@Override
public boolean equals(Object other) {
if (other instanceof POJO otherPOJO) {
return i == otherPOJO.i;
} else {
return false;
}
}

POJO(int i) {
this.i = i;
}
}

// Test WeakReferenceWrapper with null, Integer (could be a value object),
// String (always an identity object), and a POJO (identity object).
@Test
public void testWeakReferenceWrapper() {
var nullRef = new WeakReferenceWrapper<Object>(null);
assertNull(nullRef.get());

Integer i = 123;
var intRef = new WeakReferenceWrapper<Integer>(i);
assertEquals(i, intRef.get());

String str = "abc";
var strRef = new WeakReferenceWrapper<String>(str);
assertEquals(str, strRef.get());

POJO pojo = new POJO(456);
var pojoRef = new WeakReferenceWrapper<POJO>(pojo);
assertEquals(pojo, pojoRef.get());
}

// Test that a WeakReferenceWrapper of an identity object holds the object
// weakly and does not prevent the object from being collected.
@Test
public void testWeakReferenceToIdentityObjIsCollectable() {
var pojo = new POJO(789);
var pojoWeakRef = new WeakReference<POJO>(pojo);
var pojoRef = new WeakReferenceWrapper<POJO>(pojo);

JMemoryBuddy.assertNotCollectable(pojoWeakRef);
assertNotNull(pojoRef.get());
assertSame(pojo, pojoRef.get());

pojo = null;
JMemoryBuddy.assertCollectable(pojoWeakRef);
assertNull(pojoRef.get());
}
}