From 199d0ced1607d7eb583562f1a18ce89a311834aa Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 7 Oct 2022 12:20:21 -0700 Subject: [PATCH 01/24] 8294809: original code --- .../javafx/scene/control/Disconnectable.java | 11 + .../javafx/scene/control/FxDisconnector.java | 476 ++++++++++++++++++ 2 files changed, 487 insertions(+) create mode 100644 modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java create mode 100644 modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java new file mode 100644 index 00000000000..b9881b25f69 --- /dev/null +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java @@ -0,0 +1,11 @@ +// Copyright © 2021-2022 Andy Goryachev +package com.sun.javafx.scene.control; + + +/** + * Disconnectable. + */ +public interface Disconnectable +{ + public void disconnect(); +} \ No newline at end of file diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java new file mode 100644 index 00000000000..2de60b4215f --- /dev/null +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java @@ -0,0 +1,476 @@ +// Copyright © 2021-2022 Andy Goryachev +package com.sun.javafx.scene.control; +import goryachev.common.util.CList; +import goryachev.common.util.Disconnectable; +import java.lang.ref.WeakReference; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.Node; + + +/** + * Fx Disconnector. + */ +public class FxDisconnector + implements Disconnectable +{ + private final CList items = new CList(); + private static final Object KEY = new Object(); + + + public FxDisconnector() + { + } + + + public static FxDisconnector get(Node n) + { + Object x = n.getProperties().get(KEY); + if(x instanceof FxDisconnector) + { + return (FxDisconnector)x; + } + FxDisconnector d = new FxDisconnector(); + n.getProperties().put(KEY, d); + return d; + } + + + public static void disconnect(Node n) + { + Object x = n.getProperties().get(KEY); + if(x instanceof FxDisconnector) + { + ((FxDisconnector)x).disconnect(); + } + } + + + public void addDisconnectable(Disconnectable d) + { + items.add(d); + } + + + public void disconnect() + { + for(int i=items.size()-1; i>=0; i--) + { + Disconnectable d = items.remove(i); + d.disconnect(); + } + } + + + // change listeners + + + public Disconnectable addChangeListener(Runnable callback, ObservableValue ... props) + { + return addChangeListener(callback, false, props); + } + + + public Disconnectable addChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue ... props) + { + ChLi li = new ChLi() + { + public void disconnect() + { + for(ObservableValue p: props) + { + p.removeListener(this); + } + } + + + public void changed(ObservableValue p, Object oldValue, Object newValue) + { + onChange.run(); + } + }; + + items.add(li); + + for(ObservableValue p: props) + { + p.addListener(li); + } + + if(fireImmediately) + { + onChange.run(); + } + + return li; + } + + + public Disconnectable addChangeListener(ObservableValue prop, ChangeListener li) + { + return addChangeListener(prop, false, li); + } + + + public Disconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener li) + { + Disconnectable d = new Disconnectable() + { + public void disconnect() + { + prop.removeListener(li); + } + }; + + items.add(d); + prop.addListener(li); + + if(fireImmediately) + { + T v = prop.getValue(); + li.changed(prop, null, v); + } + + return d; + } + + + public Disconnectable addWeakChangeListener(Runnable onChange, ObservableValue ... props) + { + return addWeakChangeListener(onChange, false, props); + } + + + public Disconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue ... props) + { + ChLi li = new ChLi() + { + WeakReference ref = new WeakReference(onChange); + + + public void disconnect() + { + for(ObservableValue p: props) + { + p.removeListener(this); + } + } + + + public void changed(ObservableValue p, Object oldValue, Object newValue) + { + Runnable r = ref.get(); + if(r == null) + { + disconnect(); + } + else + { + r.run(); + } + } + }; + + items.add(li); + + for(ObservableValue p: props) + { + p.addListener(li); + } + + if(fireImmediately) + { + onChange.run(); + } + + return li; + } + + + public Disconnectable addWeakChangeListener(ObservableValue prop, ChangeListener li) + { + return addChangeListener(prop, false, li); + } + + + public Disconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener listener) + { + ChLi d = new ChLi() + { + WeakReference> ref = new WeakReference<>(listener); + + + public void disconnect() + { + prop.removeListener(this); + } + + + public void changed(ObservableValue p, T oldValue, T newValue) + { + ChangeListener li = ref.get(); + if(li == null) + { + disconnect(); + } + else + { + li.changed(p, oldValue, newValue); + } + } + }; + + items.add(d); + prop.addListener(d); + + if(fireImmediately) + { + T v = prop.getValue(); + listener.changed(prop, null, v); + } + + return d; + } + + + // invalidation listeners + + + public Disconnectable addInvalidationListener(Runnable callback, ObservableValue ... props) + { + return addInvalidationListener(callback, false, props); + } + + + public Disconnectable addInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue ... props) + { + InLi li = new InLi() + { + public void disconnect() + { + for(ObservableValue p: props) + { + p.removeListener(this); + } + } + + + public void invalidated(Observable p) + { + onChange.run(); + } + }; + + items.add(li); + + for(ObservableValue p: props) + { + p.addListener(li); + } + + if(fireImmediately) + { + onChange.run(); + } + + return li; + } + + + public Disconnectable addInvalidationListener(ObservableValue prop, InvalidationListener li) + { + return addInvalidationListener(prop, false, li); + } + + + public Disconnectable addInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener li) + { + Disconnectable d = new Disconnectable() + { + public void disconnect() + { + prop.removeListener(li); + } + }; + + items.add(d); + prop.addListener(li); + + if(fireImmediately) + { + li.invalidated(prop); + } + + return d; + } + + + public Disconnectable addWeakInvalidationListener(Runnable onChange, ObservableValue ... props) + { + return addWeakInvalidationListener(onChange, false, props); + } + + + public Disconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue ... props) + { + InLi li = new InLi() + { + WeakReference ref = new WeakReference(onChange); + + + public void disconnect() + { + for(ObservableValue p: props) + { + p.removeListener(this); + } + } + + + public void invalidated(Observable p) + { + Runnable r = ref.get(); + if(r == null) + { + disconnect(); + } + else + { + r.run(); + } + } + }; + + items.add(li); + + for(ObservableValue p: props) + { + p.addListener(li); + } + + if(fireImmediately) + { + onChange.run(); + } + + return li; + } + + + public Disconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener li) + { + return addWeakInvalidationListener(prop, false, li); + } + + + public Disconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener listener) + { + InLi d = new InLi() + { + WeakReference ref = new WeakReference<>(listener); + + + public void disconnect() + { + prop.removeListener(this); + } + + + public void invalidated(Observable p) + { + InvalidationListener li = ref.get(); + if(li == null) + { + disconnect(); + } + else + { + li.invalidated(p); + } + } + }; + + items.add(d); + prop.addListener(d); + + if(fireImmediately) + { + listener.invalidated(prop); + } + + return d; + } + + + // list change listeners + + + public Disconnectable addListChangeListener(ObservableList list, ListChangeListener listener) + { + Disconnectable d = new Disconnectable() + { + public void disconnect() + { + list.removeListener(listener); + } + }; + + items.add(d); + list.addListener(listener); + + return d; + } + + + public Disconnectable addWeakListChangeListener(ObservableList list, ListChangeListener listener) + { + LiChLi li = new LiChLi() + { + WeakReference> ref = new WeakReference<>(listener); + + + public void disconnect() + { + list.removeListener(this); + } + + public void onChanged(Change ch) + { + ListChangeListener li = ref.get(); + if(li == null) + { + disconnect(); + } + else + { + li.onChanged(ch); + } + } + }; + + items.add(li); + list.addListener(li); + + return li; + } + + + // TODO event handlers + + + // TODO event filters + + + // + + + protected static abstract class ChLi implements Disconnectable, ChangeListener { } + + protected static abstract class InLi implements Disconnectable, InvalidationListener { } + + protected static abstract class LiChLi implements Disconnectable, ListChangeListener { } +} \ No newline at end of file From 019adacd8deb187e33b57dde72ab87ba2eb6329e Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 7 Oct 2022 12:36:31 -0700 Subject: [PATCH 02/24] 8294809: relicensed by author --- .../javafx/scene/control/Disconnectable.java | 42 +- .../javafx/scene/control/FxDisconnector.java | 840 ++++++++---------- 2 files changed, 413 insertions(+), 469 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java index b9881b25f69..2d19b1fca9b 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java @@ -1,11 +1,41 @@ -// Copyright © 2021-2022 Andy Goryachev +/* + * Copyright (c) 2022, 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; - /** - * Disconnectable. + * A functional interface that provides a {@link #disconnect()} method. + *

+ * Original code is re-licensed to Oracle by the author. + * https://github.com/andy-goryachev/FxTextEditor/blob/master/src/goryachev/common/util/Disconnectable.java + * Copyright © 2021-2022 Andy Goryachev */ -public interface Disconnectable -{ - public void disconnect(); +@FunctionalInterface +public interface Disconnectable { + /** + * Disconnects what has been connected. May be called multiple times, only the + * first invocation actually disconnects. + */ + public void disconnect(); } \ No newline at end of file diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java index 2de60b4215f..a439f97cc1a 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java @@ -1,8 +1,32 @@ -// Copyright © 2021-2022 Andy Goryachev +/* + * Copyright (c) 2022, 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 goryachev.common.util.CList; -import goryachev.common.util.Disconnectable; + import java.lang.ref.WeakReference; +import java.util.ArrayList; + import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.value.ChangeListener; @@ -11,466 +35,356 @@ import javafx.collections.ObservableList; import javafx.scene.Node; - /** - * Fx Disconnector. + * This class provides convenience methods for adding various listeners, both strong and weak, + * as well as a single {@link #disconnect()} method to remove all listeners. + *

+ * There are two usage patterns: + *

    + *
  • Client code registers a number of listeners and removes them all at once via {@link #disconnect()} call. + *
  • Client code registers a number of listeners and removes one via its {@link IDisconnectable} instance. + *
+ *

+ * Original code is re-licensed to Oracle by the author. + * https://github.com/andy-goryachev/FxTextEditor/blob/master/src/goryachev/fx/FxDisconnector.java + * Copyright © 2021-2022 Andy Goryachev */ -public class FxDisconnector - implements Disconnectable -{ - private final CList items = new CList(); - private static final Object KEY = new Object(); - - - public FxDisconnector() - { - } - - - public static FxDisconnector get(Node n) - { - Object x = n.getProperties().get(KEY); - if(x instanceof FxDisconnector) - { - return (FxDisconnector)x; - } - FxDisconnector d = new FxDisconnector(); - n.getProperties().put(KEY, d); - return d; - } - - - public static void disconnect(Node n) - { - Object x = n.getProperties().get(KEY); - if(x instanceof FxDisconnector) - { - ((FxDisconnector)x).disconnect(); - } - } - - - public void addDisconnectable(Disconnectable d) - { - items.add(d); - } - - - public void disconnect() - { - for(int i=items.size()-1; i>=0; i--) - { - Disconnectable d = items.remove(i); - d.disconnect(); - } - } - - - // change listeners - - - public Disconnectable addChangeListener(Runnable callback, ObservableValue ... props) - { - return addChangeListener(callback, false, props); - } - - - public Disconnectable addChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue ... props) - { - ChLi li = new ChLi() - { - public void disconnect() - { - for(ObservableValue p: props) - { - p.removeListener(this); - } - } - - - public void changed(ObservableValue p, Object oldValue, Object newValue) - { - onChange.run(); - } - }; - - items.add(li); - - for(ObservableValue p: props) - { - p.addListener(li); - } - - if(fireImmediately) - { - onChange.run(); - } - - return li; - } - - - public Disconnectable addChangeListener(ObservableValue prop, ChangeListener li) - { - return addChangeListener(prop, false, li); - } - - - public Disconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener li) - { - Disconnectable d = new Disconnectable() - { - public void disconnect() - { - prop.removeListener(li); - } - }; - - items.add(d); - prop.addListener(li); - - if(fireImmediately) - { - T v = prop.getValue(); - li.changed(prop, null, v); - } - - return d; - } - - - public Disconnectable addWeakChangeListener(Runnable onChange, ObservableValue ... props) - { - return addWeakChangeListener(onChange, false, props); - } - - - public Disconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue ... props) - { - ChLi li = new ChLi() - { - WeakReference ref = new WeakReference(onChange); - - - public void disconnect() - { - for(ObservableValue p: props) - { - p.removeListener(this); - } - } - - - public void changed(ObservableValue p, Object oldValue, Object newValue) - { - Runnable r = ref.get(); - if(r == null) - { - disconnect(); - } - else - { - r.run(); - } - } - }; - - items.add(li); - - for(ObservableValue p: props) - { - p.addListener(li); - } - - if(fireImmediately) - { - onChange.run(); - } - - return li; - } - - - public Disconnectable addWeakChangeListener(ObservableValue prop, ChangeListener li) - { - return addChangeListener(prop, false, li); - } - - - public Disconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener listener) - { - ChLi d = new ChLi() - { - WeakReference> ref = new WeakReference<>(listener); - - - public void disconnect() - { - prop.removeListener(this); - } - - - public void changed(ObservableValue p, T oldValue, T newValue) - { - ChangeListener li = ref.get(); - if(li == null) - { - disconnect(); - } - else - { - li.changed(p, oldValue, newValue); - } - } - }; - - items.add(d); - prop.addListener(d); - - if(fireImmediately) - { - T v = prop.getValue(); - listener.changed(prop, null, v); - } - - return d; - } - - - // invalidation listeners - - - public Disconnectable addInvalidationListener(Runnable callback, ObservableValue ... props) - { - return addInvalidationListener(callback, false, props); - } - - - public Disconnectable addInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue ... props) - { - InLi li = new InLi() - { - public void disconnect() - { - for(ObservableValue p: props) - { - p.removeListener(this); - } - } - - - public void invalidated(Observable p) - { - onChange.run(); - } - }; - - items.add(li); - - for(ObservableValue p: props) - { - p.addListener(li); - } - - if(fireImmediately) - { - onChange.run(); - } - - return li; - } - - - public Disconnectable addInvalidationListener(ObservableValue prop, InvalidationListener li) - { - return addInvalidationListener(prop, false, li); - } - - - public Disconnectable addInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener li) - { - Disconnectable d = new Disconnectable() - { - public void disconnect() - { - prop.removeListener(li); - } - }; - - items.add(d); - prop.addListener(li); - - if(fireImmediately) - { - li.invalidated(prop); - } - - return d; - } - - - public Disconnectable addWeakInvalidationListener(Runnable onChange, ObservableValue ... props) - { - return addWeakInvalidationListener(onChange, false, props); - } - - - public Disconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue ... props) - { - InLi li = new InLi() - { - WeakReference ref = new WeakReference(onChange); - - - public void disconnect() - { - for(ObservableValue p: props) - { - p.removeListener(this); - } - } - - - public void invalidated(Observable p) - { - Runnable r = ref.get(); - if(r == null) - { - disconnect(); - } - else - { - r.run(); - } - } - }; - - items.add(li); - - for(ObservableValue p: props) - { - p.addListener(li); - } - - if(fireImmediately) - { - onChange.run(); - } - - return li; - } - - - public Disconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener li) - { - return addWeakInvalidationListener(prop, false, li); - } - - - public Disconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener listener) - { - InLi d = new InLi() - { - WeakReference ref = new WeakReference<>(listener); - - - public void disconnect() - { - prop.removeListener(this); - } - - - public void invalidated(Observable p) - { - InvalidationListener li = ref.get(); - if(li == null) - { - disconnect(); - } - else - { - li.invalidated(p); - } - } - }; - - items.add(d); - prop.addListener(d); - - if(fireImmediately) - { - listener.invalidated(prop); - } - - return d; - } - - - // list change listeners - - - public Disconnectable addListChangeListener(ObservableList list, ListChangeListener listener) - { - Disconnectable d = new Disconnectable() - { - public void disconnect() - { - list.removeListener(listener); - } - }; - - items.add(d); - list.addListener(listener); - - return d; - } - - - public Disconnectable addWeakListChangeListener(ObservableList list, ListChangeListener listener) - { - LiChLi li = new LiChLi() - { - WeakReference> ref = new WeakReference<>(listener); - - - public void disconnect() - { - list.removeListener(this); - } - - public void onChanged(Change ch) - { - ListChangeListener li = ref.get(); - if(li == null) - { - disconnect(); - } - else - { - li.onChanged(ch); - } - } - }; - - items.add(li); - list.addListener(li); - - return li; - } - - - // TODO event handlers - - - // TODO event filters - - - // - - - protected static abstract class ChLi implements Disconnectable, ChangeListener { } - - protected static abstract class InLi implements Disconnectable, InvalidationListener { } - - protected static abstract class LiChLi implements Disconnectable, ListChangeListener { } +public class FxDisconnector implements Disconnectable { + private final ArrayList items = new ArrayList<>(); + private static final Object KEY = new Object(); + + public FxDisconnector() { + } + + public static FxDisconnector get(Node n) { + Object x = n.getProperties().get(KEY); + if (x instanceof FxDisconnector) { + return (FxDisconnector)x; + } + FxDisconnector d = new FxDisconnector(); + n.getProperties().put(KEY, d); + return d; + } + + public static void disconnect(Node n) { + Object x = n.getProperties().get(KEY); + if (x instanceof FxDisconnector) { + ((FxDisconnector)x).disconnect(); + } + } + + public void addDisconnectable(Disconnectable d) { + items.add(d); + } + + public void disconnect() { + for (int i = items.size() - 1; i >= 0; i--) { + Disconnectable d = items.remove(i); + d.disconnect(); + } + } + + // change listeners + + public Disconnectable addChangeListener(Runnable callback, ObservableValue... props) { + return addChangeListener(callback, false, props); + } + + public Disconnectable addChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { + ChLi li = new ChLi() { + public void disconnect() { + for (ObservableValue p : props) { + p.removeListener(this); + } + } + + public void changed(ObservableValue p, Object oldValue, Object newValue) { + onChange.run(); + } + }; + + items.add(li); + + for (ObservableValue p : props) { + p.addListener(li); + } + + if (fireImmediately) { + onChange.run(); + } + + return li; + } + + public Disconnectable addChangeListener(ObservableValue prop, ChangeListener li) { + return addChangeListener(prop, false, li); + } + + public Disconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, + ChangeListener li) { + Disconnectable d = new Disconnectable() { + public void disconnect() { + prop.removeListener(li); + } + }; + + items.add(d); + prop.addListener(li); + + if (fireImmediately) { + T v = prop.getValue(); + li.changed(prop, null, v); + } + + return d; + } + + public Disconnectable addWeakChangeListener(Runnable onChange, ObservableValue... props) { + return addWeakChangeListener(onChange, false, props); + } + + public Disconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, + ObservableValue... props) { + ChLi li = new ChLi() { + WeakReference ref = new WeakReference(onChange); + + public void disconnect() { + for (ObservableValue p : props) { + p.removeListener(this); + } + } + + public void changed(ObservableValue p, Object oldValue, Object newValue) { + Runnable r = ref.get(); + if (r == null) { + disconnect(); + } else { + r.run(); + } + } + }; + + items.add(li); + + for (ObservableValue p : props) { + p.addListener(li); + } + + if (fireImmediately) { + onChange.run(); + } + + return li; + } + + public Disconnectable addWeakChangeListener(ObservableValue prop, ChangeListener li) { + return addChangeListener(prop, false, li); + } + + public Disconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, + ChangeListener listener) { + ChLi d = new ChLi() { + WeakReference> ref = new WeakReference<>(listener); + + public void disconnect() { + prop.removeListener(this); + } + + public void changed(ObservableValue p, T oldValue, T newValue) { + ChangeListener li = ref.get(); + if (li == null) { + disconnect(); + } else { + li.changed(p, oldValue, newValue); + } + } + }; + + items.add(d); + prop.addListener(d); + + if (fireImmediately) { + T v = prop.getValue(); + listener.changed(prop, null, v); + } + + return d; + } + + // invalidation listeners + + public Disconnectable addInvalidationListener(Runnable callback, ObservableValue... props) { + return addInvalidationListener(callback, false, props); + } + + public Disconnectable addInvalidationListener(Runnable onChange, boolean fireImmediately, + ObservableValue... props) { + InLi li = new InLi() { + public void disconnect() { + for (ObservableValue p : props) { + p.removeListener(this); + } + } + + public void invalidated(Observable p) { + onChange.run(); + } + }; + + items.add(li); + + for (ObservableValue p : props) { + p.addListener(li); + } + + if (fireImmediately) { + onChange.run(); + } + + return li; + } + + public Disconnectable addInvalidationListener(ObservableValue prop, InvalidationListener li) { + return addInvalidationListener(prop, false, li); + } + + public Disconnectable addInvalidationListener(ObservableValue prop, boolean fireImmediately, + InvalidationListener li) { + Disconnectable d = new Disconnectable() { + public void disconnect() { + prop.removeListener(li); + } + }; + + items.add(d); + prop.addListener(li); + + if (fireImmediately) { + li.invalidated(prop); + } + + return d; + } + + public Disconnectable addWeakInvalidationListener(Runnable onChange, ObservableValue... props) { + return addWeakInvalidationListener(onChange, false, props); + } + + public Disconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, + ObservableValue... props) { + InLi li = new InLi() { + WeakReference ref = new WeakReference(onChange); + + public void disconnect() { + for (ObservableValue p : props) { + p.removeListener(this); + } + } + + public void invalidated(Observable p) { + Runnable r = ref.get(); + if (r == null) { + disconnect(); + } else { + r.run(); + } + } + }; + + items.add(li); + + for (ObservableValue p : props) { + p.addListener(li); + } + + if (fireImmediately) { + onChange.run(); + } + + return li; + } + + public Disconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener li) { + return addWeakInvalidationListener(prop, false, li); + } + + public Disconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, + InvalidationListener listener) { + InLi d = new InLi() { + WeakReference ref = new WeakReference<>(listener); + + public void disconnect() { + prop.removeListener(this); + } + + public void invalidated(Observable p) { + InvalidationListener li = ref.get(); + if (li == null) { + disconnect(); + } else { + li.invalidated(p); + } + } + }; + + items.add(d); + prop.addListener(d); + + if (fireImmediately) { + listener.invalidated(prop); + } + + return d; + } + + // list change listeners + + public Disconnectable addListChangeListener(ObservableList list, ListChangeListener listener) { + Disconnectable d = new Disconnectable() { + public void disconnect() { + list.removeListener(listener); + } + }; + + items.add(d); + list.addListener(listener); + + return d; + } + + public Disconnectable addWeakListChangeListener(ObservableList list, ListChangeListener listener) { + LiChLi li = new LiChLi() { + WeakReference> ref = new WeakReference<>(listener); + + public void disconnect() { + list.removeListener(this); + } + + public void onChanged(Change ch) { + ListChangeListener li = ref.get(); + if (li == null) { + disconnect(); + } else { + li.onChanged(ch); + } + } + }; + + items.add(li); + list.addListener(li); + + return li; + } + + // TODO event handlers + + // TODO event filters + + // + + protected static abstract class ChLi implements Disconnectable, ChangeListener { } + + protected static abstract class InLi implements Disconnectable, InvalidationListener { } + + protected static abstract class LiChLi implements Disconnectable, ListChangeListener { } } \ No newline at end of file From 78774f433d470f6292293ad974250ba9a13fd57e Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 7 Oct 2022 12:37:58 -0700 Subject: [PATCH 03/24] 8294809: renamed --- ...sconnectable.java => IDisconnectable.java} | 2 +- ...xDisconnector.java => ListenerHelper.java} | 70 +++++++++---------- 2 files changed, 36 insertions(+), 36 deletions(-) rename modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/{Disconnectable.java => IDisconnectable.java} (98%) rename modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/{FxDisconnector.java => ListenerHelper.java} (76%) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/IDisconnectable.java similarity index 98% rename from modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java rename to modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/IDisconnectable.java index 2d19b1fca9b..85ee1b8179e 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Disconnectable.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/IDisconnectable.java @@ -32,7 +32,7 @@ * Copyright © 2021-2022 Andy Goryachev */ @FunctionalInterface -public interface Disconnectable { +public interface IDisconnectable { /** * Disconnects what has been connected. May be called multiple times, only the * first invocation actually disconnects. diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java similarity index 76% rename from modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java rename to modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index a439f97cc1a..ac43af0c719 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/FxDisconnector.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -49,48 +49,48 @@ * https://github.com/andy-goryachev/FxTextEditor/blob/master/src/goryachev/fx/FxDisconnector.java * Copyright © 2021-2022 Andy Goryachev */ -public class FxDisconnector implements Disconnectable { - private final ArrayList items = new ArrayList<>(); +public class ListenerHelper implements IDisconnectable { + private final ArrayList items = new ArrayList<>(4); private static final Object KEY = new Object(); - public FxDisconnector() { + public ListenerHelper() { } - public static FxDisconnector get(Node n) { + public static ListenerHelper get(Node n) { Object x = n.getProperties().get(KEY); - if (x instanceof FxDisconnector) { - return (FxDisconnector)x; + if (x instanceof ListenerHelper) { + return (ListenerHelper)x; } - FxDisconnector d = new FxDisconnector(); + ListenerHelper d = new ListenerHelper(); n.getProperties().put(KEY, d); return d; } public static void disconnect(Node n) { Object x = n.getProperties().get(KEY); - if (x instanceof FxDisconnector) { - ((FxDisconnector)x).disconnect(); + if (x instanceof ListenerHelper) { + ((ListenerHelper)x).disconnect(); } } - public void addDisconnectable(Disconnectable d) { + public void addDisconnectable(IDisconnectable d) { items.add(d); } public void disconnect() { for (int i = items.size() - 1; i >= 0; i--) { - Disconnectable d = items.remove(i); + IDisconnectable d = items.remove(i); d.disconnect(); } } // change listeners - public Disconnectable addChangeListener(Runnable callback, ObservableValue... props) { + public IDisconnectable addChangeListener(Runnable callback, ObservableValue... props) { return addChangeListener(callback, false, props); } - public Disconnectable addChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { + public IDisconnectable addChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { ChLi li = new ChLi() { public void disconnect() { for (ObservableValue p : props) { @@ -116,13 +116,13 @@ public void changed(ObservableValue p, Object oldValue, Object newValue) { return li; } - public Disconnectable addChangeListener(ObservableValue prop, ChangeListener li) { + public IDisconnectable addChangeListener(ObservableValue prop, ChangeListener li) { return addChangeListener(prop, false, li); } - public Disconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, + public IDisconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener li) { - Disconnectable d = new Disconnectable() { + IDisconnectable d = new IDisconnectable() { public void disconnect() { prop.removeListener(li); } @@ -139,11 +139,11 @@ public void disconnect() { return d; } - public Disconnectable addWeakChangeListener(Runnable onChange, ObservableValue... props) { + public IDisconnectable addWeakChangeListener(Runnable onChange, ObservableValue... props) { return addWeakChangeListener(onChange, false, props); } - public Disconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, + public IDisconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { ChLi li = new ChLi() { WeakReference ref = new WeakReference(onChange); @@ -177,11 +177,11 @@ public void changed(ObservableValue p, Object oldValue, Object newValue) { return li; } - public Disconnectable addWeakChangeListener(ObservableValue prop, ChangeListener li) { + public IDisconnectable addWeakChangeListener(ObservableValue prop, ChangeListener li) { return addChangeListener(prop, false, li); } - public Disconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, + public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener listener) { ChLi d = new ChLi() { WeakReference> ref = new WeakReference<>(listener); @@ -213,11 +213,11 @@ public void changed(ObservableValue p, T oldValue, T newValue) { // invalidation listeners - public Disconnectable addInvalidationListener(Runnable callback, ObservableValue... props) { + public IDisconnectable addInvalidationListener(Runnable callback, ObservableValue... props) { return addInvalidationListener(callback, false, props); } - public Disconnectable addInvalidationListener(Runnable onChange, boolean fireImmediately, + public IDisconnectable addInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { InLi li = new InLi() { public void disconnect() { @@ -244,13 +244,13 @@ public void invalidated(Observable p) { return li; } - public Disconnectable addInvalidationListener(ObservableValue prop, InvalidationListener li) { + public IDisconnectable addInvalidationListener(ObservableValue prop, InvalidationListener li) { return addInvalidationListener(prop, false, li); } - public Disconnectable addInvalidationListener(ObservableValue prop, boolean fireImmediately, + public IDisconnectable addInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener li) { - Disconnectable d = new Disconnectable() { + IDisconnectable d = new IDisconnectable() { public void disconnect() { prop.removeListener(li); } @@ -266,11 +266,11 @@ public void disconnect() { return d; } - public Disconnectable addWeakInvalidationListener(Runnable onChange, ObservableValue... props) { + public IDisconnectable addWeakInvalidationListener(Runnable onChange, ObservableValue... props) { return addWeakInvalidationListener(onChange, false, props); } - public Disconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, + public IDisconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { InLi li = new InLi() { WeakReference ref = new WeakReference(onChange); @@ -304,11 +304,11 @@ public void invalidated(Observable p) { return li; } - public Disconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener li) { + public IDisconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener li) { return addWeakInvalidationListener(prop, false, li); } - public Disconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, + public IDisconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener listener) { InLi d = new InLi() { WeakReference ref = new WeakReference<>(listener); @@ -339,8 +339,8 @@ public void invalidated(Observable p) { // list change listeners - public Disconnectable addListChangeListener(ObservableList list, ListChangeListener listener) { - Disconnectable d = new Disconnectable() { + public IDisconnectable addListChangeListener(ObservableList list, ListChangeListener listener) { + IDisconnectable d = new IDisconnectable() { public void disconnect() { list.removeListener(listener); } @@ -352,7 +352,7 @@ public void disconnect() { return d; } - public Disconnectable addWeakListChangeListener(ObservableList list, ListChangeListener listener) { + public IDisconnectable addWeakListChangeListener(ObservableList list, ListChangeListener listener) { LiChLi li = new LiChLi() { WeakReference> ref = new WeakReference<>(listener); @@ -382,9 +382,9 @@ public void onChanged(Change ch) { // - protected static abstract class ChLi implements Disconnectable, ChangeListener { } + protected static abstract class ChLi implements IDisconnectable, ChangeListener { } - protected static abstract class InLi implements Disconnectable, InvalidationListener { } + protected static abstract class InLi implements IDisconnectable, InvalidationListener { } - protected static abstract class LiChLi implements Disconnectable, ListChangeListener { } + protected static abstract class LiChLi implements IDisconnectable, ListChangeListener { } } \ No newline at end of file From 8ff39da180226be74eb56e8813dde5b68b512d64 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 7 Oct 2022 12:48:40 -0700 Subject: [PATCH 04/24] 8294809: null checks --- .../javafx/scene/control/ListenerHelper.java | 103 ++++++++++++------ 1 file changed, 68 insertions(+), 35 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index ac43af0c719..36643d7a1ec 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -58,8 +58,8 @@ public ListenerHelper() { public static ListenerHelper get(Node n) { Object x = n.getProperties().get(KEY); - if (x instanceof ListenerHelper) { - return (ListenerHelper)x; + if (x instanceof ListenerHelper h) { + return h; } ListenerHelper d = new ListenerHelper(); n.getProperties().put(KEY, d); @@ -67,9 +67,9 @@ public static ListenerHelper get(Node n) { } public static void disconnect(Node n) { - Object x = n.getProperties().get(KEY); - if (x instanceof ListenerHelper) { - ((ListenerHelper)x).disconnect(); + Object x = n.getProperties().remove(KEY); + if (x instanceof ListenerHelper h) { + h.disconnect(); } } @@ -91,6 +91,10 @@ public IDisconnectable addChangeListener(Runnable callback, ObservableValue.. } public IDisconnectable addChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { + if (onChange == null) { + throw new NullPointerException("onChange must not be null."); + } + ChLi li = new ChLi() { public void disconnect() { for (ObservableValue p : props) { @@ -116,24 +120,27 @@ public void changed(ObservableValue p, Object oldValue, Object newValue) { return li; } - public IDisconnectable addChangeListener(ObservableValue prop, ChangeListener li) { - return addChangeListener(prop, false, li); + public IDisconnectable addChangeListener(ObservableValue prop, ChangeListener listener) { + return addChangeListener(prop, false, listener); } - public IDisconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, - ChangeListener li) { + public IDisconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + IDisconnectable d = new IDisconnectable() { public void disconnect() { - prop.removeListener(li); + prop.removeListener(listener); } }; items.add(d); - prop.addListener(li); + prop.addListener(listener); if (fireImmediately) { T v = prop.getValue(); - li.changed(prop, null, v); + listener.changed(prop, null, v); } return d; @@ -143,8 +150,11 @@ public IDisconnectable addWeakChangeListener(Runnable onChange, ObservableValue< return addWeakChangeListener(onChange, false, props); } - public IDisconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, - ObservableValue... props) { + public IDisconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { + if (onChange == null) { + throw new NullPointerException("onChange must not be null."); + } + ChLi li = new ChLi() { WeakReference ref = new WeakReference(onChange); @@ -177,12 +187,15 @@ public void changed(ObservableValue p, Object oldValue, Object newValue) { return li; } - public IDisconnectable addWeakChangeListener(ObservableValue prop, ChangeListener li) { - return addChangeListener(prop, false, li); + public IDisconnectable addWeakChangeListener(ObservableValue prop, ChangeListener listener) { + return addChangeListener(prop, false, listener); } - public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, - ChangeListener listener) { + public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + ChLi d = new ChLi() { WeakReference> ref = new WeakReference<>(listener); @@ -217,8 +230,11 @@ public IDisconnectable addInvalidationListener(Runnable callback, ObservableValu return addInvalidationListener(callback, false, props); } - public IDisconnectable addInvalidationListener(Runnable onChange, boolean fireImmediately, - ObservableValue... props) { + public IDisconnectable addInvalidationListener(Runnable callback, boolean fireImmediately, ObservableValue... props) { + if (callback == null) { + throw new NullPointerException("Callback must be specified."); + } + InLi li = new InLi() { public void disconnect() { for (ObservableValue p : props) { @@ -227,7 +243,7 @@ public void disconnect() { } public void invalidated(Observable p) { - onChange.run(); + callback.run(); } }; @@ -238,29 +254,32 @@ public void invalidated(Observable p) { } if (fireImmediately) { - onChange.run(); + callback.run(); } return li; } - public IDisconnectable addInvalidationListener(ObservableValue prop, InvalidationListener li) { - return addInvalidationListener(prop, false, li); + public IDisconnectable addInvalidationListener(ObservableValue prop, InvalidationListener listener) { + return addInvalidationListener(prop, false, listener); } - public IDisconnectable addInvalidationListener(ObservableValue prop, boolean fireImmediately, - InvalidationListener li) { + public IDisconnectable addInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + IDisconnectable d = new IDisconnectable() { public void disconnect() { - prop.removeListener(li); + prop.removeListener(listener); } }; items.add(d); - prop.addListener(li); + prop.addListener(listener); if (fireImmediately) { - li.invalidated(prop); + listener.invalidated(prop); } return d; @@ -270,8 +289,11 @@ public IDisconnectable addWeakInvalidationListener(Runnable onChange, Observable return addWeakInvalidationListener(onChange, false, props); } - public IDisconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, - ObservableValue... props) { + public IDisconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { + if (onChange == null) { + throw new NullPointerException("onChange must not be null."); + } + InLi li = new InLi() { WeakReference ref = new WeakReference(onChange); @@ -304,12 +326,15 @@ public void invalidated(Observable p) { return li; } - public IDisconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener li) { - return addWeakInvalidationListener(prop, false, li); + public IDisconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener listener) { + return addWeakInvalidationListener(prop, false, listener); } - public IDisconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, - InvalidationListener listener) { + public IDisconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + InLi d = new InLi() { WeakReference ref = new WeakReference<>(listener); @@ -340,6 +365,10 @@ public void invalidated(Observable p) { // list change listeners public IDisconnectable addListChangeListener(ObservableList list, ListChangeListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + IDisconnectable d = new IDisconnectable() { public void disconnect() { list.removeListener(listener); @@ -353,6 +382,10 @@ public void disconnect() { } public IDisconnectable addWeakListChangeListener(ObservableList list, ListChangeListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + LiChLi li = new LiChLi() { WeakReference> ref = new WeakReference<>(listener); From 78dfacf9536301fd5a6305c70819dd470fb341ab Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 7 Oct 2022 12:56:22 -0700 Subject: [PATCH 05/24] 8294809: override --- .../javafx/scene/control/ListenerHelper.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index 36643d7a1ec..1ad57ae7578 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -77,6 +77,7 @@ public void addDisconnectable(IDisconnectable d) { items.add(d); } + @Override public void disconnect() { for (int i = items.size() - 1; i >= 0; i--) { IDisconnectable d = items.remove(i); @@ -96,12 +97,14 @@ public IDisconnectable addChangeListener(Runnable onChange, boolean fireImmediat } ChLi li = new ChLi() { + @Override public void disconnect() { for (ObservableValue p : props) { p.removeListener(this); } } + @Override public void changed(ObservableValue p, Object oldValue, Object newValue) { onChange.run(); } @@ -130,6 +133,7 @@ public IDisconnectable addChangeListener(ObservableValue prop, boolean fi } IDisconnectable d = new IDisconnectable() { + @Override public void disconnect() { prop.removeListener(listener); } @@ -158,12 +162,14 @@ public IDisconnectable addWeakChangeListener(Runnable onChange, boolean fireImme ChLi li = new ChLi() { WeakReference ref = new WeakReference(onChange); + @Override public void disconnect() { for (ObservableValue p : props) { p.removeListener(this); } } + @Override public void changed(ObservableValue p, Object oldValue, Object newValue) { Runnable r = ref.get(); if (r == null) { @@ -199,11 +205,13 @@ public IDisconnectable addWeakChangeListener(ObservableValue prop, boolea ChLi d = new ChLi() { WeakReference> ref = new WeakReference<>(listener); + @Override public void disconnect() { prop.removeListener(this); } - public void changed(ObservableValue p, T oldValue, T newValue) { + @Override + public void changed(ObservableValue p, T oldValue, T newValue) { ChangeListener li = ref.get(); if (li == null) { disconnect(); @@ -236,12 +244,14 @@ public IDisconnectable addInvalidationListener(Runnable callback, boolean fireIm } InLi li = new InLi() { + @Override public void disconnect() { for (ObservableValue p : props) { p.removeListener(this); } } + @Override public void invalidated(Observable p) { callback.run(); } @@ -270,6 +280,7 @@ public IDisconnectable addInvalidationListener(ObservableValue prop, bool } IDisconnectable d = new IDisconnectable() { + @Override public void disconnect() { prop.removeListener(listener); } @@ -297,12 +308,14 @@ public IDisconnectable addWeakInvalidationListener(Runnable onChange, boolean fi InLi li = new InLi() { WeakReference ref = new WeakReference(onChange); + @Override public void disconnect() { for (ObservableValue p : props) { p.removeListener(this); } } + @Override public void invalidated(Observable p) { Runnable r = ref.get(); if (r == null) { @@ -338,10 +351,12 @@ public IDisconnectable addWeakInvalidationListener(ObservableValue prop, bool InLi d = new InLi() { WeakReference ref = new WeakReference<>(listener); + @Override public void disconnect() { prop.removeListener(this); } + @Override public void invalidated(Observable p) { InvalidationListener li = ref.get(); if (li == null) { @@ -370,6 +385,7 @@ public IDisconnectable addListChangeListener(ObservableList list, ListCha } IDisconnectable d = new IDisconnectable() { + @Override public void disconnect() { list.removeListener(listener); } @@ -389,10 +405,12 @@ public IDisconnectable addWeakListChangeListener(ObservableList list, Lis LiChLi li = new LiChLi() { WeakReference> ref = new WeakReference<>(listener); + @Override public void disconnect() { list.removeListener(this); } + @Override public void onChanged(Change ch) { ListChangeListener li = ref.get(); if (li == null) { From 718e2559a6bffa3bd20383bce7364686d7e6277f Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 7 Oct 2022 13:29:49 -0700 Subject: [PATCH 06/24] 8294809: cleanup --- .../java/com/sun/javafx/scene/control/ListenerHelper.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index 1ad57ae7578..aeb9a0df9b4 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -427,10 +427,6 @@ public void onChanged(Change ch) { return li; } - // TODO event handlers - - // TODO event filters - // protected static abstract class ChLi implements IDisconnectable, ChangeListener { } From 10449406c224b948464ce5c96a793daa71b5606e Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 10 Oct 2022 14:40:04 -0700 Subject: [PATCH 07/24] 8294809: event handlers and filters --- .../javafx/scene/control/ListenerHelper.java | 200 +++++++++++++++++- 1 file changed, 199 insertions(+), 1 deletion(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index aeb9a0df9b4..12e7cb688e9 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -33,7 +33,17 @@ import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.event.EventType; import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableColumnBase; +import javafx.scene.control.TreeItem; +import javafx.scene.transform.Transform; +import javafx.stage.Window; /** * This class provides convenience methods for adding various listeners, both strong and weak, @@ -73,8 +83,9 @@ public static void disconnect(Node n) { } } - public void addDisconnectable(IDisconnectable d) { + public IDisconnectable addDisconnectable(IDisconnectable d) { items.add(d); + return d; } @Override @@ -427,6 +438,175 @@ public void onChanged(Change ch) { return li; } + // event handlers + + public IDisconnectable addEventHandler(Object x, EventType t, EventHandler h) { + + // we really need an interface here ... "HasEventHandlers" + IDisconnectable d = addDisconnectable(() -> { + if (x instanceof Node n) { + n.removeEventHandler(t, h); + } else if (x instanceof Window y) { + y.removeEventHandler(t, h); + } else if (x instanceof Scene y) { + y.removeEventHandler(t, h); + } else if (x instanceof MenuItem y) { + y.removeEventHandler(t, h); + } else if (x instanceof TreeItem y) { + y.removeEventHandler(t, h); + } else if (x instanceof TableColumnBase y) { + y.removeEventHandler(t, h); + } else if (x instanceof Transform y) { + y.removeEventHandler(t, h); + } else if (x instanceof Task y) { + y.removeEventHandler(t, h); + } + }); + + if (x instanceof Node y) { + y.addEventHandler(t, h); + } else if (x instanceof Window y) { + y.addEventHandler(t, h); + } else if (x instanceof Scene y) { + y.addEventHandler(t, h); + } else if (x instanceof MenuItem y) { + y.addEventHandler(t, h); + } else if (x instanceof TreeItem y) { + y.addEventHandler(t, h); + } else if (x instanceof TableColumnBase y) { + y.addEventHandler(t, h); + } else if (x instanceof Transform y) { + y.addEventHandler(t, h); + } else if (x instanceof Task y) { + y.addEventHandler(t, h); + } else { + throw new IllegalArgumentException("Cannot add event handler to " + x); + } + + return d; + } + + public IDisconnectable addWeakEventHandler(Object x, EventType t, EventHandler h) { + WeHa li = new WeHa(h) { + @Override + public void disconnect() { + if (x instanceof Node n) { + n.removeEventHandler(t, this); + } else if (x instanceof Window y) { + y.removeEventHandler(t, this); + } else if (x instanceof Scene y) { + y.removeEventHandler(t, this); + } else if (x instanceof MenuItem y) { + y.removeEventHandler(t, this); + } else if (x instanceof TreeItem y) { + y.removeEventHandler(t, this); + } else if (x instanceof TableColumnBase y) { + y.removeEventHandler(t, this); + } else if (x instanceof Transform y) { + y.removeEventHandler(t, this); + } else if (x instanceof Task y) { + y.removeEventHandler(t, this); + } + } + }; + + items.add(li); + + if (x instanceof Node y) { + y.addEventHandler(t, li); + } else if (x instanceof Window y) { + y.addEventHandler(t, li); + } else if (x instanceof Scene y) { + y.addEventHandler(t, li); + } else if (x instanceof MenuItem y) { + y.addEventHandler(t, li); + } else if (x instanceof TreeItem y) { + y.addEventHandler(t, li); + } else if (x instanceof TableColumnBase y) { + y.addEventHandler(t, li); + } else if (x instanceof Transform y) { + y.addEventHandler(t, li); + } else if (x instanceof Task y) { + y.addEventHandler(t, li); + } else { + throw new IllegalArgumentException("Cannot add weak event handler to " + x); + } + + return li; + } + + // event filters + + public IDisconnectable addEventFilter(Object x, EventType t, EventHandler h) { + // we really need an interface here ... "HasEventFilters" + IDisconnectable d = addDisconnectable(() -> { + if (x instanceof Node n) { + n.removeEventFilter(t, h); + } else if (x instanceof Window y) { + y.removeEventFilter(t, h); + } else if (x instanceof Scene y) { + y.removeEventFilter(t, h); + } else if (x instanceof Transform y) { + y.removeEventFilter(t, h); + } else if (x instanceof Task y) { + y.removeEventFilter(t, h); + } + }); + + if (x instanceof Node y) { + y.addEventFilter(t, h); + } else if (x instanceof Window y) { + y.addEventFilter(t, h); + } else if (x instanceof Scene y) { + y.addEventFilter(t, h); + } else if (x instanceof Transform y) { + y.addEventFilter(t, h); + } else if (x instanceof Task y) { + y.addEventFilter(t, h); + } else { + throw new IllegalArgumentException("Cannot add event filter to " + x); + } + + return d; + } + + public IDisconnectable addWeakEventFilter(Object x, EventType t, EventHandler h) { + WeHa li = new WeHa(h) { + @Override + public void disconnect() { + if (x instanceof Node n) { + n.removeEventFilter(t, this); + } else if (x instanceof Window y) { + y.removeEventFilter(t, this); + } else if (x instanceof Scene y) { + y.removeEventFilter(t, this); + } else if (x instanceof Transform y) { + y.removeEventFilter(t, this); + } else if (x instanceof Task y) { + y.removeEventFilter(t, this); + } + } + }; + + items.add(li); + + if (x instanceof Node y) { + y.addEventFilter(t, li); + } else if (x instanceof Window y) { + y.addEventFilter(t, li); + } else if (x instanceof Scene y) { + y.addEventFilter(t, li); + } else if (x instanceof Transform y) { + y.addEventFilter(t, li); + } else if (x instanceof Task y) { + y.addEventFilter(t, li); + } else { + throw new IllegalArgumentException("Cannot add weak event filter to " + x); + } + + return li; + } + // protected static abstract class ChLi implements IDisconnectable, ChangeListener { } @@ -434,4 +614,22 @@ protected static abstract class ChLi implements IDisconnectable, ChangeListen protected static abstract class InLi implements IDisconnectable, InvalidationListener { } protected static abstract class LiChLi implements IDisconnectable, ListChangeListener { } + + protected static abstract class WeHa implements IDisconnectable, EventHandler { + private final WeakReference> ref; + + public WeHa(EventHandler h) { + ref = new WeakReference<>(h); + } + + @Override + public void handle(T ev) { + EventHandler h = ref.get(); + if (h == null) { + disconnect(); + } else { + h.handle(ev); + } + } + } } \ No newline at end of file From 0589e78dbb1da65164b6b0f862bf8cf7a9ba8df0 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 11 Oct 2022 13:28:43 -0700 Subject: [PATCH 08/24] 8294809: whitespace --- .../main/java/com/sun/javafx/scene/control/ListenerHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index 12e7cb688e9..57029f577ce 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -614,7 +614,7 @@ protected static abstract class ChLi implements IDisconnectable, ChangeListen protected static abstract class InLi implements IDisconnectable, InvalidationListener { } protected static abstract class LiChLi implements IDisconnectable, ListChangeListener { } - + protected static abstract class WeHa implements IDisconnectable, EventHandler { private final WeakReference> ref; From d52802e743d04d58355e2026521b69c0be83b1f1 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 11 Oct 2022 15:18:41 -0700 Subject: [PATCH 09/24] 8294809: skin base --- .../java/javafx/scene/control/SkinBase.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java index a07635466d5..6449c27d27e 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2022, 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 @@ -29,8 +29,6 @@ import java.util.List; import java.util.function.Consumer; -import com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler; - import javafx.beans.Observable; import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener.Change; @@ -48,6 +46,9 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.Region; +import com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler; +import com.sun.javafx.scene.control.ListenerHelper; + /** * Base implementation class for defining the visual representation of user * interface controls by defining a scene graph of nodes to represent the @@ -82,8 +83,10 @@ public abstract class SkinBase implements Skin { * want to adjust the way listeners are added rather than continuing to use this * map (although it doesn't really do much harm). */ + @Deprecated // replace with listenerHelper private LambdaMultiplePropertyChangeListenerHandler lambdaChangeListenerHandler; + private ListenerHelper listenerHelper; /* ************************************************************************* @@ -158,6 +161,10 @@ protected SkinBase(final C control) { lambdaChangeListenerHandler.dispose(); } + if (listenerHelper != null) { + listenerHelper.disconnect(); + } + this.control = null; } @@ -207,6 +214,17 @@ protected final void consumeMouseEvents(boolean value) { } } + /** + * Returns the skin's instance of {@link ListenerHelper}, creating it if necessary. + * + * @since 20 + */ + protected ListenerHelper listenerHelper() { + if (listenerHelper == null) { + listenerHelper = new ListenerHelper(); + } + return listenerHelper; + } /** * Registers an operation to perform when the given {@code observable} sends a change event. @@ -219,6 +237,7 @@ protected final void consumeMouseEvents(boolean value) { * may be {@code null} * @since 9 */ + // TODO I would like to deprecate and remove these methods, and replace them by listenerHelper().add**() protected final void registerChangeListener(ObservableValue observable, Consumer> operation) { if (lambdaChangeListenerHandler == null) { lambdaChangeListenerHandler = new LambdaMultiplePropertyChangeListenerHandler(); From cd6cd5cf9c389aff4ec81a1b533ff4f0d8fe0334 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 11 Oct 2022 15:39:08 -0700 Subject: [PATCH 10/24] 8294809: change listener with callback --- .../javafx/scene/control/ListenerHelper.java | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index 57029f577ce..29efcf7e831 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -26,7 +26,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; - +import java.util.function.Consumer; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.value.ChangeListener; @@ -161,6 +161,38 @@ public void disconnect() { return d; } + public IDisconnectable addChangeListener(ObservableValue prop, Consumer callback) { + return addChangeListener(prop, false, callback); + } + + public IDisconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, Consumer callback) { + if (callback == null) { + throw new NullPointerException("Callback must be specified."); + } + + ChLi d = new ChLi() { + @Override + public void disconnect() { + prop.removeListener(this); + } + + @Override + public void changed(ObservableValue observable, T oldValue, T newValue) { + callback.accept(newValue); + } + }; + + items.add(d); + prop.addListener(d); + + if (fireImmediately) { + T v = prop.getValue(); + callback.accept(v); + } + + return d; + } + public IDisconnectable addWeakChangeListener(Runnable onChange, ObservableValue... props) { return addWeakChangeListener(onChange, false, props); } @@ -243,6 +275,45 @@ public void changed(ObservableValue p, T oldValue, T newValue) { return d; } + public IDisconnectable addWeakChangeListener(ObservableValue prop, Consumer callback) { + return addWeakChangeListener(prop, false, callback); + } + + public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, Consumer callback) { + if (callback == null) { + throw new NullPointerException("Callback must be specified."); + } + + ChLi d = new ChLi() { + WeakReference> ref = new WeakReference<>(callback); + + @Override + public void disconnect() { + prop.removeListener(this); + } + + @Override + public void changed(ObservableValue observable, T oldValue, T newValue) { + Consumer cb = ref.get(); + if (cb == null) { + disconnect(); + } else { + cb.accept(newValue); + } + } + }; + + items.add(d); + prop.addListener(d); + + if (fireImmediately) { + T v = prop.getValue(); + callback.accept(v); + } + + return d; + } + // invalidation listeners public IDisconnectable addInvalidationListener(Runnable callback, ObservableValue... props) { From 39e0bd7e37ea2f1ce0f50a8b108c94c9656bfa55 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 12 Oct 2022 11:11:01 -0700 Subject: [PATCH 11/24] 8294809: remove --- .../javafx/scene/control/ListenerHelper.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index 29efcf7e831..768fc84a617 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -83,7 +83,14 @@ public static void disconnect(Node n) { } } - public IDisconnectable addDisconnectable(IDisconnectable d) { + public IDisconnectable addDisconnectable(Runnable r) { + IDisconnectable d = new IDisconnectable() { + @Override + public void disconnect() { + items.remove(this); + r.run(); + } + }; items.add(d); return d; } @@ -113,6 +120,7 @@ public void disconnect() { for (ObservableValue p : props) { p.removeListener(this); } + items.remove(this); } @Override @@ -147,6 +155,7 @@ public IDisconnectable addChangeListener(ObservableValue prop, boolean fi @Override public void disconnect() { prop.removeListener(listener); + items.remove(this); } }; @@ -174,6 +183,7 @@ public IDisconnectable addChangeListener(ObservableValue prop, boolean fi @Override public void disconnect() { prop.removeListener(this); + items.remove(this); } @Override @@ -210,6 +220,7 @@ public void disconnect() { for (ObservableValue p : props) { p.removeListener(this); } + items.remove(this); } @Override @@ -251,6 +262,7 @@ public IDisconnectable addWeakChangeListener(ObservableValue prop, boolea @Override public void disconnect() { prop.removeListener(this); + items.remove(this); } @Override @@ -290,6 +302,7 @@ public IDisconnectable addWeakChangeListener(ObservableValue prop, boolea @Override public void disconnect() { prop.removeListener(this); + items.remove(this); } @Override @@ -331,6 +344,7 @@ public void disconnect() { for (ObservableValue p : props) { p.removeListener(this); } + items.remove(this); } @Override @@ -365,6 +379,7 @@ public IDisconnectable addInvalidationListener(ObservableValue prop, bool @Override public void disconnect() { prop.removeListener(listener); + items.remove(this); } }; @@ -395,6 +410,7 @@ public void disconnect() { for (ObservableValue p : props) { p.removeListener(this); } + items.remove(this); } @Override @@ -436,6 +452,7 @@ public IDisconnectable addWeakInvalidationListener(ObservableValue prop, bool @Override public void disconnect() { prop.removeListener(this); + items.remove(this); } @Override @@ -470,6 +487,7 @@ public IDisconnectable addListChangeListener(ObservableList list, ListCha @Override public void disconnect() { list.removeListener(listener); + items.remove(this); } }; @@ -490,6 +508,7 @@ public IDisconnectable addWeakListChangeListener(ObservableList list, Lis @Override public void disconnect() { list.removeListener(this); + items.remove(this); } @Override @@ -578,6 +597,7 @@ public void disconnect() { } else if (x instanceof Task y) { y.removeEventHandler(t, this); } + items.remove(this); } }; @@ -656,6 +676,7 @@ public void disconnect() { } else if (x instanceof Task y) { y.removeEventFilter(t, this); } + items.remove(this); } }; From 512a63dc2da12f28b5fb50fd7922add587445e62 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 12 Oct 2022 12:35:46 -0700 Subject: [PATCH 12/24] 8294809: tests --- .../javafx/scene/control/ListenerHelper.java | 2 +- .../scene/control/TestListenerHelper.java | 706 ++++++++++++++++++ 2 files changed, 707 insertions(+), 1 deletion(-) create mode 100644 modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index 768fc84a617..ce6d72691d9 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -248,7 +248,7 @@ public void changed(ObservableValue p, Object oldValue, Object newValue) { } public IDisconnectable addWeakChangeListener(ObservableValue prop, ChangeListener listener) { - return addChangeListener(prop, false, listener); + return addWeakChangeListener(prop, false, listener); } public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener listener) { diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java new file mode 100644 index 00000000000..cc8ef2b30f1 --- /dev/null +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java @@ -0,0 +1,706 @@ +/* + * Copyright (c) 2022, 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.javafx.scene.control; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import javafx.beans.InvalidationListener; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ChangeListener; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.event.EventHandler; +import javafx.event.EventTarget; +import javafx.scene.Group; +import javafx.scene.Scene; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TreeItem; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; +import javafx.scene.transform.Scale; +import javafx.stage.Stage; + +import org.junit.Test; + +import com.sun.javafx.event.EventUtil; +import com.sun.javafx.scene.control.ListenerHelper; + +import test.com.sun.javafx.scene.control.infrastructure.MouseEventGenerator; +import test.util.memory.JMemoryBuddy; + +/** + * Tests ListenerHelper utility class. + */ +public class TestListenerHelper { + @Test + public void testStaticDisconnect() { + AtomicInteger ct = new AtomicInteger(); + Group node = new Group(); + ListenerHelper h = ListenerHelper.get(node); + assertNotNull(h); + + h.addDisconnectable(() -> { + ct.incrementAndGet(); + }); + + ListenerHelper.disconnect(node); + assertEquals(1, ct.get()); + } + + // change listeners + + @Test + public void testChangeListener_MultipleProperties() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addChangeListener(() -> ct.incrementAndGet(), p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(2, ct.get()); + + h.disconnect(); + + p1.set("3"); + p2.set("4"); + assertEquals(2, ct.get()); + } + + @Test + public void testChangeListener_MultipleProperties_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addChangeListener(() -> ct.incrementAndGet(), true, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(3, ct.get()); + + h.disconnect(); + + p1.set("3"); + p2.set("4"); + assertEquals(3, ct.get()); + } + + @Test + public void testChangeListener_Plain() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addChangeListener(p, (s, old, cur) -> ct.incrementAndGet()); + + p.set("1"); + assertEquals(1, ct.get()); + + h.disconnect(); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testChangeListener_Plain_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addChangeListener(p, true, (s, old, cur) -> ct.incrementAndGet()); + + p.set("1"); + assertEquals(2, ct.get()); + + h.disconnect(); + + p.set("2"); + assertEquals(2, ct.get()); + } + + @Test + public void testChangeListener_Callback() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addChangeListener(p, (cur) -> ct.incrementAndGet()); + + p.set("1"); + assertEquals(1, ct.get()); + + h.disconnect(); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testChangeListener_Callback_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addChangeListener(p, true, (cur) -> ct.incrementAndGet()); + + p.set("1"); + assertEquals(2, ct.get()); + + h.disconnect(); + + p.set("2"); + assertEquals(2, ct.get()); + } + + @Test + public void testWeakChangeListener_MultipleProperties() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Runnable li = () -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakChangeListener(li, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p1.set("3"); + p2.set("4"); + assertEquals(2, ct.get()); + } + + @Test + public void testWeakChangeListener_MultipleProperties_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Runnable li = () -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakChangeListener(() -> ct.incrementAndGet(), true, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(3, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p1.set("3"); + p2.set("4"); + assertEquals(3, ct.get()); + } + + @Test + public void testWeakChangeListener_Plain() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + ChangeListener li = (src, old, cur) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakChangeListener(p, li); + + p.set("1"); + assertEquals(1, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testWeakChangeListener_Plain_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + ChangeListener li = (src, old, cur) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakChangeListener(p, true, li); + + p.set("1"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(2, ct.get()); + } + + @Test + public void testWeakChangeListener_Callback() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Consumer li = (s) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakChangeListener(p, li); + + p.set("1"); + assertEquals(1, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testWeakChangeListener_Callback_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Consumer li = (s) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakChangeListener(p, true, li); + + p.set("1"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(2, ct.get()); + } + + // invalidation listeners + + @Test + public void testInvalidationListener_MultipleProperties() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addInvalidationListener(() -> ct.incrementAndGet(), p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(2, ct.get()); + + h.disconnect(); + + p1.set("3"); + p2.set("4"); + assertEquals(2, ct.get()); + } + + @Test + public void testInvalidationListener_MultipleProperties_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addInvalidationListener(() -> ct.incrementAndGet(), true, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(3, ct.get()); + + h.disconnect(); + + p1.set("3"); + p2.set("4"); + assertEquals(3, ct.get()); + } + + @Test + public void testInvalidationListener_Plain() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addInvalidationListener(p, (x) -> ct.incrementAndGet()); + + p.set("1"); + assertEquals(1, ct.get()); + + h.disconnect(); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testInvalidationListener_Plain_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addInvalidationListener(p, true, (x) -> ct.incrementAndGet()); + + p.set("1"); + assertEquals(2, ct.get()); + + h.disconnect(); + + p.set("2"); + assertEquals(2, ct.get()); + } + + @Test + public void testInvalidationListener_Callback() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addInvalidationListener(p, (cur) -> ct.incrementAndGet()); + + p.set("1"); + assertEquals(1, ct.get()); + + h.disconnect(); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testInvalidationListener_Callback_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + + h.addInvalidationListener(p, true, (cur) -> ct.incrementAndGet()); + + p.set("1"); + assertEquals(2, ct.get()); + + h.disconnect(); + + p.set("2"); + assertEquals(2, ct.get()); + } + + @Test + public void testWeakInvalidationListener_MultipleProperties() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Runnable li = () -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakInvalidationListener(li, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p1.set("3"); + p2.set("4"); + assertEquals(2, ct.get()); + } + + @Test + public void testWeakInvalidationListener_MultipleProperties_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Runnable li = () -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakInvalidationListener(() -> ct.incrementAndGet(), true, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(3, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p1.set("3"); + p2.set("4"); + assertEquals(3, ct.get()); + } + + @Test + public void testWeakInvalidationListener_Plain() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + InvalidationListener li = (x) -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakInvalidationListener(p, li); + + p.set("1"); + assertEquals(1, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testWeakInvalidationListener_Plain_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + InvalidationListener li = (x) -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakInvalidationListener(p, true, li); + + p.set("1"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(2, ct.get()); + } + + // list change listeners + + @Test + public void testListChangeListener() { + ListenerHelper h = new ListenerHelper(); + ObservableList list = FXCollections.observableArrayList(); + AtomicInteger ct = new AtomicInteger(); + ListChangeListener li = (ch) -> ct.incrementAndGet(); + + h.addListChangeListener(list, li); + + list.add("1"); + assertEquals(1, ct.get()); + + h.disconnect(); + + list.add("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testWeakListChangeListener() { + ListenerHelper h = new ListenerHelper(); + ObservableList list = FXCollections.observableArrayList(); + AtomicInteger ct = new AtomicInteger(); + ListChangeListener li = (ch) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakListChangeListener(list, li); + + list.add("1"); + assertEquals(1, ct.get()); + + li = null; + JMemoryBuddy.assertCollectable(ref); + + list.add("2"); + assertEquals(1, ct.get()); + } + + // event handlers + + @Test + public void testEventHandler() { + EventTarget[] items = eventHandlerTargets(); + + for (EventTarget item : items) { + ListenerHelper h = new ListenerHelper(); + AtomicInteger ct = new AtomicInteger(); + + h.addEventHandler(item, MouseEvent.ANY, (ev) -> ct.incrementAndGet()); + + MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); + EventUtil.fireEvent(ev, item); + + assertEquals(1, ct.get()); + + h.disconnect(); + + EventUtil.fireEvent(ev, item); + assertEquals(1, ct.get()); + } + } + + @Test + public void testWeakEventHandler() { + EventTarget[] items = eventHandlerTargets(); + + for (EventTarget item : items) { + ListenerHelper h = new ListenerHelper(); + AtomicInteger ct = new AtomicInteger(); + EventHandler li = (ev) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference(li); + + h.addWeakEventHandler(item, MouseEvent.ANY, li); + + MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); + EventUtil.fireEvent(ev, item); + + assertEquals(1, ct.get()); + + li = null; + JMemoryBuddy.assertCollectable(ref); + + EventUtil.fireEvent(ev, item); + assertEquals(1, ct.get()); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testEventHandlerCheck() { + ListenerHelper h = new ListenerHelper(); + h.addEventHandler(new Object(), MouseEvent.ANY, (ev) -> { throw new Error(); }); + } + + // event filters + + @Test + public void testEventFilter() { + EventTarget[] items = eventHandlerFilters(); + + for (EventTarget item : items) { + ListenerHelper h = new ListenerHelper(); + AtomicInteger ct = new AtomicInteger(); + + h.addEventFilter(item, MouseEvent.ANY, (ev) -> ct.incrementAndGet()); + + MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); + EventUtil.fireEvent(ev, item); + + assertEquals(1, ct.get()); + + h.disconnect(); + + EventUtil.fireEvent(ev, item); + assertEquals(1, ct.get()); + } + } + + @Test + public void testWeakEventFilter() { + EventTarget[] items = eventHandlerFilters(); + + for (EventTarget item : items) { + ListenerHelper h = new ListenerHelper(); + AtomicInteger ct = new AtomicInteger(); + EventHandler li = (ev) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference(li); + + h.addWeakEventFilter(item, MouseEvent.ANY, li); + + MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); + EventUtil.fireEvent(ev, item); + + assertEquals(1, ct.get()); + + li = null; + JMemoryBuddy.assertCollectable(ref); + + EventUtil.fireEvent(ev, item); + assertEquals(1, ct.get()); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testEventFilterCheck() { + ListenerHelper h = new ListenerHelper(); + h.addEventFilter(new Object(), MouseEvent.ANY, (ev) -> { throw new Error(); }); + } + + // + + protected EventTarget[] eventHandlerTargets() { + return new EventTarget[] { + new Region(), + new Stage(), + new Scene(new Group()), + new MenuItem(), + new TreeItem(), + new TableColumn(), + new Scale(), + new Task() { + @Override + protected Object call() throws Exception { + return null; + } + } + }; + } + + protected EventTarget[] eventHandlerFilters() { + return new EventTarget[] { + new Region(), + new Stage(), + new Scene(new Group()), + new Scale(), + new Task() { + @Override + protected Object call() throws Exception { + return null; + } + } + }; + } +} From af77693c4c7e778f27f03063bb0256b579de3bb9 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 12 Oct 2022 13:33:16 -0700 Subject: [PATCH 13/24] 8294809: use weak reference correctly this time --- .../javafx/scene/control/ListenerHelper.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index ce6d72691d9..b2fa4fa535a 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -212,9 +212,9 @@ public IDisconnectable addWeakChangeListener(Runnable onChange, boolean fireImme throw new NullPointerException("onChange must not be null."); } - ChLi li = new ChLi() { - WeakReference ref = new WeakReference(onChange); + WeakReference ref = new WeakReference(onChange); + ChLi li = new ChLi() { @Override public void disconnect() { for (ObservableValue p : props) { @@ -256,9 +256,9 @@ public IDisconnectable addWeakChangeListener(ObservableValue prop, boolea throw new NullPointerException("Listener must be specified."); } - ChLi d = new ChLi() { - WeakReference> ref = new WeakReference<>(listener); + WeakReference> ref = new WeakReference<>(listener); + ChLi d = new ChLi() { @Override public void disconnect() { prop.removeListener(this); @@ -296,9 +296,9 @@ public IDisconnectable addWeakChangeListener(ObservableValue prop, boolea throw new NullPointerException("Callback must be specified."); } - ChLi d = new ChLi() { - WeakReference> ref = new WeakReference<>(callback); + WeakReference> ref = new WeakReference<>(callback); + ChLi d = new ChLi() { @Override public void disconnect() { prop.removeListener(this); @@ -402,9 +402,9 @@ public IDisconnectable addWeakInvalidationListener(Runnable onChange, boolean fi throw new NullPointerException("onChange must not be null."); } - InLi li = new InLi() { - WeakReference ref = new WeakReference(onChange); + WeakReference ref = new WeakReference(onChange); + InLi li = new InLi() { @Override public void disconnect() { for (ObservableValue p : props) { @@ -446,9 +446,9 @@ public IDisconnectable addWeakInvalidationListener(ObservableValue prop, bool throw new NullPointerException("Listener must be specified."); } - InLi d = new InLi() { - WeakReference ref = new WeakReference<>(listener); + WeakReference ref = new WeakReference<>(listener); + InLi d = new InLi() { @Override public void disconnect() { prop.removeListener(this); @@ -502,9 +502,9 @@ public IDisconnectable addWeakListChangeListener(ObservableList list, Lis throw new NullPointerException("Listener must be specified."); } - LiChLi li = new LiChLi() { - WeakReference> ref = new WeakReference<>(listener); + WeakReference> ref = new WeakReference<>(listener); + LiChLi li = new LiChLi() { @Override public void disconnect() { list.removeListener(this); From 2df4a85db638d76cacaf6c54ba669cdb3dd91a18 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 14 Oct 2022 12:04:11 -0700 Subject: [PATCH 14/24] 8294809: removed weak listeners support --- .../javafx/scene/control/ListenerHelper.java | 346 +----------------- .../scene/control/TestListenerHelper.java | 298 --------------- 2 files changed, 1 insertion(+), 643 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index b2fa4fa535a..d18a7eecffc 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -24,9 +24,9 @@ */ package com.sun.javafx.scene.control; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.function.Consumer; + import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.value.ChangeListener; @@ -203,130 +203,6 @@ public void changed(ObservableValue observable, T oldValue, T newVa return d; } - public IDisconnectable addWeakChangeListener(Runnable onChange, ObservableValue... props) { - return addWeakChangeListener(onChange, false, props); - } - - public IDisconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { - if (onChange == null) { - throw new NullPointerException("onChange must not be null."); - } - - WeakReference ref = new WeakReference(onChange); - - ChLi li = new ChLi() { - @Override - public void disconnect() { - for (ObservableValue p : props) { - p.removeListener(this); - } - items.remove(this); - } - - @Override - public void changed(ObservableValue p, Object oldValue, Object newValue) { - Runnable r = ref.get(); - if (r == null) { - disconnect(); - } else { - r.run(); - } - } - }; - - items.add(li); - - for (ObservableValue p : props) { - p.addListener(li); - } - - if (fireImmediately) { - onChange.run(); - } - - return li; - } - - public IDisconnectable addWeakChangeListener(ObservableValue prop, ChangeListener listener) { - return addWeakChangeListener(prop, false, listener); - } - - public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener listener) { - if (listener == null) { - throw new NullPointerException("Listener must be specified."); - } - - WeakReference> ref = new WeakReference<>(listener); - - ChLi d = new ChLi() { - @Override - public void disconnect() { - prop.removeListener(this); - items.remove(this); - } - - @Override - public void changed(ObservableValue p, T oldValue, T newValue) { - ChangeListener li = ref.get(); - if (li == null) { - disconnect(); - } else { - li.changed(p, oldValue, newValue); - } - } - }; - - items.add(d); - prop.addListener(d); - - if (fireImmediately) { - T v = prop.getValue(); - listener.changed(prop, null, v); - } - - return d; - } - - public IDisconnectable addWeakChangeListener(ObservableValue prop, Consumer callback) { - return addWeakChangeListener(prop, false, callback); - } - - public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, Consumer callback) { - if (callback == null) { - throw new NullPointerException("Callback must be specified."); - } - - WeakReference> ref = new WeakReference<>(callback); - - ChLi d = new ChLi() { - @Override - public void disconnect() { - prop.removeListener(this); - items.remove(this); - } - - @Override - public void changed(ObservableValue observable, T oldValue, T newValue) { - Consumer cb = ref.get(); - if (cb == null) { - disconnect(); - } else { - cb.accept(newValue); - } - } - }; - - items.add(d); - prop.addListener(d); - - if (fireImmediately) { - T v = prop.getValue(); - callback.accept(v); - } - - return d; - } - // invalidation listeners public IDisconnectable addInvalidationListener(Runnable callback, ObservableValue... props) { @@ -393,89 +269,6 @@ public void disconnect() { return d; } - public IDisconnectable addWeakInvalidationListener(Runnable onChange, ObservableValue... props) { - return addWeakInvalidationListener(onChange, false, props); - } - - public IDisconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { - if (onChange == null) { - throw new NullPointerException("onChange must not be null."); - } - - WeakReference ref = new WeakReference(onChange); - - InLi li = new InLi() { - @Override - public void disconnect() { - for (ObservableValue p : props) { - p.removeListener(this); - } - items.remove(this); - } - - @Override - public void invalidated(Observable p) { - Runnable r = ref.get(); - if (r == null) { - disconnect(); - } else { - r.run(); - } - } - }; - - items.add(li); - - for (ObservableValue p : props) { - p.addListener(li); - } - - if (fireImmediately) { - onChange.run(); - } - - return li; - } - - public IDisconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener listener) { - return addWeakInvalidationListener(prop, false, listener); - } - - public IDisconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener listener) { - if (listener == null) { - throw new NullPointerException("Listener must be specified."); - } - - WeakReference ref = new WeakReference<>(listener); - - InLi d = new InLi() { - @Override - public void disconnect() { - prop.removeListener(this); - items.remove(this); - } - - @Override - public void invalidated(Observable p) { - InvalidationListener li = ref.get(); - if (li == null) { - disconnect(); - } else { - li.invalidated(p); - } - } - }; - - items.add(d); - prop.addListener(d); - - if (fireImmediately) { - listener.invalidated(prop); - } - - return d; - } - // list change listeners public IDisconnectable addListChangeListener(ObservableList list, ListChangeListener listener) { @@ -497,37 +290,6 @@ public void disconnect() { return d; } - public IDisconnectable addWeakListChangeListener(ObservableList list, ListChangeListener listener) { - if (listener == null) { - throw new NullPointerException("Listener must be specified."); - } - - WeakReference> ref = new WeakReference<>(listener); - - LiChLi li = new LiChLi() { - @Override - public void disconnect() { - list.removeListener(this); - items.remove(this); - } - - @Override - public void onChanged(Change ch) { - ListChangeListener li = ref.get(); - if (li == null) { - disconnect(); - } else { - li.onChanged(ch); - } - } - }; - - items.add(li); - list.addListener(li); - - return li; - } - // event handlers public IDisconnectable addEventHandler(Object x, EventType t, EventHandler h) { @@ -576,56 +338,6 @@ public IDisconnectable addEventHandler(Object x, EventType return d; } - public IDisconnectable addWeakEventHandler(Object x, EventType t, EventHandler h) { - WeHa li = new WeHa(h) { - @Override - public void disconnect() { - if (x instanceof Node n) { - n.removeEventHandler(t, this); - } else if (x instanceof Window y) { - y.removeEventHandler(t, this); - } else if (x instanceof Scene y) { - y.removeEventHandler(t, this); - } else if (x instanceof MenuItem y) { - y.removeEventHandler(t, this); - } else if (x instanceof TreeItem y) { - y.removeEventHandler(t, this); - } else if (x instanceof TableColumnBase y) { - y.removeEventHandler(t, this); - } else if (x instanceof Transform y) { - y.removeEventHandler(t, this); - } else if (x instanceof Task y) { - y.removeEventHandler(t, this); - } - items.remove(this); - } - }; - - items.add(li); - - if (x instanceof Node y) { - y.addEventHandler(t, li); - } else if (x instanceof Window y) { - y.addEventHandler(t, li); - } else if (x instanceof Scene y) { - y.addEventHandler(t, li); - } else if (x instanceof MenuItem y) { - y.addEventHandler(t, li); - } else if (x instanceof TreeItem y) { - y.addEventHandler(t, li); - } else if (x instanceof TableColumnBase y) { - y.addEventHandler(t, li); - } else if (x instanceof Transform y) { - y.addEventHandler(t, li); - } else if (x instanceof Task y) { - y.addEventHandler(t, li); - } else { - throw new IllegalArgumentException("Cannot add weak event handler to " + x); - } - - return li; - } - // event filters public IDisconnectable addEventFilter(Object x, EventType t, EventHandler h) { @@ -661,44 +373,6 @@ public IDisconnectable addEventFilter(Object x, EventType t return d; } - public IDisconnectable addWeakEventFilter(Object x, EventType t, EventHandler h) { - WeHa li = new WeHa(h) { - @Override - public void disconnect() { - if (x instanceof Node n) { - n.removeEventFilter(t, this); - } else if (x instanceof Window y) { - y.removeEventFilter(t, this); - } else if (x instanceof Scene y) { - y.removeEventFilter(t, this); - } else if (x instanceof Transform y) { - y.removeEventFilter(t, this); - } else if (x instanceof Task y) { - y.removeEventFilter(t, this); - } - items.remove(this); - } - }; - - items.add(li); - - if (x instanceof Node y) { - y.addEventFilter(t, li); - } else if (x instanceof Window y) { - y.addEventFilter(t, li); - } else if (x instanceof Scene y) { - y.addEventFilter(t, li); - } else if (x instanceof Transform y) { - y.addEventFilter(t, li); - } else if (x instanceof Task y) { - y.addEventFilter(t, li); - } else { - throw new IllegalArgumentException("Cannot add weak event filter to " + x); - } - - return li; - } - // protected static abstract class ChLi implements IDisconnectable, ChangeListener { } @@ -706,22 +380,4 @@ protected static abstract class ChLi implements IDisconnectable, ChangeListen protected static abstract class InLi implements IDisconnectable, InvalidationListener { } protected static abstract class LiChLi implements IDisconnectable, ListChangeListener { } - - protected static abstract class WeHa implements IDisconnectable, EventHandler { - private final WeakReference> ref; - - public WeHa(EventHandler h) { - ref = new WeakReference<>(h); - } - - @Override - public void handle(T ev) { - EventHandler h = ref.get(); - if (h == null) { - disconnect(); - } else { - h.handle(ev); - } - } - } } \ No newline at end of file diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java index cc8ef2b30f1..bee470ddd22 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java @@ -27,18 +27,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import javafx.beans.InvalidationListener; import javafx.beans.property.SimpleStringProperty; -import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.concurrent.Task; -import javafx.event.EventHandler; import javafx.event.EventTarget; import javafx.scene.Group; import javafx.scene.Scene; @@ -56,7 +51,6 @@ import com.sun.javafx.scene.control.ListenerHelper; import test.com.sun.javafx.scene.control.infrastructure.MouseEventGenerator; -import test.util.memory.JMemoryBuddy; /** * Tests ListenerHelper utility class. @@ -187,138 +181,6 @@ public void testChangeListener_Callback_FireImmediately() { assertEquals(2, ct.get()); } - @Test - public void testWeakChangeListener_MultipleProperties() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p1 = new SimpleStringProperty(); - SimpleStringProperty p2 = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Runnable li = () -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakChangeListener(li, p1, p2); - - p1.set("1"); - p2.set("2"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p1.set("3"); - p2.set("4"); - assertEquals(2, ct.get()); - } - - @Test - public void testWeakChangeListener_MultipleProperties_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p1 = new SimpleStringProperty(); - SimpleStringProperty p2 = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Runnable li = () -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakChangeListener(() -> ct.incrementAndGet(), true, p1, p2); - - p1.set("1"); - p2.set("2"); - assertEquals(3, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p1.set("3"); - p2.set("4"); - assertEquals(3, ct.get()); - } - - @Test - public void testWeakChangeListener_Plain() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - ChangeListener li = (src, old, cur) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakChangeListener(p, li); - - p.set("1"); - assertEquals(1, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(1, ct.get()); - } - - @Test - public void testWeakChangeListener_Plain_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - ChangeListener li = (src, old, cur) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakChangeListener(p, true, li); - - p.set("1"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(2, ct.get()); - } - - @Test - public void testWeakChangeListener_Callback() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Consumer li = (s) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakChangeListener(p, li); - - p.set("1"); - assertEquals(1, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(1, ct.get()); - } - - @Test - public void testWeakChangeListener_Callback_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Consumer li = (s) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakChangeListener(p, true, li); - - p.set("1"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(2, ct.get()); - } - // invalidation listeners @Test @@ -429,96 +291,6 @@ public void testInvalidationListener_Callback_FireImmediately() { assertEquals(2, ct.get()); } - @Test - public void testWeakInvalidationListener_MultipleProperties() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p1 = new SimpleStringProperty(); - SimpleStringProperty p2 = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Runnable li = () -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakInvalidationListener(li, p1, p2); - - p1.set("1"); - p2.set("2"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p1.set("3"); - p2.set("4"); - assertEquals(2, ct.get()); - } - - @Test - public void testWeakInvalidationListener_MultipleProperties_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p1 = new SimpleStringProperty(); - SimpleStringProperty p2 = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Runnable li = () -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakInvalidationListener(() -> ct.incrementAndGet(), true, p1, p2); - - p1.set("1"); - p2.set("2"); - assertEquals(3, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p1.set("3"); - p2.set("4"); - assertEquals(3, ct.get()); - } - - @Test - public void testWeakInvalidationListener_Plain() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - InvalidationListener li = (x) -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakInvalidationListener(p, li); - - p.set("1"); - assertEquals(1, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(1, ct.get()); - } - - @Test - public void testWeakInvalidationListener_Plain_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - InvalidationListener li = (x) -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakInvalidationListener(p, true, li); - - p.set("1"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(2, ct.get()); - } - // list change listeners @Test @@ -539,26 +311,6 @@ public void testListChangeListener() { assertEquals(1, ct.get()); } - @Test - public void testWeakListChangeListener() { - ListenerHelper h = new ListenerHelper(); - ObservableList list = FXCollections.observableArrayList(); - AtomicInteger ct = new AtomicInteger(); - ListChangeListener li = (ch) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakListChangeListener(list, li); - - list.add("1"); - assertEquals(1, ct.get()); - - li = null; - JMemoryBuddy.assertCollectable(ref); - - list.add("2"); - assertEquals(1, ct.get()); - } - // event handlers @Test @@ -583,31 +335,6 @@ public void testEventHandler() { } } - @Test - public void testWeakEventHandler() { - EventTarget[] items = eventHandlerTargets(); - - for (EventTarget item : items) { - ListenerHelper h = new ListenerHelper(); - AtomicInteger ct = new AtomicInteger(); - EventHandler li = (ev) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference(li); - - h.addWeakEventHandler(item, MouseEvent.ANY, li); - - MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); - EventUtil.fireEvent(ev, item); - - assertEquals(1, ct.get()); - - li = null; - JMemoryBuddy.assertCollectable(ref); - - EventUtil.fireEvent(ev, item); - assertEquals(1, ct.get()); - } - } - @Test(expected = IllegalArgumentException.class) public void testEventHandlerCheck() { ListenerHelper h = new ListenerHelper(); @@ -638,31 +365,6 @@ public void testEventFilter() { } } - @Test - public void testWeakEventFilter() { - EventTarget[] items = eventHandlerFilters(); - - for (EventTarget item : items) { - ListenerHelper h = new ListenerHelper(); - AtomicInteger ct = new AtomicInteger(); - EventHandler li = (ev) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference(li); - - h.addWeakEventFilter(item, MouseEvent.ANY, li); - - MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); - EventUtil.fireEvent(ev, item); - - assertEquals(1, ct.get()); - - li = null; - JMemoryBuddy.assertCollectable(ref); - - EventUtil.fireEvent(ev, item); - assertEquals(1, ct.get()); - } - } - @Test(expected = IllegalArgumentException.class) public void testEventFilterCheck() { ListenerHelper h = new ListenerHelper(); From c518cbf92a89593afd445c6dddf3656869bf50df Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 19 Oct 2022 08:33:16 -0700 Subject: [PATCH 15/24] Revert "8294809: removed weak listeners support" This reverts commit 2df4a85db638d76cacaf6c54ba669cdb3dd91a18. --- .../javafx/scene/control/ListenerHelper.java | 346 +++++++++++++++++- .../scene/control/TestListenerHelper.java | 298 +++++++++++++++ 2 files changed, 643 insertions(+), 1 deletion(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index d18a7eecffc..b2fa4fa535a 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -24,9 +24,9 @@ */ package com.sun.javafx.scene.control; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.function.Consumer; - import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.value.ChangeListener; @@ -203,6 +203,130 @@ public void changed(ObservableValue observable, T oldValue, T newVa return d; } + public IDisconnectable addWeakChangeListener(Runnable onChange, ObservableValue... props) { + return addWeakChangeListener(onChange, false, props); + } + + public IDisconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { + if (onChange == null) { + throw new NullPointerException("onChange must not be null."); + } + + WeakReference ref = new WeakReference(onChange); + + ChLi li = new ChLi() { + @Override + public void disconnect() { + for (ObservableValue p : props) { + p.removeListener(this); + } + items.remove(this); + } + + @Override + public void changed(ObservableValue p, Object oldValue, Object newValue) { + Runnable r = ref.get(); + if (r == null) { + disconnect(); + } else { + r.run(); + } + } + }; + + items.add(li); + + for (ObservableValue p : props) { + p.addListener(li); + } + + if (fireImmediately) { + onChange.run(); + } + + return li; + } + + public IDisconnectable addWeakChangeListener(ObservableValue prop, ChangeListener listener) { + return addWeakChangeListener(prop, false, listener); + } + + public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + + WeakReference> ref = new WeakReference<>(listener); + + ChLi d = new ChLi() { + @Override + public void disconnect() { + prop.removeListener(this); + items.remove(this); + } + + @Override + public void changed(ObservableValue p, T oldValue, T newValue) { + ChangeListener li = ref.get(); + if (li == null) { + disconnect(); + } else { + li.changed(p, oldValue, newValue); + } + } + }; + + items.add(d); + prop.addListener(d); + + if (fireImmediately) { + T v = prop.getValue(); + listener.changed(prop, null, v); + } + + return d; + } + + public IDisconnectable addWeakChangeListener(ObservableValue prop, Consumer callback) { + return addWeakChangeListener(prop, false, callback); + } + + public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, Consumer callback) { + if (callback == null) { + throw new NullPointerException("Callback must be specified."); + } + + WeakReference> ref = new WeakReference<>(callback); + + ChLi d = new ChLi() { + @Override + public void disconnect() { + prop.removeListener(this); + items.remove(this); + } + + @Override + public void changed(ObservableValue observable, T oldValue, T newValue) { + Consumer cb = ref.get(); + if (cb == null) { + disconnect(); + } else { + cb.accept(newValue); + } + } + }; + + items.add(d); + prop.addListener(d); + + if (fireImmediately) { + T v = prop.getValue(); + callback.accept(v); + } + + return d; + } + // invalidation listeners public IDisconnectable addInvalidationListener(Runnable callback, ObservableValue... props) { @@ -269,6 +393,89 @@ public void disconnect() { return d; } + public IDisconnectable addWeakInvalidationListener(Runnable onChange, ObservableValue... props) { + return addWeakInvalidationListener(onChange, false, props); + } + + public IDisconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { + if (onChange == null) { + throw new NullPointerException("onChange must not be null."); + } + + WeakReference ref = new WeakReference(onChange); + + InLi li = new InLi() { + @Override + public void disconnect() { + for (ObservableValue p : props) { + p.removeListener(this); + } + items.remove(this); + } + + @Override + public void invalidated(Observable p) { + Runnable r = ref.get(); + if (r == null) { + disconnect(); + } else { + r.run(); + } + } + }; + + items.add(li); + + for (ObservableValue p : props) { + p.addListener(li); + } + + if (fireImmediately) { + onChange.run(); + } + + return li; + } + + public IDisconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener listener) { + return addWeakInvalidationListener(prop, false, listener); + } + + public IDisconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + + WeakReference ref = new WeakReference<>(listener); + + InLi d = new InLi() { + @Override + public void disconnect() { + prop.removeListener(this); + items.remove(this); + } + + @Override + public void invalidated(Observable p) { + InvalidationListener li = ref.get(); + if (li == null) { + disconnect(); + } else { + li.invalidated(p); + } + } + }; + + items.add(d); + prop.addListener(d); + + if (fireImmediately) { + listener.invalidated(prop); + } + + return d; + } + // list change listeners public IDisconnectable addListChangeListener(ObservableList list, ListChangeListener listener) { @@ -290,6 +497,37 @@ public void disconnect() { return d; } + public IDisconnectable addWeakListChangeListener(ObservableList list, ListChangeListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + + WeakReference> ref = new WeakReference<>(listener); + + LiChLi li = new LiChLi() { + @Override + public void disconnect() { + list.removeListener(this); + items.remove(this); + } + + @Override + public void onChanged(Change ch) { + ListChangeListener li = ref.get(); + if (li == null) { + disconnect(); + } else { + li.onChanged(ch); + } + } + }; + + items.add(li); + list.addListener(li); + + return li; + } + // event handlers public IDisconnectable addEventHandler(Object x, EventType t, EventHandler h) { @@ -338,6 +576,56 @@ public IDisconnectable addEventHandler(Object x, EventType return d; } + public IDisconnectable addWeakEventHandler(Object x, EventType t, EventHandler h) { + WeHa li = new WeHa(h) { + @Override + public void disconnect() { + if (x instanceof Node n) { + n.removeEventHandler(t, this); + } else if (x instanceof Window y) { + y.removeEventHandler(t, this); + } else if (x instanceof Scene y) { + y.removeEventHandler(t, this); + } else if (x instanceof MenuItem y) { + y.removeEventHandler(t, this); + } else if (x instanceof TreeItem y) { + y.removeEventHandler(t, this); + } else if (x instanceof TableColumnBase y) { + y.removeEventHandler(t, this); + } else if (x instanceof Transform y) { + y.removeEventHandler(t, this); + } else if (x instanceof Task y) { + y.removeEventHandler(t, this); + } + items.remove(this); + } + }; + + items.add(li); + + if (x instanceof Node y) { + y.addEventHandler(t, li); + } else if (x instanceof Window y) { + y.addEventHandler(t, li); + } else if (x instanceof Scene y) { + y.addEventHandler(t, li); + } else if (x instanceof MenuItem y) { + y.addEventHandler(t, li); + } else if (x instanceof TreeItem y) { + y.addEventHandler(t, li); + } else if (x instanceof TableColumnBase y) { + y.addEventHandler(t, li); + } else if (x instanceof Transform y) { + y.addEventHandler(t, li); + } else if (x instanceof Task y) { + y.addEventHandler(t, li); + } else { + throw new IllegalArgumentException("Cannot add weak event handler to " + x); + } + + return li; + } + // event filters public IDisconnectable addEventFilter(Object x, EventType t, EventHandler h) { @@ -373,6 +661,44 @@ public IDisconnectable addEventFilter(Object x, EventType t return d; } + public IDisconnectable addWeakEventFilter(Object x, EventType t, EventHandler h) { + WeHa li = new WeHa(h) { + @Override + public void disconnect() { + if (x instanceof Node n) { + n.removeEventFilter(t, this); + } else if (x instanceof Window y) { + y.removeEventFilter(t, this); + } else if (x instanceof Scene y) { + y.removeEventFilter(t, this); + } else if (x instanceof Transform y) { + y.removeEventFilter(t, this); + } else if (x instanceof Task y) { + y.removeEventFilter(t, this); + } + items.remove(this); + } + }; + + items.add(li); + + if (x instanceof Node y) { + y.addEventFilter(t, li); + } else if (x instanceof Window y) { + y.addEventFilter(t, li); + } else if (x instanceof Scene y) { + y.addEventFilter(t, li); + } else if (x instanceof Transform y) { + y.addEventFilter(t, li); + } else if (x instanceof Task y) { + y.addEventFilter(t, li); + } else { + throw new IllegalArgumentException("Cannot add weak event filter to " + x); + } + + return li; + } + // protected static abstract class ChLi implements IDisconnectable, ChangeListener { } @@ -380,4 +706,22 @@ protected static abstract class ChLi implements IDisconnectable, ChangeListen protected static abstract class InLi implements IDisconnectable, InvalidationListener { } protected static abstract class LiChLi implements IDisconnectable, ListChangeListener { } + + protected static abstract class WeHa implements IDisconnectable, EventHandler { + private final WeakReference> ref; + + public WeHa(EventHandler h) { + ref = new WeakReference<>(h); + } + + @Override + public void handle(T ev) { + EventHandler h = ref.get(); + if (h == null) { + disconnect(); + } else { + h.handle(ev); + } + } + } } \ No newline at end of file diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java index bee470ddd22..cc8ef2b30f1 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java @@ -27,13 +27,18 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import javafx.beans.InvalidationListener; import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.concurrent.Task; +import javafx.event.EventHandler; import javafx.event.EventTarget; import javafx.scene.Group; import javafx.scene.Scene; @@ -51,6 +56,7 @@ import com.sun.javafx.scene.control.ListenerHelper; import test.com.sun.javafx.scene.control.infrastructure.MouseEventGenerator; +import test.util.memory.JMemoryBuddy; /** * Tests ListenerHelper utility class. @@ -181,6 +187,138 @@ public void testChangeListener_Callback_FireImmediately() { assertEquals(2, ct.get()); } + @Test + public void testWeakChangeListener_MultipleProperties() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Runnable li = () -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakChangeListener(li, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p1.set("3"); + p2.set("4"); + assertEquals(2, ct.get()); + } + + @Test + public void testWeakChangeListener_MultipleProperties_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Runnable li = () -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakChangeListener(() -> ct.incrementAndGet(), true, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(3, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p1.set("3"); + p2.set("4"); + assertEquals(3, ct.get()); + } + + @Test + public void testWeakChangeListener_Plain() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + ChangeListener li = (src, old, cur) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakChangeListener(p, li); + + p.set("1"); + assertEquals(1, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testWeakChangeListener_Plain_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + ChangeListener li = (src, old, cur) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakChangeListener(p, true, li); + + p.set("1"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(2, ct.get()); + } + + @Test + public void testWeakChangeListener_Callback() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Consumer li = (s) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakChangeListener(p, li); + + p.set("1"); + assertEquals(1, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testWeakChangeListener_Callback_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Consumer li = (s) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakChangeListener(p, true, li); + + p.set("1"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(2, ct.get()); + } + // invalidation listeners @Test @@ -291,6 +429,96 @@ public void testInvalidationListener_Callback_FireImmediately() { assertEquals(2, ct.get()); } + @Test + public void testWeakInvalidationListener_MultipleProperties() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Runnable li = () -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakInvalidationListener(li, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p1.set("3"); + p2.set("4"); + assertEquals(2, ct.get()); + } + + @Test + public void testWeakInvalidationListener_MultipleProperties_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p1 = new SimpleStringProperty(); + SimpleStringProperty p2 = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + Runnable li = () -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakInvalidationListener(() -> ct.incrementAndGet(), true, p1, p2); + + p1.set("1"); + p2.set("2"); + assertEquals(3, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p1.set("3"); + p2.set("4"); + assertEquals(3, ct.get()); + } + + @Test + public void testWeakInvalidationListener_Plain() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + InvalidationListener li = (x) -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakInvalidationListener(p, li); + + p.set("1"); + assertEquals(1, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(1, ct.get()); + } + + @Test + public void testWeakInvalidationListener_Plain_FireImmediately() { + ListenerHelper h = new ListenerHelper(); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + InvalidationListener li = (x) -> ct.incrementAndGet(); + WeakReference ref = new WeakReference<>(li); + + h.addWeakInvalidationListener(p, true, li); + + p.set("1"); + assertEquals(2, ct.get()); + + li = null; + + JMemoryBuddy.assertCollectable(ref); + + p.set("2"); + assertEquals(2, ct.get()); + } + // list change listeners @Test @@ -311,6 +539,26 @@ public void testListChangeListener() { assertEquals(1, ct.get()); } + @Test + public void testWeakListChangeListener() { + ListenerHelper h = new ListenerHelper(); + ObservableList list = FXCollections.observableArrayList(); + AtomicInteger ct = new AtomicInteger(); + ListChangeListener li = (ch) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference<>(li); + + h.addWeakListChangeListener(list, li); + + list.add("1"); + assertEquals(1, ct.get()); + + li = null; + JMemoryBuddy.assertCollectable(ref); + + list.add("2"); + assertEquals(1, ct.get()); + } + // event handlers @Test @@ -335,6 +583,31 @@ public void testEventHandler() { } } + @Test + public void testWeakEventHandler() { + EventTarget[] items = eventHandlerTargets(); + + for (EventTarget item : items) { + ListenerHelper h = new ListenerHelper(); + AtomicInteger ct = new AtomicInteger(); + EventHandler li = (ev) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference(li); + + h.addWeakEventHandler(item, MouseEvent.ANY, li); + + MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); + EventUtil.fireEvent(ev, item); + + assertEquals(1, ct.get()); + + li = null; + JMemoryBuddy.assertCollectable(ref); + + EventUtil.fireEvent(ev, item); + assertEquals(1, ct.get()); + } + } + @Test(expected = IllegalArgumentException.class) public void testEventHandlerCheck() { ListenerHelper h = new ListenerHelper(); @@ -365,6 +638,31 @@ public void testEventFilter() { } } + @Test + public void testWeakEventFilter() { + EventTarget[] items = eventHandlerFilters(); + + for (EventTarget item : items) { + ListenerHelper h = new ListenerHelper(); + AtomicInteger ct = new AtomicInteger(); + EventHandler li = (ev) -> ct.incrementAndGet(); + WeakReference> ref = new WeakReference(li); + + h.addWeakEventFilter(item, MouseEvent.ANY, li); + + MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); + EventUtil.fireEvent(ev, item); + + assertEquals(1, ct.get()); + + li = null; + JMemoryBuddy.assertCollectable(ref); + + EventUtil.fireEvent(ev, item); + assertEquals(1, ct.get()); + } + } + @Test(expected = IllegalArgumentException.class) public void testEventFilterCheck() { ListenerHelper h = new ListenerHelper(); From e36d3289ad574583382e2cec0ec4fa160c7f2713 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 19 Oct 2022 14:07:06 -0700 Subject: [PATCH 16/24] 8294809: is alive --- .../javafx/scene/control/ListenerHelper.java | 442 ++++-------------- .../scene/control/TestListenerHelper.java | 325 ++----------- 2 files changed, 113 insertions(+), 654 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index b2fa4fa535a..ca835ccd390 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -27,6 +27,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.function.Consumer; + import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.value.ChangeListener; @@ -46,13 +47,16 @@ import javafx.stage.Window; /** - * This class provides convenience methods for adding various listeners, both strong and weak, - * as well as a single {@link #disconnect()} method to remove all listeners. + * This class provides convenience methods for adding various listeners, both + * strong and weak, as well as a single {@link #disconnect()} method to remove + * all listeners. *

* There are two usage patterns: *

    - *
  • Client code registers a number of listeners and removes them all at once via {@link #disconnect()} call. - *
  • Client code registers a number of listeners and removes one via its {@link IDisconnectable} instance. + *
  • Client code registers a number of listeners and removes them all at once + * via {@link #disconnect()} call. + *
  • Client code registers a number of listeners and removes one via its + * {@link IDisconnectable} instance. *
*

* Original code is re-licensed to Oracle by the author. @@ -60,9 +64,14 @@ * Copyright © 2021-2022 Andy Goryachev */ public class ListenerHelper implements IDisconnectable { + private WeakReference ownerRef; private final ArrayList items = new ArrayList<>(4); private static final Object KEY = new Object(); + public ListenerHelper(Object owner) { + ownerRef = new WeakReference<>(owner); + } + public ListenerHelper() { } @@ -71,7 +80,7 @@ public static ListenerHelper get(Node n) { if (x instanceof ListenerHelper h) { return h; } - ListenerHelper d = new ListenerHelper(); + ListenerHelper d = new ListenerHelper(n); n.getProperties().put(KEY, d); return d; } @@ -103,6 +112,16 @@ public void disconnect() { } } + protected boolean isAliveOrDisconnect() { + if (ownerRef != null) { + if (ownerRef.get() == null) { + disconnect(); + return false; + } + } + return true; + } + // change listeners public IDisconnectable addChangeListener(Runnable callback, ObservableValue... props) { @@ -125,7 +144,9 @@ public void disconnect() { @Override public void changed(ObservableValue p, Object oldValue, Object newValue) { - onChange.run(); + if (isAliveOrDisconnect()) { + onChange.run(); + } } }; @@ -151,35 +172,7 @@ public IDisconnectable addChangeListener(ObservableValue prop, boolean fi throw new NullPointerException("Listener must be specified."); } - IDisconnectable d = new IDisconnectable() { - @Override - public void disconnect() { - prop.removeListener(listener); - items.remove(this); - } - }; - - items.add(d); - prop.addListener(listener); - - if (fireImmediately) { - T v = prop.getValue(); - listener.changed(prop, null, v); - } - - return d; - } - - public IDisconnectable addChangeListener(ObservableValue prop, Consumer callback) { - return addChangeListener(prop, false, callback); - } - - public IDisconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, Consumer callback) { - if (callback == null) { - throw new NullPointerException("Callback must be specified."); - } - - ChLi d = new ChLi() { + ChLi li = new ChLi() { @Override public void disconnect() { prop.removeListener(this); @@ -187,118 +180,34 @@ public void disconnect() { } @Override - public void changed(ObservableValue observable, T oldValue, T newValue) { - callback.accept(newValue); - } - }; - - items.add(d); - prop.addListener(d); - - if (fireImmediately) { - T v = prop.getValue(); - callback.accept(v); - } - - return d; - } - - public IDisconnectable addWeakChangeListener(Runnable onChange, ObservableValue... props) { - return addWeakChangeListener(onChange, false, props); - } - - public IDisconnectable addWeakChangeListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { - if (onChange == null) { - throw new NullPointerException("onChange must not be null."); - } - - WeakReference ref = new WeakReference(onChange); - - ChLi li = new ChLi() { - @Override - public void disconnect() { - for (ObservableValue p : props) { - p.removeListener(this); - } - items.remove(this); - } - - @Override - public void changed(ObservableValue p, Object oldValue, Object newValue) { - Runnable r = ref.get(); - if (r == null) { - disconnect(); - } else { - r.run(); + public void changed(ObservableValue src, T oldValue, T newValue) { + if (isAliveOrDisconnect()) { + listener.changed(src, oldValue, newValue); } } }; items.add(li); - - for (ObservableValue p : props) { - p.addListener(li); - } - - if (fireImmediately) { - onChange.run(); - } - - return li; - } - - public IDisconnectable addWeakChangeListener(ObservableValue prop, ChangeListener listener) { - return addWeakChangeListener(prop, false, listener); - } - - public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, ChangeListener listener) { - if (listener == null) { - throw new NullPointerException("Listener must be specified."); - } - - WeakReference> ref = new WeakReference<>(listener); - - ChLi d = new ChLi() { - @Override - public void disconnect() { - prop.removeListener(this); - items.remove(this); - } - - @Override - public void changed(ObservableValue p, T oldValue, T newValue) { - ChangeListener li = ref.get(); - if (li == null) { - disconnect(); - } else { - li.changed(p, oldValue, newValue); - } - } - }; - - items.add(d); - prop.addListener(d); + prop.addListener(li); if (fireImmediately) { T v = prop.getValue(); listener.changed(prop, null, v); } - return d; + return li; } - public IDisconnectable addWeakChangeListener(ObservableValue prop, Consumer callback) { - return addWeakChangeListener(prop, false, callback); + public IDisconnectable addChangeListener(ObservableValue prop, Consumer callback) { + return addChangeListener(prop, false, callback); } - public IDisconnectable addWeakChangeListener(ObservableValue prop, boolean fireImmediately, Consumer callback) { + public IDisconnectable addChangeListener(ObservableValue prop, boolean fireImmediately, Consumer callback) { if (callback == null) { throw new NullPointerException("Callback must be specified."); } - WeakReference> ref = new WeakReference<>(callback); - - ChLi d = new ChLi() { + ChLi li = new ChLi() { @Override public void disconnect() { prop.removeListener(this); @@ -307,24 +216,21 @@ public void disconnect() { @Override public void changed(ObservableValue observable, T oldValue, T newValue) { - Consumer cb = ref.get(); - if (cb == null) { - disconnect(); - } else { - cb.accept(newValue); + if (isAliveOrDisconnect()) { + callback.accept(newValue); } } }; - items.add(d); - prop.addListener(d); + items.add(li); + prop.addListener(li); if (fireImmediately) { T v = prop.getValue(); callback.accept(v); } - return d; + return li; } // invalidation listeners @@ -349,7 +255,9 @@ public void disconnect() { @Override public void invalidated(Observable p) { - callback.run(); + if (isAliveOrDisconnect()) { + callback.run(); + } } }; @@ -375,80 +283,7 @@ public IDisconnectable addInvalidationListener(ObservableValue prop, bool throw new NullPointerException("Listener must be specified."); } - IDisconnectable d = new IDisconnectable() { - @Override - public void disconnect() { - prop.removeListener(listener); - items.remove(this); - } - }; - - items.add(d); - prop.addListener(listener); - - if (fireImmediately) { - listener.invalidated(prop); - } - - return d; - } - - public IDisconnectable addWeakInvalidationListener(Runnable onChange, ObservableValue... props) { - return addWeakInvalidationListener(onChange, false, props); - } - - public IDisconnectable addWeakInvalidationListener(Runnable onChange, boolean fireImmediately, ObservableValue... props) { - if (onChange == null) { - throw new NullPointerException("onChange must not be null."); - } - - WeakReference ref = new WeakReference(onChange); - InLi li = new InLi() { - @Override - public void disconnect() { - for (ObservableValue p : props) { - p.removeListener(this); - } - items.remove(this); - } - - @Override - public void invalidated(Observable p) { - Runnable r = ref.get(); - if (r == null) { - disconnect(); - } else { - r.run(); - } - } - }; - - items.add(li); - - for (ObservableValue p : props) { - p.addListener(li); - } - - if (fireImmediately) { - onChange.run(); - } - - return li; - } - - public IDisconnectable addWeakInvalidationListener(ObservableValue prop, InvalidationListener listener) { - return addWeakInvalidationListener(prop, false, listener); - } - - public IDisconnectable addWeakInvalidationListener(ObservableValue prop, boolean fireImmediately, InvalidationListener listener) { - if (listener == null) { - throw new NullPointerException("Listener must be specified."); - } - - WeakReference ref = new WeakReference<>(listener); - - InLi d = new InLi() { @Override public void disconnect() { prop.removeListener(this); @@ -456,24 +291,21 @@ public void disconnect() { } @Override - public void invalidated(Observable p) { - InvalidationListener li = ref.get(); - if (li == null) { - disconnect(); - } else { - li.invalidated(p); + public void invalidated(Observable observable) { + if (isAliveOrDisconnect()) { + listener.invalidated(observable); } } }; - items.add(d); - prop.addListener(d); + items.add(li); + prop.addListener(li); if (fireImmediately) { listener.invalidated(prop); } - return d; + return li; } // list change listeners @@ -483,27 +315,6 @@ public IDisconnectable addListChangeListener(ObservableList list, ListCha throw new NullPointerException("Listener must be specified."); } - IDisconnectable d = new IDisconnectable() { - @Override - public void disconnect() { - list.removeListener(listener); - items.remove(this); - } - }; - - items.add(d); - list.addListener(listener); - - return d; - } - - public IDisconnectable addWeakListChangeListener(ObservableList list, ListChangeListener listener) { - if (listener == null) { - throw new NullPointerException("Listener must be specified."); - } - - WeakReference> ref = new WeakReference<>(listener); - LiChLi li = new LiChLi() { @Override public void disconnect() { @@ -513,11 +324,8 @@ public void disconnect() { @Override public void onChanged(Change ch) { - ListChangeListener li = ref.get(); - if (li == null) { - disconnect(); - } else { - li.onChanged(ch); + if (isAliveOrDisconnect()) { + listener.onChanged(ch); } } }; @@ -530,54 +338,8 @@ public void onChanged(Change ch) { // event handlers - public IDisconnectable addEventHandler(Object x, EventType t, EventHandler h) { - - // we really need an interface here ... "HasEventHandlers" - IDisconnectable d = addDisconnectable(() -> { - if (x instanceof Node n) { - n.removeEventHandler(t, h); - } else if (x instanceof Window y) { - y.removeEventHandler(t, h); - } else if (x instanceof Scene y) { - y.removeEventHandler(t, h); - } else if (x instanceof MenuItem y) { - y.removeEventHandler(t, h); - } else if (x instanceof TreeItem y) { - y.removeEventHandler(t, h); - } else if (x instanceof TableColumnBase y) { - y.removeEventHandler(t, h); - } else if (x instanceof Transform y) { - y.removeEventHandler(t, h); - } else if (x instanceof Task y) { - y.removeEventHandler(t, h); - } - }); - - if (x instanceof Node y) { - y.addEventHandler(t, h); - } else if (x instanceof Window y) { - y.addEventHandler(t, h); - } else if (x instanceof Scene y) { - y.addEventHandler(t, h); - } else if (x instanceof MenuItem y) { - y.addEventHandler(t, h); - } else if (x instanceof TreeItem y) { - y.addEventHandler(t, h); - } else if (x instanceof TableColumnBase y) { - y.addEventHandler(t, h); - } else if (x instanceof Transform y) { - y.addEventHandler(t, h); - } else if (x instanceof Task y) { - y.addEventHandler(t, h); - } else { - throw new IllegalArgumentException("Cannot add event handler to " + x); - } - - return d; - } - - public IDisconnectable addWeakEventHandler(Object x, EventType t, EventHandler h) { - WeHa li = new WeHa(h) { + public IDisconnectable addEventHandler(Object x, EventType t, EventHandler handler) { + EvHa h = new EvHa<>(handler) { @Override public void disconnect() { if (x instanceof Node n) { @@ -597,72 +359,39 @@ public void disconnect() { } else if (x instanceof Task y) { y.removeEventHandler(t, this); } - items.remove(this); } }; - items.add(li); + items.add(h); + // we really need an interface here ... "HasEventHandlers" if (x instanceof Node y) { - y.addEventHandler(t, li); + y.addEventHandler(t, h); } else if (x instanceof Window y) { - y.addEventHandler(t, li); + y.addEventHandler(t, h); } else if (x instanceof Scene y) { - y.addEventHandler(t, li); + y.addEventHandler(t, h); } else if (x instanceof MenuItem y) { - y.addEventHandler(t, li); + y.addEventHandler(t, h); } else if (x instanceof TreeItem y) { - y.addEventHandler(t, li); + y.addEventHandler(t, h); } else if (x instanceof TableColumnBase y) { - y.addEventHandler(t, li); + y.addEventHandler(t, h); } else if (x instanceof Transform y) { - y.addEventHandler(t, li); + y.addEventHandler(t, h); } else if (x instanceof Task y) { - y.addEventHandler(t, li); + y.addEventHandler(t, h); } else { - throw new IllegalArgumentException("Cannot add weak event handler to " + x); + throw new IllegalArgumentException("Cannot add event handler to " + x); } - return li; + return h; } // event filters - public IDisconnectable addEventFilter(Object x, EventType t, EventHandler h) { - // we really need an interface here ... "HasEventFilters" - IDisconnectable d = addDisconnectable(() -> { - if (x instanceof Node n) { - n.removeEventFilter(t, h); - } else if (x instanceof Window y) { - y.removeEventFilter(t, h); - } else if (x instanceof Scene y) { - y.removeEventFilter(t, h); - } else if (x instanceof Transform y) { - y.removeEventFilter(t, h); - } else if (x instanceof Task y) { - y.removeEventFilter(t, h); - } - }); - - if (x instanceof Node y) { - y.addEventFilter(t, h); - } else if (x instanceof Window y) { - y.addEventFilter(t, h); - } else if (x instanceof Scene y) { - y.addEventFilter(t, h); - } else if (x instanceof Transform y) { - y.addEventFilter(t, h); - } else if (x instanceof Task y) { - y.addEventFilter(t, h); - } else { - throw new IllegalArgumentException("Cannot add event filter to " + x); - } - - return d; - } - - public IDisconnectable addWeakEventFilter(Object x, EventType t, EventHandler h) { - WeHa li = new WeHa(h) { + public IDisconnectable addEventFilter(Object x, EventType t, EventHandler handler) { + EvHa h = new EvHa<>(handler) { @Override public void disconnect() { if (x instanceof Node n) { @@ -676,27 +405,27 @@ public void disconnect() { } else if (x instanceof Task y) { y.removeEventFilter(t, this); } - items.remove(this); } }; - items.add(li); + items.add(h); + // we really need an interface here ... "HasEventFilters" if (x instanceof Node y) { - y.addEventFilter(t, li); + y.addEventFilter(t, h); } else if (x instanceof Window y) { - y.addEventFilter(t, li); + y.addEventFilter(t, h); } else if (x instanceof Scene y) { - y.addEventFilter(t, li); + y.addEventFilter(t, h); } else if (x instanceof Transform y) { - y.addEventFilter(t, li); + y.addEventFilter(t, h); } else if (x instanceof Task y) { - y.addEventFilter(t, li); + y.addEventFilter(t, h); } else { - throw new IllegalArgumentException("Cannot add weak event filter to " + x); + throw new IllegalArgumentException("Cannot add event filter to " + x); } - return li; + return h; } // @@ -707,20 +436,17 @@ protected static abstract class InLi implements IDisconnectable, InvalidationLis protected static abstract class LiChLi implements IDisconnectable, ListChangeListener { } - protected static abstract class WeHa implements IDisconnectable, EventHandler { - private final WeakReference> ref; + protected abstract class EvHa implements IDisconnectable, EventHandler { + private final EventHandler handler; - public WeHa(EventHandler h) { - ref = new WeakReference<>(h); + public EvHa(EventHandler h) { + this.handler = h; } @Override public void handle(T ev) { - EventHandler h = ref.get(); - if (h == null) { - disconnect(); - } else { - h.handle(ev); + if (isAliveOrDisconnect()) { + handler.handle(ev); } } } diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java index cc8ef2b30f1..7d9d3ec3452 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java @@ -29,16 +29,12 @@ import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import javafx.beans.InvalidationListener; import javafx.beans.property.SimpleStringProperty; -import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.concurrent.Task; -import javafx.event.EventHandler; import javafx.event.EventTarget; import javafx.scene.Group; import javafx.scene.Scene; @@ -77,6 +73,35 @@ public void testStaticDisconnect() { assertEquals(1, ct.get()); } + @Test + public void testCheckAlive() { + Object owner = new Object(); + WeakReference ref = new WeakReference<>(owner); + SimpleStringProperty p = new SimpleStringProperty(); + AtomicInteger ct = new AtomicInteger(); + AtomicInteger disconnected = new AtomicInteger(); + + ListenerHelper h = new ListenerHelper(owner); + + h.addChangeListener(p, (v) -> ct.incrementAndGet()); + h.addDisconnectable(() -> disconnected.incrementAndGet()); + + // check that the listener is working + p.set("1"); + assertEquals(1, ct.get()); + + // collect + owner = null; + JMemoryBuddy.assertCollectable(ref); + + // fire an event that should be ignored + p.set("2"); + assertEquals(1, ct.get()); + + // check that helper has disconnected all its items + assertEquals(1, disconnected.get()); + } + // change listeners @Test @@ -187,138 +212,6 @@ public void testChangeListener_Callback_FireImmediately() { assertEquals(2, ct.get()); } - @Test - public void testWeakChangeListener_MultipleProperties() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p1 = new SimpleStringProperty(); - SimpleStringProperty p2 = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Runnable li = () -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakChangeListener(li, p1, p2); - - p1.set("1"); - p2.set("2"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p1.set("3"); - p2.set("4"); - assertEquals(2, ct.get()); - } - - @Test - public void testWeakChangeListener_MultipleProperties_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p1 = new SimpleStringProperty(); - SimpleStringProperty p2 = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Runnable li = () -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakChangeListener(() -> ct.incrementAndGet(), true, p1, p2); - - p1.set("1"); - p2.set("2"); - assertEquals(3, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p1.set("3"); - p2.set("4"); - assertEquals(3, ct.get()); - } - - @Test - public void testWeakChangeListener_Plain() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - ChangeListener li = (src, old, cur) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakChangeListener(p, li); - - p.set("1"); - assertEquals(1, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(1, ct.get()); - } - - @Test - public void testWeakChangeListener_Plain_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - ChangeListener li = (src, old, cur) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakChangeListener(p, true, li); - - p.set("1"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(2, ct.get()); - } - - @Test - public void testWeakChangeListener_Callback() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Consumer li = (s) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakChangeListener(p, li); - - p.set("1"); - assertEquals(1, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(1, ct.get()); - } - - @Test - public void testWeakChangeListener_Callback_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Consumer li = (s) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakChangeListener(p, true, li); - - p.set("1"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(2, ct.get()); - } - // invalidation listeners @Test @@ -429,96 +322,6 @@ public void testInvalidationListener_Callback_FireImmediately() { assertEquals(2, ct.get()); } - @Test - public void testWeakInvalidationListener_MultipleProperties() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p1 = new SimpleStringProperty(); - SimpleStringProperty p2 = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Runnable li = () -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakInvalidationListener(li, p1, p2); - - p1.set("1"); - p2.set("2"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p1.set("3"); - p2.set("4"); - assertEquals(2, ct.get()); - } - - @Test - public void testWeakInvalidationListener_MultipleProperties_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p1 = new SimpleStringProperty(); - SimpleStringProperty p2 = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - Runnable li = () -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakInvalidationListener(() -> ct.incrementAndGet(), true, p1, p2); - - p1.set("1"); - p2.set("2"); - assertEquals(3, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p1.set("3"); - p2.set("4"); - assertEquals(3, ct.get()); - } - - @Test - public void testWeakInvalidationListener_Plain() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - InvalidationListener li = (x) -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakInvalidationListener(p, li); - - p.set("1"); - assertEquals(1, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(1, ct.get()); - } - - @Test - public void testWeakInvalidationListener_Plain_FireImmediately() { - ListenerHelper h = new ListenerHelper(); - SimpleStringProperty p = new SimpleStringProperty(); - AtomicInteger ct = new AtomicInteger(); - InvalidationListener li = (x) -> ct.incrementAndGet(); - WeakReference ref = new WeakReference<>(li); - - h.addWeakInvalidationListener(p, true, li); - - p.set("1"); - assertEquals(2, ct.get()); - - li = null; - - JMemoryBuddy.assertCollectable(ref); - - p.set("2"); - assertEquals(2, ct.get()); - } - // list change listeners @Test @@ -539,26 +342,6 @@ public void testListChangeListener() { assertEquals(1, ct.get()); } - @Test - public void testWeakListChangeListener() { - ListenerHelper h = new ListenerHelper(); - ObservableList list = FXCollections.observableArrayList(); - AtomicInteger ct = new AtomicInteger(); - ListChangeListener li = (ch) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference<>(li); - - h.addWeakListChangeListener(list, li); - - list.add("1"); - assertEquals(1, ct.get()); - - li = null; - JMemoryBuddy.assertCollectable(ref); - - list.add("2"); - assertEquals(1, ct.get()); - } - // event handlers @Test @@ -583,31 +366,6 @@ public void testEventHandler() { } } - @Test - public void testWeakEventHandler() { - EventTarget[] items = eventHandlerTargets(); - - for (EventTarget item : items) { - ListenerHelper h = new ListenerHelper(); - AtomicInteger ct = new AtomicInteger(); - EventHandler li = (ev) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference(li); - - h.addWeakEventHandler(item, MouseEvent.ANY, li); - - MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); - EventUtil.fireEvent(ev, item); - - assertEquals(1, ct.get()); - - li = null; - JMemoryBuddy.assertCollectable(ref); - - EventUtil.fireEvent(ev, item); - assertEquals(1, ct.get()); - } - } - @Test(expected = IllegalArgumentException.class) public void testEventHandlerCheck() { ListenerHelper h = new ListenerHelper(); @@ -638,31 +396,6 @@ public void testEventFilter() { } } - @Test - public void testWeakEventFilter() { - EventTarget[] items = eventHandlerFilters(); - - for (EventTarget item : items) { - ListenerHelper h = new ListenerHelper(); - AtomicInteger ct = new AtomicInteger(); - EventHandler li = (ev) -> ct.incrementAndGet(); - WeakReference> ref = new WeakReference(li); - - h.addWeakEventFilter(item, MouseEvent.ANY, li); - - MouseEvent ev = MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0); - EventUtil.fireEvent(ev, item); - - assertEquals(1, ct.get()); - - li = null; - JMemoryBuddy.assertCollectable(ref); - - EventUtil.fireEvent(ev, item); - assertEquals(1, ct.get()); - } - } - @Test(expected = IllegalArgumentException.class) public void testEventFilterCheck() { ListenerHelper h = new ListenerHelper(); From 71ca35ee4e6a93c5458eb4af6fdfed9b57f7ce58 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 19 Oct 2022 15:07:07 -0700 Subject: [PATCH 17/24] 8294809: generics --- .../main/java/com/sun/javafx/scene/control/ListenerHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index ca835ccd390..a635298736f 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -180,7 +180,7 @@ public void disconnect() { } @Override - public void changed(ObservableValue src, T oldValue, T newValue) { + public void changed(ObservableValue src, T oldValue, T newValue) { if (isAliveOrDisconnect()) { listener.changed(src, oldValue, newValue); } From 7a1fa625d81ad25cbc30d6a4c4be208228d64ac2 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 21 Oct 2022 13:20:34 -0700 Subject: [PATCH 18/24] 8294809: map change listener --- .../javafx/scene/control/ListenerHelper.java | 33 ++++++++++++++++++- .../scene/control/TestListenerHelper.java | 33 ++++++++++++++----- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index a635298736f..ff4f4b5d265 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -27,13 +27,14 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.function.Consumer; - import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; +import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; import javafx.concurrent.Task; import javafx.event.Event; import javafx.event.EventHandler; @@ -336,6 +337,34 @@ public void onChanged(Change ch) { return li; } + // map change listener + + public IDisconnectable addMapChangeListener(ObservableMap list, MapChangeListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + + MaChLi li = new MaChLi() { + @Override + public void disconnect() { + list.removeListener(this); + items.remove(this); + } + + @Override + public void onChanged(Change ch) { + if (isAliveOrDisconnect()) { + listener.onChanged(ch); + } + } + }; + + items.add(li); + list.addListener(li); + + return li; + } + // event handlers public IDisconnectable addEventHandler(Object x, EventType t, EventHandler handler) { @@ -436,6 +465,8 @@ protected static abstract class InLi implements IDisconnectable, InvalidationLis protected static abstract class LiChLi implements IDisconnectable, ListChangeListener { } + protected static abstract class MaChLi implements IDisconnectable, MapChangeListener { } + protected abstract class EvHa implements IDisconnectable, EventHandler { private final EventHandler handler; diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java index 7d9d3ec3452..8e3cd8127e4 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java @@ -26,14 +26,17 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; - import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicInteger; - +import org.junit.Test; +import com.sun.javafx.event.EventUtil; +import com.sun.javafx.scene.control.ListenerHelper; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; +import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; import javafx.concurrent.Task; import javafx.event.EventTarget; import javafx.scene.Group; @@ -45,12 +48,6 @@ import javafx.scene.layout.Region; import javafx.scene.transform.Scale; import javafx.stage.Stage; - -import org.junit.Test; - -import com.sun.javafx.event.EventUtil; -import com.sun.javafx.scene.control.ListenerHelper; - import test.com.sun.javafx.scene.control.infrastructure.MouseEventGenerator; import test.util.memory.JMemoryBuddy; @@ -342,6 +339,26 @@ public void testListChangeListener() { assertEquals(1, ct.get()); } + // map change listeners + + @Test + public void testMapChangeListener() { + ListenerHelper h = new ListenerHelper(); + ObservableMap m = FXCollections.observableHashMap(); + AtomicInteger ct = new AtomicInteger(); + MapChangeListener li = (ch) -> ct.incrementAndGet(); + + h.addMapChangeListener(m, li); + + m.put("1", "a"); + assertEquals(1, ct.get()); + + h.disconnect(); + + m.put("2", "b"); + assertEquals(1, ct.get()); + } + // event handlers @Test From e78ed6db62eb65d09851366ea72206f1d250e869 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 28 Oct 2022 12:50:02 -0700 Subject: [PATCH 19/24] 8294809: no public api --- .../javafx/scene/control/ListenerHelper.java | 58 ++++++++++++++----- .../java/javafx/scene/control/SkinBase.java | 10 +++- .../scene/control/TestListenerHelper.java | 30 +++++++++- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index ff4f4b5d265..18b8b0a176d 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -27,6 +27,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.function.Consumer; +import java.util.function.Function; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.value.ChangeListener; @@ -35,6 +36,8 @@ import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; +import javafx.collections.ObservableSet; +import javafx.collections.SetChangeListener; import javafx.concurrent.Task; import javafx.event.Event; import javafx.event.EventHandler; @@ -42,6 +45,7 @@ import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.MenuItem; +import javafx.scene.control.SkinBase; import javafx.scene.control.TableColumnBase; import javafx.scene.control.TreeItem; import javafx.scene.transform.Transform; @@ -67,7 +71,7 @@ public class ListenerHelper implements IDisconnectable { private WeakReference ownerRef; private final ArrayList items = new ArrayList<>(4); - private static final Object KEY = new Object(); + private static Function,ListenerHelper> accessor; public ListenerHelper(Object owner) { ownerRef = new WeakReference<>(owner); @@ -76,21 +80,17 @@ public ListenerHelper(Object owner) { public ListenerHelper() { } - public static ListenerHelper get(Node n) { - Object x = n.getProperties().get(KEY); - if (x instanceof ListenerHelper h) { - return h; - } - ListenerHelper d = new ListenerHelper(n); - n.getProperties().put(KEY, d); - return d; + public static void setAccessor(Function,ListenerHelper> a) { + accessor = a; } - public static void disconnect(Node n) { - Object x = n.getProperties().remove(KEY); - if (x instanceof ListenerHelper h) { - h.disconnect(); - } + public static ListenerHelper get(SkinBase skin) { + return accessor.apply(skin); + } + + public static void disconnect(SkinBase skin) { + ListenerHelper h = get(skin); + h.disconnect(); } public IDisconnectable addDisconnectable(Runnable r) { @@ -365,6 +365,34 @@ public void onChanged(Change ch) { return li; } + // set change listeners + + public IDisconnectable addSetChangeListener(ObservableSet set, SetChangeListener listener) { + if (listener == null) { + throw new NullPointerException("Listener must be specified."); + } + + SeChLi li = new SeChLi() { + @Override + public void disconnect() { + set.removeListener(this); + items.remove(this); + } + + @Override + public void onChanged(Change ch) { + if (isAliveOrDisconnect()) { + listener.onChanged(ch); + } + } + }; + + items.add(li); + set.addListener(li); + + return li; + } + // event handlers public IDisconnectable addEventHandler(Object x, EventType t, EventHandler handler) { @@ -467,6 +495,8 @@ protected static abstract class LiChLi implements IDisconnectable, ListChange protected static abstract class MaChLi implements IDisconnectable, MapChangeListener { } + protected static abstract class SeChLi implements IDisconnectable, SetChangeListener { } + protected abstract class EvHa implements IDisconnectable, EventHandler { private final EventHandler handler; diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java index 6449c27d27e..ea799648ccb 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java @@ -82,11 +82,16 @@ public abstract class SkinBase implements Skin { * This is part of the workaround introduced during delomboking. We probably will * want to adjust the way listeners are added rather than continuing to use this * map (although it doesn't really do much harm). + * + * TODO remove after migration to ListenerHelper */ - @Deprecated // replace with listenerHelper private LambdaMultiplePropertyChangeListenerHandler lambdaChangeListenerHandler; private ListenerHelper listenerHelper; + + static { + ListenerHelper.setAccessor((skin) -> skin.listenerHelper()); + } /* ************************************************************************* @@ -219,7 +224,7 @@ protected final void consumeMouseEvents(boolean value) { * * @since 20 */ - protected ListenerHelper listenerHelper() { + ListenerHelper listenerHelper() { if (listenerHelper == null) { listenerHelper = new ListenerHelper(); } @@ -237,7 +242,6 @@ protected ListenerHelper listenerHelper() { * may be {@code null} * @since 9 */ - // TODO I would like to deprecate and remove these methods, and replace them by listenerHelper().add**() protected final void registerChangeListener(ObservableValue observable, Consumer> operation) { if (lambdaChangeListenerHandler == null) { lambdaChangeListenerHandler = new LambdaMultiplePropertyChangeListenerHandler(); diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java index 8e3cd8127e4..59b4bf64d77 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java @@ -37,13 +37,17 @@ import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; +import javafx.collections.ObservableSet; +import javafx.collections.SetChangeListener; import javafx.concurrent.Task; import javafx.event.EventTarget; import javafx.scene.Group; import javafx.scene.Scene; +import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.TableColumn; import javafx.scene.control.TreeItem; +import javafx.scene.control.skin.LabelSkin; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Region; import javafx.scene.transform.Scale; @@ -58,15 +62,15 @@ public class TestListenerHelper { @Test public void testStaticDisconnect() { AtomicInteger ct = new AtomicInteger(); - Group node = new Group(); - ListenerHelper h = ListenerHelper.get(node); + LabelSkin skin = new LabelSkin(new Label("yo")); + ListenerHelper h = ListenerHelper.get(skin); assertNotNull(h); h.addDisconnectable(() -> { ct.incrementAndGet(); }); - ListenerHelper.disconnect(node); + ListenerHelper.disconnect(skin); assertEquals(1, ct.get()); } @@ -339,6 +343,26 @@ public void testListChangeListener() { assertEquals(1, ct.get()); } + // set change listeners + + @Test + public void testSetChangeListener() { + ListenerHelper h = new ListenerHelper(); + ObservableSet list = FXCollections.observableSet(); + AtomicInteger ct = new AtomicInteger(); + SetChangeListener li = (ch) -> ct.incrementAndGet(); + + h.addSetChangeListener(list, li); + + list.add("1"); + assertEquals(1, ct.get()); + + h.disconnect(); + + list.add("2"); + assertEquals(1, ct.get()); + } + // map change listeners @Test From e0e0aabb9c429294f44bebbda9ad2a4dbe371651 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 31 Oct 2022 09:39:47 -0700 Subject: [PATCH 20/24] 8294809: whitespace --- .../src/main/java/javafx/scene/control/SkinBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java index ea799648ccb..9df186cd92a 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java @@ -88,7 +88,7 @@ public abstract class SkinBase implements Skin { private LambdaMultiplePropertyChangeListenerHandler lambdaChangeListenerHandler; private ListenerHelper listenerHelper; - + static { ListenerHelper.setAccessor((skin) -> skin.listenerHelper()); } From 470f42c1a909457859330d2e331e775466342c17 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Fri, 4 Nov 2022 13:27:18 -0700 Subject: [PATCH 21/24] 8294809: review comments --- .../javafx/scene/control/IDisconnectable.java | 9 +++---- .../javafx/scene/control/ListenerHelper.java | 26 +++++++------------ .../java/javafx/scene/control/SkinBase.java | 2 -- .../scene/control/TestListenerHelper.java | 15 ----------- 4 files changed, 14 insertions(+), 38 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/IDisconnectable.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/IDisconnectable.java index 85ee1b8179e..4ddbd1a3442 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/IDisconnectable.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/IDisconnectable.java @@ -22,14 +22,13 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +// Original code is re-licensed to Oracle by the author. +// https://github.com/andy-goryachev/FxTextEditor/blob/master/src/goryachev/common/util/Disconnectable.java +// Copyright © 2021-2022 Andy Goryachev package com.sun.javafx.scene.control; /** * A functional interface that provides a {@link #disconnect()} method. - *

- * Original code is re-licensed to Oracle by the author. - * https://github.com/andy-goryachev/FxTextEditor/blob/master/src/goryachev/common/util/Disconnectable.java - * Copyright © 2021-2022 Andy Goryachev */ @FunctionalInterface public interface IDisconnectable { @@ -38,4 +37,4 @@ public interface IDisconnectable { * first invocation actually disconnects. */ public void disconnect(); -} \ No newline at end of file +} diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index 18b8b0a176d..9f0b3c78e02 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -22,6 +22,9 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +// Original code is re-licensed to Oracle by the author. +// https://github.com/andy-goryachev/FxTextEditor/blob/master/src/goryachev/fx/FxDisconnector.java +// Copyright © 2021-2022 Andy Goryachev package com.sun.javafx.scene.control; import java.lang.ref.WeakReference; @@ -63,10 +66,6 @@ *

  • Client code registers a number of listeners and removes one via its * {@link IDisconnectable} instance. * - *

    - * Original code is re-licensed to Oracle by the author. - * https://github.com/andy-goryachev/FxTextEditor/blob/master/src/goryachev/fx/FxDisconnector.java - * Copyright © 2021-2022 Andy Goryachev */ public class ListenerHelper implements IDisconnectable { private WeakReference ownerRef; @@ -88,11 +87,6 @@ public static ListenerHelper get(SkinBase skin) { return accessor.apply(skin); } - public static void disconnect(SkinBase skin) { - ListenerHelper h = get(skin); - h.disconnect(); - } - public IDisconnectable addDisconnectable(Runnable r) { IDisconnectable d = new IDisconnectable() { @Override @@ -487,17 +481,17 @@ public void disconnect() { // - protected static abstract class ChLi implements IDisconnectable, ChangeListener { } + private static abstract class ChLi implements IDisconnectable, ChangeListener { } - protected static abstract class InLi implements IDisconnectable, InvalidationListener { } + private static abstract class InLi implements IDisconnectable, InvalidationListener { } - protected static abstract class LiChLi implements IDisconnectable, ListChangeListener { } + private static abstract class LiChLi implements IDisconnectable, ListChangeListener { } - protected static abstract class MaChLi implements IDisconnectable, MapChangeListener { } + private static abstract class MaChLi implements IDisconnectable, MapChangeListener { } - protected static abstract class SeChLi implements IDisconnectable, SetChangeListener { } + private static abstract class SeChLi implements IDisconnectable, SetChangeListener { } - protected abstract class EvHa implements IDisconnectable, EventHandler { + private abstract class EvHa implements IDisconnectable, EventHandler { private final EventHandler handler; public EvHa(EventHandler h) { @@ -511,4 +505,4 @@ public void handle(T ev) { } } } -} \ No newline at end of file +} diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java index 9df186cd92a..056a6733806 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java @@ -221,8 +221,6 @@ protected final void consumeMouseEvents(boolean value) { /** * Returns the skin's instance of {@link ListenerHelper}, creating it if necessary. - * - * @since 20 */ ListenerHelper listenerHelper() { if (listenerHelper == null) { diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java index 59b4bf64d77..af474aa53b6 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java @@ -59,21 +59,6 @@ * Tests ListenerHelper utility class. */ public class TestListenerHelper { - @Test - public void testStaticDisconnect() { - AtomicInteger ct = new AtomicInteger(); - LabelSkin skin = new LabelSkin(new Label("yo")); - ListenerHelper h = ListenerHelper.get(skin); - assertNotNull(h); - - h.addDisconnectable(() -> { - ct.incrementAndGet(); - }); - - ListenerHelper.disconnect(skin); - assertEquals(1, ct.get()); - } - @Test public void testCheckAlive() { Object owner = new Object(); From 2a295c24f6bde88af7ecdc4c14dbf7ab3fb88827 Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Mon, 14 Nov 2022 12:18:13 -0800 Subject: [PATCH 22/24] 8294809: review comments --- .../src/main/java/javafx/scene/control/SkinBase.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java index 056a6733806..a50bd016963 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/SkinBase.java @@ -59,6 +59,11 @@ */ public abstract class SkinBase implements Skin { + static { + // must be the first code to execute + ListenerHelper.setAccessor((skin) -> skin.listenerHelper()); + } + /* ************************************************************************* * * * Private fields * @@ -89,11 +94,6 @@ public abstract class SkinBase implements Skin { private ListenerHelper listenerHelper; - static { - ListenerHelper.setAccessor((skin) -> skin.listenerHelper()); - } - - /* ************************************************************************* * * * Event Handlers / Listeners * From 8cef69c133b4ad7d62b8ccf7af73e74b2d5603ab Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Tue, 15 Nov 2022 10:27:08 -0800 Subject: [PATCH 23/24] 8294809: review comments --- .../java/com/sun/javafx/scene/control/ListenerHelper.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index 9f0b3c78e02..b626d70b318 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -66,6 +66,10 @@ *
  • Client code registers a number of listeners and removes one via its * {@link IDisconnectable} instance. * + * + * This class is currently used for clean replacement of {@link Skin}s. + * We should consider making this class a part of the public API in {@code javax.base}, + * since it proved itself useful in removing listeners and handlers in bulk at the application level. */ public class ListenerHelper implements IDisconnectable { private WeakReference ownerRef; From 79ae28ce5a5b060fa4e52fc23d870d115c0fec0e Mon Sep 17 00:00:00 2001 From: Andy Goryachev Date: Wed, 23 Nov 2022 09:15:23 -0800 Subject: [PATCH 24/24] 8294809: review comments --- .../java/com/sun/javafx/scene/control/ListenerHelper.java | 2 +- .../java/test/javafx/scene/control/TestListenerHelper.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java index b626d70b318..e7dfb202025 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/ListenerHelper.java @@ -111,7 +111,7 @@ public void disconnect() { } } - protected boolean isAliveOrDisconnect() { + private boolean isAliveOrDisconnect() { if (ownerRef != null) { if (ownerRef.get() == null) { disconnect(); diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java index af474aa53b6..530d25c659e 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TestListenerHelper.java @@ -280,7 +280,7 @@ public void testInvalidationListener_Callback() { SimpleStringProperty p = new SimpleStringProperty(); AtomicInteger ct = new AtomicInteger(); - h.addInvalidationListener(p, (cur) -> ct.incrementAndGet()); + h.addInvalidationListener(() -> ct.incrementAndGet(), p); p.set("1"); assertEquals(1, ct.get()); @@ -297,7 +297,7 @@ public void testInvalidationListener_Callback_FireImmediately() { SimpleStringProperty p = new SimpleStringProperty(); AtomicInteger ct = new AtomicInteger(); - h.addInvalidationListener(p, true, (cur) -> ct.incrementAndGet()); + h.addInvalidationListener(() -> ct.incrementAndGet(), true, p); p.set("1"); assertEquals(2, ct.get());