My aim is to load a document from a web server and then parse its DOM for specific content. Loading the DOM is my problem.
I am trying to use a javafx.scene.web.WebEngine as this seems as if it should be able to do all the necessary mechanics, including javascript execution, which may affect the final DOM.
When loading a document, it appears to get stuck in the RUNNING state and never reaches the SUCCEEDED state, which I believe is required before accessing the DOM from WebEngine.getDocument().
This occurs whether loading from a URL or literal content (as used in this minimal example).
Can anyone see what I’m doing wrong, or misunderstanding?
Thanks in advance for any help.
import java.util.concurrent.ExecutionException;
import org.w3c.dom.Document;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.embed.swing.JFXPanel;
import javafx.scene.web.WebEngine;
public class WebEngineProblem {
private static Task<WebEngine> getEngineTask() {
Task<WebEngine> task = new Task<>() {
#Override
protected WebEngine call() throws Exception {
WebEngine webEngine = new WebEngine();
final Worker<Void> loadWorker = webEngine.getLoadWorker();
loadWorker.stateProperty().addListener((obs, oldValue, newValue) -> {
System.out.println("state:" + newValue);
if (newValue == State.SUCCEEDED) {
System.out.println("finished loading");
}
});
webEngine.loadContent("<!DOCTYPE html>\r\n" + "<html>\r\n" + "<head>\r\n" + "<meta charset=\"UTF-8\">\r\n"
+ "<title>Content Title</title>\r\n" + "</head>\r\n" + "<body>\r\n" + "<p>Body</p>\r\n" + "</body>\r\n"
+ "</html>\r\n");
State priorState = State.CANCELLED; //should never be CANCELLED
double priorWork = Double.NaN;
while (loadWorker.isRunning()) {
final double workDone = loadWorker.getWorkDone();
if (loadWorker.getState() != priorState || priorWork != workDone) {
priorState = loadWorker.stateProperty().getValue();
priorWork = workDone;
System.out.println(priorState + " " + priorWork + "/" + loadWorker.getTotalWork());
}
Thread.sleep(1000);
}
return webEngine;
}
};
return task;
}
public static void main(String[] args) {
new JFXPanel(); // Initialise the JavaFx Platform
WebEngine engine = null;
Task<WebEngine> task = getEngineTask();
try {
Platform.runLater(task);
Thread.sleep(1000);
engine = task.get(); // Never completes as always RUNNING
}
catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// This code is never reached as the content never completes loading
// It would fail as it's not on the FX thread.
Document doc = engine.getDocument();
String content = doc.getTextContent();
System.out.println(content);
}
}
The change to a Worker's state property will occur on the FX Application Thread, even though that worker is running on a background thread. (JavaFX properties are essentially single-threaded.) Somewhere in the implementation of the thread that loads the web engine's content, there is a call to Platform.runLater(...) that changes the state of the worker.
Since your task blocks until the state of the worker has changed, and since you make your task run on the FX Application Thread, you have essentially deadlocked the FX Application Thread: the change to the load worker's state can't occur until your task completes (because it is running on the same thread), and your task can't complete until the state changes (as that's what you programmed the task to do).
It is basically always an error to block the FX Application Thread. Instead, you should block another thread until the conditions you want are true (web engine is created and loading thread completes), and then execute the next thing you want to do when that occurs (using Platform.runLater(...) again if it needs to be executed on the FX Application Thread).
Here is an example doing what I think you are trying to do:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import org.w3c.dom.Document;
import javafx.application.Platform;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.embed.swing.JFXPanel;
import javafx.scene.web.WebEngine;
public class WebEngineProblem {
public static void main(String[] args) throws InterruptedException, ExecutionException {
new JFXPanel(); // Initialise the JavaFx Platform
CountDownLatch loaded = new CountDownLatch(1);
FutureTask<WebEngine> createEngineTask = new FutureTask<WebEngine>( () -> {
WebEngine webEngine = new WebEngine();
final Worker<Void> loadWorker = webEngine.getLoadWorker();
loadWorker.stateProperty().addListener((obs, oldValue, newValue) -> {
System.out.println("state:" + newValue);
if (newValue == State.SUCCEEDED) {
System.out.println("finished loading");
loaded.countDown();
}
});
webEngine.loadContent("<!DOCTYPE html>\r\n" + "<html>\r\n" + "<head>\r\n" + "<meta charset=\"UTF-8\">\r\n"
+ "<title>Content Title</title>\r\n" + "</head>\r\n" + "<body>\r\n" + "<p>Body</p>\r\n" + "</body>\r\n"
+ "</html>\r\n");
return webEngine ;
});
Platform.runLater(createEngineTask);
WebEngine engine = createEngineTask.get();
loaded.await();
Platform.runLater(() -> {
Document doc = engine.getDocument();
String content = doc.getDocumentElement().getTextContent();
System.out.println(content);
});
}
}
Related
I found a way to open a link on default browser using HostServices.
getHostServices().showDocument("http://www.google.com");
Is there any way to open a media in default media player?
Is there any way to launch a specific File or Application?
Generally speaking, you can use Desktop#open(file) to open a file natively as next:
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.OPEN)) {
desktop.open(file);
} else {
throw new UnsupportedOperationException("Open action not supported");
}
Launches the associated application to open the file. If the specified
file is a directory, the file manager of the current platform is
launched to open it.
More specifically, in case of a browser you can use directly Desktop#browse(uri), as next:
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
desktop.browse(uri);
} else {
throw new UnsupportedOperationException("Browse action not supported");
}
Launches the default browser to display a URI. If the default browser
is not able to handle the specified URI, the application registered
for handling URIs of the specified type is invoked. The application is
determined from the protocol and path of the URI, as defined by the
URI class. If the calling thread does not have the necessary
permissions, and this is invoked from within an applet,
AppletContext.showDocument() is used. Similarly, if the calling does
not have the necessary permissions, and this is invoked from within a
Java Web Started application, BasicService.showDocument() is used.
If you want to either open a URL which has an http: scheme in the browser, or open a file using the default application for that file type, the HostServices.showDocument(...) method you referenced provides a "pure JavaFX" way to do this. Note that you can't use this (as far as I can tell) to download a file from a web server and open it with the default application.
To open a file with the default application, you must convert the file to the string representation of the file: URL. Here is a simple example:
import java.io.File;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class OpenResourceNatively extends Application {
#Override
public void start(Stage primaryStage) {
TextField textField = new TextField("http://stackoverflow.com/questions/39898704");
Button openURLButton = new Button("Open URL");
EventHandler<ActionEvent> handler = e -> open(textField.getText());
textField.setOnAction(handler);
openURLButton.setOnAction(handler);
FileChooser fileChooser = new FileChooser();
Button openFileButton = new Button("Open File...");
openFileButton.setOnAction(e -> {
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null) {
open(file.toURI().toString());
}
});
VBox root = new VBox(5,
new HBox(new Label("URL:"), textField, openURLButton),
new HBox(openFileButton)
);
root.setPadding(new Insets(20));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private void open(String resource) {
getHostServices().showDocument(resource);
}
public static void main(String[] args) {
launch(args);
}
}
Only the solution with java.awt.Desktop worked for me to open a file from JavaFX.
However, at first, my application got stuck and I had to figure out that it is necessary to call Desktop#open(File file) from a new thread. Calling the method from the current thread or the JavaFX application thread Platform#runLater(Runnable runnable) resulted in the application to hang indefinitely without an exception being thrown.
This is a small sample JavaFX application with the working file open solution:
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class FileOpenDemo extends Application {
#Override
public void start(Stage primaryStage) {
final Button button = new Button("Open file");
button.setOnAction(event -> {
final FileChooser fileChooser = new FileChooser();
final File file = fileChooser.showOpenDialog(primaryStage.getOwner());
if (file == null)
return;
System.out.println("File selected: " + file.getName());
if (!Desktop.isDesktopSupported()) {
System.out.println("Desktop not supported");
return;
}
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
System.out.println("File opening not supported");
return;
}
final Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
try {
Desktop.getDesktop().open(file);
} catch (IOException e) {
System.err.println(e.toString());
}
return null;
}
};
final Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
});
primaryStage.setScene(new Scene(button));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The other proposed solution with javafx.application.HostServices did not work at all. I am using OpenJFX 8u141 on Ubuntu 17.10 amd64 and I got the following exception when invoking HostServices#showDocument(String uri):
java.lang.ClassNotFoundException: com.sun.deploy.uitoolkit.impl.fx.HostServicesFactory
Obviously, JavaFX HostServices is not yet properly implemented on all platforms. On this topic see also: https://github.com/Qabel/qabel-desktop/issues/420
Pretty much, I'm trying to write a simple program that lets the user choose a file. Unfortunately, JFileChooser through Swing is a little outdated, so I am trying to use JavaFX FileChooser for this. The goal is to run FileGetter as a thread, transfer the file data to the Main Class, and continue from there.
Main Class:
package application;
import java.io.File;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(new FileGetter());
FileGetter fg = new FileGetter();
t1.start();
boolean isReady = false;
while(isReady == false){
isReady = FileGetter.getIsReady();
}
File file = FileGetter.getFile();
System.out.println(file.getAbsolutePath());
...
}
}
FileGetter Class:
package application;
import java.io.File;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class FileGetter extends Application implements Runnable {
static File file;
static boolean isReady = false;
#Override
public void start(Stage primaryStage) {
try {
FileChooser fc = new FileChooser();
while(file == null){
file = fc.showOpenDialog(primaryStage);
}
isReady = true;
Platform.exit();
} catch(Exception e) {
e.printStackTrace();
}
}
#Override
public void run() {
launch();
}
public static boolean getIsReady(){
return isReady;
}
public static File getFile(){
return file;
}
}
Problem is that the value of isReady in the while loop doesn't update to true when the user picked a file (the reason I have it is to prevent the code in Main from continuing with a File set to null).
Any help, alternative suggestions, or explanations as of why this happens is very much appreciated!
The java memory model does not require variable values to be the same in different threads except under specific conditions.
What is happening here is that the FileGetter thread is updating the value in the own memory that is only accessed from this thread, but your main thread doesn't see the updated value, since it only sees the version of the variable stored in it's own memory that is different from the one of the FileGetter thread. Each of the threads has it's own copy of the field in memory, which is perfectly fine according to the java specification.
To fix this, you can simply add the volatile modifier to isReady:
static volatile boolean isReady = false;
which makes sure the updated value will be visible from your main thread.
Furthermore I recommend reducing the number of FileGetter instances you create. In your code 3 instances are created, but only 1 is used.
Thread t1 = new Thread(() -> Application.launch(FileGetter.class));
t1.start();
...
The easiest way to implement this
Instead of trying to drive the horse with the cart, why not just follow the standard JavaFX lifecycle? In other words, make your Main class a subclass of Application, get the file in the start() method, and then proceed (in a background thread) with the rest of the application?
public class Main extends Application {
#Override
public void init() {
// make sure we don't exit when file chooser is closed...
Platform.setImplicitExit(false);
}
#Override
public void start(Stage primaryStage) {
File file = null ;
FileChooser fc = new FileChooser();
while(file == null){
file = fc.showOpenDialog(primaryStage);
}
final File theFile = file ;
new Thread(() -> runApplication(theFile)).start();
}
private void runApplication(File file) {
// run your application here...
}
}
What is wrong with your code
If you really want the Main class to be separate from the JavaFX Application class (which doesn't really make sense: once you have decided to use a JavaFX FileChooser, you have decided you are writing a JavaFX application, so the startup class should be a subclass of Application), then it gets a bit tricky. There are several issues with your code as it stands, some of which are addressed in other answers. The main issue, as shown in Fabian's answer, is that you are referencing FileGetter.isReady from multiple threads without ensuring liveness. This is exactly the issue addressed in Josh Bloch's Effective Java (Item 66 in the 2nd edition).
Another issue with your code is that you won't be able to use the FileGetter more than once (you can't call launch() more than once), which might not be an issue in your code now, but almost certainly will be at some point with this application as development progresses. The problem is that you have mixed two issues: starting the FX toolkit, and retrieving a File from a FileChooser. The first thing must only be done once; the second should be written to be reusable.
And finally your loop
while(isReady == false){
isReady = FileGetter.getIsReady();
}
is very bad practice: it checks the isReady flag as fast as it possibly can. Under some (fairly unusual) circumstances, it could even prevent the FX Application thread from having any resources to run. This should just block until the file is ready.
How to fix without making Main a JavaFX Application
So, again only if you have a really pressing need to do so, I would first create a class that just has the responsibility of starting the FX toolkit. Something like:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
public class FXStarter extends Application {
private static final AtomicBoolean startRequested = new AtomicBoolean(false);
private static final CountDownLatch latch = new CountDownLatch(1);
#Override
public void init() {
Platform.setImplicitExit(false);
}
#Override
public void start(Stage primaryStage) {
latch.countDown();
}
/** Starts the FX toolkit, if not already started via this method,
** and blocks execution until it is running.
**/
public static void startFXIfNeeded() throws InterruptedException {
if (! startRequested.getAndSet(true)) {
new Thread(Application::launch).start();
}
latch.await();
}
}
Now create a class that gets a file for you. This should ensure the FX toolkit is running, using the previous class. This implementation allows you to call getFile() from any thread:
import java.io.File;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javafx.application.Platform;
import javafx.stage.FileChooser;
public class FileGetter {
/**
** Retrieves a file from a JavaFX File chooser. This method can
** be called from any thread, and will block until the user chooses
** a file.
**/
public File getFile() throws InterruptedException {
FXStarter.startFXIfNeeded() ;
if (Platform.isFxApplicationThread()) {
return doGetFile();
} else {
FutureTask<File> task = new FutureTask<File>(this::doGetFile);
Platform.runLater(task);
try {
return task.get();
} catch (ExecutionException exc) {
throw new RuntimeException(exc);
}
}
}
private File doGetFile() {
File file = null ;
FileChooser chooser = new FileChooser() ;
while (file == null) {
file = chooser.showOpenDialog(null) ;
}
return file ;
}
}
and finally your Main is just
import java.io.File;
public class Main {
public static void main(String[] args) throws InterruptedException {
File file = new FileGetter().getFile();
// proceed...
}
}
Again, this is pretty complex; I see no reason not to simply use the standard FX Application lifecycle for this, as in the very first code block in the answer.
In this code
while(isReady == false){
isReady = FileGetter.getIsReady();
}
there is nothing that is going to change the state of FileGetter's isReady to true
How do I get a handle on a JavaFX application started using the following code?
CPUUsageChart.launch(CPUUsageChart.class);
CPUUsageChart extends Application from JavaFX and I am launching it from a main method of a simple Java project.
What I ultimately want to achieve is, that I can start the App and use its methods in the simple Java code, so that I do not have to do the calling in the Constructor of the Application extending class. I only want to use JavaFX's abilities for drawing charts and save them to HDD, for later usage, but I do not need to see any GUI made in JavaFX.
Proposed Solution
You can only launch an application once, so there will only ever be a single instance of your application class.
Because there is only a single instance of the application, you can store a reference to the instance in a static variable of the application when the application is started and you can get the instance as required from a static method (a kind of singleton pattern).
Caveats
Care must be taken to ensure:
The instance is available before you try to use it.
That threading rules are appropriately observed.
That the JavaFX Platform is appropriately shutdown when it is no longer required.
Sample Solution
The sample code below uses a lock and a condition to ensure that the application instance is available before you try to use it. It will also require explicit shutdown of the JavaFX platform when it is no longer required.
Thanks to StackOverflow user James-D for some edit assistance with this code.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CPUUsageChart extends Application {
private static CPUUsageChart appInstance;
private static final Lock lock = new ReentrantLock();
private static final Condition appStarted = lock.newCondition();
/**
* Starts the application and records the instance.
* Sets the JavaFX platform not to exit implicitly.
* (e.g. an explicit call to Platform.exit() is required
* to exit the JavaFX Platform).
*/
#Override
public void start(Stage primaryStage) {
lock.lock();
try {
Platform.setImplicitExit(false);
appInstance = this;
appStarted.signalAll();
} finally {
lock.unlock();
}
}
/**
* Get an instance of the application.
* If the application has not already been launched it will be launched.
* This method will block the calling thread until the
* start method of the application has been invoked and the instance set.
* #return application instance (will not return null).
*/
public static CPUUsageChart getInstance() throws InterruptedException {
lock.lock();
try {
if (appInstance == null) {
Thread launchThread = new Thread(
() -> launch(CPUUsageChart.class),
"chart-launcher"
);
launchThread.setDaemon(true);
launchThread.start();
appStarted.await();
}
} finally {
lock.unlock();
}
return appInstance;
}
/**
* Public method which can be called to perform the main operation
* for this application.
* (render a chart and store the chart image to disk).
* This method can safely be called from any thread.
* Once this method is invoked, the data list should not be modified
* off of the JavaFX application thread.
*/
public void renderChart(
ObservableList<XYChart.Data<Number, Number>> data
) {
// ensure chart is rendered on the JavaFX application thread.
if (!Platform.isFxApplicationThread()) {
Platform.runLater(() -> this.renderChartImpl(data));
} else {
this.renderChartImpl(data);
}
}
/**
* Private method which can be called to perform the main operation
* for this application.
* (render a chart and store the chart image to disk).
* This method must be invoked on the JavaFX application thread.
*/
private void renderChartImpl(
ObservableList<XYChart.Data<Number, Number>> data
) {
LineChart<Number, Number> chart = new LineChart<>(
new NumberAxis(),
new NumberAxis(0, 100, 10)
);
chart.setAnimated(false);
chart.getData().add(
new XYChart.Series<>("CPU Usage", data)
);
Scene scene = new Scene(chart);
try {
LocalDateTime now = LocalDateTime.now();
File file = Paths.get(
System.getProperty("user.dir"),
"cpu-usage-chart-" + now + ".png"
).toFile();
ImageIO.write(
SwingFXUtils.fromFXImage(
chart.snapshot(null, null),
null
),
"png",
file
);
System.out.println("Chart saved as: " + file);
} catch (IOException e) {
e.printStackTrace();
}
}
}
To use this (from any thread):
try {
// get chartApp instance, blocking until it is available.
CPUUsageChart chartApp = CPUUsageChart.getInstance();
// call render chart as many times as you want
chartApp.renderChart(cpuUsageData);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// note your program should only ever exit the platform once.
Platform.exit();
}
Complete sample application which creates five graphs of cpu usage data with ten samples in each chart, each sample spaced by 100 milliseconds. As the sample invokes the chart application to render the charts, it will create chart png image files in the current java working directory and the file names will be output to the system console. No JavaFX stage or window is displayed.
Code to sample CPU usage copied from: How to get percentage of CPU usage of OS from java
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.XYChart;
import javax.management.*;
import java.lang.management.ManagementFactory;
public class ChartTest {
public static void main(String[] args) {
try {
CPUUsageChart chart = CPUUsageChart.getInstance();
for (int i = 0; i < 5; i++) {
ObservableList<XYChart.Data<Number, Number>> cpuUsageData = FXCollections.observableArrayList();
for (int j = 0; j < 10; j++) {
cpuUsageData.add(
new XYChart.Data<>(
j / 10.0,
getSystemCpuLoad()
)
);
Thread.sleep(100);
}
chart.renderChart(cpuUsageData);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (MalformedObjectNameException | ReflectionException | InstanceNotFoundException e) {
e.printStackTrace();
} finally {
Platform.exit();
}
}
public static double getSystemCpuLoad() throws MalformedObjectNameException, ReflectionException, InstanceNotFoundException {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = ObjectName.getInstance("java.lang:type=OperatingSystem");
AttributeList list = mbs.getAttributes(name, new String[]{ "SystemCpuLoad" });
if (list.isEmpty()) return Double.NaN;
Attribute att = (Attribute)list.get(0);
Double value = (Double)att.getValue();
if (value == -1.0) return Double.NaN; // usually takes a couple of seconds before we get real values
return ((int)(value * 1000) / 10.0); // returns a percentage value with 1 decimal point precision
}
}
Sample output (percentage CPU usage on the Y axis, and time in tenth of second sample spacing on the X axis).
Background Information
Application javadoc to further understand the JavaFX application lifecycle.
Related question: How do I start again an external JavaFX program? Launch prevents this, even if the JavaFX program ended with Platform.Exit
Alternate Implementations
You could use a JFXPanel rather than a class which extends Application. Though, then your application would also have a dependency on Swing.
You could make the main class of your application extend Application, so the application is automatically launched when your application is started rather than having a separate Application just for your usage chart.
If you have lots and lots of charts to render you could look a this off screen chart renderer implementation.
I have a problem with my java application. On the startup of the application the application needs a connection to another application. My idea was to check on startup of my application to check every second if the other application is available (no condition like wait 5 Minutes, just wait infinitly until available).
I tryed this in the abstract example shown below. In the excample my application will get the other application after 3 trys ...
package main;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
private static final Logger logger = LogManager.getLogger(Main.class);
public static void main(String[] args) {
final Callable<String> sleeper = new Callable<String>() {
// local timer for resource getter
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> timer;
String returnValue;
#Override
public String call() throws Exception {
// start on first call
if (null == timer) {
logger.info("sleeper - starting getter ...");
timer = executor.scheduleAtFixedRate(new getter(3), 0, 1, TimeUnit.SECONDS);
}
// the right way - this seems to be ugly!
try {
timer.get();
} catch (CancellationException | ExecutionException | InterruptedException e) {
logger.error("sleeper - got an exception ... but nothing bad?!");
}
logger.info("sleeper - returning="+returnValue);
return returnValue;
}
class getter implements Runnable {
int _trys;
int _maxTrys;
String _res = null;
getter(int maxTrys) {
logger.info("getter - init, maxTrys=" + maxTrys);
_maxTrys=maxTrys;
}
#Override
public void run() {
if (null == _res) {
if (_trys<_maxTrys) {
++_trys;
logger.info("getter - sleeping trys="+_trys + "/" + _maxTrys);
} else {
_res = "*MIGHTY RESOURCE*";
logger.info("getter - found resource after "+_trys+" trys!");
}
} else {
logger.info("getter - returning resource to parent");
returnValue = _res; // hand over
this.notify(); // exit?
}
}
}
};
logger.info("Main - starting sleeper");
ScheduledExecutorService sleeperExecutor = Executors.newScheduledThreadPool(1);
Future<String> resource = sleeperExecutor.submit(sleeper);
try {
logger.info("Main - got="+resource.get());
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}
This seems to be a bad solution for me because I can only exit with an exception (see timer.get()). And it seems to be very mich code for such a simple problem.
I imagine something like this (pseudo code):
Application {
start() {
while (!otherAppl.isAvailable) {
// wait until other appl is available
}
// other application is available ... go on with startup
}
}
Regards
S.
I'm using Watcher in JDK7 which relies on inotify events. If the file is on a NFS, I want my program to fallback and use polling instead. Is there a way to detect if a file is on a remote drive (other than using Runtime.exec and parsing the mount table)? I'm only concerned with Linux compatibility for now.
I suppose one option is to use both inotify and polling when the program starts, but then disable the polling thread if an inotify event for my file is created.
You should be able to get relatively reliable info about the underlying file system type with FileStore.type().
It will definitely tell you if it's an NFS, or CIFS, not sure about other network mount types.
However I have no info about how reliable it is, #hoaz's suggestion to check if events are coming through might be a good idea.
I had the same problem. I have solved it by creating a new thread in de main class and touching the files periodically so a new change event gets fired.
The sample polls the dir for every 10 seconds does a touch.
Here a sample of the code:
package com.ardevco.files;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.List;
public class Touch implements Runnable {
private Path touchPath;
public Touch(Path touchPath) {
this.touchPath = touchPath;
this.checkPath = checkPath;
}
public static void touch(Path file) throws IOException {
long timestamp = System.currentTimeMillis();
touch(file, timestamp);
}
public static void touch(Path file, long timestamp) throws IOException {
if (Files.exists(file)) {
FileTime ft = FileTime.fromMillis(timestamp);
Files.setLastModifiedTime(file, ft);
}
}
List<Path> listFiles(Path path) throws IOException {
final List<Path> files = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
files.addAll(listFiles(entry));
}
files.add(entry);
}
}
return files;
}
#Override
public void run() {
while (true) {
try {
for (Path path : listFiles(touchPath)) {
touch(path);
}
} catch (IOException e) {
System.out.println("Exception: " + e);
}
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
System.out.println("Exception: " + e);
}
}
}
}