blob: fca7e1d78b21e969cb84c1acc1451a4149f32cb8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014 EM-SOFTWARE and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Christoph Keimel <c.keimel@emsw.de> - initial API and implementation
*******************************************************************************/
package org.eclipse.fx.core;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.eclipse.jdt.annotation.Nullable;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
/**
* Class to synchronize with a predefined thread e.g. the UI-Thread
*
* @since 2.0
*/
public interface ThreadSynchronize {
/**
* Sync with the sync thread and provide a result when done
* <p>
* <b>WARNING: Using synchronized execute can easily lead to deadlocks. You
* are better of using {@link #asyncExec(Callable)}</b>
* </p>
*
* @param callable
* the callable to execute
* @param defaultValue
* a default value to return
* @return the result of callable.call()
*/
<V> V syncExec(final Callable<V> callable, V defaultValue);
/**
* Execute the function in the UI-Thread
*
* @param value
* the value
* @param function
* the function
* @param defaultValue
* the default value
* @return the return value of the function
* @since 2.3.0
*/
default <T, R> R syncExec(final T value, final Function<T, R> function, R defaultValue) {
Callable<R> c = () -> function.apply(value);
return syncExec(c, defaultValue);
}
/**
* Executes the runnable on the sync thread and blocks until the runnable is
* finished
* <p>
* <b>WARNING: Using synchronized execute can easily lead to deadlocks. You
* are better of using {@link #asyncExec(Runnable)}</b>
* </p>
*
* @param runnable
* the runnable to execute
*/
void syncExec(Runnable runnable);
/**
* Schedules the runnable on the sync thread for execution and returns
* immediately
*
* @param callable
* the callable to execute
* @return interface to the result of the callable
*/
<V> Future<V> asyncExec(final Callable<V> callable);
/**
* Schedules the runnable on the sync thread for execution and returns
* immediately
*
* @param runnable
* the runnable to execute
*/
void asyncExec(Runnable runnable);
/**
* Executes the consumer in the ui thread
*
* @param value
* the value
* @param consumer
* the consumer
* @since 2.3.0
*/
default <@Nullable T> void asyncExec(T value, Consumer<T> consumer) {
asyncExec(() -> consumer.accept(value));
}
/**
* Schedule the execution of the runnable
*
* @param delay
* the delay
* @param runnable
* the runnable to execute
* @return subscription to cancel the execution
*/
Subscription scheduleExecution(long delay, Runnable runnable);
/**
* Schedule the execution of the callable
*
* @param delay
* the delay
* @param runnable
* the callable to execute
* @return future to get informed about the value
*/
<T> CompletableFuture<T> scheduleExecution(long delay, Callable<T> runnable);
/**
* Schedule a delayed execution on the provided property.
* <p>
* The consumer is called whenever the value of the property changes with
* the provided delay. In case the value changes within the given delay the
* scheduled consumer execution is discarded.
* </p>
*
* @param delay
* the delay
* @param property
* the property
* @param consumer
* the consumer (always called on the synchronize thread)
* @return the subscription
* @since 2.2.0
*/
default <T> Subscription delayedChangeExecution(long delay, Property<T> property, Consumer<T> consumer) {
ChangeListener<T> l = new ChangeListener<T>() {
private Subscription currentSubscription;
@Override
public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
if (this.currentSubscription != null) {
this.currentSubscription.dispose();
}
this.currentSubscription = scheduleExecution(delay, () -> {
consumer.accept(newValue);
this.currentSubscription = null;
});
}
};
property.addListener(l);
return new Subscription() {
@Override
public void dispose() {
property.removeListener(l);
}
};
}
/**
* Schedule a delayed execution on the provided list of properties.
*
* @param delay
* the delay
* @param consumer
* the consumer receiving the values of the properties
* @param properties
* the list of properties
* @return the subscription
* @since 3.0
*/
default <T> Subscription delayedChangeExecution(long delay, Consumer<List<T>> consumer, @SuppressWarnings("unchecked") Property<T>... properties) {
Runnable r = () -> {
List<T> l = new ArrayList<>();
for (Property<T> p : properties) {
l.add(p.getValue());
}
consumer.accept(l);
};
ChangeListener<T> l = new ChangeListener<T>() {
private Subscription currentSubscription;
@Override
public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
if (this.currentSubscription != null) {
this.currentSubscription.dispose();
}
this.currentSubscription = scheduleExecution(delay, () -> {
r.run();
this.currentSubscription = null;
});
}
};
for (Property<T> p : properties) {
p.addListener(l);
}
return new Subscription() {
@Override
public void dispose() {
for (Property<T> p : properties) {
p.removeListener(l);
}
}
};
}
/**
* Wraps a runnable so that it is called on the UI thread.
* <p>
* This is handy if you pass a {@link Runnable} as callback into some async
* API
* </p>
*
* @param r
* the runnable to wrap
* @return a new runnable who invokes the real runnable on the UI thread
*/
default Runnable wrap(Runnable r) {
return () -> asyncExec(r);
}
/**
* Wraps a consumer so that it is called on the sync thread.
* <p>
* This is handy if you pass a {@link Consumer} as callback into some async
* API
* </p>
*
* @param c
* the consumer to wrap
* @return a new consumer who invokes the real consumer on the UI thread
*/
default <T> Consumer<T> wrap(Consumer<T> c) {
return (t) -> asyncExec(() -> c.accept(t));
}
/**
* Wraps a bi-consumer so that it is called on the UI thread
* <p>
* This is handy if you pass a {@link BiConsumer} as callback into some
* async API
* </p>
*
* @param c
* the consumer to wrap
* @return a new consumer who invokes the real consumer on the UI thread
*/
default <T, U> BiConsumer<T, U> wrap(BiConsumer<T, U> c) {
return (t, u) -> asyncExec(() -> c.accept(t, u));
}
/**
* Wraps a supplier so that it is called on the UI thread
*
* @param s
* the supplier
* @return a new supplier who invokes the real supplier on the UI thread
*/
default <@Nullable T> Supplier<T> wrap(Supplier<T> s) {
return () -> syncExec(() -> s.get(), null);
}
// /**
// * Returns the named queue
// *
// * @param name
// * the name of the queue
// * @return the queue
// */
// public SynchronizeQueue getQueue(String name);
//
// /**
// * A synchronization queue allowing to prioritize ui updates and
// optimizing
// * the update by e.g. removing duplicate update requests
// */
// public interface SynchronizeQueue {
// public void schedule(int priority);
//
// public <O> void schedule(int priority, O value, Consumer<O> consumer);
// }
// TODO Make API in 3.0
// /**
// * Block the UI-Thread in a way that events are still processed until the
// * given condition is released
// *
// * @param blockCondition
// * the condition
// * @return the value
// */
// <T> @Nullable T block(@NonNull BlockCondition<T> blockCondition);
/**
* A block condition
*
* @param <T>
* the type
* @since 2.3.0
*/
public static class BlockCondition<T> {
List<Consumer<T>> callbacks = new ArrayList<>();
private boolean isBlocked = true;
/**
* Subscribe to unblocking
*
* @param r
* the callback
* @return the subscription
*/
public Subscription subscribeUnblockedCallback(Consumer<T> r) {
if (!this.isBlocked) {
throw new IllegalStateException();
}
this.callbacks.add(r);
return new Subscription() {
@Override
public void dispose() {
BlockCondition.this.callbacks.remove(r);
}
};
}
/**
* @return check if still blocked
*/
public boolean isBlocked() {
return this.isBlocked;
}
/**
* Release the lock and pass value
*
* @param value
* the value to pass
*/
public void release(T value) {
for (Consumer<T> r : this.callbacks) {
r.accept(value);
}
this.callbacks.clear();
this.isBlocked = false;
}
}
}