Example usage for java.util.concurrent Executors defaultThreadFactory

List of usage examples for java.util.concurrent Executors defaultThreadFactory

Introduction

In this page you can find the example usage for java.util.concurrent Executors defaultThreadFactory.

Prototype

public static ThreadFactory defaultThreadFactory() 

Source Link

Document

Returns a default thread factory used to create new threads.

Usage

From source file:org.apache.nifi.minifi.bootstrap.RunMiNiFi.java

public RunMiNiFi(final File bootstrapConfigFile) throws IOException {
    this.bootstrapConfigFile = bootstrapConfigFile;

    loggingExecutor = Executors.newFixedThreadPool(2, new ThreadFactory() {
        @Override//  w  w w .  j av  a 2  s .  c  o m
        public Thread newThread(final Runnable runnable) {
            final Thread t = Executors.defaultThreadFactory().newThread(runnable);
            t.setDaemon(true);
            t.setName("MiNiFi logging handler");
            return t;
        }
    });
}

From source file:org.apache.nifi.cluster.coordination.http.replication.ThreadPoolRequestReplicator.java

/**
 * Creates an instance./*from   ww  w.  jav  a  2s.c o m*/
 *
 * @param corePoolSize core size of the thread pool
 * @param maxPoolSize the max number of threads in the thread pool
 * @param maxConcurrentRequests maximum number of concurrent requests
 * @param client a client for making requests
 * @param clusterCoordinator the cluster coordinator to use for interacting with node statuses
 * @param connectionTimeout the connection timeout specified in milliseconds
 * @param readTimeout the read timeout specified in milliseconds
 * @param callback a callback that will be called whenever all of the responses have been gathered for a request. May be null.
 * @param eventReporter an EventReporter that can be used to notify users of interesting events. May be null.
 * @param nifiProperties properties
 */
public ThreadPoolRequestReplicator(final int corePoolSize, final int maxPoolSize,
        final int maxConcurrentRequests, final Client client, final ClusterCoordinator clusterCoordinator,
        final String connectionTimeout, final String readTimeout, final RequestCompletionCallback callback,
        final EventReporter eventReporter, final NiFiProperties nifiProperties) {
    if (corePoolSize <= 0) {
        throw new IllegalArgumentException("The Core Pool Size must be greater than zero.");
    } else if (maxPoolSize < corePoolSize) {
        throw new IllegalArgumentException("Max Pool Size must be >= Core Pool Size.");
    } else if (client == null) {
        throw new IllegalArgumentException("Client may not be null.");
    }

    this.client = client;
    this.clusterCoordinator = clusterCoordinator;
    this.connectionTimeoutMs = (int) FormatUtils.getTimeDuration(connectionTimeout, TimeUnit.MILLISECONDS);
    this.readTimeoutMs = (int) FormatUtils.getTimeDuration(readTimeout, TimeUnit.MILLISECONDS);
    this.maxConcurrentRequests = maxConcurrentRequests;
    this.responseMapper = new StandardHttpResponseMapper(nifiProperties);
    this.eventReporter = eventReporter;
    this.callback = callback;
    this.nifiProperties = nifiProperties;

    client.property(ClientProperties.CONNECT_TIMEOUT, connectionTimeoutMs);
    client.property(ClientProperties.READ_TIMEOUT, readTimeoutMs);
    client.property(ClientProperties.FOLLOW_REDIRECTS, Boolean.TRUE);

    final AtomicInteger threadId = new AtomicInteger(0);
    final ThreadFactory threadFactory = r -> {
        final Thread t = Executors.defaultThreadFactory().newThread(r);
        t.setDaemon(true);
        t.setName("Replicate Request Thread-" + threadId.incrementAndGet());
        return t;
    };

    executorService = new ThreadPoolExecutor(corePoolSize, maxPoolSize, 5, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(), threadFactory);

    maintenanceExecutor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
        @Override
        public Thread newThread(final Runnable r) {
            final Thread t = Executors.defaultThreadFactory().newThread(r);
            t.setDaemon(true);
            t.setName(ThreadPoolRequestReplicator.class.getSimpleName() + " Maintenance Thread");
            return t;
        }
    });

    maintenanceExecutor.scheduleWithFixedDelay(() -> purgeExpiredRequests(), 1, 1, TimeUnit.SECONDS);
}

From source file:org.apache.nifi.minifi.bootstrap.configuration.ingestors.FileChangeIngestor.java

@Override
public void start() {
    executorService = Executors.newScheduledThreadPool(1, new ThreadFactory() {
        @Override//from  ww w . ja v a2s .  c  o  m
        public Thread newThread(final Runnable r) {
            final Thread t = Executors.defaultThreadFactory().newThread(r);
            t.setName("File Change Notifier Thread");
            t.setDaemon(true);
            return t;
        }
    });
    this.executorService.scheduleWithFixedDelay(this, 0, pollingSeconds, DEFAULT_POLLING_PERIOD_UNIT);
}

From source file:org.apache.nifi.remote.util.SiteToSiteRestApiClient.java

public SiteToSiteRestApiClient(final SSLContext sslContext, final HttpProxy proxy,
        final EventReporter eventReporter) {
    this.sslContext = sslContext;
    this.proxy = proxy;
    this.eventReporter = eventReporter;

    ttlExtendTaskExecutor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
        private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();

        @Override/* w w  w.  j  a v  a 2s  .c  o  m*/
        public Thread newThread(final Runnable r) {
            final Thread thread = defaultFactory.newThread(r);
            thread.setName(Thread.currentThread().getName() + " TTLExtend");
            thread.setDaemon(true);
            return thread;
        }
    });
}

From source file:mondrian.olap.Util.java

/**
 * Creates an {@link ExecutorService} object backed by a thread pool.
 * @param maximumPoolSize Maximum number of concurrent
 * threads./*from w w w . ja va  2 s.  com*/
 * @param corePoolSize Minimum number of concurrent
 * threads to maintain in the pool, even if they are
 * idle.
 * @param keepAliveTime Time, in seconds, for which to
 * keep alive unused threads.
 * @param name The name of the threads.
 * @param rejectionPolicy The rejection policy to enforce.
 * @return An executor service preconfigured.
 */
public static ExecutorService getExecutorService(int maximumPoolSize, int corePoolSize, long keepAliveTime,
        final String name, RejectedExecutionHandler rejectionPolicy) {
    if (Util.PreJdk16) {
        // On JDK1.5, if you specify corePoolSize=0, nothing gets executed.
        // Bummer.
        corePoolSize = Math.max(corePoolSize, 1);
    }

    // We must create a factory where the threads
    // have the right name and are marked as daemon threads.
    final ThreadFactory factory = new ThreadFactory() {
        private final AtomicInteger counter = new AtomicInteger(0);

        public Thread newThread(Runnable r) {
            final Thread t = Executors.defaultThreadFactory().newThread(r);
            t.setDaemon(true);
            t.setName(name + '_' + counter.incrementAndGet());
            return t;
        }
    };

    // Ok, create the executor
    final ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,
            maximumPoolSize > 0 ? maximumPoolSize : Integer.MAX_VALUE, keepAliveTime, TimeUnit.SECONDS,
            // we use a sync queue. any other type of queue
            // will prevent the tasks from running concurrently
            // because the executors API requires blocking queues.
            // Important to pass true here. This makes the
            // order of tasks deterministic.
            // TODO Write a non-blocking queue which implements
            // the blocking queue API so we can pass that to the
            // executor.
            new SynchronousQueue<Runnable>(true), factory);

    // Set the rejection policy if required.
    if (rejectionPolicy != null) {
        executor.setRejectedExecutionHandler(rejectionPolicy);
    }

    // Done
    return executor;
}

From source file:org.trend.hgraph.util.test.HGraphClientPerformanceTest.java

@Override
public int run(String[] args) throws Exception {
    if (null == args || args.length < 4) {
        System.err.println("Options must greater than 4");
        printUsage();//from w w  w. ja v a  2s.c o  m
        return -1;
    }

    System.out.println("args=" + Arrays.toString(args));
    String cmd = null;
    int mustStartIdx = -1;
    int level = 2;
    int threads = 100;
    long interval = 1000; // ms
    boolean isMs = false;
    for (int a = 0; a < args.length; a++) {
        cmd = args[a];
        if (cmd.startsWith("-")) {
            if (mustStartIdx != -1) {
                System.err.println("must start option order is incorrect");
                printUsage();
                return -1;
            }

            if ("-l".equals(cmd)) {
                a++;
                cmd = args[a];
                try {
                    level = Integer.parseInt(cmd);
                } catch (NumberFormatException e) {
                    System.err.println("parse number for -l:" + cmd + " failed");
                    printUsage();
                    return -1;
                }
            } else if ("-t".equals(cmd)) {
                a++;
                cmd = args[a];
                try {
                    threads = Integer.parseInt(cmd);
                } catch (NumberFormatException e) {
                    System.err.println("parse number for -t:" + cmd + " failed");
                    printUsage();
                    return -1;
                }
            } else if ("-m".equals(cmd)) {
                isMs = true;
            } else if ("-i".equals(cmd)) {
                a++;
                cmd = args[a];
                try {
                    interval = Long.parseLong(cmd);
                } catch (NumberFormatException e) {
                    System.err.println("parse number for -i:" + cmd + " failed");
                    printUsage();
                    return -1;
                }

            } else {
                System.err.println("undefined option:" + cmd);
                printUsage();
                return -1;
            }

        } else {
            if (mustStartIdx == -1) {
                mustStartIdx = a;
                break;
            } else {
                System.err.println("must start option order is incorrect");
                printUsage();
                return -1;
            }
        }
    }

    if (mustStartIdx + 4 != args.length) {
        System.err.println("The must option still not satisfied !!");
        printUsage();
        return -1;
    }

    String vt = args[mustStartIdx];
    String et = args[mustStartIdx + 1];
    File ipf = new File(args[mustStartIdx + 2]);
    File opp = new File(args[mustStartIdx + 3]);
    Configuration conf = this.getConf();

    conf.set(HBaseGraphConstants.HBASE_GRAPH_TABLE_VERTEX_NAME_KEY, vt);
    conf.set(HBaseGraphConstants.HBASE_GRAPH_TABLE_EDGE_NAME_KEY, et);

    // run test threads
    ThreadFactory tf = new DaemonThreadFactory(Executors.defaultThreadFactory());
    ExecutorService pool = Executors.newFixedThreadPool(threads, tf);
    @SuppressWarnings("rawtypes")
    List<Future> fs = new ArrayList<Future>();
    @SuppressWarnings("rawtypes")
    Future f = null;

    for (int a = 0; a < threads; a++) {
        fs.add(pool.submit(new Task(ipf, opp, conf, level, isMs)));
        synchronized (this) {
            wait(interval);
        }
    }

    while (fs.size() > 0) {
        f = fs.get(0);
        f.get();
        if (f.isDone()) {
            if (f.isCancelled()) {
                LOGGER.warn("a future:" + f + " was cancelled !!");
            }
            fs.remove(0);
        }
    }

    return 0;
}

From source file:it.geosolutions.tools.io.file.Copy.java

/**
 * Copy a list of files (preserving data) to a destination (which can be on
 * nfs) waiting (at least) 'seconds' seconds for each file propagation.
 * //from w  w  w.  ja v  a2  s .  co m
 * @param es
 *            The ExecutorService or null if you want to use a
 *            CachedThreadPool.
 * @note potentially this is a bad executor (for log lists of big files)
 *       NOTE: we should make some tests on this 22 Aug 2011
 * @param list
 * @param baseDestDir
 * @param overwrite
 *            if false and destination exists() do not overwrite the file
 * @param seconds
 * @return the resulting moved file list or null
 * 
 */
public static List<File> parallelCopyListFileToNFS(ExecutorService es, final List<File> list,
        final File baseDestDir, final int seconds) {

    try {

        /*
         * this could be potentially a bad executor (for log lists of big
         * files) NOTE: we should make some tests on this 22 Aug 2011
         */
        if (es == null) {
            final ThreadFactory threadFactory = Executors.defaultThreadFactory();
            es = Executors.newCachedThreadPool(threadFactory);
        }

        final List<FutureTask<File>> futureFileList = asynchCopyListFileToNFS(es, list, baseDestDir, seconds);

        // list
        if (futureFileList == null) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Failed to copy files.");
            return null;
        }
        final int size = futureFileList.size();
        if (size == 0) {
            if (LOGGER.isErrorEnabled())
                LOGGER.error("Failed to copy file list using an empty list");
            return null;
        }

        final List<File> ret = new ArrayList<File>(size);
        for (Future<File> futureFile : futureFileList) {

            if (futureFile != null) {

                File file;
                try {
                    file = futureFile.get();
                    if (file != null && file.exists()) {
                        ret.add(file);
                    } else {
                        if (LOGGER.isWarnEnabled())
                            LOGGER.warn("SKIPPING file:\n\t" + file + ".\nUnable to copy a not existent file.");
                    }
                } catch (InterruptedException e) {
                    if (LOGGER.isErrorEnabled())
                        LOGGER.error("Unable to get the file from this future File copy. ", e);
                } catch (ExecutionException e) {
                    if (LOGGER.isErrorEnabled())
                        LOGGER.error("Unable to get the file from this future File copy. ", e);
                }
            }
        }

        return ret;
    } catch (Throwable t) {
        if (LOGGER.isErrorEnabled())
            LOGGER.error("Unrecognized error occurred. ", t);
    } finally {
        if (es != null)
            es.shutdownNow();
    }
    return null;

}

From source file:it.crs4.pydoop.mapreduce.pipes.TaskLog.java

public static ScheduledExecutorService createLogSyncer() {
    final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
        @Override//from w  ww .j av  a2  s .  c  o m
        public Thread newThread(Runnable r) {
            final Thread t = Executors.defaultThreadFactory().newThread(r);
            t.setDaemon(true);
            t.setName("Thread for syncLogs");
            return t;
        }
    });
    ShutdownHookManager.get().addShutdownHook(new Runnable() {
        @Override
        public void run() {
            TaskLog.syncLogsShutdown(scheduler);
        }
    }, 50);
    scheduler.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            TaskLog.syncLogs();
        }
    }, 0L, 5L, TimeUnit.SECONDS);
    return scheduler;
}

From source file:mondrian.olap.Util.java

/**
 * Creates an {@link ScheduledExecutorService} object backed by a
 * thread pool with a fixed number of threads..
 * @param maxNbThreads Maximum number of concurrent
 * threads.//from   w ww  . ja v  a  2s  .  c  o  m
 * @param name The name of the threads.
 * @return An scheduled executor service preconfigured.
 */
public static ScheduledExecutorService getScheduledExecutorService(final int maxNbThreads, final String name) {
    return Executors.newScheduledThreadPool(maxNbThreads, new ThreadFactory() {
        final AtomicInteger counter = new AtomicInteger(0);

        public Thread newThread(Runnable r) {
            final Thread thread = Executors.defaultThreadFactory().newThread(r);
            thread.setDaemon(true);
            thread.setName(name + '_' + counter.incrementAndGet());
            return thread;
        }
    });
}

From source file:net.rptools.tokentool.controller.TokenTool_Controller.java

@FXML
void initialize() {
    // Note: A Pane is added to the compositeTokenPane so the ScrollPane doesn't consume the mouse events
    assert fileManageOverlaysMenu != null : "fx:id=\"fileManageOverlaysMenu\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert fileSaveAsMenu != null : "fx:id=\"fileSaveAsMenu\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert fileExitMenu != null : "fx:id=\"fileExitMenu\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert editCaptureScreenMenu != null : "fx:id=\"editCaptureScreenMenu\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert editCopyImageMenu != null : "fx:id=\"editCopyImageMenu\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert editPasteImageMenu != null : "fx:id=\"editPasteImageMenu\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert helpAboutMenu != null : "fx:id=\"helpAboutMenu\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert saveOptionsPane != null : "fx:id=\"saveOptionsPane\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert overlayOptionsPane != null : "fx:id=\"overlayOptionsPane\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert backgroundOptionsPane != null : "fx:id=\"backgroundOptionsPane\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert zoomOptionsPane != null : "fx:id=\"zoomOptionsPane\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert compositeTokenPane != null : "fx:id=\"compositeTokenPane\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert tokenPreviewPane != null : "fx:id=\"tokenPreviewPane\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert portraitScrollPane != null : "fx:id=\"portraitScrollPane\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert compositeGroup != null : "fx:id=\"compositeGroup\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert overlayTreeView != null : "fx:id=\"overlayTreeview\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert portraitImageView != null : "fx:id=\"portraitImageView\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert maskImageView != null : "fx:id=\"maskImageView\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert overlayImageView != null : "fx:id=\"overlayImageView\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert tokenImageView != null : "fx:id=\"tokenImageView\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert useFileNumberingCheckbox != null : "fx:id=\"useFileNumberingCheckbox\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert overlayUseAsBaseCheckbox != null : "fx:id=\"overlayUseAsBaseCheckbox\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert clipPortraitCheckbox != null : "fx:id=\"clipPortraitCheckbox\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert fileNameTextField != null : "fx:id=\"fileNameTextField\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert fileNameSuffixLabel != null : "fx:id=\"fileNameSuffixLabel\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert fileNameSuffixTextField != null : "fx:id=\"fileNameSuffixTextField\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert overlayNameLabel != null : "fx:id=\"overlayNameLabel\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert backgroundColorPicker != null : "fx:id=\"backgroundColorPicker\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert overlayAspectToggleButton != null : "fx:id=\"overlayAspectToggleButton\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert portraitTransparencySlider != null : "fx:id=\"portraitTransparencySlider\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert portraitBlurSlider != null : "fx:id=\"portraitBlurSlider\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert portraitGlowSlider != null : "fx:id=\"portraitGlowSlider\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert overlayTransparencySlider != null : "fx:id=\"overlayTransparencySlider\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert overlayWidthSpinner != null : "fx:id=\"overlayWidthSpinner\" was not injected: check your FXML file 'TokenTool.fxml'.";
    assert overlayHeightSpinner != null : "fx:id=\"overlayHeightSpinner\" was not injected: check your FXML file 'TokenTool.fxml'.";

    assert overlayTreeProgressBar != null : "fx:id=\"overlayTreeProgressIndicator\" was not injected: check your FXML file 'ManageOverlays.fxml'.";

    executorService = Executors.newCachedThreadPool(runable -> {
        loadOverlaysThread = Executors.defaultThreadFactory().newThread(runable);
        loadOverlaysThread.setDaemon(true);
        return loadOverlaysThread;
    });/*w  w w .j a  v  a2s  .  com*/

    overlayTreeView.setShowRoot(false);
    overlayTreeView.getSelectionModel().selectedItemProperty().addListener(
            (observable, oldValue, newValue) -> updateCompositImageView((TreeItem<Path>) newValue));

    addPseudoClassToLeafs(overlayTreeView);

    // Bind color picker to compositeTokenPane background fill
    backgroundColorPicker.setValue(Color.TRANSPARENT);
    ObjectProperty<Background> background = compositeTokenPane.backgroundProperty();
    background.bind(Bindings.createObjectBinding(() -> {
        BackgroundFill fill = new BackgroundFill(backgroundColorPicker.getValue(), CornerRadii.EMPTY,
                Insets.EMPTY);
        return new Background(fill);
    }, backgroundColorPicker.valueProperty()));

    // Bind transparency slider to portraitImageView opacity
    portraitTransparencySlider.valueProperty().addListener(new ChangeListener<Number>() {
        public void changed(ObservableValue<? extends Number> ov, Number old_val, Number new_val) {
            portraitImageView.setOpacity(new_val.doubleValue());
            updateTokenPreviewImageView();
        }
    });

    // // Restrict text field to valid filename characters
    // Pattern validDoubleText = Pattern.compile("[^a-zA-Z0-9\\\\._ \\\\/`~!@#$%\\\\^&\\\\(\\\\)\\\\-\\\\=\\\\+\\\\[\\\\]\\\\{\\\\}',\\\\\\\\:]");
    // Pattern validText = Pattern.compile("[^a-zA-Z0-9 ]");
    // TextFormatter<> textFormatter = new TextFormatter<>(
    // change -> {
    // String newText = change.getControlNewText();
    // if (validText.matcher(newText).matches()) {
    // return change;
    // } else
    // return null;
    // });

    // UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
    // @Override
    // public TextFormatter.Change apply(TextFormatter.Change t) {
    // String validText = "[^a-zA-Z0-9]";
    //
    // if (t.isReplaced())
    // if (t.getText().matches(validText))
    // t.setText(t.getControlText().substring(t.getRangeStart(), t.getRangeEnd()));
    //
    // if (t.isAdded()) {
    // if (t.getText().matches(validText)) {
    // return null;
    // }
    // }
    //
    // return t;
    // }
    // };

    UnaryOperator<Change> filter = change -> {
        String text = change.getText();

        if (text.matches(AppConstants.VALID_FILE_NAME_PATTERN)) {
            return change;
        } else {
            change.setText(FileSaveUtil.cleanFileName(text));
            ;
            return change;
        }
        //
        // return null;
    };
    TextFormatter<String> textFormatter = new TextFormatter<>(filter);
    fileNameTextField.setTextFormatter(textFormatter);

    // Effects
    GaussianBlur gaussianBlur = new GaussianBlur(0);
    Glow glow = new Glow(0);
    gaussianBlur.setInput(glow);

    // Bind blur slider to portraitImageView opacity
    portraitBlurSlider.valueProperty().addListener(new ChangeListener<Number>() {
        public void changed(ObservableValue<? extends Number> ov, Number old_val, Number new_val) {
            gaussianBlur.setRadius(new_val.doubleValue());
            portraitImageView.setEffect(gaussianBlur);
            updateTokenPreviewImageView();
        }
    });

    // Bind glow slider to portraitImageView opacity
    portraitGlowSlider.valueProperty().addListener(new ChangeListener<Number>() {
        public void changed(ObservableValue<? extends Number> ov, Number old_val, Number new_val) {
            glow.setLevel(new_val.doubleValue());
            portraitImageView.setEffect(gaussianBlur);
            updateTokenPreviewImageView();
        }
    });

    // Bind transparency slider to overlayImageView opacity
    overlayTransparencySlider.valueProperty().addListener(new ChangeListener<Number>() {
        public void changed(ObservableValue<? extends Number> ov, Number old_val, Number new_val) {
            overlayImageView.setOpacity(new_val.doubleValue());
            updateTokenPreviewImageView();
        }
    });

    // Bind width/height spinners to overlay width/height
    overlayWidthSpinner.getValueFactory().valueProperty()
            .bindBidirectional(overlayHeightSpinner.getValueFactory().valueProperty());
    overlayWidthSpinner.valueProperty().addListener(
            (observable, oldValue, newValue) -> overlayWidthSpinner_onTextChanged(oldValue, newValue));
    overlayHeightSpinner.valueProperty().addListener(
            (observable, oldValue, newValue) -> overlayHeightSpinner_onTextChanged(oldValue, newValue));
}