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

jimschubert
In Chapter 3, the source for index introduces Config on page 31, followed by more code including tests; Config isn’t introduced until pag...
New
herminiotorres
Hi @Margaret , On page VII the book tells us the example and snippets will be all using Elixir version 1.11 But on page 3 almost the en...
New
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
Henrai
Hi, I’m working on the Chapter 8 of the book. After I add add the point_offset, I’m still able to see acne: In the image above, I re...
New
a.zampa
@mfazio23 I’m following the indications of the book and arriver ad chapter 10, but the app cannot be compiled due to an error in the Bas...
New
ggerico
I got this error when executing the plot files on macOS Ventura 13.0.1 with Python 3.10.8 and matplotlib 3.6.1: programming_ML/code/03_...
New
SlowburnAZ
Getting an error when installing the dependencies at the start of this chapter: could not compile dependency :exla, "mix compile" failed...
New
dachristenson
I just bought this book to learn about Android development, and I’m already running into a major issue in Ch. 1, p. 20: “Update activity...
New
roadbike
From page 13: On Python 3.7, you can install the libraries with pip by running these commands inside a Python venv using Visual Studio ...
New

Other popular topics Top

DevotionGeo
I know that these benchmarks might not be the exact picture of real-world scenario, but still I expect a Rust web framework performing a ...
New
New
AstonJ
Thanks to @foxtrottwist’s and @Tomas’s posts in this thread: Poll: Which code editor do you use? I bought Onivim! :nerd_face: https://on...
New
AstonJ
Biggest jackpot ever apparently! :upside_down_face: I don’t (usually) gamble/play the lottery, but working on a program to predict the...
New
AstonJ
If you want a quick and easy way to block any website on your Mac using Little Snitch simply… File &gt; New Rule: And select Deny, O...
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
Programming Ruby is the most complete book on Ruby, covering both the language itself and the standard library as well as commonly used t...
New
CommunityNews
A Brief Review of the Minisforum V3 AMD Tablet. Update: I have created an awesome-minisforum-v3 GitHub repository to list information fo...
New
PragmaticBookshelf
Get the comprehensive, insider information you need for Rails 8 with the new edition of this award-winning classic. Sam Ruby @rubys ...
New
AnfaengerAlex
Hello, I’m a beginner in Android development and I’m facing an issue with my project setup. In my build.gradle.kts file, I have the foll...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: