1

I am trying to develop an Application, which should be able to run in CLI-Only Environments as well as on GUI Enabled mode. As some of my work is done by JavaFX Threads, I need to start a JavaFX Main Thread without starting the graphic engine, as this will Crash in CLI-only Environments. How do I do this?

I already wrote a first main class, which will decide with the commandline Args, if the GUI will be started or it should run in CLI Mode. The GUI already works, I just need to figure out how to run the FX Main Thread without GUI in another class.

----- EDIT ------

Further Explanation:

Consider I have 2 UI's, one CLI and one GUI. Code is cut into UI Handling and Operations. The UI Handling does only parsing Commandline Arguments in CLI Mode and Drawing a GUI in GUI Mode. The Button Events will just call Code from within the Operations Classes.

This Question and Answer shows which code is lying beneath in my Operations Segment Code Reference

I am now trying to reuse the Code of the operations within my CLI Interface. The code there is shortened, consider the create Method as more code.

As stated above, CLI Mode is designed for Environments, where Graphic Environment can not be started. Trying to inherit from Application and implementing the start (Stage s) Method will result within an UnsupportedOperationException : unable to open DISPLAY even if the stage will be ignored within the start method

--- second edit ---

Take the Code described in [here] 1 Consider I want to call createKey(length) not from an Button, but from an second UI which is Commandline Only

   private static void progressBar(Task task) {
    
    task.progressProperty().addListener((new ChangeListener() {
        @Override
        public void changed(ObservableValue observable, Object oldValue, Object newValue) {
            // Return to line beginning
            System.out.print("\r");
            int percentage = (int) (100 * task.progressProperty().get());
            System.out.format("[%3d%%] %s", percentage, task.messageProperty().get());
            
            if (percentage == 100) {
                System.out.println("Finished");
            }
        }
    }));

If I try to run this from a new main class, this will not execute, as the Worker thread waits for an JavaFX Main Thread. I Need to create a JavaFX Main Thread, which is able to Call progressBar(Task) but does not try to create a GUI, as this will result in above posted error

---- Edit Three --- I am trying to post this as a minimal example. My Application Startup Looks like this

    public static void main(String[] args) {

        // If option has been set, start CLI
        if (0 < args.length) {
        
            // Start CLI
               CLI.Main.execute(args);
        
        } else {

            // Trying if GUI can be started
            try {
                GUI.Main.execute();
            }

GUI Main Contains a JavaFX GUI and is working fine, as desired. If we are in an Environment which does not Support drawing a GUI, we have to start the program with arguments. This will invoke the CLI

    public class CLI extends Application {

     public static void execute(String[] args){
     launch(args);
    }
    
    @Override
    public void start(Stage s) {
      progressBar(Creator.createKey());
    }

    private static void progressBar(Task task) {
        
        task.progressProperty().addListener((new ChangeListener() {
            @Override
            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                // Return to line beginning
                System.out.print("\r");
                int percentage = (int) (100 * task.progressProperty().get());
                System.out.format("[%3d%%] %s", percentage, task.messageProperty().get());
                
                if (percentage == 100) {
                    System.out.println("Finished");
                }
            }
        }));
        
    }

The GUI will call the Creator within a Button Event

    private void createKeyPressed(ActionEvent event) {

        // Make Progressbar visible
        pbKeyProgress.visibleProperty().set(true);
        Task<Void> task = Creator.createKey();
        pbKeyProgress.progressProperty().bind(task.progressProperty());
        lblKeyProgress.textProperty().bind(task.messageProperty());
    }

Consider the Creator.createKey() as

    public static Task<Void> createKey() {

    Task<Void> task;
    task = new Task<Void>() {

        final int totalSteps = ... ;
        @Override
        public Void call() throws Exception {
            updateProgress(0, totalSteps);
            updateMessage("Start");
            doStuff();
            updateProgress(1, totalSteps);
            updateMessage("First");
            doStuff();
            updateProgress(2, totalSteps);
            updateMessage("Second");
            // and so on

            return null;

        }
    };

    new Thread(task)
            .start();

    return task ;

}

Within the GUI, the whole code is working as desired. Now I try to make everything working in a non graphic Environment but by using the same Java Version with installed JavaFX. The Creator.createKey should be callable and executable. If I try to execute the CLI on a Machine which supports GUIs, the Commandline Output is as desired and gets updated, the threads are working. If I try it without the JavaFX Extension within the CLI Main Class, the Threads in Creator will not execute, because there is no JavaFX Main Thread. If I try to execute the solution posted above in an Environment which does not allow to draw a GUI, I get an UnsupportedOperationException : Unable to open Display

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
chenino
  • 454
  • 2
  • 7
  • 19
  • 2
    look into headless javafx – bichito Apr 11 '17 at 13:17
  • It's not really clear what you mean here. Why would you be doing work on the JavaFX thread in the case that you're running in "CLI-only" mode? Can't you just switch on the command line arguments in `main()` and call `launch()` if it's running in JavaFX, and just do whatever work you need directly otherwise. – James_D Apr 11 '17 at 13:24
  • See [this question and answer](http://stackoverflow.com/a/24117945/1795426) – user11153 Apr 11 '17 at 13:53
  • This just looks like an [X-Y problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), at least without further justification. The only things that should happen on the JavaFX application thread should be UI-related. If there's no UI, there should be no need for the FX Application Thread. The linked question, AIUI, shows how to create images without the FX toolkit (and FX Application Thread). I think the answer to this question is to refactor the code so that the FX Application Thread is not needed if there is no UI. – James_D Apr 11 '17 at 14:08
  • Well, i implemented my operations in JavaFX Threads, to enable them to update the GUI. I want to run the same operations from within a commandline only Environment. if i just wrap the commandline main class within the Application code of JavaFX Application and exclude the stage parts, i will still get an UnsupportedOperationException : Unable to open Display. If i refactor the code without threads, i will have duplicates of large pieces of code, wich is not desirable – chenino Apr 11 '17 at 14:29
  • @chenino That just sounds like a bad design. Factor the non-UI code into separate classes. Then you can call those methods on whatever thread you want to call them on without duplicating or modifying anything. For the UI, your event handlers would invoke those methods (on the FX Application Thread) and update the UI with the results, for the non-UI code you invoke those methods on the main (or background) thread and do whatever you need with the results. Create a small [MCVE] and [edit] your question to include it if that is not enough to answer the question. – James_D Apr 11 '17 at 14:37
  • It sounds a bit like you are confusing "thread" with "class", btw. You don't "implement operations on threads": you implement operations in methods that are part of classes. When the methods are invoked, they are invoked on a particular thread. But there is never any reason to duplicate code because you want to invoke it on one thread in one circumstance and on another in another circumstance. – James_D Apr 11 '17 at 14:39
  • I changed my initial question to make things more clear, please take a look at the initial question. @James_D The UI code is split from the code wich should be executed. I just saw, it is your own Suggestion wich i used to split operations from UI, link to the question and your solution is appended – chenino Apr 11 '17 at 14:43
  • Yeah, sorry, I really can't follow that description. You probably need to create a very simple (but complete) version of what you are trying to do (i.e. a [MCVE]) and post it in your question. – James_D Apr 11 '17 at 14:49
  • @James_D edited again. Do you know understand what i mean? – chenino Apr 11 '17 at 14:55
  • Not really, no. I have no idea what `createKey()` is, for a start. It looks like you are trying to use the `javafx.concurrent` API in an environment that doesn't have the JavaFX toolkit started. That won't work, because `Task`, for example, relies on the JavaFX Application Thread running, which won't be the case in a "CLI-only Environment". So you should use concurrency API that doesn't require the JavaFX thread for code you want to share between CLI and JavaFX. – James_D Apr 11 '17 at 15:00
  • @James_D createKey is described within the Link. And the opening question was, is it possible to start a JavaFX Application thread without opening a GUI, or start an JavaFX Toolkit in a commandline only Environment – chenino Apr 11 '17 at 15:02
  • The answer to that is "no". (Surely the definition of "commandline only environment", in this context, is simply an environment where there is no JavaFX Toolkit available? So your question appears to be "Can I start the JavaFX Toolkit in an environment where the JavaFX Toolkit is not available?") – James_D Apr 11 '17 at 15:04
  • Well, the JavaFX Toolkit is installed. I just Need to prevent the Toolkit from accessing anything graphic related and only use the threading of it – chenino Apr 11 '17 at 15:08
  • Well then why are you talking about CLI-only environments? This is not the question you asked **at all**. – James_D Apr 11 '17 at 15:09
  • In this context, commandline only meant, that i can not draw a graphical user Interface, as this is for example a remote Shell. A fully installed Java Environment is lying underneath, but it will Crash if you try to Access graphic renderes or DISPLAY, as written above. In my initial question it is written, that i try to implement an CLI mode for Environments wich do not allow showing a GUI and those are described as command line only Environment, this did not include JavaFX not beeing installed – chenino Apr 11 '17 at 15:11
  • Do you just want to start a javafx thread in a headless environment? Or do you want to create a CLI javafx "gui"? – matt Apr 11 '17 at 15:16
  • @matt I am not really sure if i do understand the difference. I want to execute the Programm with a command line Argument, for example wich allows to execute the createKey() Process and write the updates to the commandline like in progressBar() . As far as i understood this, i Need to have a javafx thread to execute the worker thread. The program should be runnable on a normal Java virtual machine and at best without addiotional commandline arguments. If i did understood your question right, i want to create a UI without beeing graphical but using javafx. Headless was only for testing, right? – chenino Apr 11 '17 at 15:21
  • One is, your program runs on the javafx application thread. It wouldn't update a progress bar because there isn't one. You might add a different listener to your data that updates when your data changes, but you don't get any javafx like gui components. – matt Apr 11 '17 at 15:28
  • @matt If i run the Programm calling progressbar() and createKey() within a JavaFX Main Class, it will print my Output and update the progress. The progressBar() Methods is not an JavaFX Progressbar, but writes a Progressbar to System.out . Code for this is above in my question. There will be no GUI shown. But if i try to execute this code within an Environment wich is not able to Show a GUI, even if i did not configure a GUI i get the above mentioned Display Exception. So, update and javafx thread are working, but i Need to prevent Access to graphic pipelines – chenino Apr 11 '17 at 15:32
  • Can you reduce this down to a minimal compilable example that demonstrates your problem? How are you starting your tasks? It seems like what you are asking should be downstream of javafx. So you might be able to start your taskes w/out the javafx application thread. – matt Apr 11 '17 at 15:40
  • I Wrote it down to an example, wich should be runnable. The GUI Part is not important, as the GUI Mode is not needed for this. – chenino Apr 11 '17 at 16:05
  • The JavaFX application thread is a specific thread that is started by the JavaFX toolkit: that thread is used to render the JavaFX scene graph and process user events that happen on the scene graph. If you don't have a JavaFX toolkit, that thread doesn't exist (and if you don't have a JavaFX UI, there would be no purpose for it anyway). It doesn't make any sense for your `CLI` class, which has no GUI, to extend `Application`, which is a class representing a JavaFX application. Anyway, see the answer, I think it is what you are looking for. – James_D Apr 11 '17 at 16:54

1 Answers1

5

It seems you want to write an application that can be run either in a full JavaFX environment, or in an environment that doesn't have a native graphical toolkit (and so cannot start the JavaFX toolkit). The application requires some threading and you want to avoid replicating code.

The JavaFX concurrency API requires the JavaFX toolkit to work properly: it updates its status on the FX Application Thread, for example, so the FX Application Thread (and consequently the FX toolkit) must be running. So your shared code cannot use the JavaFX concurrency API. (It can, technically, use JavaFX properties, though it's probably cleaner to avoid using these too.)

Suppose you want a simple countdown timer. The user enters the number of seconds to countdown, and the timer then counts down. The application needs to be informed when the number of seconds remaining changes, and when the timer reaches zero. Start with an FX-agnostic class for doing the countdown. It can have fields representing callbacks for the number of seconds remaining and for when the timer finishes:

package countdown;

import java.util.Timer;
import java.util.TimerTask;
import java.util.function.IntConsumer;

public class CountdownTimer {


    private IntConsumer secondsRemainingChangedHandler ;
    private Runnable onFinishedHandler ;

    private final Timer timer = new Timer();
    private int secondsRemaining ;

    public CountdownTimer(int totalSeconds) {
        this.secondsRemaining = totalSeconds ;
    }

    public void setSecondsRemainingChangedHandler(IntConsumer secondsRemainingChangedHandler) {
        this.secondsRemainingChangedHandler = secondsRemainingChangedHandler;
    }

    public void setOnFinishedHandler(Runnable onFinishedHandler) {
        this.onFinishedHandler = onFinishedHandler ;
    }

    private void tick() {
        secondsRemaining-- ;
        if (secondsRemainingChangedHandler != null) {
            secondsRemainingChangedHandler.accept(secondsRemaining);
        }
        if (secondsRemaining == 0) {
            timer.cancel();
            if (onFinishedHandler != null) {
                onFinishedHandler.run();
            }
        }
    }

    public void start() {
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                tick();
            }
        }, 1000, 1000);
    }
}

Now you can use this from a purely command line application, with

package countdown.cli;

import java.util.Scanner;

import countdown.CountdownTimer;

public class CLICountdownApp {

    public void runApp() {

        Scanner scanner = new Scanner(System.in);
        System.out.println("Enter time for timer:");
        int time = scanner.nextInt() ;
        scanner.close();
        CountdownTimer timer = new CountdownTimer(time);
        timer.setSecondsRemainingChangedHandler(t -> System.out.println(t +" seconds remaining"));
        timer.setOnFinishedHandler(() -> System.out.println("Timer finished!"));
        timer.start();
    }
}

Or you can use it directly in a JavaFX UI. Note that the callbacks are invoked on the background thread created by the java.util.Timer, so you need to use Platform.runLater() in the callbacks if you want to update the UI:

int time = Integer.parseInt(timeTextField.getText());
CountdownTimer timer = new CountdownTimer(time);
timer.setSecondsRemainingChangedHandler(t -> Platform.runLater(() -> progressBar.setProgress(1.0*(time-t)/time)));
timer.setOnFinishedHandler(() -> Platform.runLater(() -> label.setText("Timer Complete")));
timer.start();

With a little work, you can wrap it in a Task. You probably want the task not to complete until the timer completes. Here the callbacks update the task's progress property, and allow the task to complete, respectively. (This is basically an implementation of the "Facade" design pattern, creating a Task which is a facade to the CountdownTimer. The Task, of course is easier to use in a JavaFX environment. Note this is part of the gui package, which I did because it will only work if the FX toolkit is running.)

package countdown.gui;

import java.util.concurrent.CountDownLatch;

import countdown.CountdownTimer;
import javafx.concurrent.Task;

public class CountdownTask extends Task<Void> {

    private final int totalSeconds ;

    public CountdownTask(int totalSeconds) {
        this.totalSeconds = totalSeconds ;
    }

    @Override
    protected Void call() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        CountdownTimer timer = new CountdownTimer(totalSeconds);
        timer.setSecondsRemainingChangedHandler(t -> updateProgress(totalSeconds -t , totalSeconds));
        timer.setOnFinishedHandler(() -> latch.countDown());
        timer.start();
        latch.await();
        return null ;
    }
}

Then you can use this in the usual JavaFX way:

package countdown.gui;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class JavaFXCountdownApp extends Application {

    @Override
    public void start(Stage primaryStage) {

        ProgressBar progressBar = new ProgressBar(0);
        Label label =  new Label() ;
        TextField timeTextField = new TextField();
        timeTextField.setOnAction(e -> {
            CountdownTask countdownTask = new CountdownTask(Integer.parseInt(timeTextField.getText()));
            progressBar.progressProperty().bind(countdownTask.progressProperty());
            countdownTask.setOnSucceeded(evt -> label.setText("Timer finished!"));
            Thread t = new Thread(countdownTask);
            t.setDaemon(true);
            t.start();
        });

        VBox root = new VBox(5, timeTextField, progressBar, label);
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

And of course it's trivial to launch this with a main class that switches, based on (for example) the command line args:

package countdown;

import countdown.cli.CLICountdownApp;
import countdown.gui.JavaFXCountdownApp;
import javafx.application.Application;

public class Countdown {

    public static void main(String[] args) {
        if (args.length == 1 && "cli".equalsIgnoreCase(args[0])) {
            new CLICountdownApp().runApp();
        } else {
            Application.launch(JavaFXCountdownApp.class);
        }
    }

}

I bundled the complete classes above into a jar file called Countdown.jar, with the main class countdown.Countdown specified in the manifest, and tested this on a Mac OS X with JDK 1.8.0_121, and then via a ssh terminal to a Linux box running the same JDK, but without graphical support in the terminal.

Running java -jar Countdown.jar on the Mac gave the JavaFX UI. Running the same command on the ssh terminal gave, as expected, a java.lang.UnsupportedOperationException (Unable to open DISPLAY). Running java -jar Countdown.jar cli on either ran the command line version.

Note this uses no other techniques than simply separating concerns. The CountdownTimer is general and does not require JavaFX to be running (or even available). The CountdownTask doesn't do anything specific to the logic of the countdown (which of course would be something much more complex in a real app), but just wraps this up as a JavaFX task, updating the progress on the FX Application thread (via Task.updateProgress(...)), and exiting when the whole thing is complete, etc. The CLICountdownApp manages user input and output from/to the console, and the JavaFXCountdownApp just builds and displays the UI for interacting with the CountdownTask.

James_D
  • 201,275
  • 16
  • 291
  • 322