Skip to content

Commit a5878e0

Browse files
committed
8214699: Node.getPseudoClassStates must return the same instance on every call
Reviewed-by: fastegal, kcr
1 parent 8440b64 commit a5878e0

File tree

5 files changed

+87
-10
lines changed

5 files changed

+87
-10
lines changed

modules/javafx.controls/src/test/java/test/javafx/scene/control/ComboBoxTest.java

+2-7
Original file line numberDiff line numberDiff line change
@@ -654,23 +654,18 @@ public void defaultConverterCanHandleIncorrectType_2() {
654654
assertTrue(pseudoClassStates.size() >= 0);
655655

656656
comboBox.setEditable(true);
657-
pseudoClassStates = comboBox.getPseudoClassStates();
658657
assertTrue(pseudoClassStates.contains(PseudoClass.getPseudoClass("editable")));
659658

660659
comboBox.setEditable(false);
661-
pseudoClassStates = comboBox.getPseudoClassStates();
662-
assertTrue(pseudoClassStates.contains(PseudoClass.getPseudoClass("editable")) == false);
660+
assertFalse(pseudoClassStates.contains(PseudoClass.getPseudoClass("editable")));
663661

664662
comboBox.show();
665-
pseudoClassStates = comboBox.getPseudoClassStates();
666663
assertTrue(pseudoClassStates.contains(PseudoClass.getPseudoClass("showing")));
667664

668665
comboBox.hide();
669-
pseudoClassStates = comboBox.getPseudoClassStates();
670-
assertTrue(pseudoClassStates.contains(PseudoClass.getPseudoClass("showing")) == false);
666+
assertFalse(pseudoClassStates.contains(PseudoClass.getPseudoClass("showing")));
671667

672668
comboBox.arm();
673-
pseudoClassStates = comboBox.getPseudoClassStates();
674669
assertTrue(pseudoClassStates.contains(PseudoClass.getPseudoClass("armed")));
675670

676671
}

modules/javafx.graphics/src/main/java/javafx/scene/Node.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -9367,14 +9367,15 @@ public final void pseudoClassStateChanged(PseudoClass pseudoClass, boolean activ
93679367

93689368
// package so that StyleHelper can get at it
93699369
final ObservableSet<PseudoClass> pseudoClassStates = new PseudoClassState();
9370+
private final ObservableSet<PseudoClass> unmodifiablePseudoClassStates =
9371+
FXCollections.unmodifiableObservableSet(pseudoClassStates);
93709372
/**
93719373
* @return The active pseudo-class states of this Node, wrapped in an unmodifiable ObservableSet
93729374
* @since JavaFX 8.0
93739375
*/
9376+
@Override
93749377
public final ObservableSet<PseudoClass> getPseudoClassStates() {
9375-
9376-
return FXCollections.unmodifiableObservableSet(pseudoClassStates);
9377-
9378+
return unmodifiablePseudoClassStates;
93789379
}
93799380

93809381
// Walks up the tree telling each parent that the pseudo class state of

modules/javafx.graphics/src/shims/java/javafx/scene/NodeShim.java

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import com.sun.javafx.scene.DirtyBits;
2929
import com.sun.javafx.sg.prism.NGNode;
3030

31+
import javafx.collections.ObservableSet;
32+
import javafx.css.PseudoClass;
3133
import javafx.scene.transform.Transform;
3234

3335
public class NodeShim {
@@ -75,4 +77,8 @@ public static void updateBounds(Node n) {
7577
public static <P extends NGNode> P getPeer(Node n) {
7678
return n.getPeer();
7779
}
80+
81+
public static ObservableSet<PseudoClass> pseudoClassStates(Node n) {
82+
return n.pseudoClassStates;
83+
}
7884
}

modules/javafx.graphics/src/test/java/test/javafx/scene/NodeTest.java

+58
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@
4444
import test.com.sun.javafx.test.objects.TestStage;
4545
import com.sun.javafx.tk.Toolkit;
4646
import com.sun.javafx.util.Utils;
47+
import javafx.beans.InvalidationListener;
4748
import javafx.beans.property.*;
49+
import javafx.collections.SetChangeListener;
50+
import javafx.css.PseudoClass;
4851
import javafx.geometry.BoundingBox;
4952
import javafx.geometry.Bounds;
5053
import javafx.geometry.NodeOrientation;
@@ -59,8 +62,11 @@
5962
import org.junit.Test;
6063
import org.junit.rules.ExpectedException;
6164

65+
import java.lang.ref.WeakReference;
6266
import java.lang.reflect.Method;
6367
import java.util.Comparator;
68+
import java.util.Set;
69+
6470
import javafx.scene.Group;
6571
import javafx.scene.GroupShim;
6672
import javafx.scene.Node;
@@ -161,6 +167,58 @@ public class NodeTest {
161167
* *
162168
**************************************************************************/
163169

170+
@Test
171+
public void testGetPseudoClassStatesShouldReturnSameSet() {
172+
Rectangle node = new Rectangle();
173+
Set<PseudoClass> set1 = node.getPseudoClassStates();
174+
Set<PseudoClass> set2 = node.getPseudoClassStates();
175+
assertSame("getPseudoClassStates() should always return the same instance",
176+
set1, set2);
177+
}
178+
179+
@Test(expected=UnsupportedOperationException.class)
180+
public void testPseudoClassStatesIsUnmodifiable() {
181+
Node node = new Rectangle();
182+
node.getPseudoClassStates().add(PseudoClass.getPseudoClass("dummy"));
183+
}
184+
185+
@Test
186+
public void testUnmodifiablePseudoClassStatesEqualsBackingStates() {
187+
Rectangle node = new Rectangle();
188+
PseudoClass pseudo = PseudoClass.getPseudoClass("Pseudo");
189+
node.pseudoClassStateChanged(pseudo, true);
190+
assertEquals(1, node.getPseudoClassStates().size());
191+
assertEquals(NodeShim.pseudoClassStates(node).size(), node.getPseudoClassStates().size());
192+
assertTrue(NodeShim.pseudoClassStates(node).contains(pseudo));
193+
assertTrue(node.getPseudoClassStates().contains(pseudo));
194+
}
195+
196+
private boolean isInvalidationListenerInvoked;
197+
private boolean isChangeListenerInvoked;
198+
@Test
199+
public void testPseudoClassStatesListenersAreInvoked() {
200+
Rectangle node = new Rectangle();
201+
node.getPseudoClassStates().addListener((InvalidationListener) inv -> {
202+
isInvalidationListenerInvoked = true;
203+
});
204+
node.getPseudoClassStates().addListener((SetChangeListener<PseudoClass>) c -> {
205+
isChangeListenerInvoked = true;
206+
});
207+
208+
PseudoClass pseudo = PseudoClass.getPseudoClass("Pseudo");
209+
node.pseudoClassStateChanged(pseudo, true);
210+
assertTrue(isInvalidationListenerInvoked);
211+
assertTrue(isChangeListenerInvoked);
212+
}
213+
214+
@Test
215+
public void testPseudoClassStatesNotGCed() {
216+
Node node = new Rectangle();
217+
WeakReference<Set<?>> weakRef = new WeakReference<>(node.getPseudoClassStates());
218+
TestUtils.attemptGC(weakRef);
219+
assertNotNull("pseudoClassStates must not be gc'ed", weakRef.get());
220+
}
221+
164222
// TODO disable this because it depends on TestNode
165223
// @Test public void testPeerNotifiedOfVisibilityChanges() {
166224
// Rectangle rect = new Rectangle();

modules/javafx.graphics/src/test/java/test/javafx/scene/shape/TestUtils.java

+17
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.sun.prism.paint.Color;
3131
import javafx.scene.Node;
3232

33+
import java.lang.ref.WeakReference;
3334
import java.lang.reflect.Method;
3435

3536
import static org.junit.Assert.*;
@@ -126,4 +127,20 @@ public static Object getObjectValue(Node node, String pgPropertyName, boolean is
126127
public static Object getObjectValue(Node node, String pgPropertyName) throws Exception {
127128
return getObjectValue(node, pgPropertyName, false);
128129
}
130+
131+
public static void attemptGC(WeakReference<?> weakRef) {
132+
for (int i = 0; i < 10; i++) {
133+
System.gc();
134+
System.runFinalization();
135+
136+
if (weakRef.get() == null) {
137+
break;
138+
}
139+
try {
140+
Thread.sleep(50);
141+
} catch (InterruptedException e) {
142+
fail("InterruptedException occurred during Thread.sleep()");
143+
}
144+
}
145+
}
129146
}

0 commit comments

Comments
 (0)