All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fxmisc.wellbehaved.event.EventHandlerHelper Maven / Gradle / Ivy

The newest version!
package org.fxmisc.wellbehaved.event;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

import javafx.beans.property.ObjectProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;

/**
 * Methods of this class could be added directly to the {@link EventHandler}
 * interface. The interface could further be extended with default methods
 * 
 * {@code
 * EventHandler orElse(EventHandler other);
 * EventHandler without(EventHandler other);
 * }
 * 
* The latter may replace the {@link #exclude(EventHandler, EventHandler)} * static method. * @param */ public final class EventHandlerHelper { public static abstract class Builder { private static Builder empty() { return new Builder() { @Override List> getHandlers(int additionalCapacity) { return new ArrayList<>(additionalCapacity); } }; } // private constructor to prevent subclassing by the user private Builder() {} public On on(EventPattern eventMatcher) { return new On<>(this, eventMatcher); } public On on(EventType eventType) { return on(EventPattern.eventTypePattern(eventType)); } public Builder addHandler(EventHandler handler) { return new CompositeBuilder<>(this, handler); } public final EventHandler create() { List> handlers = getHandlers(); return new CompositeEventHandler<>(handlers); } List> getHandlers() { return getHandlers(0); } abstract List> getHandlers(int additionalCapacity); } private static class CompositeBuilder extends Builder { private final Builder previousBuilder; private final EventHandler handler; private CompositeBuilder( Builder previousBuilder, EventHandler handler) { this.previousBuilder = previousBuilder; this.handler = handler; } @Override List> getHandlers(int additionalCapacity) { List> handlers = previousBuilder.getHandlers(additionalCapacity + 1); handlers.add(handler); return handlers; } } public static class On { private final Builder previousBuilder; private final EventPattern eventMatcher; private On( Builder previous, EventPattern eventMatcher) { this.previousBuilder = previous; this.eventMatcher = eventMatcher; } public On where(Predicate condition) { return new On<>(previousBuilder, eventMatcher.and(condition)); } public Builder act(Consumer action) { return previousBuilder.addHandler(t -> { eventMatcher.match(t).ifPresent(u -> { action.accept(u); t.consume(); }); }); } } public static On on( EventPattern eventMatcher) { return Builder.empty().on(eventMatcher); } public static On on( EventType eventType) { return Builder.empty().on(eventType); } public static Builder startWith(EventHandler handler) { return Builder.empty().addHandler(handler); } static EventHandler empty() { return EmptyEventHandler.instance(); } @SafeVarargs public static EventHandler chain(EventHandler... handlers) { ArrayList> nonEmptyHandlers = new ArrayList<>(handlers.length); for(EventHandler handler: handlers) { if(handler != empty()) { nonEmptyHandlers.add(handler); } } if(nonEmptyHandlers.isEmpty()) { return empty(); } else if(nonEmptyHandlers.size() == 1) { return nonEmptyHandlers.get(0); } else { nonEmptyHandlers.trimToSize(); return new CompositeEventHandler<>(nonEmptyHandlers); } } public static EventHandler exclude(EventHandler handler, EventHandler subHandler) { if(handler instanceof CompositeEventHandler) { return ((CompositeEventHandler) handler).without(subHandler); } else if(handler.equals(subHandler)) { return empty(); } else { return handler; } } public static void install( ObjectProperty> handlerProperty, EventHandler handler) { EventHandler oldHandler = handlerProperty.get(); if(oldHandler != null) { handlerProperty.set(EventHandlerHelper.chain(handler, oldHandler)); } else { handlerProperty.set(handler); } } public static void installAfter( ObjectProperty> handlerProperty, EventHandler handler) { EventHandler oldHandler = handlerProperty.get(); if(oldHandler != null) { handlerProperty.set(EventHandlerHelper.chain(oldHandler, handler)); } else { handlerProperty.set(handler); } } public static void remove( ObjectProperty> handlerProperty, EventHandler handler) { EventHandler oldHandler = handlerProperty.get(); if(oldHandler != null) { handlerProperty.set(EventHandlerHelper.exclude(oldHandler, handler)); } } // prevent instantiation private EventHandlerHelper() {} } final class EmptyEventHandler implements EventHandler { private static EmptyEventHandler INSTANCE = new EmptyEventHandler<>(); @SuppressWarnings("unchecked") static EmptyEventHandler instance() { return (EmptyEventHandler) INSTANCE; } private EmptyEventHandler() {} @Override public void handle(T event) { // do nothing } } class CompositeEventHandler implements EventHandler { private final List> handlers; CompositeEventHandler(List> handlers) { // Since this constructor is package-private, we can be sure that // the given list of handlers is never mutated, thus there is no need // to create a copy. this.handlers = handlers; } @Override public void handle(T event) { for(EventHandler handler: handlers) { handler.handle(event); if(event.isConsumed()) { break; } } } public EventHandler without(EventHandler other) { if(this.equals(other)) { return EmptyEventHandler.instance(); } else { boolean changed = false; List> newHandlers = new ArrayList<>(handlers.size()); for(EventHandler handler: handlers) { EventHandler h = EventHandlerHelper.exclude(handler, other); if(h != handler) { changed = true; } if(h != EmptyEventHandler.instance()) { newHandlers.add(h); } } if(!changed) { return this; } else if(newHandlers.isEmpty()) { return EmptyEventHandler.instance(); } else if(newHandlers.size() == 1) { return newHandlers.get(0); } else { return new CompositeEventHandler<>(newHandlers); } } } @Override public boolean equals(Object other) { return other instanceof CompositeEventHandler && this.handlers.equals(((CompositeEventHandler) other).handlers); } @Override public int hashCode() { return handlers.hashCode(); } }