Skip to content

Commit 8b69fa7

Browse files
committed
refactor!: implementation of KVA.createReadData
* add LazyRead interface, * add HttpLazyRead, FileLazyRead * add KeyValueAccessReadData using LazyRead
1 parent a90db72 commit 8b69fa7

4 files changed

Lines changed: 157 additions & 42 deletions

File tree

src/main/java/org/janelia/saalfeldlab/n5/FileSystemKeyValueAccess.java

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@
8686
import java.util.Iterator;
8787
import java.util.stream.Stream;
8888

89+
import org.janelia.saalfeldlab.n5.readdata.KeyValueAccessReadData;
90+
import org.janelia.saalfeldlab.n5.readdata.LazyRead;
8991
import org.janelia.saalfeldlab.n5.readdata.ReadData;
9092

9193
/**
@@ -210,7 +212,7 @@ public FileSystemKeyValueAccess(final FileSystem fileSystem) {
210212

211213
@Override
212214
public ReadData createReadData(final String normalPath) {
213-
return new FileLazyReadData(this, normalPath, 0, -1);
215+
return new KeyValueAccessReadData(new FileLazyRead(normalPath));
214216
}
215217

216218
@Override
@@ -620,39 +622,45 @@ protected static void createAndCheckIsDirectory(
620622
}
621623
}
622624

623-
private static class FileLazyReadData extends KeyValueAccessLazyReadData<FileSystemKeyValueAccess> {
625+
private class FileLazyRead implements LazyRead {
624626

625-
public FileLazyReadData(FileSystemKeyValueAccess kva, String normalKey, long offset, long length) {
626-
super(kva, normalKey, offset, length);
627-
}
627+
private final String normalKey;
628628

629-
@Override
630-
void read() throws N5IOException {
629+
FileLazyRead(String normalKey) {
630+
this.normalKey = normalKey;
631+
}
631632

632-
try (FileChannel channel = kva.lockForReading(normalKey).getFileChannel()) {
633-
channel.position(offset);
634-
if (length > Integer.MAX_VALUE)
635-
throw new IOException("Attempt to materialize too large data");
633+
@Override
634+
public long size() {
635+
return FileSystemKeyValueAccess.this.size(normalKey);
636+
}
636637

637-
final long channelSize = channel.size();
638-
if( !validBounds(channelSize, offset, length))
639-
throw new IndexOutOfBoundsException();
638+
@Override
639+
public ReadData materialize(final long offset, final long length) {
640640

641-
final int sz = (int)(length < 0 ? channelSize : length);
642-
final byte[] data = new byte[sz];
643-
final ByteBuffer buf = ByteBuffer.wrap(data);
644-
channel.read(buf);
645-
materialized = ReadData.from(data);
641+
try (final LockedFileChannel lfs = new LockedFileChannel(normalKey, true)) {
642+
final FileChannel channel = lfs.getFileChannel();
643+
channel.position(offset);
644+
if (length > Integer.MAX_VALUE)
645+
throw new IOException("Attempt to materialize too large data");
646646

647-
} catch (final IOException e) {
648-
throw new N5Exception.N5IOException(e);
649-
}
650-
}
647+
final long channelSize = channel.size();
648+
if (!validBounds(channelSize, offset, length))
649+
throw new IndexOutOfBoundsException();
650+
651+
final int sz = (int) (length < 0 ? channelSize : length);
652+
final byte[] data = new byte[sz];
653+
final ByteBuffer buf = ByteBuffer.wrap(data);
654+
channel.read(buf);
655+
return ReadData.from(data);
656+
657+
} catch (final NoSuchFileException e) {
658+
throw new N5NoSuchKeyException("No such file", e);
659+
} catch (IOException | UncheckedIOException e) {
660+
throw new N5Exception.N5IOException(e);
661+
}
662+
}
651663

652-
@Override
653-
KeyValueAccessLazyReadData<FileSystemKeyValueAccess> lazySlice(long offset, long length) {
654-
return new FileLazyReadData(kva, normalKey, offset, length);
655-
}
656664
}
657665

658666
private static boolean validBounds(long channelSize, long offset, long length) {

src/main/java/org/janelia/saalfeldlab/n5/HttpKeyValueAccess.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.apache.commons.lang3.function.TriFunction;
3434
import org.janelia.saalfeldlab.n5.N5Exception.N5IOException;
3535
import org.janelia.saalfeldlab.n5.http.ListResponseParser;
36+
import org.janelia.saalfeldlab.n5.readdata.KeyValueAccessReadData;
37+
import org.janelia.saalfeldlab.n5.readdata.LazyRead;
3638
import org.janelia.saalfeldlab.n5.readdata.ReadData;
3739

3840
import java.io.Closeable;
@@ -231,8 +233,8 @@ private HttpURLConnection httpRequest(String normalPath, String method) throws I
231233
}
232234

233235
@Override
234-
public HttpLazyReadData createReadData(final String normalPath) {
235-
return new HttpLazyReadData(this, normalPath, 0, -1);
236+
public ReadData createReadData(final String normalPath) {
237+
return new KeyValueAccessReadData(new HttpLazyRead(normalPath));
236238
}
237239

238240
public LockedChannel lockForReading(final String normalPath) throws N5IOException {
@@ -427,28 +429,29 @@ public void close() throws IOException {
427429
}
428430
}
429431

430-
private class HttpLazyReadData extends KeyValueAccessLazyReadData<HttpKeyValueAccess> {
432+
private class HttpLazyRead implements LazyRead {
431433

432-
public HttpLazyReadData(HttpKeyValueAccess kva, String normalKey, long offset, long length) {
433-
super(kva, normalKey, offset, length);
434+
private final String normalKey;
435+
436+
HttpLazyRead(String normalKey) {
437+
this.normalKey = normalKey;
438+
}
439+
440+
@Override
441+
public long size() {
442+
return HttpKeyValueAccess.this.size(normalKey);
434443
}
435444

436445
@Override
437-
void read() throws N5IOException {
438-
// TODO does this throw out-of-bounds when it should
439-
try( final HttpObjectChannel ch = new HttpObjectChannel(kva.uri(normalKey), offset, length) ) {
440-
materialized = ReadData.from(ch.newInputStream()).materialize();
446+
public ReadData materialize(long offset, long length) {
447+
try (final HttpObjectChannel ch = new HttpObjectChannel(uri(normalKey), offset, length)) {
448+
return ReadData.from(ch.newInputStream()).materialize();
441449
} catch (IOException e) {
442-
throw new N5Exception.N5IOException(e);
450+
throw new N5IOException(e);
443451
} catch (URISyntaxException e) {
444452
throw new N5Exception(e);
445453
}
446454
}
447-
448-
@Override
449-
KeyValueAccessLazyReadData<HttpKeyValueAccess> lazySlice(long offset, long length) {
450-
return new HttpLazyReadData(kva, normalKey, offset, length);
451-
}
452455
}
453456

454457
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.janelia.saalfeldlab.n5.readdata;
2+
3+
import java.io.InputStream;
4+
5+
import org.janelia.saalfeldlab.n5.N5Exception.N5IOException;
6+
7+
public class KeyValueAccessReadData implements ReadData {
8+
9+
private final LazyRead lazyRead;
10+
private ReadData materialized;
11+
private final long offset;
12+
private long length;
13+
14+
public KeyValueAccessReadData(LazyRead lazyRead) {
15+
this(lazyRead, 0, -1);
16+
}
17+
18+
public KeyValueAccessReadData(final LazyRead lazyRead, final long offset, final long length) {
19+
this.lazyRead = lazyRead;
20+
this.offset = offset;
21+
this.length = length;
22+
}
23+
24+
@Override
25+
public ReadData materialize() throws N5IOException {
26+
if (materialized == null)
27+
materialized = lazyRead.materialize(offset, length);
28+
return materialized;
29+
}
30+
31+
@Override
32+
public ReadData slice(final long offset, final long length) throws N5IOException {
33+
if (offset < 0)
34+
throw new IndexOutOfBoundsException("Negative offset: " + offset);
35+
36+
if (materialized != null)
37+
return materialized.slice(offset, length);
38+
39+
// if a slice of indeterminate length is requested, but the
40+
// length is already known, use the known length;
41+
final int lengthArg;
42+
if (this.length > 0 && length < 0)
43+
lengthArg = (int)(this.length - offset);
44+
else
45+
lengthArg = (int)length;
46+
47+
return new KeyValueAccessReadData(lazyRead, this.offset + offset, lengthArg);
48+
}
49+
50+
@Override
51+
public InputStream inputStream() throws N5IOException, IllegalStateException {
52+
return materialize().inputStream();
53+
}
54+
55+
@Override
56+
public byte[] allBytes() throws N5IOException, IllegalStateException {
57+
return materialize().allBytes();
58+
}
59+
60+
@Override
61+
public long length() throws N5IOException {
62+
if (materialized != null)
63+
return materialized.length();
64+
if (length < 0) {
65+
length = lazyRead.size() - offset;
66+
}
67+
return length;
68+
}
69+
70+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.janelia.saalfeldlab.n5.readdata;
2+
3+
/**
4+
* A lazy reading strategy for lazy, partial reading of data from some source.
5+
* <p>
6+
* Implementations of this interface handle the specifics of accessing data from
7+
* their respective sources.
8+
*
9+
* @see ReadData
10+
* @see KeyValueAccessReadData
11+
*/
12+
public interface LazyRead {
13+
14+
/**
15+
* Materializes a portion of the data into a concrete {@link ReadData} instance.
16+
* <p>
17+
* This method performs the actual read operation from the underlying source,
18+
* loading only the requested portion of data. The implementation should handle
19+
* bounds checking and throw appropriate exceptions for invalid ranges.
20+
*
21+
* @param offset the starting position in the data source
22+
* @param length the number of bytes to read, or -1 to read from offset to end
23+
* @return a materialized {@link ReadData} instance containing the requested data
24+
*/
25+
ReadData materialize(long offset, long length);
26+
27+
/**
28+
* Returns the total size of the data source in bytes.
29+
*
30+
* @return the size of the data source in bytes
31+
*/
32+
long size();
33+
34+
}

0 commit comments

Comments
 (0)