PackIndexV1.java

  1. /*
  2.  * Copyright (C) 2008-2009, Google Inc.
  3.  * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  4.  * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
  5.  * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> and others
  6.  *
  7.  * This program and the accompanying materials are made available under the
  8.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  9.  * https://www.eclipse.org/org/documents/edl-v10.php.
  10.  *
  11.  * SPDX-License-Identifier: BSD-3-Clause
  12.  */

  13. package org.eclipse.jgit.internal.storage.file;

  14. import java.io.IOException;
  15. import java.io.InputStream;
  16. import java.util.Arrays;
  17. import java.util.Iterator;
  18. import java.util.NoSuchElementException;
  19. import java.util.Set;

  20. import org.eclipse.jgit.errors.CorruptObjectException;
  21. import org.eclipse.jgit.internal.JGitText;
  22. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  23. import org.eclipse.jgit.lib.AnyObjectId;
  24. import org.eclipse.jgit.lib.Constants;
  25. import org.eclipse.jgit.lib.ObjectId;
  26. import org.eclipse.jgit.util.IO;
  27. import org.eclipse.jgit.util.NB;

  28. class PackIndexV1 extends PackIndex {
  29.     private static final int IDX_HDR_LEN = 256 * 4;

  30.     private final long[] idxHeader;

  31.     byte[][] idxdata;

  32.     private long objectCnt;

  33.     PackIndexV1(final InputStream fd, final byte[] hdr)
  34.             throws CorruptObjectException, IOException {
  35.         final byte[] fanoutTable = new byte[IDX_HDR_LEN];
  36.         System.arraycopy(hdr, 0, fanoutTable, 0, hdr.length);
  37.         IO.readFully(fd, fanoutTable, hdr.length, IDX_HDR_LEN - hdr.length);

  38.         idxHeader = new long[256]; // really unsigned 32-bit...
  39.         for (int k = 0; k < idxHeader.length; k++)
  40.             idxHeader[k] = NB.decodeUInt32(fanoutTable, k * 4);
  41.         idxdata = new byte[idxHeader.length][];
  42.         for (int k = 0; k < idxHeader.length; k++) {
  43.             long n;
  44.             if (k == 0) {
  45.                 n = idxHeader[k];
  46.             } else {
  47.                 n = idxHeader[k] - idxHeader[k - 1];
  48.             }
  49.             if (n > 0) {
  50.                 final long len = n * (Constants.OBJECT_ID_LENGTH + 4);
  51.                 if (len > Integer.MAX_VALUE - 8) // http://stackoverflow.com/a/8381338
  52.                     throw new IOException(JGitText.get().indexFileIsTooLargeForJgit);

  53.                 idxdata[k] = new byte[(int) len];
  54.                 IO.readFully(fd, idxdata[k], 0, idxdata[k].length);
  55.             }
  56.         }
  57.         objectCnt = idxHeader[255];

  58.         packChecksum = new byte[20];
  59.         IO.readFully(fd, packChecksum, 0, packChecksum.length);
  60.     }

  61.     /** {@inheritDoc} */
  62.     @Override
  63.     public long getObjectCount() {
  64.         return objectCnt;
  65.     }

  66.     /** {@inheritDoc} */
  67.     @Override
  68.     public long getOffset64Count() {
  69.         long n64 = 0;
  70.         for (MutableEntry e : this) {
  71.             if (e.getOffset() >= Integer.MAX_VALUE)
  72.                 n64++;
  73.         }
  74.         return n64;
  75.     }

  76.     private int findLevelOne(long nthPosition) {
  77.         int levelOne = Arrays.binarySearch(idxHeader, nthPosition + 1);
  78.         if (levelOne >= 0) {
  79.             // If we hit the bucket exactly the item is in the bucket, or
  80.             // any bucket before it which has the same object count.
  81.             //
  82.             long base = idxHeader[levelOne];
  83.             while (levelOne > 0 && base == idxHeader[levelOne - 1])
  84.                 levelOne--;
  85.         } else {
  86.             // The item is in the bucket we would insert it into.
  87.             //
  88.             levelOne = -(levelOne + 1);
  89.         }
  90.         return levelOne;
  91.     }

  92.     private int getLevelTwo(long nthPosition, int levelOne) {
  93.         final long base = levelOne > 0 ? idxHeader[levelOne - 1] : 0;
  94.         return (int) (nthPosition - base);
  95.     }

  96.     /** {@inheritDoc} */
  97.     @Override
  98.     public ObjectId getObjectId(long nthPosition) {
  99.         final int levelOne = findLevelOne(nthPosition);
  100.         final int p = getLevelTwo(nthPosition, levelOne);
  101.         final int dataIdx = idOffset(p);
  102.         return ObjectId.fromRaw(idxdata[levelOne], dataIdx);
  103.     }

  104.     @Override
  105.     long getOffset(long nthPosition) {
  106.         final int levelOne = findLevelOne(nthPosition);
  107.         final int levelTwo = getLevelTwo(nthPosition, levelOne);
  108.         final int p = (4 + Constants.OBJECT_ID_LENGTH) * levelTwo;
  109.         return NB.decodeUInt32(idxdata[levelOne], p);
  110.     }

  111.     /** {@inheritDoc} */
  112.     @Override
  113.     public long findOffset(AnyObjectId objId) {
  114.         final int levelOne = objId.getFirstByte();
  115.         byte[] data = idxdata[levelOne];
  116.         if (data == null)
  117.             return -1;
  118.         int high = data.length / (4 + Constants.OBJECT_ID_LENGTH);
  119.         int low = 0;
  120.         do {
  121.             final int mid = (low + high) >>> 1;
  122.             final int pos = idOffset(mid);
  123.             final int cmp = objId.compareTo(data, pos);
  124.             if (cmp < 0)
  125.                 high = mid;
  126.             else if (cmp == 0) {
  127.                 int b0 = data[pos - 4] & 0xff;
  128.                 int b1 = data[pos - 3] & 0xff;
  129.                 int b2 = data[pos - 2] & 0xff;
  130.                 int b3 = data[pos - 1] & 0xff;
  131.                 return (((long) b0) << 24) | (b1 << 16) | (b2 << 8) | (b3);
  132.             } else
  133.                 low = mid + 1;
  134.         } while (low < high);
  135.         return -1;
  136.     }

  137.     /** {@inheritDoc} */
  138.     @Override
  139.     public long findCRC32(AnyObjectId objId) {
  140.         throw new UnsupportedOperationException();
  141.     }

  142.     /** {@inheritDoc} */
  143.     @Override
  144.     public boolean hasCRC32Support() {
  145.         return false;
  146.     }

  147.     /** {@inheritDoc} */
  148.     @Override
  149.     public Iterator<MutableEntry> iterator() {
  150.         return new IndexV1Iterator();
  151.     }

  152.     /** {@inheritDoc} */
  153.     @Override
  154.     public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
  155.             int matchLimit) throws IOException {
  156.         byte[] data = idxdata[id.getFirstByte()];
  157.         if (data == null)
  158.             return;
  159.         int max = data.length / (4 + Constants.OBJECT_ID_LENGTH);
  160.         int high = max;
  161.         int low = 0;
  162.         do {
  163.             int p = (low + high) >>> 1;
  164.             final int cmp = id.prefixCompare(data, idOffset(p));
  165.             if (cmp < 0)
  166.                 high = p;
  167.             else if (cmp == 0) {
  168.                 // We may have landed in the middle of the matches.  Move
  169.                 // backwards to the start of matches, then walk forwards.
  170.                 //
  171.                 while (0 < p && id.prefixCompare(data, idOffset(p - 1)) == 0)
  172.                     p--;
  173.                 for (; p < max && id.prefixCompare(data, idOffset(p)) == 0; p++) {
  174.                     matches.add(ObjectId.fromRaw(data, idOffset(p)));
  175.                     if (matches.size() > matchLimit)
  176.                         break;
  177.                 }
  178.                 return;
  179.             } else
  180.                 low = p + 1;
  181.         } while (low < high);
  182.     }

  183.     private static int idOffset(int mid) {
  184.         return ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4;
  185.     }

  186.     private class IndexV1Iterator extends EntriesIterator {
  187.         int levelOne;

  188.         int levelTwo;

  189.         @Override
  190.         protected MutableEntry initEntry() {
  191.             return new MutableEntry() {
  192.                 @Override
  193.                 protected void ensureId() {
  194.                     idBuffer.fromRaw(idxdata[levelOne], levelTwo
  195.                             - Constants.OBJECT_ID_LENGTH);
  196.                 }
  197.             };
  198.         }

  199.         @Override
  200.         public MutableEntry next() {
  201.             for (; levelOne < idxdata.length; levelOne++) {
  202.                 if (idxdata[levelOne] == null)
  203.                     continue;
  204.                 if (levelTwo < idxdata[levelOne].length) {
  205.                     entry.offset = NB.decodeUInt32(idxdata[levelOne], levelTwo);
  206.                     levelTwo += Constants.OBJECT_ID_LENGTH + 4;
  207.                     returnedNumber++;
  208.                     return entry;
  209.                 }
  210.                 levelTwo = 0;
  211.             }
  212.             throw new NoSuchElementException();
  213.         }
  214.     }
  215. }