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

abtin
page 20: … protoc command… I had to additionally run the following go get commands in order to be able to compile protobuf code using go...
New
iPaul
page 37 ANTLRInputStream input = new ANTLRInputStream(is); as of ANTLR 4 .8 should be: CharStream stream = CharStreams.fromStream(i...
New
johnp
Hi Brian, Looks like the api for tinydb has changed a little. Noticed while working on chapter 7 that the .purge() call to the db throws...
New
jesse050717
Title: Web Development with Clojure, Third Edition, pg 116 Hi - I just started chapter 5 and I am stuck on page 116 while trying to star...
New
Chrichton
Dear Sophie. I tried to do the “Authorization” exercise and have two questions: When trying to plug in an email-service, I found the ...
New
adamwoolhether
When trying to generate the protobuf .go file, I receive this error: Unknown flag: --go_opt libprotoc 3.12.3 MacOS 11.3.1 Googling ...
New
brunogirin
When trying to run tox in parallel as explained on page 151, I got the following error: tox: error: argument -p/–parallel: expected one...
New
brunogirin
When running tox for the first time, I got the following error: ERROR: InterpreterNotFound: python3.10 I realised that I was running ...
New
taguniversalmachine
It seems the second code snippet is missing the code to set the current_user: current_user: Accounts.get_user_by_session_token(session["...
New
gorkaio
root_layout: {PentoWeb.LayoutView, :root}, This results in the following following error: no “root” html template defined for PentoWeb...
New

Other popular topics Top

AstonJ
A thread that every forum needs! Simply post a link to a track on YouTube (or SoundCloud or Vimeo amongst others!) on a separate line an...
New
ohm
Which, if any, games do you play? On what platform? I just bought (and completed) Minecraft Dungeons for my Nintendo Switch. Other than ...
New
AstonJ
We have a thread about the keyboards we have, but what about nice keyboards we come across that we want? If you have seen any that look n...
New
dimitarvp
Small essay with thoughts on macOS vs. Linux: I know @Exadra37 is just waiting around the corner to scream at me “I TOLD YOU SO!!!” but I...
New
PragmaticBookshelf
Tailwind CSS is an exciting new CSS framework that allows you to design your site by composing simple utility classes to create complex e...
New
First poster: joeb
The File System Access API with Origin Private File System. WebKit supports new API that makes it possible for web apps to create, open,...
New
PragmaticBookshelf
Author Spotlight: VM Brasseur @vmbrasseur We have a treat for you today! We turn the spotlight onto Open Source as we sit down with V...
New
PragmaticBookshelf
Author Spotlight: Peter Ullrich @PJUllrich Data is at the core of every business, but it is useless if nobody can access and analyze ...
New
AstonJ
This is a very quick guide, you just need to: Download LM Studio: https://lmstudio.ai/ Click on search Type DeepSeek, then select the o...
New
Fl4m3Ph03n1x
Background Lately I am in a quest to find a good quality TTS ai generation tool to run locally in order to create audio for some videos I...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: