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();
}
}
|