Functional Programming in Java, Second Edition: Functional Programming in Java, Second Edition: JUnit code improvements for Chapter 11, pages 192 ff “Refactoring File Processing”

In this code:

  • We actually create the (temporary) file we want to read and delete it again at the end.
  • Default charsets for reading text files are the work of the devil, we set UTF-8 explicitly.
  • The files need to be properly closed with try-with-resources, this also applies to the stream approach.
  • A “long” count as return value seems excessive, dropping to int.
package chapter11; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.io.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; public class FileProcessingTest { private static File actualFile; private final static Charset charset = StandardCharsets.UTF_8; // Creates a file "/tmp/WordCount13982583023783245787.java" for example @BeforeAll static void createTmpFile() throws IOException { actualFile = File.createTempFile("WordCount", ".java"); try (FileWriter writer = new FileWriter(actualFile, charset)) { writer.write("package foo;\n"); writer.write("public class X {\n"); writer.write("}\n"); writer.write("public class Y {\n"); writer.write("}\n"); } } @AfterAll static void deleteTmpFile() throws IOException { // or one could have installed a handler with deleteOnExit() actualFile.delete(); } interface WordCount { int countInFile(String word, File file) throws IOException; } // https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/io/FileReader.html // https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/io/BufferedReader.html static class WordCountBefore implements WordCount { public int countInFile(final String searchWord, final File file) throws IOException { int count = 0; // Use try-with-resources / automatic resource management to clean up. // Specify the charset, "default chartset" is the devil's work! try (final var bufferedReader = new BufferedReader(new FileReader(file, charset))) { String line; while ((line = bufferedReader.readLine()) != null) { final String[] words = line.split(" "); for (String word : words) { if (word.equals(searchWord)) { count++; } } } return count; } } } // https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/nio/file/Files.html static class WordCountAfter implements WordCount { // The Files.lines() call may throw IOException. // Additionally, the count() this yields a "long", so we need to cast to fit the interface. // We still must use try-with-resources to close the file. public int countInFile(final String searchWord, final File file) throws IOException { try (final Stream<String> stream = Files.lines(file.toPath(), charset)) { return (int) stream .flatMap(line -> Stream.of(line.split(" "))) .filter(word -> word.equals(searchWord)) .count(); } } } private static void commonFileProcessingTests(final WordCount wordCount, File file) { assertAll( () -> assertEquals(2, wordCount.countInFile("public", file)), () -> assertEquals(1, wordCount.countInFile("package", file))); } @Test void fileProcessingBefore() { commonFileProcessingTests(new WordCountBefore(), actualFile); } @Test void fileProcessingAfter() { commonFileProcessingTests(new WordCountAfter(), actualFile); } }