dtonhofer

dtonhofer

Functional Programming in Java, Second Edition: p.65 "WatchFileChange.java"

Suggesting a text change:

We read:

We’ve registered a WatchService to observe any change to the current directory.

but what actually happens is

We’ve registered a path with a filesystem WatchService to get told about ‘modification’ changes to the current directory.

On my system, file deletion does not give rise to a notification, but creation does (probably because after creation, the file is additionally modified)

The text

"Report any file changed within next 1 minute..."

should really say

System.out.println("Report the first change on '" + path + "' within the next 1 minute...");

Note that the code given uses an inner loop. At this point, I really feel we should use collect():

if(watchKey != null) {
   watchKey.pollEvents()
      .stream()
      .forEach(event ->
         System.out.println(event.context()));
}

Nicer:

String res = (watchKey == null) ? "nothing happened at all!" :
                        watchKey.pollEvents()
                                .stream()
                                .map(event -> event.context().toString())
                                .collect(Collectors.joining(", "));

As I had some trouble understanding how the WatchService actually works and what those keys are doing, here is the full method

    public void watchFileChange() throws IOException {
        final Path path = Paths.get(theDir);
        String res;
        // Try-with-resources to close the WatchService at the end
        // (and thus cancel all the WatchKeys registered with it)
        try (WatchService watchService = path.getFileSystem().newWatchService()) {
            try {
                // No need to retain the WatchKey returned by path.register()
                path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
                System.out.println("Report the first change on '" + path + "' within the next 1 minute...");
                WatchKey watchKey = null;
                // poll() "Retrieves and removes the next watch key, waiting if necessary up to
                // the specified wait time if none are yet present."
                try {
                    watchKey = watchService.poll(1, TimeUnit.MINUTES);
                } catch (InterruptedException ex) {
                    System.out.println("Got interrupted");
                }
                res = (watchKey == null) ? "nothing happened at all!" :
                        watchKey.pollEvents()
                                .stream()
                                .map(event -> event.context().toString())
                                .collect(Collectors.joining(", "));
            } catch (NoSuchFileException ex) {
                res = "Looks like there is no filesystem entry '" + path + "'";
            }
        }
        System.out.println(res);
    }

First Post!

dtonhofer

dtonhofer

I can no longer edit this post (why!), so here is the latest version of my file “WatchFileChanges.java”

It contains a modified book version and one which uses Optional (an interesting modification that has to be attempted with an IDE to get unexpected lessons in type inference! )

package chapter3;

import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.file.*;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

// ---
// Described in "Functional Programming in Java" on page 64.
// Derived from the original "compare/fpij/WatchFileChange.java".
// This file is "src/test/java/chapter3/WatchFileChanges.java"
// Instead of having a main() we code it into a JUnit test case.
// ---

public class WatchFileChanges {

    private final static String where = "/home/aloy";
    private final static Path thePath = Paths.get(where);

    private String stringAddClass(final Object o) {
        assert o != null;
        return o.getClass().getName() + ": " + o;
    }

    // As in the book

    @Test
    public void watchFileChange() throws IOException, InterruptedException {
        String txt; // cannot be made final
        // Try-with-resources to close the WatchService at the end
        // (and thus cancel all the WatchKeys registered with it)
        try (final WatchService watchService = thePath.getFileSystem().newWatchService()) {
            try {
                // No need to retain the WatchKey returned by path.register()
                thePath.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
                System.out.println("Report the first change on '" + thePath + "' within the next 1 minute...");
                // poll() "retrieves and removes the next watch key", i.e. returns with a WatchKey once a
                // change has been detected. Otherwise, it times out returning null.
                // Note that we do NOT catch the InterruptedException, but leave it up the stack.
                // If someone interrupted us, there must be reasons.
                WatchKey watchKey = watchService.poll(1, TimeUnit.MINUTES);
                if (watchKey == null) {
                    txt = "Nothing happened at all, the WatchKey is null!";
                } else {
                    txt = "Changes in: " +
                            watchKey.pollEvents()
                                    .stream()
                                    .map(event -> event.context().toString()) // MAKE STRING!
                                    .collect(Collectors.joining(", "));
                }
            } catch (NoSuchFileException ex) {
                txt = "Looks like there is no filesystem entry '" + thePath + "'";
            }
        }
        System.out.println(txt);
    }

    // More interesting, using Optional<>

    @Test
    void watchingAFileChangeFirstMoreInteresting() throws IOException, InterruptedException {
        final String txt;
        try (final WatchService watchService = thePath.getFileSystem().newWatchService()) {
            thePath.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
            System.out.println("Report the first change on '" + thePath + "' within the next 1 minute...");
            // poll() "retrieves and removes the next watch key", i.e. returns with a WatchKey once a
            // change has been detected. Otherwise, it times out returning null.
            // Note that we do NOT catch the InterruptedException, but leave it up the stack.
            // If someone interrupted us, there must be reasons.
            final WatchKey watchKey = watchService.poll(1, TimeUnit.MINUTES);
            if (watchKey == null) {
                txt = "Nothing happened at all, the WatchKey is (null)!";
            } else {
                // The typing here is special as WatchEvent<?> may be parametrized by different actual types
                {
                    Stream<WatchEvent<?>> watchKeyStream = watchKey.pollEvents().stream();
                    Stream<Object> contextStream = watchKeyStream.map(WatchEvent::context);
                    Stream<Optional<Object>> optContextStream = contextStream.map(Optional::of);
                    List<Optional<Object>> list = optContextStream.toList();
                    // The map() makes a string including the class name of 
                    // whatever the "Stream<Optional<Object>>" gave us
                    txt = "Changes in: " +
                            list.stream()
                                    .map(opt -> opt.map(o -> stringAddClass(o)).orElse("(null)")) 
                                    .collect(Collectors.joining(", "));
                }
                /* This does not pass typecheck:
                {
                    List<Optional<Object>> list = watchKey.pollEvents().stream()
                            .map(WatchEvent::context)
                            .map(Optional::of)
                            .toList();
                    String txt = list.stream()
                        .map(opt -> opt.map(o -> o.getClass().getName() + ": " + o).orElse("(null)"))
                        .collect(Collectors.joining("\n"));
                    System.out.println(txt);
                }
                */
            }
        }
        System.out.println(txt);
    }
}

Where Next?

Popular Pragmatic Bookshelf topics Top

JohnS
I can’t setup the Rails source code. This happens in a working directory containing multiple (postgres) Rails apps. With: ruby-3.0.0 s...
New
rmurray10127
Title: Intuitive Python: docker run… denied error (page 2) Attempted to run the docker command in both CLI and Powershell PS C:\Users\r...
New
swlaschin
The book has the same “Problem space/Solution space” diagram on page 18 as is on page 17. The correct Problem/Solution space diagrams ar...
New
jskubick
I’m under the impression that when the reader gets to page 136 (“View Data with the Database Inspector”), the code SHOULD be able to buil...
New
Charles
In general, the book isn’t yet updated for Phoenix version 1.6. On page 18 of the book, the authors indicate that an auto generated of ro...
New
brunogirin
When installing Cards as an editable package, I get the following error: ERROR: File “setup.py” not found. Directory cannot be installe...
New
rainforest
Hi, I’ve got a question about the implementation of PubSub when using a Phoenix.Socket.Transport behaviour rather than channels. Before ...
New
mert
AWDWR 7, page 152, page 153: Hello everyone, I’m a little bit lost on the hotwire part. I didn’t fully understand it. On page 152 @rub...
New
redconfetti
Docker-Machine became part of the Docker Toolbox, which was deprecated in 2020, long after Docker Desktop supported Docker Engine nativel...
New
dachristenson
I’ve got to the end of Ch. 11, and the app runs, with all tabs displaying what they should – at first. After switching around between St...
New

Other popular topics Top

PragmaticBookshelf
Write Elixir tests that you can be proud of. Dive into Elixir’s test philosophy and gain mastery over the terminology and concepts that u...
New
AstonJ
What chair do you have while working… and why? Is there a ‘best’ type of chair or working position for developers?
New
New
Maartz
Hi folks, I don’t know if I saw this here but, here’s a new programming language, called Roc Reminds me a bit of Elm and thus Haskell. ...
New
AstonJ
If you get Can't find emacs in your PATH when trying to install Doom Emacs on your Mac you… just… need to install Emacs first! :lol: bre...
New
New
hilfordjames
There appears to have been an update that has changed the terminology for what has previously been known as the Taskbar Overflow - this h...
New
First poster: bot
zig/http.zig at 7cf2cbb33ef34c1d211135f56d30fe23b6cacd42 · ziglang/zig. General-purpose programming language and toolchain for maintaini...
New
First poster: AstonJ
Jan | Rethink the Computer. Jan turns your computer into an AI machine by running LLMs locally on your computer. It’s a privacy-focus, l...
New
PragmaticBookshelf
Develop, deploy, and debug BEAM applications using BEAMOps: a new paradigm that focuses on scalability, fault tolerance, and owning each ...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: