Skip to content

Commit

Permalink
Added mkdir to FileBuilder.
Browse files Browse the repository at this point in the history
  • Loading branch information
renelink committed Jan 3, 2024
1 parent c6bd1f9 commit 10a7eae
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 90 deletions.
110 changes: 50 additions & 60 deletions lis-commons-io/src/main/java/com/link_intersystems/io/FileBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,93 +3,83 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

import static java.nio.charset.StandardCharsets.UTF_8;
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 {

private static final int EOF = -1;
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private int writeBufferSize = 8192;

public static interface ContentWriter<T> {

public void write(T writer) throws IOException;
}

private final Path path;

public FileBuilder(Path path) {
this.path = Objects.requireNonNull(path);
}

public Path getPath() {
return path;
}

public Path writeFile(String name) throws IOException {

return writeFileStream(name, new ByteArrayInputStream(EMPTY_BYTE_ARRAY));
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 writeFile(String name, String content) throws IOException {

return writeFile(name, content, UTF_8);
public Path getDirpath() {
return dirpath;
}

public Path writeFile(String name, String content, Charset charset) throws IOException {
/**
* 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 writeFileStream(name, new ByteArrayInputStream(content.getBytes(charset)));
return createFile(name, Channels.newChannel(new ByteArrayInputStream(EMPTY_BYTE_ARRAY)));
}

public Path writeFileStream(String name, InputStream content) throws IOException {
try(ReadableByteChannel readableByteChannel = Channels.newChannel(content)){
return writeFile(name, readableByteChannel);
}
/**
* 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));
}

public Path writeFile(String name, ReadableByteChannel content) throws IOException {
return writeFile(name, contentWriter -> {
ByteBuffer byteBuffer = createByteBuffer(writeBufferSize);
while (content.read(byteBuffer) != EOF) {
byteBuffer.flip();
contentWriter.write(byteBuffer);
byteBuffer.flip();
}
});
}

public Path writeFileStream(String name, ContentWriter<OutputStream> contentWriter) throws IOException {
return writeFile(name, writer -> {
try (OutputStream outputStream = Channels.newOutputStream(writer)) {
contentWriter.write(outputStream);
}
});
}

public Path writeFile(String name, ContentWriter<WritableByteChannel> contentWriter) throws IOException {
Path filepath = path.resolve(name);
/**
* 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)) {
contentWriter.write(writableChannel);
writableChannelConsumer.accept(writableChannel);
}

return filepath;
}

private ByteBuffer createByteBuffer(int size) {
return ByteBuffer.allocateDirect(size);
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;
}
}
Loading

0 comments on commit 10a7eae

Please sign in to comment.