001package conexp.fx.gui.task;
002
003import java.util.Arrays;
004import java.util.concurrent.Callable;
005import java.util.concurrent.CountDownLatch;
006
007import conexp.fx.gui.dataset.Dataset;
008import conexp.fx.gui.util.Platform2;
009
010/*
011 * #%L
012 * Concept Explorer FX
013 * %%
014 * Copyright (C) 2010 - 2023 Francesco Kriegel
015 * %%
016 * This program is free software: you can redistribute it and/or modify
017 * it under the terms of the GNU General Public License as
018 * published by the Free Software Foundation, either version 3 of the
019 * License, or (at your option) any later version.
020 * 
021 * This program is distributed in the hope that it will be useful,
022 * but WITHOUT ANY WARRANTY; without even the implied warranty of
023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
024 * GNU General Public License for more details.
025 * 
026 * You should have received a copy of the GNU General Public
027 * License along with this program.  If not, see
028 * <http://www.gnu.org/licenses/gpl-3.0.html>.
029 * #L%
030 */
031
032import javafx.application.Platform;
033import javafx.beans.property.ReadOnlyLongProperty;
034import javafx.beans.property.ReadOnlyLongWrapper;
035import javafx.concurrent.Task;
036
037public abstract class TimeTask<T> extends Task<T> {
038
039  @FunctionalInterface
040  public static interface RunnableWithException<E extends Exception> {
041
042    public void run() throws E;
043
044    public default Callable<Void> toCallable() {
045      return () -> {
046        run();
047        return null;
048      };
049    }
050
051  }
052
053  public static final TimeTask<Void>
054      create(final Dataset dataset, final String title, final RunnableWithException<?> runnable) {
055    return create(dataset, title, runnable.toCallable());
056  }
057
058  public static final TimeTask<Void> create(
059      final Dataset dataset,
060      final String title,
061      final RunnableWithException<?> runnable,
062      final boolean onFXThread) {
063    return create(dataset, title, runnable.toCallable(), onFXThread);
064  }
065
066  public static final <T> TimeTask<T> create(final Dataset dataset, final String title, final Callable<T> callable) {
067    return create(dataset, title, callable, false);
068  }
069
070  public static final <T> TimeTask<T>
071      create(final Dataset dataset, final String title, final Callable<T> callable, final boolean onFXThread) {
072    return new TimeTask<T>(dataset, title, onFXThread) {
073
074      @Override
075      protected T call() throws Exception {
076        updateProgress(0d, 1d);
077        if (isCancelled())
078          return null;
079        final T result = callable.call();
080        updateProgress(1d, 1d);
081        return result;
082      }
083    };
084  }
085
086  @SafeVarargs
087  public static final TimeTask<Void> compose(final Dataset dataset, final String title, final TimeTask<Void>... tasks) {
088    return new TimeTask<Void>(dataset, title) {
089
090      @Override
091      protected Void call() throws Exception {
092        for (TimeTask<Void> task : Arrays.asList(tasks)) {
093          updateTitle(title + " - " + task.getTitle());
094          task.progressProperty().addListener((__, ___, p) -> updateProgress(p.doubleValue(), 1d));
095          task.messageProperty().addListener((__, ___, m) -> updateMessage(m));
096//          task.exceptionProperty().addListener((__, ___, e) -> setException(e));
097          task.call();
098        }
099        return null;
100      }
101    };
102  }
103
104  public static final <T> TimeTask<T> encapsulateTaskOnFXThread(final TimeTask<T> task) {
105    return new TimeTask<T>(task.getDataset(), task.getTitle()) {
106
107      @Override
108      protected T call() throws Exception {
109        final CountDownLatch cdl = new CountDownLatch(1);
110        task.progressProperty().addListener((____, _____, p) -> updateProgress(p.doubleValue(), 1d));
111        task.messageProperty().addListener((____, _____, m) -> updateMessage(m));
112        Platform2.runOnFXThread(() -> {
113          task.run();
114          cdl.countDown();
115        });
116        cdl.await();
117        return task.get();
118      }
119    };
120  }
121
122  private final Dataset dataset;
123  private final boolean onFXThread;
124
125  public TimeTask(final String title) {
126    this(null, title);
127  }
128
129  public TimeTask(final Dataset dataset, final String title) {
130    this(dataset, title, false);
131  }
132
133  public TimeTask(final Dataset dataset, final String title, final boolean onFXThread) {
134    super();
135    this.dataset = dataset;
136    if (dataset != null)
137      updateTitle(dataset.id.get() + " - " + title);
138    else
139      updateTitle(title);
140    this.onFXThread = onFXThread;
141  }
142
143  public final Dataset getDataset() {
144    return dataset;
145  }
146
147  public final boolean onFXThread() {
148    return onFXThread;
149  }
150
151  private final ObservableTimer timer = new ObservableTimer();
152
153  public final ReadOnlyLongProperty runTimeNanosProperty() {
154    return timer.runTimeNanosProperty();
155  }
156
157  private static final class ObservableTimer {
158
159    private final ReadOnlyLongWrapper runTimeNanos = new ReadOnlyLongWrapper(0l);
160    private long                      startTimeNanos;
161    private Thread                    thread;
162    private final long                updateDelayMillis;
163
164    public ObservableTimer(final long updateDelayMillis) {
165      super();
166      this.updateDelayMillis = updateDelayMillis;
167    }
168
169    public ObservableTimer() {
170      this(250l);
171    }
172
173    public final ReadOnlyLongProperty runTimeNanosProperty() {
174      return runTimeNanos.getReadOnlyProperty();
175    }
176
177    public final void start() {
178      synchronized (this) {
179        if (thread != null)
180          return;
181        startTimeNanos = System.nanoTime();
182        thread = new Thread(() -> {
183          try {
184            while (true) {
185              update();
186              Thread.sleep(updateDelayMillis);
187            }
188          } catch (InterruptedException e) {}
189        });
190        thread.start();
191      }
192    }
193
194    public final void stop() {
195      synchronized (this) {
196        if (thread == null)
197          return;
198        thread.interrupt();
199        update();
200        thread = null;
201      }
202    }
203
204    public final void update() {
205      if (thread == null)
206        return;
207      final long currentTimeNanos = System.nanoTime();
208      Platform.runLater(() -> runTimeNanos.set(currentTimeNanos - startTimeNanos));
209    }
210  }
211
212  @Override
213  protected void running() {
214    super.running();
215    timer.start();
216  }
217
218  @Override
219  protected void succeeded() {
220    super.succeeded();
221    timer.stop();
222  }
223
224  @Override
225  protected void cancelled() {
226    super.cancelled();
227    if (getProgress() < 0)
228      updateProgress(0d, 1d);
229    timer.stop();
230  };
231
232  @Override
233  protected void failed() {
234    super.failed();
235    if (getProgress() < 0)
236      updateProgress(0d, 1d);
237    timer.stop();
238  }
239
240}