Skip to content

Commit 4a3859f

Browse files
committed
Implementation of unconsumed event handlers
1 parent d0011b2 commit 4a3859f

File tree

4 files changed

+395
-6
lines changed

4 files changed

+395
-6
lines changed

modules/javafx.base/src/main/java/com/sun/javafx/event/EventUtil.java

+46-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,13 +25,41 @@
2525

2626
package com.sun.javafx.event;
2727

28+
import java.util.List;
2829
import java.util.concurrent.atomic.AtomicBoolean;
29-
3030
import javafx.event.Event;
3131
import javafx.event.EventDispatchChain;
3232
import javafx.event.EventTarget;
3333

3434
public final class EventUtil {
35+
36+
static {
37+
try {
38+
Class.forName(Event.class.getName(), true, Event.class.getClassLoader());
39+
} catch (ClassNotFoundException e) {
40+
throw new AssertionError(e);
41+
}
42+
}
43+
44+
public interface Accessor {
45+
List<UnconsumedEventHandler> getUnconsumedEventHandlers(Event event);
46+
void markDeliveryCompleted(Event event);
47+
}
48+
49+
public static void setAccessor(Accessor accessor) {
50+
EventUtil.accessor = accessor;
51+
}
52+
53+
public static List<UnconsumedEventHandler> getUnconsumedEventHandlers(Event event) {
54+
return accessor.getUnconsumedEventHandlers(event);
55+
}
56+
57+
public static void markDeliveryCompleted(Event event) {
58+
accessor.markDeliveryCompleted(event);
59+
}
60+
61+
private static Accessor accessor;
62+
3563
private static final EventDispatchChainImpl eventDispatchChain =
3664
new EventDispatchChainImpl();
3765

@@ -71,6 +99,21 @@ private static Event fireEventImpl(EventDispatchChain eventDispatchChain,
7199
Event event) {
72100
final EventDispatchChain targetDispatchChain =
73101
eventTarget.buildEventDispatchChain(eventDispatchChain);
74-
return targetDispatchChain.dispatchEvent(event);
102+
Event resultEvent = targetDispatchChain.dispatchEvent(event);
103+
104+
if (resultEvent != null) {
105+
markDeliveryCompleted(resultEvent);
106+
107+
List<UnconsumedEventHandler> handlers = getUnconsumedEventHandlers(resultEvent);
108+
if (handlers != null) {
109+
for (UnconsumedEventHandler handler : handlers) {
110+
if (handler.handle()) {
111+
return null;
112+
}
113+
}
114+
}
115+
}
116+
117+
return resultEvent;
75118
}
76119
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.sun.javafx.event;
27+
28+
import javafx.event.Event;
29+
import javafx.event.EventHandler;
30+
import java.util.Objects;
31+
32+
/**
33+
* Captures an {@code EventHandler} that will handle an unconsumed event, as well as the
34+
* event instance as it existed at the time the handler was captured.
35+
*
36+
* @param originalEvent the original event
37+
* @param handler the event handler
38+
*/
39+
public record UnconsumedEventHandler(Event originalEvent, EventHandler<Event> handler) {
40+
41+
public UnconsumedEventHandler {
42+
Objects.requireNonNull(originalEvent, "originalEvent cannot be null");
43+
Objects.requireNonNull(handler, "handler cannot be null");
44+
}
45+
46+
/**
47+
* Invokes the handler with the original event.
48+
*
49+
* @return {@code true} if the event was consumed, {@code false} otherwise
50+
*/
51+
public boolean handle() {
52+
EventUtil.markDeliveryCompleted(originalEvent);
53+
handler.handle(originalEvent);
54+
return originalEvent.isConsumed();
55+
}
56+
}

modules/javafx.base/src/main/java/javafx/event/Event.java

+72-3
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@
2525

2626
package javafx.event;
2727

28-
import java.util.EventObject;
29-
3028
import com.sun.javafx.event.EventUtil;
29+
import com.sun.javafx.event.UnconsumedEventHandler;
30+
import java.util.ArrayList;
31+
import java.util.EventObject;
3132
import java.io.IOException;
33+
import java.io.Serial;
34+
import java.util.List;
3235
import javafx.beans.NamedArg;
3336

3437
// PENDING_DOC_REVIEW
@@ -43,7 +46,23 @@
4346
*/
4447
public class Event extends EventObject implements Cloneable {
4548

46-
private static final long serialVersionUID = 20121107L;
49+
static {
50+
EventUtil.setAccessor(new EventUtil.Accessor() {
51+
@Override
52+
public List<UnconsumedEventHandler> getUnconsumedEventHandlers(Event event) {
53+
return event.unconsumedEventHandlers;
54+
}
55+
56+
@Override
57+
public void markDeliveryCompleted(Event event) {
58+
event.completed = true;
59+
}
60+
});
61+
}
62+
63+
@Serial
64+
private static final long serialVersionUID = 20241110L;
65+
4766
/**
4867
* The constant which represents an unknown event source / target.
4968
*/
@@ -65,11 +84,23 @@ public class Event extends EventObject implements Cloneable {
6584
*/
6685
protected transient EventTarget target;
6786

87+
/**
88+
* The list of handlers that have expressed their interest in handling the event
89+
* if it is still unconsumed at the end of the bubble phase of event delivery.
90+
*/
91+
private transient List<UnconsumedEventHandler> unconsumedEventHandlers;
92+
6893
/**
6994
* Whether this event has been consumed by any filter or handler.
7095
*/
7196
protected boolean consumed;
7297

98+
/**
99+
* Indicates whether this event has completed both delivery phases and can no
100+
* longer accept registrations of unconsumed event handlers.
101+
*/
102+
private boolean completed;
103+
73104
/**
74105
* Construct a new {@code Event} with the specified event type. The source
75106
* and target of the event is set to {@code NULL_SOURCE_TARGET}.
@@ -155,6 +186,44 @@ public void consume() {
155186
consumed = true;
156187
}
157188

189+
/**
190+
* Specifies an event handler that will handle this event if it is still unconsumed after both phases
191+
* of event delivery have completed. The unconsumed event handlers are invoked in the order they were
192+
* registered. As soon as an event handler consumes the event, further propagation is stopped.
193+
* <p>
194+
* This method can only be called from an event filter or event handler during event delivery; any
195+
* attempt to call it after event delivery is complete will fail with {@link IllegalStateException}.
196+
*
197+
* @param handler the event handler
198+
* @param <E> the type of the event
199+
* @throws IllegalStateException when event delivery has already been completed
200+
* @since 24
201+
*/
202+
public final <E extends Event> void ifUnconsumed(EventHandler<E> handler) {
203+
if (completed) {
204+
throw new IllegalStateException("Event delivery is not in progress");
205+
}
206+
207+
if (unconsumedEventHandlers == null) {
208+
unconsumedEventHandlers = new ArrayList<>(2); // most of the time we only expect a single handler
209+
}
210+
211+
@SuppressWarnings("unchecked")
212+
EventHandler<Event> untypedHandler = (EventHandler<Event>)handler;
213+
unconsumedEventHandlers.add(new UnconsumedEventHandler(this, untypedHandler));
214+
}
215+
216+
/**
217+
* Discards all event handlers that were added with {@link #ifUnconsumed(EventHandler)}.
218+
*
219+
* @since 24
220+
*/
221+
public final void discardUnconsumedEventHandlers() {
222+
if (!completed && unconsumedEventHandlers != null) {
223+
unconsumedEventHandlers.clear();
224+
}
225+
}
226+
158227
/**
159228
* Creates and returns a copy of this {@code Event}.
160229
* @return a new instance of {@code Event} with all values copied from

0 commit comments

Comments
 (0)