Skip to content

Commit

Permalink
Added Directory and RegularFile facades for convenient file operations.
Browse files Browse the repository at this point in the history
  • Loading branch information
renelink committed Apr 21, 2024
1 parent cf181de commit 02cfa98
Show file tree
Hide file tree
Showing 10 changed files with 670 additions and 0 deletions.
Empty file added lis-commons-io/README.md
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.link_intersystems.io;

import java.io.IOException;

/**
* An {@link java.util.function.Consumer} like interface for I/O related stuff.
*
* @param <T> an I/O type that might raise an {@link IOException}.
*/
@FunctionalInterface
public interface IOConsumer<T> {

/**
* An {@link IOConsumer} that does nothing (<b>no</b> <b>op</b>eration).
*
* @param <T>
* @return
*/
public static <T> IOConsumer<T> noop() {
return t -> {
};
}

public void accept(T t) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.link_intersystems.io.file;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

import static java.util.Objects.requireNonNull;

public abstract class AbstractFile {

private Path path;

protected AbstractFile(Path path) {
this.path = requireNonNull(path);
}

/**
* @return the {@link Path} of this {@link AbstractFile}.
*/
public Path getPath() {
return path;
}

/**
* Creates this file. This method does nothing, if the file already exists.
*
* @throws IOException
*/
public abstract void create() throws IOException;

/**
* @return the parent file of this file if any or <code>null</code>.
* @throws IOException
*/
public abstract AbstractFile getParent();

public boolean exists() {
return Files.exists(getPath());
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractFile that = (AbstractFile) o;
return Objects.equals(getPath(), that.getPath());
}

@Override
public int hashCode() {
return Objects.hash(getPath());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.link_intersystems.io.file;

import com.link_intersystems.io.IOConsumer;

import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class Directory extends AbstractFile {

/**
* Creates a {@link Directory} based on the given {@link File}.
*/
public Directory(File dir) {
this(dir.toPath());
}

/**
* Creates a {@link Directory} based on the given dirpath.
*
* @param dirpath the path of this {@link Directory}.
*/
public Directory(Path dirpath) {
super(dirpath);
}

/**
* {@inheritDoc}
* <p>
* If this directory does not exist, all parent directories that also do not exist
* will be created as well.
*
* @throws IOException
*/
@Override
public void create() throws IOException {
Files.createDirectories(getPath());
}

@Override
public Directory getParent() {
Path parent = getPath().getParent();

if (parent != null) {
return new Directory(parent);
}

return null;
}

/**
* @param directoryRelativePath
* @return the {@link RegularFile} of the given path relative to this directory.
* @throws IOException
*/
public RegularFile file(String directoryRelativePath) throws IOException {
return file(Paths.get(directoryRelativePath));
}

/**
* @param directoryRelativePath
* @return the {@link RegularFile} of the given path relative to this directory.
* @throws IOException
*/
public RegularFile file(Path directoryRelativePath) throws IOException {
return new RegularFile(getPath().resolve(directoryRelativePath));
}

/**
* @return all regular files in this directory.
* @throws IOException
*/
public List<RegularFile> listFiles() throws IOException {
return listFiles(path -> true);
}

/**
* @return all regular files in this directory that match the given file filter.
* @throws IOException
*/
public List<RegularFile> listFiles(DirectoryStream.Filter<Path> fileFilter) throws IOException {
List<RegularFile> files = new ArrayList<>();
forEachFiles(files::add, fileFilter);
return files;
}

/**
* Invokes the given {@link IOConsumer} for each {@link RegularFile} in this directory.
*
* @throws IOException
*/
public void forEachFiles(IOConsumer<RegularFile> fileConsumer) throws IOException {
forEachFiles(fileConsumer, path -> true);
}

/**
* Invokes the given {@link IOConsumer} for each {@link RegularFile} in this directory
* that match the given file filter.
*
* @throws IOException
*/
public void forEachFiles(IOConsumer<RegularFile> fileConsumer, DirectoryStream.Filter<Path> fileFilter) throws IOException {
DirectoryStream.Filter<Path> filter = path -> Files.isRegularFile(path) && fileFilter.accept(path);

try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(getPath(), filter)) {
for (Path path : directoryStream) {
RegularFile regularFile = new RegularFile(path);
fileConsumer.accept(regularFile);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.link_intersystems.io.file;

import com.link_intersystems.io.IOConsumer;

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardOpenOption.*;

public class RegularFile extends AbstractFile {


/**
* Creates a {@link RegularFile} based on the given {@link File}.
*/
public RegularFile(File file) {
this(file.toPath());
}

/**
* Creates a {@link RegularFile} based on the given filepath.
*
* @param filepath the path of this {@link RegularFile}.
*/
public RegularFile(Path filepath) {
super(filepath);
}

/**
* {@inheritDoc}
* <p>
* If the file is created it will be empty.
* <p>
* You do not have to call this method before any
* {@link #write(IOConsumer, Charset)} or {@link #append(IOConsumer, Charset)}
* invocation, because these methods will also create this file if it does not exist.
*
* @throws IOException
*/
@Override
public void create() throws IOException {
append(IOConsumer.noop());
}

@Override
public Directory getParent() {
Path parent = getPath().getParent();

if (parent != null) {
return new Directory(parent);
}

return null;
}

/**
* Writes the content provided by the {@link Appendable} to this {@link RegularFile} using {@link java.nio.charset.StandardCharsets#UTF_8}.
*
* @see #write(IOConsumer, Charset)
*/
public void write(IOConsumer<Appendable> contentWriter) throws IOException {
write(contentWriter, UTF_8);
}

/**
* Writes the content provided by the {@link Appendable} to this {@link RegularFile} using the specified {@link Charset}.
*
* <ul>
* <li>If parent directories do not exist, they will be created.</li>
* <li>If the file does not exist, it will be created.</li>
* <li>If the file already exists, it will be overwritten.</li>
* <li>If the file is an existent directory, an {@link IOException} is raised.</li>
* </ul>
* <p>
* This method can also be used to create an empty file
*
* <pre>
* RegularFile regularFile = ...;
* regularFile.write({@link IOConsumer#noop()});
* </pre>
*
* @param contentWriter an {@link Appendable} {@link IOConsumer} used to write the content of this file.
* @throws IOException if the file is an existent directory or if the content could not be written.
*/
public void write(IOConsumer<Appendable> contentWriter, Charset charset) throws IOException {
ensureParentDirs();

try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(Files.newOutputStream(getPath(), CREATE, TRUNCATE_EXISTING), charset))) {
contentWriter.accept(pw);
}
}

private void ensureParentDirs() throws IOException {
Path filePath = getPath();
Path parentPath = filePath.getParent();
if (parentPath != null && !Files.exists(parentPath)) {
Files.createDirectories(parentPath);
}
}

public void append(IOConsumer<Appendable> contentWriter) throws IOException {
append(contentWriter, UTF_8);
}

/**
* Appends content provided by the {@link Appendable} to this {@link RegularFile} using the specified {@link Charset}.
*
* <ul>
* <li>If parent directories do not exist, they will be created.</li>
* <li>If the file does not exist, it will be created and the content will be appended.</li>
* <li>If the file already exists, the content will be appended.</li>
* <li>If the file is an existent directory an {@link IOException} is raised.</li>
* </ul>
*
* @param contentWriter an {@link Appendable} {@link IOConsumer} used to append to the content of this file.
* @throws IOException if the file is an existent directory or if the content could not be appended.
*/
public void append(IOConsumer<Appendable> contentWriter, Charset charset) throws IOException {
ensureParentDirs();

try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(Files.newOutputStream(getPath(), CREATE, APPEND), charset))) {
contentWriter.accept(pw);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.link_intersystems.io.file;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.*;

class AbstractFileTest {

private static class TestFile extends AbstractFile {

protected TestFile(Path path) {
super(path);
}

@Override
public void create() {

}

@Override
public AbstractFile getParent() {
return null;
}
}

private AbstractFile abstractFile;

@BeforeEach
void setUp(@TempDir Path tempDir) {
abstractFile = new TestFile(tempDir);
}

@Test
void exists() {
assertTrue(abstractFile.exists());
}

@Test
void testEquals(@TempDir Path unequalPath) {
TestFile equal = new TestFile(abstractFile.getPath());
TestFile unequal = new TestFile(unequalPath);

assertEquals(abstractFile, equal);
assertEquals(equal, abstractFile);
assertNotEquals(equal, unequal);
assertNotEquals(equal, null);
assertNotEquals(equal, "");
}

@Test
void testHashCode() {
TestFile actual = new TestFile(abstractFile.getPath());

assertEquals(abstractFile.hashCode(), actual.hashCode());
}
}
Loading

0 comments on commit 02cfa98

Please sign in to comment.