Skip to content

Commit a5cdcaa

Browse files
committed
Add SegmentedReadData
This wraps ReadData and annotates ranges as Segments that are tracked through slicing and concatenating pieces of ReadData. This will be useful for assembling serialized DataBlocks into shards.
1 parent 2e1a2b6 commit a5cdcaa

6 files changed

Lines changed: 1031 additions & 0 deletions

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package org.janelia.saalfeldlab.n5.readdata.segment;
2+
3+
import java.io.InputStream;
4+
import java.io.OutputStream;
5+
import java.nio.ByteBuffer;
6+
import java.util.ArrayList;
7+
import java.util.Collections;
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
import org.janelia.saalfeldlab.n5.N5Exception.N5IOException;
12+
import org.janelia.saalfeldlab.n5.readdata.Range;
13+
import org.janelia.saalfeldlab.n5.readdata.ReadData;
14+
15+
/**
16+
* Implementation of a {@link SegmentedReadData} representing the concatenation
17+
* of several {@code SegmentedReadData}s.
18+
* <p>
19+
* {@code ConcatenatedReadData} contains the segments of all concatenated {@code
20+
* SegmentedReadData}s with appropriately offset locations.
21+
* <p>
22+
* In particular, it is also possible to concatenate {@code SegmentedReadData}s
23+
* with (yet) unknown length. (This is useful for postponing compression of
24+
* DataBlocks until they are actually written.) In that case, segment locations
25+
* are only available, after all lengths become known. This happens when this
26+
* {@code ConcatenatedReadData} (or all its constituents) is
27+
* {@link #materialize() materialized} or {@link #writeTo(OutputStream) written}.
28+
*/
29+
class ConcatenatedReadData implements SegmentedReadData {
30+
31+
private final List<SegmentedReadData> content;
32+
private final ReadData delegate;
33+
private final List<Segment> segments;
34+
private final List<Range> locations;
35+
private final Map<Segment, Range> segmentToLocation;
36+
private boolean locationsBuilt;
37+
private long length;
38+
39+
ConcatenatedReadData(final List<SegmentedReadData> content) {
40+
this.content = content;
41+
delegate = ReadData.from(os -> content.forEach(d -> d.writeTo(os)));
42+
segments = new ArrayList<>();
43+
locations = new ArrayList<>();
44+
segmentToLocation = new HashMap<>();
45+
locationsBuilt = false;
46+
length = -1;
47+
}
48+
49+
// constructor for slices
50+
private ConcatenatedReadData(final ReadData delegate, final List<Segment> segments,
51+
final List<Range> locations) {
52+
content = null;
53+
this.delegate = delegate;
54+
this.segments = segments;
55+
this.locations = locations;
56+
segmentToLocation = new HashMap<>();
57+
for (int i = 0; i < segments.size(); i++) {
58+
segmentToLocation.put(segments.get(i), locations.get(i));
59+
}
60+
locationsBuilt = true;
61+
}
62+
63+
/**
64+
* Verify that all {@code content} elements have known length.
65+
* Builds {@code segments} and {@code locations} if they have not been built yet.
66+
*
67+
* @throws IllegalStateException if any of the concatenated ReadData don't know their length yet
68+
*/
69+
private void ensureKnownSize() throws IllegalStateException {
70+
if (!locationsBuilt) {
71+
long offset = 0;
72+
for (int i = 0; i < content.size(); i++) {
73+
final SegmentedReadData data = content.get(i);
74+
if (data.length() < 0) {
75+
throw new IllegalStateException("Some of concatenated ReadData don't know their length yet.");
76+
}
77+
segments.addAll(data.segments());
78+
for (Segment segment : data.segments()) {
79+
final Range l = data.location(segment);
80+
locations.add(Range.at(l.offset() + offset, l.length()));
81+
}
82+
offset += data.length();
83+
}
84+
length = offset;
85+
86+
for (int i = 0; i < segments.size(); i++) {
87+
segmentToLocation.put(segments.get(i), locations.get(i));
88+
}
89+
90+
locationsBuilt = true;
91+
}
92+
}
93+
94+
@Override
95+
public Range location(final Segment segment) throws IllegalArgumentException {
96+
ensureKnownSize();
97+
final Range location = segmentToLocation.get(segment);
98+
if (location == null) {
99+
throw new IllegalArgumentException();
100+
}
101+
return location;
102+
}
103+
104+
@Override
105+
public List<? extends Segment> segments() {
106+
return segments;
107+
}
108+
109+
@Override
110+
public long length() {
111+
if (length < 0) {
112+
length = 0;
113+
for (final ReadData data : content) {
114+
final long l = data.length();
115+
if (l < 0) {
116+
length = -1;
117+
break;
118+
}
119+
length += l;
120+
}
121+
}
122+
return length;
123+
}
124+
125+
@Override
126+
public long requireLength() throws N5IOException {
127+
if (length < 0) {
128+
length = 0;
129+
for (final ReadData data : content) {
130+
length += data.requireLength();
131+
}
132+
}
133+
return length;
134+
}
135+
136+
@Override
137+
public SegmentedReadData slice(final Segment segment) throws IllegalArgumentException, N5IOException {
138+
ensureKnownSize();
139+
final Range l = location(segment);
140+
return slice(l.offset(), l.length());
141+
}
142+
143+
@Override
144+
public SegmentedReadData slice(final long offset, final long length) throws N5IOException {
145+
ensureKnownSize();
146+
final ReadData delegateSlice = delegate.slice(offset, length);
147+
final long sliceLength = delegateSlice.length();
148+
149+
// fromIndex: find first segment with offset >= sourceOffset
150+
int fromIndex = Collections.binarySearch(locations, Range.at(offset, -1), Range.COMPARATOR);
151+
if (fromIndex < 0) {
152+
fromIndex = -fromIndex - 1;
153+
}
154+
155+
// toIndex: find first segment with offset >= sourceOffset + length
156+
int toIndex = Collections.binarySearch(locations, Range.at(offset + sliceLength, -1), Range.COMPARATOR);
157+
if (toIndex < 0) {
158+
toIndex = -toIndex - 1;
159+
}
160+
161+
// contained: find segments in [fromIndex, toIndex) with s.offset() + s.length() <= sourceOffset + length
162+
final List<Segment> containedSegments = new ArrayList<>();
163+
final List<Range> containedSegmentLocations = new ArrayList<>();
164+
for (int i = fromIndex; i < toIndex; ++i) {
165+
final Range l = locations.get(i);
166+
if (l.offset() + l.length() <= offset + sliceLength) {
167+
containedSegments.add(segments.get(i));
168+
containedSegmentLocations.add(Range.at(l.offset() - offset, l.length()));
169+
}
170+
}
171+
172+
return new ConcatenatedReadData(delegateSlice, containedSegments, containedSegmentLocations);
173+
}
174+
175+
@Override
176+
public InputStream inputStream() throws N5IOException, IllegalStateException {
177+
return delegate.inputStream();
178+
}
179+
180+
@Override
181+
public byte[] allBytes() throws N5IOException, IllegalStateException {
182+
return delegate.allBytes();
183+
}
184+
185+
@Override
186+
public ByteBuffer toByteBuffer() throws N5IOException, IllegalStateException {
187+
return delegate.toByteBuffer();
188+
}
189+
190+
@Override
191+
public SegmentedReadData materialize() throws N5IOException {
192+
delegate.materialize();
193+
return this;
194+
}
195+
196+
@Override
197+
public void writeTo(final OutputStream outputStream) throws N5IOException, IllegalStateException {
198+
delegate.writeTo(outputStream);
199+
}
200+
201+
@Override
202+
public ReadData encode(final OutputStreamOperator encoder) {
203+
return delegate.encode(encoder);
204+
}
205+
}

0 commit comments

Comments
 (0)