Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgDataFile.java @ 602:e3717fc7d26f
Refactor metadata parsing in HgDataFile, moved to standalone class
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Mon, 06 May 2013 17:11:29 +0200 |
parents | 46f29b73e51e |
children | 707b5c7c6fa4 |
comparison
equal
deleted
inserted
replaced
601:8143c1f77d45 | 602:e3717fc7d26f |
---|---|
16 */ | 16 */ |
17 package org.tmatesoft.hg.repo; | 17 package org.tmatesoft.hg.repo; |
18 | 18 |
19 import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex; | 19 import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex; |
20 import static org.tmatesoft.hg.repo.HgRepository.*; | 20 import static org.tmatesoft.hg.repo.HgRepository.*; |
21 import static org.tmatesoft.hg.util.LogFacility.Severity.*; | 21 import static org.tmatesoft.hg.util.LogFacility.Severity.Info; |
22 | 22 import static org.tmatesoft.hg.util.LogFacility.Severity.Warn; |
23 import java.io.ByteArrayOutputStream; | 23 |
24 import java.io.File; | 24 import java.io.File; |
25 import java.io.FileInputStream; | 25 import java.io.FileInputStream; |
26 import java.io.IOException; | 26 import java.io.IOException; |
27 import java.nio.ByteBuffer; | 27 import java.nio.ByteBuffer; |
28 import java.nio.channels.FileChannel; | 28 import java.nio.channels.FileChannel; |
29 import java.util.ArrayList; | |
30 import java.util.Arrays; | 29 import java.util.Arrays; |
31 import java.util.Collection; | |
32 | 30 |
33 import org.tmatesoft.hg.core.HgChangesetFileSneaker; | 31 import org.tmatesoft.hg.core.HgChangesetFileSneaker; |
34 import org.tmatesoft.hg.core.Nodeid; | 32 import org.tmatesoft.hg.core.Nodeid; |
35 import org.tmatesoft.hg.internal.DataAccess; | 33 import org.tmatesoft.hg.internal.DataAccess; |
36 import org.tmatesoft.hg.internal.FilterByteChannel; | 34 import org.tmatesoft.hg.internal.FilterByteChannel; |
37 import org.tmatesoft.hg.internal.FilterDataAccess; | 35 import org.tmatesoft.hg.internal.FilterDataAccess; |
38 import org.tmatesoft.hg.internal.IntMap; | |
39 import org.tmatesoft.hg.internal.Internals; | 36 import org.tmatesoft.hg.internal.Internals; |
37 import org.tmatesoft.hg.internal.Metadata; | |
40 import org.tmatesoft.hg.internal.RevlogStream; | 38 import org.tmatesoft.hg.internal.RevlogStream; |
41 import org.tmatesoft.hg.util.ByteChannel; | 39 import org.tmatesoft.hg.util.ByteChannel; |
42 import org.tmatesoft.hg.util.CancelSupport; | 40 import org.tmatesoft.hg.util.CancelSupport; |
43 import org.tmatesoft.hg.util.CancelledException; | 41 import org.tmatesoft.hg.util.CancelledException; |
44 import org.tmatesoft.hg.util.LogFacility; | 42 import org.tmatesoft.hg.util.LogFacility; |
277 } | 275 } |
278 if (sink == null) { | 276 if (sink == null) { |
279 throw new IllegalArgumentException(); | 277 throw new IllegalArgumentException(); |
280 } | 278 } |
281 if (metadata == null) { | 279 if (metadata == null) { |
282 metadata = new Metadata(); | 280 metadata = new Metadata(getRepo()); |
283 } | 281 } |
284 ErrorHandlingInspector insp; | 282 ErrorHandlingInspector insp; |
285 final LogFacility lf = getRepo().getSessionContext().getLog(); | 283 final LogFacility lf = getRepo().getSessionContext().getLog(); |
286 if (metadata.none(fileRevisionIndex)) { | 284 if (metadata.none(fileRevisionIndex)) { |
287 insp = new ContentPipe(sink, 0, lf); | 285 insp = new ContentPipe(sink, 0, lf); |
288 } else if (metadata.known(fileRevisionIndex)) { | 286 } else if (metadata.known(fileRevisionIndex)) { |
289 insp = new ContentPipe(sink, metadata.dataOffset(fileRevisionIndex), lf); | 287 insp = new ContentPipe(sink, metadata.dataOffset(fileRevisionIndex), lf); |
290 } else { | 288 } else { |
291 // do not know if there's metadata | 289 // do not know if there's metadata |
292 insp = new MetadataInspector(metadata, lf, new ContentPipe(sink, 0, lf)); | 290 insp = new MetadataInspector(metadata, new ContentPipe(sink, 0, lf)); |
293 } | 291 } |
294 insp.checkCancelled(); | 292 insp.checkCancelled(); |
295 super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); | 293 super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); |
296 try { | 294 try { |
297 insp.checkFailed(); | 295 insp.checkFailed(); |
439 return sb.toString(); | 437 return sb.toString(); |
440 } | 438 } |
441 | 439 |
442 private void checkAndRecordMetadata(int localRev) throws HgInvalidControlFileException { | 440 private void checkAndRecordMetadata(int localRev) throws HgInvalidControlFileException { |
443 if (metadata == null) { | 441 if (metadata == null) { |
444 metadata = new Metadata(); | 442 metadata = new Metadata(getRepo()); |
445 } | 443 } |
446 // use MetadataInspector without delegate to process metadata only | 444 // use MetadataInspector without delegate to process metadata only |
447 RevlogStream.Inspector insp = new MetadataInspector(metadata, getRepo().getSessionContext().getLog(), null); | 445 RevlogStream.Inspector insp = new MetadataInspector(metadata, null); |
448 super.content.iterate(localRev, localRev, true, insp); | 446 super.content.iterate(localRev, localRev, true, insp); |
449 } | |
450 | |
451 private static final class MetadataEntry { | |
452 private final String entry; | |
453 private final int valueStart; | |
454 | |
455 // key may be null | |
456 /*package-local*/MetadataEntry(String key, String value) { | |
457 if (key == null) { | |
458 entry = value; | |
459 valueStart = -1; // not 0 to tell between key == null and key == "" | |
460 } else { | |
461 entry = key + value; | |
462 valueStart = key.length(); | |
463 } | |
464 } | |
465 /*package-local*/boolean matchKey(String key) { | |
466 return key == null ? valueStart == -1 : key.length() == valueStart && entry.startsWith(key); | |
467 } | |
468 // uncomment once/if needed | |
469 // public String key() { | |
470 // return entry.substring(0, valueStart); | |
471 // } | |
472 public String value() { | |
473 return valueStart == -1 ? entry : entry.substring(valueStart); | |
474 } | |
475 } | |
476 | |
477 private static class Metadata { | |
478 private static class Record { | |
479 public final int offset; | |
480 public final MetadataEntry[] entries; | |
481 | |
482 public Record(int off, MetadataEntry[] entr) { | |
483 offset = off; | |
484 entries = entr; | |
485 } | |
486 } | |
487 // XXX sparse array needed | |
488 private final IntMap<Record> entries = new IntMap<Record>(5); | |
489 | |
490 private final Record NONE = new Record(-1, null); // don't want statics | |
491 | |
492 // true when there's metadata for given revision | |
493 boolean known(int revision) { | |
494 Record i = entries.get(revision); | |
495 return i != null && NONE != i; | |
496 } | |
497 | |
498 // true when revision has been checked for metadata presence. | |
499 public boolean checked(int revision) { | |
500 return entries.containsKey(revision); | |
501 } | |
502 | |
503 // true when revision has been checked and found not having any metadata | |
504 boolean none(int revision) { | |
505 Record i = entries.get(revision); | |
506 return i == NONE; | |
507 } | |
508 | |
509 // mark revision as having no metadata. | |
510 void recordNone(int revision) { | |
511 Record i = entries.get(revision); | |
512 if (i == NONE) { | |
513 return; // already there | |
514 } | |
515 if (i != null) { | |
516 throw new IllegalStateException(String.format("Trying to override Metadata state for revision %d (known offset: %d)", revision, i)); | |
517 } | |
518 entries.put(revision, NONE); | |
519 } | |
520 | |
521 // since this is internal class, callers are supposed to ensure arg correctness (i.e. ask known() before) | |
522 int dataOffset(int revision) { | |
523 return entries.get(revision).offset; | |
524 } | |
525 void add(int revision, int dataOffset, Collection<MetadataEntry> e) { | |
526 assert !entries.containsKey(revision); | |
527 entries.put(revision, new Record(dataOffset, e.toArray(new MetadataEntry[e.size()]))); | |
528 } | |
529 | |
530 String find(int revision, String key) { | |
531 for (MetadataEntry me : entries.get(revision).entries) { | |
532 if (me.matchKey(key)) { | |
533 return me.value(); | |
534 } | |
535 } | |
536 return null; | |
537 } | |
538 } | 447 } |
539 | 448 |
540 private static class MetadataInspector extends ErrorHandlingInspector implements RevlogStream.Inspector { | 449 private static class MetadataInspector extends ErrorHandlingInspector implements RevlogStream.Inspector { |
541 private final Metadata metadata; | 450 private final Metadata metadata; |
542 private final RevlogStream.Inspector delegate; | 451 private final RevlogStream.Inspector delegate; |
543 private final LogFacility log; | |
544 | 452 |
545 /** | 453 /** |
546 * @param _metadata never <code>null</code> | 454 * @param _metadata never <code>null</code> |
547 * @param logFacility log facility from the session context | |
548 * @param chain <code>null</code> if no further data processing other than metadata is desired | 455 * @param chain <code>null</code> if no further data processing other than metadata is desired |
549 */ | 456 */ |
550 public MetadataInspector(Metadata _metadata, LogFacility logFacility, RevlogStream.Inspector chain) { | 457 public MetadataInspector(Metadata _metadata, RevlogStream.Inspector chain) { |
551 metadata = _metadata; | 458 metadata = _metadata; |
552 log = logFacility; | |
553 delegate = chain; | 459 delegate = chain; |
554 setCancelSupport(CancelSupport.Factory.get(chain)); | 460 setCancelSupport(CancelSupport.Factory.get(chain)); |
555 } | 461 } |
556 | 462 |
557 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { | 463 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { |
558 try { | 464 try { |
559 final int daLength = data.length(); | 465 if (metadata.tryRead(revisionNumber, data)) { |
560 if (daLength < 4 || data.readByte() != 1 || data.readByte() != 10) { | |
561 metadata.recordNone(revisionNumber); | |
562 data.reset(); | |
563 } else { | |
564 ArrayList<MetadataEntry> _metadata = new ArrayList<MetadataEntry>(); | |
565 int offset = parseMetadata(data, daLength, _metadata); | |
566 metadata.add(revisionNumber, offset, _metadata); | |
567 // da is in prepared state (i.e. we consumed all bytes up to metadata end). | 466 // da is in prepared state (i.e. we consumed all bytes up to metadata end). |
568 // However, it's not safe to assume delegate won't call da.reset() for some reason, | 467 // However, it's not safe to assume delegate won't call da.reset() for some reason, |
569 // and we need to ensure predictable result. | 468 // and we need to ensure predictable result. |
570 data.reset(); | 469 data.reset(); |
571 data = new FilterDataAccess(data, offset, daLength - offset); | 470 int offset = metadata.dataOffset(revisionNumber); |
471 data = new FilterDataAccess(data, offset, data.length() - offset); | |
472 } else { | |
473 data.reset(); | |
572 } | 474 } |
573 if (delegate != null) { | 475 if (delegate != null) { |
574 delegate.next(revisionNumber, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeid, data); | 476 delegate.next(revisionNumber, actualLen, baseRevision, linkRevision, parent1Revision, parent2Revision, nodeid, data); |
575 } | 477 } |
576 } catch (IOException ex) { | 478 } catch (IOException ex) { |
579 // TODO RevlogStream, where this RevlogStream.Inspector goes, shall set File (as it's the only one having access to it) | 481 // TODO RevlogStream, where this RevlogStream.Inspector goes, shall set File (as it's the only one having access to it) |
580 recordFailure(ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(revisionNumber)); | 482 recordFailure(ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(revisionNumber)); |
581 } | 483 } |
582 } | 484 } |
583 | 485 |
584 private int parseMetadata(DataAccess data, final int daLength, ArrayList<MetadataEntry> _metadata) throws IOException, HgInvalidControlFileException { | |
585 int lastEntryStart = 2; | |
586 int lastColon = -1; | |
587 // XXX in fact, need smth like ByteArrayBuilder, similar to StringBuilder, | |
588 // which can't be used here because we can't convert bytes to chars as we read them | |
589 // (there might be multi-byte encoding), and we need to collect all bytes before converting to string | |
590 ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
591 String key = null, value = null; | |
592 boolean byteOne = false; | |
593 boolean metadataIsComplete = false; | |
594 for (int i = 2; i < daLength; i++) { | |
595 byte b = data.readByte(); | |
596 if (b == '\n') { | |
597 if (byteOne) { // i.e. \n follows 1 | |
598 lastEntryStart = i+1; | |
599 metadataIsComplete = true; | |
600 // XXX is it possible to have here incomplete key/value (i.e. if last pair didn't end with \n) | |
601 // if yes, need to set metadataIsComplete to true in that case as well | |
602 break; | |
603 } | |
604 if (key == null || lastColon == -1 || i <= lastColon) { | |
605 log.dump(getClass(), Error, "Missing key in file revision metadata at index %d", i); | |
606 } | |
607 value = new String(bos.toByteArray()).trim(); | |
608 bos.reset(); | |
609 _metadata.add(new MetadataEntry(key, value)); | |
610 key = value = null; | |
611 lastColon = -1; | |
612 lastEntryStart = i+1; | |
613 continue; | |
614 } | |
615 // byteOne has to be consumed up to this line, if not yet, consume it | |
616 if (byteOne) { | |
617 // insert 1 we've read on previous step into the byte builder | |
618 bos.write(1); | |
619 byteOne = false; | |
620 // fall-through to consume current byte | |
621 } | |
622 if (b == (int) ':') { | |
623 assert value == null; | |
624 key = new String(bos.toByteArray()); | |
625 bos.reset(); | |
626 lastColon = i; | |
627 } else if (b == 1) { | |
628 byteOne = true; | |
629 } else { | |
630 bos.write(b); | |
631 } | |
632 } | |
633 // data.isEmpty is not reliable, renamed files of size==0 keep only metadata | |
634 if (!metadataIsComplete) { | |
635 // XXX perhaps, worth a testcase (empty file, renamed, read or ask ifCopy | |
636 throw new HgInvalidControlFileException("Metadata is not closed properly", null, null); | |
637 } | |
638 return lastEntryStart; | |
639 } | |
640 | |
641 @Override | 486 @Override |
642 public void checkFailed() throws HgRuntimeException, IOException, CancelledException { | 487 public void checkFailed() throws HgRuntimeException, IOException, CancelledException { |
643 super.checkFailed(); | 488 super.checkFailed(); |
644 if (delegate instanceof ErrorHandlingInspector) { | 489 if (delegate instanceof ErrorHandlingInspector) { |
645 // TODO need to add ErrorDestination (ErrorTarget/Acceptor?) and pass it around (much like CancelSupport get passed) | 490 // TODO need to add ErrorDestination (ErrorTarget/Acceptor?) and pass it around (much like CancelSupport get passed) |