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}