From fc5f296eeb67b924285424ddf34275da1180a07f Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sat, 3 Dec 2022 18:20:50 +0000 Subject: [PATCH] Make Mount.openForRead always return a SeekableByteChannel I want to make some further changes to Mount here, but this helps simplify BinaryReadableHandle a little. --- .../computercraft/api/filesystem/Mount.java | 10 +-- .../apis/handles/BinaryReadableHandle.java | 89 ++++++++----------- .../core/filesystem/ArchiveMount.java | 4 +- .../core/filesystem/FileMount.java | 2 +- .../core/filesystem/FileSystem.java | 4 +- .../core/filesystem/MountWrapper.java | 4 +- .../core/filesystem/SubMount.java | 4 +- .../test/core/filesystem/MemoryMount.java | 4 +- 8 files changed, 53 insertions(+), 68 deletions(-) diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/filesystem/Mount.java b/projects/core-api/src/main/java/dan200/computercraft/api/filesystem/Mount.java index 7008f2c20..97bd83e02 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/filesystem/Mount.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/filesystem/Mount.java @@ -8,7 +8,7 @@ package dan200.computercraft.api.filesystem; import dan200.computercraft.api.peripheral.IComputerAccess; import java.io.IOException; -import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; import java.nio.file.attribute.BasicFileAttributes; import java.util.List; @@ -60,15 +60,13 @@ public interface Mount { long getSize(String path) throws IOException; /** - * Opens a file with a given path, and returns an {@link ReadableByteChannel} representing its contents. + * Opens a file with a given path, and returns a {@link SeekableByteChannel} representing its contents. * * @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram". - * @return A channel representing the contents of the file. If the channel implements - * {@link java.nio.channels.SeekableByteChannel}, one will be able to seek to arbitrary positions when using binary - * mode. + * @return A channel representing the contents of the file. * @throws IOException If the file does not exist, or could not be opened. */ - ReadableByteChannel openForRead(String path) throws IOException; + SeekableByteChannel openForRead(String path) throws IOException; /** * Get attributes about the given file. diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java b/projects/core/src/main/java/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java index 20f2f4eb2..ead17be38 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java @@ -8,13 +8,11 @@ package dan200.computercraft.core.apis.handles; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.core.filesystem.TrackingCloseable; -import dan200.computercraft.core.util.Nullability; import javax.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; import java.util.ArrayList; import java.util.List; @@ -29,22 +27,19 @@ import java.util.Optional; public class BinaryReadableHandle extends HandleGeneric { private static final int BUFFER_SIZE = 8192; - private final ReadableByteChannel reader; - final @Nullable SeekableByteChannel seekable; + private final SeekableByteChannel channel; private final ByteBuffer single = ByteBuffer.allocate(1); - BinaryReadableHandle(ReadableByteChannel reader, @Nullable SeekableByteChannel seekable, TrackingCloseable closeable) { + BinaryReadableHandle(SeekableByteChannel channel, TrackingCloseable closeable) { super(closeable); - this.reader = reader; - this.seekable = seekable; + this.channel = channel; } - public static BinaryReadableHandle of(ReadableByteChannel channel, TrackingCloseable closeable) { - var seekable = asSeekable(channel); - return seekable == null ? new BinaryReadableHandle(channel, null, closeable) : new Seekable(seekable, closeable); + public static BinaryReadableHandle of(SeekableByteChannel channel, TrackingCloseable closeable) { + return new BinaryReadableHandle(channel, closeable); } - public static BinaryReadableHandle of(ReadableByteChannel channel) { + public static BinaryReadableHandle of(SeekableByteChannel channel) { return of(channel, new TrackingCloseable.Impl(channel)); } @@ -69,21 +64,19 @@ public class BinaryReadableHandle extends HandleGeneric { if (countArg.isPresent()) { int count = countArg.get(); if (count < 0) throw new LuaException("Cannot read a negative number of bytes"); - if (count == 0 && seekable != null) { - return seekable.position() >= seekable.size() ? null : new Object[]{ "" }; - } + if (count == 0) return channel.position() >= channel.size() ? null : new Object[]{ "" }; if (count <= BUFFER_SIZE) { var buffer = ByteBuffer.allocate(count); - var read = reader.read(buffer); + var read = channel.read(buffer); if (read < 0) return null; buffer.flip(); return new Object[]{ buffer }; } else { // Read the initial set of characters, failing if none are read. var buffer = ByteBuffer.allocate(BUFFER_SIZE); - var read = reader.read(buffer); + var read = channel.read(buffer); if (read < 0) return null; // If we failed to read "enough" here, let's just abort @@ -99,7 +92,7 @@ public class BinaryReadableHandle extends HandleGeneric { parts.add(buffer); while (read >= BUFFER_SIZE && totalRead < count) { buffer = ByteBuffer.allocate(Math.min(BUFFER_SIZE, count - totalRead)); - read = reader.read(buffer); + read = channel.read(buffer); if (read < 0) break; totalRead += read; @@ -117,7 +110,7 @@ public class BinaryReadableHandle extends HandleGeneric { } } else { single.clear(); - var b = reader.read(single); + var b = channel.read(single); return b == -1 ? null : new Object[]{ single.get(0) & 0xFF }; } } catch (IOException e) { @@ -139,14 +132,14 @@ public class BinaryReadableHandle extends HandleGeneric { checkOpen(); try { var expected = 32; - if (seekable != null) expected = Math.max(expected, (int) (seekable.size() - seekable.position())); + expected = Math.max(expected, (int) (channel.size() - channel.position())); var stream = new ByteArrayOutputStream(expected); var buf = ByteBuffer.allocate(8192); var readAnything = false; while (true) { buf.clear(); - var r = reader.read(buf); + var r = channel.read(buf); if (r == -1) break; readAnything = true; @@ -179,7 +172,7 @@ public class BinaryReadableHandle extends HandleGeneric { boolean readAnything = false, readRc = false; while (true) { single.clear(); - var read = reader.read(single); + var read = channel.read(single); if (read <= 0) { // Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it // back. @@ -211,35 +204,29 @@ public class BinaryReadableHandle extends HandleGeneric { } } - public static class Seekable extends BinaryReadableHandle { - Seekable(SeekableByteChannel seekable, TrackingCloseable closeable) { - super(seekable, seekable, closeable); - } - - /** - * Seek to a new position within the file, changing where bytes are written to. The new position is an offset - * given by {@code offset}, relative to a start position determined by {@code whence}: - *

- * - {@code "set"}: {@code offset} is relative to the beginning of the file. - * - {@code "cur"}: Relative to the current position. This is the default. - * - {@code "end"}: Relative to the end of the file. - *

- * In case of success, {@code seek} returns the new file position from the beginning of the file. - * - * @param whence Where the offset is relative to. - * @param offset The offset to seek to. - * @return The new position. - * @throws LuaException If the file has been closed. - * @cc.treturn [1] number The new position. - * @cc.treturn [2] nil If seeking failed. - * @cc.treturn string The reason seeking failed. - * @cc.since 1.80pr1.9 - */ - @Nullable - @LuaFunction - public final Object[] seek(Optional whence, Optional offset) throws LuaException { - checkOpen(); - return handleSeek(Nullability.assertNonNull(seekable), whence, offset); - } + /** + * Seek to a new position within the file, changing where bytes are written to. The new position is an offset + * given by {@code offset}, relative to a start position determined by {@code whence}: + *

+ * - {@code "set"}: {@code offset} is relative to the beginning of the file. + * - {@code "cur"}: Relative to the current position. This is the default. + * - {@code "end"}: Relative to the end of the file. + *

+ * In case of success, {@code seek} returns the new file position from the beginning of the file. + * + * @param whence Where the offset is relative to. + * @param offset The offset to seek to. + * @return The new position. + * @throws LuaException If the file has been closed. + * @cc.treturn [1] number The new position. + * @cc.treturn [2] nil If seeking failed. + * @cc.treturn string The reason seeking failed. + * @cc.since 1.80pr1.9 + */ + @Nullable + @LuaFunction + public final Object[] seek(Optional whence, Optional offset) throws LuaException { + checkOpen(); + return handleSeek(channel, whence, offset); } } diff --git a/projects/core/src/main/java/dan200/computercraft/core/filesystem/ArchiveMount.java b/projects/core/src/main/java/dan200/computercraft/core/filesystem/ArchiveMount.java index 019996c83..068629f51 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/filesystem/ArchiveMount.java +++ b/projects/core/src/main/java/dan200/computercraft/core/filesystem/ArchiveMount.java @@ -14,7 +14,7 @@ import dan200.computercraft.core.apis.handles.ArrayByteChannel; import javax.annotation.Nullable; import java.io.IOException; -import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; import java.nio.file.attribute.BasicFileAttributes; import java.util.List; import java.util.Map; @@ -110,7 +110,7 @@ public abstract class ArchiveMount> implemen protected abstract long getSize(T file) throws IOException; @Override - public ReadableByteChannel openForRead(String path) throws IOException { + public SeekableByteChannel openForRead(String path) throws IOException { var file = get(path); if (file == null || file.isDirectory()) throw new FileOperationException(path, NO_SUCH_FILE); diff --git a/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileMount.java b/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileMount.java index 41cc542f2..6e2cfc370 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileMount.java +++ b/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileMount.java @@ -182,7 +182,7 @@ public class FileMount implements WritableMount { } @Override - public ReadableByteChannel openForRead(String path) throws IOException { + public SeekableByteChannel openForRead(String path) throws IOException { if (created()) { var file = getRealPath(path); if (file.exists() && !file.isDirectory()) return FileChannel.open(file.toPath(), READ_OPTIONS); diff --git a/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java b/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java index 3eb163973..f19606f17 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java +++ b/projects/core/src/main/java/dan200/computercraft/core/filesystem/FileSystem.java @@ -18,7 +18,7 @@ import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.nio.channels.Channel; -import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.file.AccessDeniedException; import java.nio.file.attribute.BasicFileAttributes; @@ -346,7 +346,7 @@ public class FileSystem { } } - public synchronized FileSystemWrapper openForRead(String path, Function open) throws FileSystemException { + public synchronized FileSystemWrapper openForRead(String path, Function open) throws FileSystemException { cleanup(); path = sanitizePath(path); diff --git a/projects/core/src/main/java/dan200/computercraft/core/filesystem/MountWrapper.java b/projects/core/src/main/java/dan200/computercraft/core/filesystem/MountWrapper.java index 1135bc4d0..018c90763 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/filesystem/MountWrapper.java +++ b/projects/core/src/main/java/dan200/computercraft/core/filesystem/MountWrapper.java @@ -11,7 +11,7 @@ import dan200.computercraft.api.filesystem.WritableMount; import javax.annotation.Nullable; import java.io.IOException; -import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.file.AccessDeniedException; import java.nio.file.attribute.BasicFileAttributes; @@ -120,7 +120,7 @@ class MountWrapper { } } - public ReadableByteChannel openForRead(String path) throws FileSystemException { + public SeekableByteChannel openForRead(String path) throws FileSystemException { path = toLocal(path); try { if (mount.exists(path) && !mount.isDirectory(path)) { diff --git a/projects/core/src/main/java/dan200/computercraft/core/filesystem/SubMount.java b/projects/core/src/main/java/dan200/computercraft/core/filesystem/SubMount.java index f2f644416..b0044ec62 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/filesystem/SubMount.java +++ b/projects/core/src/main/java/dan200/computercraft/core/filesystem/SubMount.java @@ -8,7 +8,7 @@ package dan200.computercraft.core.filesystem; import dan200.computercraft.api.filesystem.Mount; import java.io.IOException; -import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; import java.nio.file.attribute.BasicFileAttributes; import java.util.List; @@ -42,7 +42,7 @@ public class SubMount implements Mount { } @Override - public ReadableByteChannel openForRead(String path) throws IOException { + public SeekableByteChannel openForRead(String path) throws IOException { return parent.openForRead(getFullPath(path)); } diff --git a/projects/core/src/testFixtures/java/dan200/computercraft/test/core/filesystem/MemoryMount.java b/projects/core/src/testFixtures/java/dan200/computercraft/test/core/filesystem/MemoryMount.java index f7b7e8feb..6b58f1648 100644 --- a/projects/core/src/testFixtures/java/dan200/computercraft/test/core/filesystem/MemoryMount.java +++ b/projects/core/src/testFixtures/java/dan200/computercraft/test/core/filesystem/MemoryMount.java @@ -13,7 +13,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; import java.nio.charset.StandardCharsets; import java.util.*; @@ -111,7 +111,7 @@ public class MemoryMount implements WritableMount { } @Override - public ReadableByteChannel openForRead(String path) throws FileOperationException { + public SeekableByteChannel openForRead(String path) throws FileOperationException { var file = files.get(path); if (file == null) throw new FileOperationException(path, "File not found"); return new ArrayByteChannel(file);