File: Chunk.java

package info (click to toggle)
libglazedlists-java 1.9.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 3,024 kB
  • ctags: 4,252
  • sloc: java: 22,561; xml: 818; sh: 51; makefile: 5
file content (375 lines) | stat: -rw-r--r-- 11,848 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl.pmap;

// NIO is used for CTP
import ca.odell.glazedlists.impl.io.Bufferlo;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

/**
 * A chunk of a file.
 *
 * @author <a href="mailto:jesse@swank.ca">Jesse Wilson</a>
 */
public final class Chunk {

    /** the host PersistentMap */
    private PersistentMap persistentMap = null;

    /** the offset of this chunk into the file */
    private int offset = -1;

    /** the current size of this chunk and previous/next size */
    private int[] size = new int[] { -1, -1 };
    /** whether the size 0 or size 1 */
    private int sizeToUse = -1;

    /** whether this chunk is on or off */
    private boolean on = false;

    /** the order in which this chunk was written to file */
    private int sequenceId = -1;

    /** the key for this chunk, should be immutable or treated as such */
    private Object key = null;
    private Bufferlo keyBytes = null;
    private int keyBytesLength = -1;

    /** the value for this chunk */
    private int valueBytesLength = -1;
    private Bufferlo valueBytes = null;

    /**
     * Creates a {@link Chunk} with the specified value.
     */
    public Chunk(Bufferlo value) {
        valueBytes = value.duplicate();
        valueBytesLength = valueBytes.length();
    }

    /**
     * Creates a {@link Chunk} from the specified data from disk.
     */
    private Chunk(PersistentMap persistentMap, int offset, boolean on, int sizeToUse, int[] size) {
        this.persistentMap = persistentMap;
        this.offset = offset;
        this.on = on;
        this.sizeToUse = sizeToUse;
        this.size = size;
    }

    /**
     * Fetches the value for this chunk and sends it to the specified ValueCallback.
     */
    public void fetchValue(ValueCallback valueCallback) {
        persistentMap.getNIODaemon().invokeLater(new LoadValue(this, valueCallback));
    }

    /**
     * Gets the value for this Chunk. Since the value may not be available immediately,
     * this method will block until it is available. For non-blocking access, use
     * the {@link #fetchValue(ValueCallback)} method.
     */
    public Bufferlo getValue() {
        return BlockingValueCallback.get(this);
    }

    /**
     * Gets the map that hosts this chunk.
     */
    PersistentMap getPersistentMap() {
        return persistentMap;
    }

    /**
     * Get the current size of this chunk.
     */
    int size() {
        return size[sizeToUse];
    }

    /**
     * Gets whether this Chunk contains active data.
     */
    boolean isOn() {
        return on;
    }

    /**
     * Get the offset of this chunk.
     */
    public int getOffset() {
        return offset;
    }

    /**
     * Gets the key that indexes this chunk.
     */
    Object getKey() {
        return key;
    }

    /**
     * Sets the ID in which this chunk was created.
     */
    void setSequenceId(int sequenceId) {
        this.sequenceId = sequenceId;
    }

    /**
     * Get the sequence of this chunk.
     */
    int getSequenceId() {
        return sequenceId;
    }

    /**
     * Sets up how this chunk will be persisted.
     */
    void initializeForPersistence(PersistentMap persistentMap, Object key) {
        // we can't reuse chunks (yet)
        if(this.persistentMap != null) throw new IllegalStateException("doubly-initialized chunk");

        // save the host
        this.persistentMap = persistentMap;

        // save the key and take a binary snapshot of it
        this.key = key;
        try {
            keyBytes = new Bufferlo();
            persistentMap.getKeyCoder().encode(key, keyBytes.getOutputStream());
            keyBytesLength = keyBytes.length();
        } catch(IOException e) {
            persistentMap.fail(e, "Unexpected encoding exception");
        }
    }

    /**
     * Get the number of bytes required to write out this chunk.
     */
    int bytesRequired() {
        int required = 0;

        // header
        required += 4; // on/off
        required += 4; // sizeToUse
        required += 4; // size one
        required += 4; // size two

        // body
        required += 4; // sequence ID
        required += 4; // key size
        required += 4; // value size
        required += keyBytesLength; // key
        required += valueBytesLength; // value

        return required;
    }

    /**
     * Set the size of this Chunk. This writes the new size to disk. This requires
     * 1 flush to disk.
     */
    void allocateAsNew(int offset, int size) throws IOException {
        this.offset = offset;
        this.sizeToUse = 0;
        this.size[0] = size;
        this.size[1] = size;
        this.on = false;

        // write the size
        FileChannel fileChannel = persistentMap.getFileChannel();
        Bufferlo sizeData = new Bufferlo();
        DataOutputStream sizeDataOut = new DataOutputStream(sizeData.getOutputStream());

        sizeDataOut.writeInt(0); // on == false
        sizeDataOut.writeInt(sizeToUse);
        sizeDataOut.writeInt(this.size[0]);
        sizeDataOut.writeInt(this.size[1]);
        sizeData.writeToChannel(fileChannel.position(offset));
        fileChannel.force(false);
    }

    /**
     * Deletes this Chunk. This simply marks the Chunk's on value to off.
     */
    void delete() throws IOException {
        assert(offset != -1);
        if(!on) return;

        // turn the chunk off
        this.on = false;

        // write that to disk
        FileChannel fileChannel = persistentMap.getFileChannel();
        Bufferlo sizeData = new Bufferlo();
        DataOutputStream sizeDataOut = new DataOutputStream(sizeData.getOutputStream());

        sizeDataOut.writeInt(0); // on == false
        sizeData.writeToChannel(fileChannel.position(offset));
        fileChannel.force(false);
     }

    /**
     * Reads the chunk into memory.
     */
    static Chunk readChunk(PersistentMap persistentMap) throws IOException {
        // prepare to read
        FileChannel fileChannel = persistentMap.getFileChannel();
        Bufferlo sizeData = new Bufferlo();
        DataInputStream dataIn = new DataInputStream(sizeData.getInputStream());

        // read the header
        int offset = (int)fileChannel.position();
        int bytesRequired = 16;
        int read = sizeData.readFromChannel(fileChannel, bytesRequired);
        if(read < 0) return null;
        else if(read < bytesRequired) throw new IOException("Insufficent bytes available");

        // parse the header
        int on = dataIn.readInt();
        int sizeToUse = dataIn.readInt();
        int size[] = new int[] { -1 , -1 };
        size[0] = dataIn.readInt();
        size[1] = dataIn.readInt();

        // validate the header data
        if(on != 0 && on != 1) throw new IOException("Unexpected on value: " + on);
        if(sizeToUse != 0 && sizeToUse != 1) throw new IOException("Unexpected size to use value " + sizeToUse);
        if(size[sizeToUse] < 0) throw new IOException("Unexpected size: " + size[sizeToUse]);

        // header success
        Chunk chunk = new Chunk(persistentMap, offset, (on == 1), sizeToUse, size);

        // read the data
        if(chunk.on) {
            chunk.readHeader();
        }

        // adjust the position to after this chunk
        fileChannel.position(offset + size[sizeToUse]);

        // success
        return chunk;
    }

    /**
     * Writes the data of this chunk to file. This requires 2 flushes to disk.
     */
    void writeData() throws IOException {
        assert(offset != -1);
        assert(!on);

        // prepare to write
        FileChannel fileChannel = persistentMap.getFileChannel();
        Bufferlo chunkData = new Bufferlo();
        DataOutputStream chunkDataOut = new DataOutputStream(chunkData.getOutputStream());

        // write the data
        chunkDataOut.writeInt(sequenceId);
        chunkDataOut.writeInt(keyBytesLength);
        chunkDataOut.writeInt(valueBytesLength);
        chunkData.append(keyBytes);
        chunkData.append(valueBytes);

        chunkData.writeToChannel(fileChannel.position(offset + 16));
        fileChannel.force(false);

        // turn the written data on
        on = true;
        chunkDataOut.writeInt(1); // on == true
        chunkData.writeToChannel(fileChannel.position(offset));
        fileChannel.force(false);

        // clean up stuff we don't need no more
        keyBytes = null;
        valueBytes = null;
    }

    /**
     * Reads the sequence ID, key and value size for this chunk.
     */
    private void readHeader() throws IOException {
        assert(offset != -1);
        assert(size() != -1);

        // prepare to read
        FileChannel fileChannel = persistentMap.getFileChannel();
        Bufferlo chunkAsBytes = new Bufferlo();
        DataInputStream dataIn = new DataInputStream(chunkAsBytes.getInputStream());

        // read the whole chunk
        int bytesRequired = size();
        int read = chunkAsBytes.readFromChannel(fileChannel.position(offset), bytesRequired);
        if(read < bytesRequired) throw new IOException("Expected " + bytesRequired + " but found " + read + " bytes");

        // skip the chunk header
        dataIn.readInt(); // on/off
        dataIn.readInt(); // size to use
        dataIn.readInt(); // size 1
        dataIn.readInt(); // size 2

        // read the data
        sequenceId = dataIn.readInt();
        keyBytesLength = dataIn.readInt();
        valueBytesLength = dataIn.readInt();
        keyBytes = chunkAsBytes.consume(keyBytesLength);

        // skip any excess
        chunkAsBytes.clear();

        // process the read data
        key = persistentMap.getKeyCoder().decode(keyBytes.getInputStream());
    }

    /**
     * Reads the value for this chunk.
     */
    Bufferlo readValue() throws IOException {
        assert(offset != -1);
        assert(size() != -1);
        assert(valueBytesLength != -1);

        // prepare to read
        FileChannel fileChannel = persistentMap.getFileChannel();
        Bufferlo valueBytes = new Bufferlo();
        int valueLocation = offset;

        // adjust the value location: header
        valueLocation += 4; // on/off
        valueLocation += 4; // sizeToUse
        valueLocation += 4; // size one
        valueLocation += 4; // size two

        // adjust the value location: body
        valueLocation += 4; // sequence ID
        valueLocation += 4; // key size
        valueLocation += 4; // value size
        valueLocation += keyBytesLength; // key

        // read
        int read = valueBytes.readFromChannel(fileChannel.position(valueLocation), valueBytesLength);
        if(read < valueBytesLength) throw new IOException("Expected " + valueBytesLength + " but found " + read + " bytes");

        // done
        return valueBytes;
    }

    @Override
    public String toString() {
        StringBuffer result = new StringBuffer();
        result.append("<chunk      offset=\"").append(offset).append("\"");
        result.append("\n              size=\"").append(size()).append("\"");
        result.append("\n                on=\"").append(isOn()).append("\"");
        result.append("\n       sequence_id=\"").append(sequenceId).append("\"");
        result.append("\n               key=\"").append(key).append("\"");
        result.append("\n    keyBytesLength=\"").append(keyBytesLength).append("\"");
        result.append("\n  valueBytesLength=\"").append(valueBytesLength).append("\">");
        return result.toString();
    }
}