Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unconsumed event handlers #1633

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,13 +25,41 @@

package com.sun.javafx.event;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventTarget;

public final class EventUtil {

static {
try {
Class.forName(Event.class.getName(), true, Event.class.getClassLoader());
} catch (ClassNotFoundException e) {
throw new AssertionError(e);
}
}

public interface Accessor {
List<UnconsumedEventHandler> getUnconsumedEventHandlers(Event event);
void markDeliveryCompleted(Event event);
}

public static void setAccessor(Accessor accessor) {
EventUtil.accessor = accessor;
}

public static List<UnconsumedEventHandler> getUnconsumedEventHandlers(Event event) {
return accessor.getUnconsumedEventHandlers(event);
}

public static void markDeliveryCompleted(Event event) {
accessor.markDeliveryCompleted(event);
}

private static Accessor accessor;

private static final EventDispatchChainImpl eventDispatchChain =
new EventDispatchChainImpl();

Expand Down Expand Up @@ -71,6 +99,21 @@ private static Event fireEventImpl(EventDispatchChain eventDispatchChain,
Event event) {
final EventDispatchChain targetDispatchChain =
eventTarget.buildEventDispatchChain(eventDispatchChain);
return targetDispatchChain.dispatchEvent(event);
Event resultEvent = targetDispatchChain.dispatchEvent(event);

if (resultEvent != null) {
markDeliveryCompleted(resultEvent);

List<UnconsumedEventHandler> handlers = getUnconsumedEventHandlers(resultEvent);
if (handlers != null) {
for (UnconsumedEventHandler handler : handlers) {
if (handler.handle()) {
return null;
}
}
}
}

return resultEvent;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024, 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.event;

import javafx.event.Event;
import javafx.event.EventHandler;
import java.util.Objects;

/**
* Captures an {@code EventHandler} that will handle an unconsumed event, as well as the
* event instance as it existed at the time the handler was captured.
*
* @param originalEvent the original event
* @param handler the event handler
*/
public record UnconsumedEventHandler(Event originalEvent, EventHandler<Event> handler) {

public UnconsumedEventHandler {
Objects.requireNonNull(originalEvent, "originalEvent cannot be null");
Objects.requireNonNull(handler, "handler cannot be null");
}

/**
* Invokes the handler with the original event.
*
* @return {@code true} if the event was consumed, {@code false} otherwise
*/
public boolean handle() {
EventUtil.markDeliveryCompleted(originalEvent);
handler.handle(originalEvent);
return originalEvent.isConsumed();
}
}
75 changes: 72 additions & 3 deletions modules/javafx.base/src/main/java/javafx/event/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@

package javafx.event;

import java.util.EventObject;

import com.sun.javafx.event.EventUtil;
import com.sun.javafx.event.UnconsumedEventHandler;
import java.util.ArrayList;
import java.util.EventObject;
import java.io.IOException;
import java.io.Serial;
import java.util.List;
import javafx.beans.NamedArg;

// PENDING_DOC_REVIEW
Expand All @@ -43,7 +46,23 @@
*/
public class Event extends EventObject implements Cloneable {

private static final long serialVersionUID = 20121107L;
static {
EventUtil.setAccessor(new EventUtil.Accessor() {
@Override
public List<UnconsumedEventHandler> getUnconsumedEventHandlers(Event event) {
return event.unconsumedEventHandlers;
}

@Override
public void markDeliveryCompleted(Event event) {
event.completed = true;
}
});
}

@Serial
private static final long serialVersionUID = 20241110L;

/**
* The constant which represents an unknown event source / target.
*/
Expand All @@ -65,11 +84,23 @@ public class Event extends EventObject implements Cloneable {
*/
protected transient EventTarget target;

/**
* The list of handlers that have expressed their interest in handling the event
* if it is still unconsumed at the end of the bubble phase of event delivery.
*/
private transient List<UnconsumedEventHandler> unconsumedEventHandlers;

/**
* Whether this event has been consumed by any filter or handler.
*/
protected boolean consumed;

/**
* Indicates whether this event has completed both delivery phases and can no
* longer accept registrations of unconsumed event handlers.
*/
private boolean completed;

/**
* Construct a new {@code Event} with the specified event type. The source
* and target of the event is set to {@code NULL_SOURCE_TARGET}.
Expand Down Expand Up @@ -155,6 +186,44 @@ public void consume() {
consumed = true;
}

/**
* Specifies an event handler that will handle this event if it is still unconsumed after both phases
* of event delivery have completed. The unconsumed event handlers are invoked in the order they were
* registered. As soon as an event handler consumes the event, further propagation is stopped.
* <p>
* This method can only be called from an event filter or event handler during event delivery; any
* attempt to call it after event delivery is complete will fail with {@link IllegalStateException}.
*
* @param handler the event handler
* @param <E> the type of the event
* @throws IllegalStateException when event delivery has already been completed
* @since 24
*/
public final <E extends Event> void ifUnconsumed(EventHandler<E> handler) {
if (completed) {
throw new IllegalStateException("Event delivery is not in progress");
}

if (unconsumedEventHandlers == null) {
unconsumedEventHandlers = new ArrayList<>(2); // most of the time we only expect a single handler
}

@SuppressWarnings("unchecked")
EventHandler<Event> untypedHandler = (EventHandler<Event>)handler;
unconsumedEventHandlers.add(new UnconsumedEventHandler(this, untypedHandler));
}

/**
* Discards all event handlers that were added with {@link #ifUnconsumed(EventHandler)}.
*
* @since 24
*/
public final void discardUnconsumedEventHandlers() {
if (!completed && unconsumedEventHandlers != null) {
unconsumedEventHandlers.clear();
}
}

/**
* Creates and returns a copy of this {@code Event}.
* @return a new instance of {@code Event} with all values copied from
Expand Down
Loading