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>
  6.  * and other copyright owners as documented in the project's IP log.
  7.  *
  8.  * This program and the accompanying materials are made available
  9.  * under the terms of the Eclipse Distribution License v1.0 which
  10.  * accompanies this distribution, is reproduced below, and is
  11.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  12.  *
  13.  * All rights reserved.
  14.  *
  15.  * Redistribution and use in source and binary forms, with or
  16.  * without modification, are permitted provided that the following
  17.  * conditions are met:
  18.  *
  19.  * - Redistributions of source code must retain the above copyright
  20.  *   notice, this list of conditions and the following disclaimer.
  21.  *
  22.  * - Redistributions in binary form must reproduce the above
  23.  *   copyright notice, this list of conditions and the following
  24.  *   disclaimer in the documentation and/or other materials provided
  25.  *   with the distribution.
  26.  *
  27.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  28.  *   names of its contributors may be used to endorse or promote
  29.  *   products derived from this software without specific prior
  30.  *   written permission.
  31.  *
  32.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  33.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  34.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  35.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  36.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  37.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  38.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  39.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  40.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  41.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  42.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  43.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  44.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  45.  */

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

  47. import java.io.IOException;
  48. import java.io.InputStream;
  49. import java.util.Arrays;
  50. import java.util.Iterator;
  51. import java.util.NoSuchElementException;
  52. import java.util.Set;

  53. import org.eclipse.jgit.errors.CorruptObjectException;
  54. import org.eclipse.jgit.internal.JGitText;
  55. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  56. import org.eclipse.jgit.lib.AnyObjectId;
  57. import org.eclipse.jgit.lib.Constants;
  58. import org.eclipse.jgit.lib.ObjectId;
  59. import org.eclipse.jgit.util.IO;
  60. import org.eclipse.jgit.util.NB;

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

  63.     private final long[] idxHeader;

  64.     byte[][] idxdata;

  65.     private long objectCnt;

  66.     PackIndexV1(final InputStream fd, final byte[] hdr)
  67.             throws CorruptObjectException, IOException {
  68.         final byte[] fanoutTable = new byte[IDX_HDR_LEN];
  69.         System.arraycopy(hdr, 0, fanoutTable, 0, hdr.length);
  70.         IO.readFully(fd, fanoutTable, hdr.length, IDX_HDR_LEN - hdr.length);

  71.         idxHeader = new long[256]; // really unsigned 32-bit...
  72.         for (int k = 0; k < idxHeader.length; k++)
  73.             idxHeader[k] = NB.decodeUInt32(fanoutTable, k * 4);
  74.         idxdata = new byte[idxHeader.length][];
  75.         for (int k = 0; k < idxHeader.length; k++) {
  76.             int n;
  77.             if (k == 0) {
  78.                 n = (int) (idxHeader[k]);
  79.             } else {
  80.                 n = (int) (idxHeader[k] - idxHeader[k - 1]);
  81.             }
  82.             if (n > 0) {
  83.                 final long len = n * (Constants.OBJECT_ID_LENGTH + 4);
  84.                 if (len > Integer.MAX_VALUE - 8) // http://stackoverflow.com/a/8381338
  85.                     throw new IOException(JGitText.get().indexFileIsTooLargeForJgit);

  86.                 idxdata[k] = new byte[(int) len];
  87.                 IO.readFully(fd, idxdata[k], 0, idxdata[k].length);
  88.             }
  89.         }
  90.         objectCnt = idxHeader[255];

  91.         packChecksum = new byte[20];
  92.         IO.readFully(fd, packChecksum, 0, packChecksum.length);
  93.     }

  94.     /** {@inheritDoc} */
  95.     @Override
  96.     public long getObjectCount() {
  97.         return objectCnt;
  98.     }

  99.     /** {@inheritDoc} */
  100.     @Override
  101.     public long getOffset64Count() {
  102.         long n64 = 0;
  103.         for (MutableEntry e : this) {
  104.             if (e.getOffset() >= Integer.MAX_VALUE)
  105.                 n64++;
  106.         }
  107.         return n64;
  108.     }

  109.     private int findLevelOne(long nthPosition) {
  110.         int levelOne = Arrays.binarySearch(idxHeader, nthPosition + 1);
  111.         if (levelOne >= 0) {
  112.             // If we hit the bucket exactly the item is in the bucket, or
  113.             // any bucket before it which has the same object count.
  114.             //
  115.             long base = idxHeader[levelOne];
  116.             while (levelOne > 0 && base == idxHeader[levelOne - 1])
  117.                 levelOne--;
  118.         } else {
  119.             // The item is in the bucket we would insert it into.
  120.             //
  121.             levelOne = -(levelOne + 1);
  122.         }
  123.         return levelOne;
  124.     }

  125.     private int getLevelTwo(long nthPosition, int levelOne) {
  126.         final long base = levelOne > 0 ? idxHeader[levelOne - 1] : 0;
  127.         return (int) (nthPosition - base);
  128.     }

  129.     /** {@inheritDoc} */
  130.     @Override
  131.     public ObjectId getObjectId(long nthPosition) {
  132.         final int levelOne = findLevelOne(nthPosition);
  133.         final int p = getLevelTwo(nthPosition, levelOne);
  134.         final int dataIdx = idOffset(p);
  135.         return ObjectId.fromRaw(idxdata[levelOne], dataIdx);
  136.     }

  137.     @Override
  138.     long getOffset(long nthPosition) {
  139.         final int levelOne = findLevelOne(nthPosition);
  140.         final int levelTwo = getLevelTwo(nthPosition, levelOne);
  141.         final int p = (4 + Constants.OBJECT_ID_LENGTH) * levelTwo;
  142.         return NB.decodeUInt32(idxdata[levelOne], p);
  143.     }

  144.     /** {@inheritDoc} */
  145.     @Override
  146.     public long findOffset(AnyObjectId objId) {
  147.         final int levelOne = objId.getFirstByte();
  148.         byte[] data = idxdata[levelOne];
  149.         if (data == null)
  150.             return -1;
  151.         int high = data.length / (4 + Constants.OBJECT_ID_LENGTH);
  152.         int low = 0;
  153.         do {
  154.             final int mid = (low + high) >>> 1;
  155.             final int pos = idOffset(mid);
  156.             final int cmp = objId.compareTo(data, pos);
  157.             if (cmp < 0)
  158.                 high = mid;
  159.             else if (cmp == 0) {
  160.                 int b0 = data[pos - 4] & 0xff;
  161.                 int b1 = data[pos - 3] & 0xff;
  162.                 int b2 = data[pos - 2] & 0xff;
  163.                 int b3 = data[pos - 1] & 0xff;
  164.                 return (((long) b0) << 24) | (b1 << 16) | (b2 << 8) | (b3);
  165.             } else
  166.                 low = mid + 1;
  167.         } while (low < high);
  168.         return -1;
  169.     }

  170.     /** {@inheritDoc} */
  171.     @Override
  172.     public long findCRC32(AnyObjectId objId) {
  173.         throw new UnsupportedOperationException();
  174.     }

  175.     /** {@inheritDoc} */
  176.     @Override
  177.     public boolean hasCRC32Support() {
  178.         return false;
  179.     }

  180.     /** {@inheritDoc} */
  181.     @Override
  182.     public Iterator<MutableEntry> iterator() {
  183.         return new IndexV1Iterator();
  184.     }

  185.     /** {@inheritDoc} */
  186.     @Override
  187.     public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
  188.             int matchLimit) throws IOException {
  189.         byte[] data = idxdata[id.getFirstByte()];
  190.         if (data == null)
  191.             return;
  192.         int max = data.length / (4 + Constants.OBJECT_ID_LENGTH);
  193.         int high = max;
  194.         int low = 0;
  195.         do {
  196.             int p = (low + high) >>> 1;
  197.             final int cmp = id.prefixCompare(data, idOffset(p));
  198.             if (cmp < 0)
  199.                 high = p;
  200.             else if (cmp == 0) {
  201.                 // We may have landed in the middle of the matches.  Move
  202.                 // backwards to the start of matches, then walk forwards.
  203.                 //
  204.                 while (0 < p && id.prefixCompare(data, idOffset(p - 1)) == 0)
  205.                     p--;
  206.                 for (; p < max && id.prefixCompare(data, idOffset(p)) == 0; p++) {
  207.                     matches.add(ObjectId.fromRaw(data, idOffset(p)));
  208.                     if (matches.size() > matchLimit)
  209.                         break;
  210.                 }
  211.                 return;
  212.             } else
  213.                 low = p + 1;
  214.         } while (low < high);
  215.     }

  216.     private static int idOffset(int mid) {
  217.         return ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4;
  218.     }

  219.     private class IndexV1Iterator extends EntriesIterator {
  220.         int levelOne;

  221.         int levelTwo;

  222.         @Override
  223.         protected MutableEntry initEntry() {
  224.             return new MutableEntry() {
  225.                 @Override
  226.                 protected void ensureId() {
  227.                     idBuffer.fromRaw(idxdata[levelOne], levelTwo
  228.                             - Constants.OBJECT_ID_LENGTH);
  229.                 }
  230.             };
  231.         }

  232.         @Override
  233.         public MutableEntry next() {
  234.             for (; levelOne < idxdata.length; levelOne++) {
  235.                 if (idxdata[levelOne] == null)
  236.                     continue;
  237.                 if (levelTwo < idxdata[levelOne].length) {
  238.                     entry.offset = NB.decodeUInt32(idxdata[levelOne], levelTwo);
  239.                     levelTwo += Constants.OBJECT_ID_LENGTH + 4;
  240.                     returnedNumber++;
  241.                     return entry;
  242.                 }
  243.                 levelTwo = 0;
  244.             }
  245.             throw new NoSuchElementException();
  246.         }
  247.     }
  248. }