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

mikecargal
Title: Hands-on Rust: question about get_component (page 295) (feel free to respond. “You dug you’re own hole… good luck”) I have somet...
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
raul
Page 28: It implements io.ReaderAt on the store type. Sorry if it’s a dumb question but was the io.ReaderAt supposed to be io.ReadAt? ...
New
conradwt
First, the code resources: Page 237: rumbl_umbrella/apps/rumbl/mix.exs Note: That this file is missing. Page 238: rumbl_umbrella/app...
New
alanq
This isn’t directly about the book contents so maybe not the right forum…but in some of the code apps (e.g. turbo/06) it sends a TURBO_ST...
New
patoncrispy
I’m new to Rust and am using this book to learn more as well as to feed my interest in game dev. I’ve just finished the flappy dragon exa...
New
jgchristopher
“The ProductLive.Index template calls a helper function, live_component/3, that in turn calls on the modal component. ” Excerpt From: Br...
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
kolossal
Hi, I need some help, I’m new to rust and was learning through your book. but I got stuck at the last stage of distribution. Whenever I t...
New
SlowburnAZ
Getting an error when installing the dependencies at the start of this chapter: could not compile dependency :exla, "mix compile" failed...
New

Other popular topics Top

malloryerik
Any thoughts on Svelte? Svelte is a radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue...
New
PragmaticBookshelf
A PragProg Hero’s Journey with Brian P. Hogan @bphogan Have you ever worried that your only legacy will be in the form of legacy...
New
PragmaticBookshelf
Design and develop sophisticated 2D games that are as much fun to make as they are to play. From particle effects and pathfinding to soci...
New
brentjanderson
Bought the Moonlander mechanical keyboard. Cherry Brown MX switches. Arms and wrists have been hurting enough that it’s time I did someth...
New
AstonJ
Just done a fresh install of macOS Big Sur and on installing Erlang I am getting: asdf install erlang 23.1.2 Configure failed. checking ...
New
PragmaticBookshelf
Build highly interactive applications without ever leaving Elixir, the way the experts do. Let LiveView take care of performance, scalabi...
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
PragmaticBookshelf
Author Spotlight James Stanier @jstanier James Stanier, author of Effective Remote Work , discusses how to rethink the office as we e...
New
PragmaticBookshelf
Author Spotlight Mike Riley @mriley This month, we turn the spotlight on Mike Riley, author of Portable Python Projects. Mike’s book ...
New
AstonJ
If you’re getting errors like this: psql: error: connection to server on socket “/tmp/.s.PGSQL.5432” failed: No such file or directory ...
New

Latest in Functional Programming in Java, Second Edition

Functional Programming in Java, Second Edition Portal

Sub Categories: