Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion lis-commons-io/pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lis-commons</artifactId>
<groupId>com.link-intersystems.commons</groupId>
Expand All @@ -12,4 +13,12 @@
<name>LIS Commons IO</name>
<description>Link Intersystems Commons IO (lis-commons-io) provides utilities for java.io.</description>

<dependencies>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.link_intersystems.io;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;

import static java.nio.file.Files.newByteChannel;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.WRITE;

/**
* A helper factory for creating directory structures.
*/
public class FileBuilder {

public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private final Path dirpath;

/**
* @param dirpath the directory path that this {@link FileBuilder} operates on.
*/
public FileBuilder(Path dirpath) {
if (!Files.isDirectory(dirpath)) {
throw new IllegalArgumentException(dirpath + " is not a directory.");
}
this.dirpath = dirpath;
}

public Path getDirpath() {
return dirpath;
}

/**
* Creates an empty file.
*
* @param name
* @return the path of the written file.
* @throws IOException if the file already exists.
*/
public Path createFile(String name) throws IOException {

return createFile(name, Channels.newChannel(new ByteArrayInputStream(EMPTY_BYTE_ARRAY)));
}

/**
* Writes a file of the given name with the content provided by the {@link ReadableByteChannel}.
* Use {@link Channels#newChannel(InputStream)} if you want to use an {@link InputStream} instead.
*
* @param name
* @param content
* @throws IOException if the file exists.
*/
public Path createFile(String name, ReadableByteChannel content) throws IOException {
return createFile(name, IOConsumers.readableChannelCopyConsumer(content));
}

/**
* Writes a file of the given name, the content is provided by the {@link IOConsumer<WritableByteChannel>} callback.
* Use {@link IOConsumers#adaptOutputStream(IOConsumer)} if you want to use an {@link java.io.OutputStream} instead.
*
* @param name
* @param writableChannelConsumer
* @throws IOException if the file exists.
*/
public Path createFile(String name, IOConsumer<WritableByteChannel> writableChannelConsumer) throws IOException {
Path filepath = getDirpath().resolve(name);

try (WritableByteChannel writableChannel = newByteChannel(filepath, CREATE_NEW, WRITE)) {
writableChannelConsumer.accept(writableChannel);
}

return filepath;
}

public FileBuilder mkdir(String name) throws IOException {
Path newDirpath = dirpath.resolve(name);
Files.createDirectories(newDirpath);
return new FileBuilder(newDirpath);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.link_intersystems.io;

import java.io.IOException;

/**
* An io-related {@link java.util.function.Consumer} api that supports {@link IOException}s.
*
* @param <T>
*/
public interface IOConsumer<T> {

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

import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;

/**
* Factory for creating {@link IOConsumer} adapters.
*/
public class IOConsumers {

/**
* Creates an {@link IOConsumer<WritableByteChannel>} adapter for an {@link IOConsumer<OutputStream>}.
*
* @param outputStreamIOConsumer
* @return
*/
public static IOConsumer<WritableByteChannel> adaptOutputStream(IOConsumer<OutputStream> outputStreamIOConsumer) {
return writer -> {
try (OutputStream outputStream = Channels.newOutputStream(writer)) {
outputStreamIOConsumer.accept(outputStream);
}
};
}

/**
* Creates an {@link IOConsumer<WritableByteChannel>} that will copy the given {@link ReadableByteChannel} to the {@link WritableByteChannel} when
* {@link IOConsumer#accept(Object)} is invoked. A direct {@link ByteBuffer} of size 8192 is used.
*
* @param readableByteChannel
*/
public static IOConsumer<WritableByteChannel> readableChannelCopyConsumer(ReadableByteChannel readableByteChannel) {
return readableChannelCopyConsumer(readableByteChannel, ByteBuffer.allocateDirect(8192));
}

/**
* Creates an {@link IOConsumer<WritableByteChannel>} that will copy the given {@link ReadableByteChannel} to the {@link WritableByteChannel} when
* {@link IOConsumer#accept(Object)} is invoked. The given {@link ByteBuffer} will be used for the copy process.
*
* @param readableByteChannel
* @param byteBuffer the {@link ByteBuffer} to use for the copy process.
*/
public static IOConsumer<WritableByteChannel> readableChannelCopyConsumer(ReadableByteChannel readableByteChannel, ByteBuffer byteBuffer) {
return contentWriter -> {
while (readableByteChannel.read(byteBuffer) != -1) {
byteBuffer.flip();
contentWriter.write(byteBuffer);
byteBuffer.flip();
}
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.link_intersystems.io;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;

/**
* An {@link InputStream} adapter for a {@link CharSequence}.Even though this class is based on the {@link CharSequence} interface,
* the is named {@link StringInputStream}, because almost noone would find it if it would be named CharSequenceInputStream.
*/
public class StringInputStream extends InputStream {
private CharBuffer charBuffer = CharBuffer.allocate(1);
private ByteBuffer byteBuffer = ByteBuffer.allocate(0);

private int pos = 0;
private CharSequence charSequence;

private Charset charset;
private int readLimitPos = -1;
private int resetPos = -1;

public StringInputStream(CharSequence charSequence) {
this(charSequence, UTF_8);
}

public StringInputStream(CharSequence charSequence, Charset charset) {
this.charSequence = requireNonNull(charSequence);
this.charset = requireNonNull(charset);
}

@Override
public boolean markSupported() {
return true;
}

@Override
public synchronized void mark(int readlimit) {
this.readLimitPos = pos + readlimit;
this.resetPos = pos;
}

@Override
public synchronized void reset() throws IOException {
if (readLimitPos == -1) {
throw new IOException("Stream not marked.");
}

if (pos > readLimitPos) {
throw new IOException("Read limit exceeded.");
}

pos = resetPos;
byteBuffer = ByteBuffer.allocate(0);
}

@Override
public int read() throws IOException {
try {
if (!byteBuffer.hasRemaining()) {
int charAt = readChar();
if (charAt == -1) {
return -1;
}

charBuffer.put((char) charAt);
charBuffer.flip();
byteBuffer = charset.encode(charBuffer);
charBuffer.flip();
}

return (int) byteBuffer.get();
} catch (NullPointerException e) {
throw new IOException("Stream closed.");
}
}

private int readChar() {
if (pos < charSequence.length()) {
return charSequence.charAt(pos++);
}
return -1;
}

@Override
public void close() throws IOException {
charSequence = null;
byteBuffer = null;
charBuffer = null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.link_intersystems.io;

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.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

class FileBuilderTest {

private static final String TEST_CONTENT = "abcdefghijklmnopqrstuvwxyzéßöäü";

private FileBuilder fileBuilder;
private Path tempDirPath;

@BeforeEach
void setUp(@TempDir Path tempDirPath) {
this.tempDirPath = tempDirPath;

fileBuilder = new FileBuilder(tempDirPath);

assertEquals(tempDirPath, fileBuilder.getDirpath());

}

@Test
void dirpathIsNotADirectory() throws IOException {
Path test = tempDirPath.resolve("test");
Path filepath = Files.createFile(test);

assertThrows(IllegalArgumentException.class, () -> new FileBuilder(filepath));
}

@Test
void createEmptyFile() throws IOException {
Path file = fileBuilder.createFile("test");

assertEquals(tempDirPath.resolve("test"), file);
assertThat(file).hasFileName("test");
assertThat(file).content(UTF_8).isEmpty();
}

@Test
void createFile() throws IOException {
Path file = fileBuilder.createFile("test", writer -> {
writer.write(ByteBuffer.wrap("Hello World".getBytes(UTF_8)));
});

assertEquals(tempDirPath.resolve("test"), file);
assertThat(file).hasFileName("test");
assertThat(file).content(UTF_8).isEqualTo("Hello World");
}

@Test
void createDirectory() throws IOException {
FileBuilder dir1 = fileBuilder.mkdir("dir1");

assertNotNull(dir1);
Path expectedDirpath = tempDirPath.resolve("dir1");

assertEquals(expectedDirpath, dir1.getDirpath());
}
}
Loading