Mercurial > hg4j
comparison src/com/tmate/hgkit/ll/RevlogStream.java @ 10:382cfe9463db
Dirstate parsing. DataAccess refactored to allow reuse and control over constants
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Sat, 25 Dec 2010 21:50:12 +0100 |
parents | d6d2a630f4a6 |
children | 603806cd2dc6 |
comparison
equal
deleted
inserted
replaced
9:d6d2a630f4a6 | 10:382cfe9463db |
---|---|
1 /** | 1 /* |
2 * Copyright (c) 2010 Artem Tikhomirov | 2 * Copyright (c) 2010 Artem Tikhomirov |
3 */ | 3 */ |
4 package com.tmate.hgkit.ll; | 4 package com.tmate.hgkit.ll; |
5 | 5 |
6 import static com.tmate.hgkit.ll.HgRepository.TIP; | 6 import static com.tmate.hgkit.ll.HgRepository.TIP; |
7 | 7 |
8 import java.io.File; | 8 import java.io.File; |
9 import java.io.FileInputStream; | |
10 import java.io.IOException; | 9 import java.io.IOException; |
11 import java.nio.ByteBuffer; | |
12 import java.nio.MappedByteBuffer; | |
13 import java.nio.channels.FileChannel; | |
14 import java.util.ArrayList; | 10 import java.util.ArrayList; |
15 import java.util.Collections; | 11 import java.util.Collections; |
16 import java.util.LinkedList; | 12 import java.util.LinkedList; |
17 import java.util.List; | 13 import java.util.List; |
18 import java.util.zip.DataFormatException; | 14 import java.util.zip.DataFormatException; |
19 import java.util.zip.Inflater; | 15 import java.util.zip.Inflater; |
16 | |
17 import com.tmate.hgkit.fs.DataAccess; | |
18 import com.tmate.hgkit.fs.DataAccessProvider; | |
20 | 19 |
21 /** | 20 /** |
22 * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), | 21 * ? Single RevlogStream per file per repository with accessor to record access session (e.g. with back/forward operations), |
23 * or numerous RevlogStream with separate representation of the underlaying data (cached, lazy ChunkStream)? | 22 * or numerous RevlogStream with separate representation of the underlaying data (cached, lazy ChunkStream)? |
24 * @author artem | 23 * @author artem |
28 public class RevlogStream { | 27 public class RevlogStream { |
29 | 28 |
30 private List<IndexEntry> index; // indexed access highly needed | 29 private List<IndexEntry> index; // indexed access highly needed |
31 private boolean inline = false; | 30 private boolean inline = false; |
32 private final File indexFile; | 31 private final File indexFile; |
33 | 32 private final DataAccessProvider dataAccess; |
34 RevlogStream(File indexFile) { | 33 |
34 // if we need anything else from HgRepo, might replace DAP parameter with HgRepo and query it for DAP. | |
35 RevlogStream(DataAccessProvider dap, File indexFile) { | |
36 this.dataAccess = dap; | |
35 this.indexFile = indexFile; | 37 this.indexFile = indexFile; |
36 } | 38 } |
37 | 39 |
38 /*package*/ DataAccess getIndexStream() { | 40 /*package*/ DataAccess getIndexStream() { |
39 return create(indexFile); | 41 return dataAccess.create(indexFile); |
40 } | 42 } |
41 | 43 |
42 /*package*/ DataAccess getDataStream() { | 44 /*package*/ DataAccess getDataStream() { |
43 final String indexName = indexFile.getName(); | 45 final String indexName = indexFile.getName(); |
44 File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d"); | 46 File dataFile = new File(indexFile.getParentFile(), indexName.substring(0, indexName.length() - 1) + "d"); |
45 return create(dataFile); | 47 return dataAccess.create(dataFile); |
46 } | 48 } |
47 | 49 |
48 private DataAccess create(File f) { | |
49 if (!f.exists()) { | |
50 return new DataAccess(); | |
51 } | |
52 try { | |
53 FileChannel fc = new FileInputStream(f).getChannel(); | |
54 final int MAPIO_MAGIC_BOUNDARY = 100 * 1024; | |
55 if (fc.size() > MAPIO_MAGIC_BOUNDARY) { | |
56 return new MemoryMapFileAccess(fc, fc.size()); | |
57 } else { | |
58 return new FileAccess(fc, fc.size()); | |
59 } | |
60 } catch (IOException ex) { | |
61 // unlikely to happen, we've made sure file exists. | |
62 ex.printStackTrace(); // FIXME log error | |
63 } | |
64 return new DataAccess(); // non-null, empty. | |
65 } | |
66 | |
67 public int revisionCount() { | 50 public int revisionCount() { |
68 initOutline(); | 51 initOutline(); |
69 return index.size(); | 52 return index.size(); |
70 } | 53 } |
71 | 54 |
218 da.skip(3*4 + 32 + compressedLen); // Check: 44 (skip) + 20 (read) = 64 (total RevlogNG record size) | 201 da.skip(3*4 + 32 + compressedLen); // Check: 44 (skip) + 20 (read) = 64 (total RevlogNG record size) |
219 } else { | 202 } else { |
220 res.add(new IndexEntry(offset, baseRevision)); | 203 res.add(new IndexEntry(offset, baseRevision)); |
221 da.skip(3*4 + 32); | 204 da.skip(3*4 + 32); |
222 } | 205 } |
223 if (da.nonEmpty()) { | 206 if (da.isEmpty()) { |
224 long l = da.readLong(); | |
225 offset = l >>> 16; | |
226 } else { | |
227 // fine, done then | 207 // fine, done then |
228 index = res; | 208 index = res; |
229 break; | 209 break; |
210 } else { | |
211 // start reading next record | |
212 long l = da.readLong(); | |
213 offset = l >>> 16; | |
230 } | 214 } |
231 } | 215 } |
232 } catch (IOException ex) { | 216 } catch (IOException ex) { |
233 ex.printStackTrace(); // log error | 217 ex.printStackTrace(); // log error |
234 // too bad, no outline then. | 218 // too bad, no outline then. |
282 this.len = len; | 266 this.len = len; |
283 data = new byte[len]; | 267 data = new byte[len]; |
284 System.arraycopy(src, srcOffset, data, 0, len); | 268 System.arraycopy(src, srcOffset, data, 0, len); |
285 } | 269 } |
286 } | 270 } |
287 | |
288 /*package-local*/ class DataAccess { | |
289 public boolean nonEmpty() { | |
290 return false; | |
291 } | |
292 // absolute positioning | |
293 public void seek(long offset) throws IOException { | |
294 throw new UnsupportedOperationException(); | |
295 } | |
296 // relative positioning | |
297 public void skip(int bytes) throws IOException { | |
298 throw new UnsupportedOperationException(); | |
299 } | |
300 // shall be called once this object no longer needed | |
301 public void done() { | |
302 // no-op in this empty implementation | |
303 } | |
304 public int readInt() throws IOException { | |
305 byte[] b = new byte[4]; | |
306 readBytes(b, 0, 4); | |
307 return b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF); | |
308 } | |
309 public long readLong() throws IOException { | |
310 byte[] b = new byte[8]; | |
311 readBytes(b, 0, 8); | |
312 int i1 = b[0] << 24 | (b[1] & 0xFF) << 16 | (b[2] & 0xFF) << 8 | (b[3] & 0xFF); | |
313 int i2 = b[4] << 24 | (b[5] & 0xFF) << 16 | (b[6] & 0xFF) << 8 | (b[7] & 0xFF); | |
314 return ((long) i1) << 32 | ((long) i2 & 0xFFFFFFFF); | |
315 } | |
316 public void readBytes(byte[] buf, int offset, int length) throws IOException { | |
317 throw new UnsupportedOperationException(); | |
318 } | |
319 } | |
320 | |
321 // DOESN'T WORK YET | |
322 private class MemoryMapFileAccess extends DataAccess { | |
323 private FileChannel fileChannel; | |
324 private final long size; | |
325 private long position = 0; | |
326 | |
327 public MemoryMapFileAccess(FileChannel fc, long channelSize) { | |
328 fileChannel = fc; | |
329 size = channelSize; | |
330 } | |
331 | |
332 @Override | |
333 public void seek(long offset) { | |
334 position = offset; | |
335 } | |
336 | |
337 @Override | |
338 public void skip(int bytes) throws IOException { | |
339 position += bytes; | |
340 } | |
341 | |
342 private boolean fill() throws IOException { | |
343 final int BUFFER_SIZE = 8 * 1024; | |
344 long left = size - position; | |
345 MappedByteBuffer rv = fileChannel.map(FileChannel.MapMode.READ_ONLY, position, left < BUFFER_SIZE ? left : BUFFER_SIZE); | |
346 position += rv.capacity(); | |
347 return rv.hasRemaining(); | |
348 } | |
349 | |
350 @Override | |
351 public void done() { | |
352 if (fileChannel != null) { | |
353 try { | |
354 fileChannel.close(); | |
355 } catch (IOException ex) { | |
356 ex.printStackTrace(); // log debug | |
357 } | |
358 fileChannel = null; | |
359 } | |
360 } | |
361 } | |
362 | |
363 private class FileAccess extends DataAccess { | |
364 private FileChannel fileChannel; | |
365 private final long size; | |
366 private ByteBuffer buffer; | |
367 private long bufferStartInFile = 0; // offset of this.buffer in the file. | |
368 | |
369 public FileAccess(FileChannel fc, long channelSize) { | |
370 fileChannel = fc; | |
371 size = channelSize; | |
372 final int BUFFER_SIZE = 8 * 1024; | |
373 // XXX once implementation is more or less stable, | |
374 // may want to try ByteBuffer.allocateDirect() to see | |
375 // if there's any performance gain. | |
376 buffer = ByteBuffer.allocate(size < BUFFER_SIZE ? (int) size : BUFFER_SIZE); | |
377 buffer.flip(); // or .limit(0) to indicate it's empty | |
378 } | |
379 | |
380 @Override | |
381 public boolean nonEmpty() { | |
382 return bufferStartInFile + buffer.position() < size; | |
383 } | |
384 | |
385 @Override | |
386 public void seek(long offset) throws IOException { | |
387 if (offset < bufferStartInFile + buffer.limit() && offset >= bufferStartInFile) { | |
388 buffer.position((int) (offset - bufferStartInFile)); | |
389 } else { | |
390 // out of current buffer, invalidate it (force re-read) | |
391 // XXX or ever re-read it right away? | |
392 bufferStartInFile = offset; | |
393 buffer.clear(); | |
394 buffer.limit(0); // or .flip() to indicate we switch to reading | |
395 fileChannel.position(offset); | |
396 } | |
397 } | |
398 | |
399 @Override | |
400 public void skip(int bytes) throws IOException { | |
401 final int newPos = buffer.position() + bytes; | |
402 if (newPos >= 0 && newPos < buffer.limit()) { | |
403 // no need to move file pointer, just rewind/seek buffer | |
404 buffer.position(newPos); | |
405 } else { | |
406 // | |
407 seek(fileChannel.position()+ bytes); | |
408 } | |
409 } | |
410 | |
411 private boolean fill() throws IOException { | |
412 if (!buffer.hasRemaining()) { | |
413 bufferStartInFile += buffer.limit(); | |
414 buffer.clear(); | |
415 if (bufferStartInFile < size) { // just in case there'd be any exception on EOF, not -1 | |
416 fileChannel.read(buffer); | |
417 // may return -1 when EOF, but empty will reflect this, hence no explicit support here | |
418 } | |
419 buffer.flip(); | |
420 } | |
421 return buffer.hasRemaining(); | |
422 } | |
423 | |
424 @Override | |
425 public void readBytes(byte[] buf, int offset, int length) throws IOException { | |
426 final int tail = buffer.remaining(); | |
427 if (tail >= length) { | |
428 buffer.get(buf, offset, length); | |
429 } else { | |
430 buffer.get(buf, offset, tail); | |
431 if (fill()) { | |
432 buffer.get(buf, offset + tail, length - tail); | |
433 } else { | |
434 throw new IOException(); // shall not happen provided stream contains expected data and no attempts to read past nonEmpty() == false are made. | |
435 } | |
436 } | |
437 } | |
438 | |
439 @Override | |
440 public void done() { | |
441 if (buffer != null) { | |
442 buffer = null; | |
443 } | |
444 if (fileChannel != null) { | |
445 try { | |
446 fileChannel.close(); | |
447 } catch (IOException ex) { | |
448 ex.printStackTrace(); // log debug | |
449 } | |
450 fileChannel = null; | |
451 } | |
452 } | |
453 } | |
454 } | 271 } |