PackBitmapIndexBuilder.java

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

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

  44. import java.text.MessageFormat;
  45. import java.util.Collections;
  46. import java.util.Comparator;
  47. import java.util.Iterator;
  48. import java.util.List;
  49. import java.util.NoSuchElementException;

  50. import org.eclipse.jgit.internal.JGitText;
  51. import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl.CompressedBitmap;
  52. import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
  53. import org.eclipse.jgit.lib.AnyObjectId;
  54. import org.eclipse.jgit.lib.BitmapIndex.Bitmap;
  55. import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
  56. import org.eclipse.jgit.lib.Constants;
  57. import org.eclipse.jgit.lib.ObjectId;
  58. import org.eclipse.jgit.lib.ObjectIdOwnerMap;
  59. import org.eclipse.jgit.util.BlockList;

  60. import com.googlecode.javaewah.EWAHCompressedBitmap;

  61. /**
  62.  * Helper for constructing
  63.  * {@link org.eclipse.jgit.internal.storage.file.PackBitmapIndex}es.
  64.  */
  65. public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
  66.     private static final int MAX_XOR_OFFSET_SEARCH = 10;

  67.     private final EWAHCompressedBitmap commits;
  68.     private final EWAHCompressedBitmap trees;
  69.     private final EWAHCompressedBitmap blobs;
  70.     private final EWAHCompressedBitmap tags;
  71.     private final BlockList<PositionEntry> byOffset;
  72.     final BlockList<StoredBitmap>
  73.             byAddOrder = new BlockList<>();
  74.     final ObjectIdOwnerMap<PositionEntry>
  75.             positionEntries = new ObjectIdOwnerMap<>();

  76.     /**
  77.      * Creates a PackBitmapIndex used for building the contents of an index
  78.      * file.
  79.      *
  80.      * @param objects
  81.      *            objects sorted by name. The list must be initially sorted by
  82.      *            ObjectId (name); it will be resorted in place.
  83.      */
  84.     public PackBitmapIndexBuilder(List<ObjectToPack> objects) {
  85.         super(new ObjectIdOwnerMap<StoredBitmap>());
  86.         byOffset = new BlockList<>(objects.size());
  87.         sortByOffsetAndIndex(byOffset, positionEntries, objects);

  88.         // 64 objects fit in a single long word (64 bits).
  89.         // On average a repository is 30% commits, 30% trees, 30% blobs.
  90.         // Initialize bitmap capacity for worst case to minimize growing.
  91.         int sizeInWords = Math.max(4, byOffset.size() / 64 / 3);
  92.         commits = new EWAHCompressedBitmap(sizeInWords);
  93.         trees = new EWAHCompressedBitmap(sizeInWords);
  94.         blobs = new EWAHCompressedBitmap(sizeInWords);
  95.         tags = new EWAHCompressedBitmap(sizeInWords);
  96.         for (int i = 0; i < objects.size(); i++) {
  97.             int type = objects.get(i).getType();
  98.             switch (type) {
  99.             case Constants.OBJ_COMMIT:
  100.                 commits.set(i);
  101.                 break;
  102.             case Constants.OBJ_TREE:
  103.                 trees.set(i);
  104.                 break;
  105.             case Constants.OBJ_BLOB:
  106.                 blobs.set(i);
  107.                 break;
  108.             case Constants.OBJ_TAG:
  109.                 tags.set(i);
  110.                 break;
  111.             default:
  112.                 throw new IllegalArgumentException(MessageFormat.format(
  113.                         JGitText.get().badObjectType, String.valueOf(type)));
  114.             }
  115.         }
  116.         commits.trim();
  117.         trees.trim();
  118.         blobs.trim();
  119.         tags.trim();
  120.     }

  121.     private static void sortByOffsetAndIndex(BlockList<PositionEntry> byOffset,
  122.             ObjectIdOwnerMap<PositionEntry> positionEntries,
  123.             List<ObjectToPack> entries) {
  124.         for (int i = 0; i < entries.size(); i++) {
  125.             positionEntries.add(new PositionEntry(entries.get(i), i));
  126.         }
  127.         Collections.sort(entries, new Comparator<ObjectToPack>() {
  128.             @Override
  129.             public int compare(ObjectToPack a, ObjectToPack b) {
  130.                 return Long.signum(a.getOffset() - b.getOffset());
  131.             }
  132.         });
  133.         for (int i = 0; i < entries.size(); i++) {
  134.             PositionEntry e = positionEntries.get(entries.get(i));
  135.             e.offsetPosition = i;
  136.             byOffset.add(e);
  137.         }
  138.     }

  139.     /**
  140.      * Get set of objects included in the pack.
  141.      *
  142.      * @return set of objects included in the pack.
  143.      */
  144.     public ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> getObjectSet() {
  145.         ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> r = new ObjectIdOwnerMap<>();
  146.         for (PositionEntry e : byOffset) {
  147.             r.add(new ObjectIdOwnerMap.Entry(e) {
  148.                 // A new entry that copies the ObjectId
  149.             });
  150.         }
  151.         return r;
  152.     }

  153.     /**
  154.      * Stores the bitmap for the objectId.
  155.      *
  156.      * @param objectId
  157.      *            the object id key for the bitmap.
  158.      * @param bitmap
  159.      *            the bitmap
  160.      * @param flags
  161.      *            the flags to be stored with the bitmap
  162.      */
  163.     public void addBitmap(AnyObjectId objectId, Bitmap bitmap, int flags) {
  164.         if (bitmap instanceof BitmapBuilder)
  165.             bitmap = ((BitmapBuilder) bitmap).build();

  166.         EWAHCompressedBitmap compressed;
  167.         if (bitmap instanceof CompressedBitmap)
  168.             compressed = ((CompressedBitmap) bitmap).getEwahCompressedBitmap();
  169.         else
  170.             throw new IllegalArgumentException(bitmap.getClass().toString());

  171.         addBitmap(objectId, compressed, flags);
  172.     }

  173.     /**
  174.      * Stores the bitmap for the objectId.
  175.      *
  176.      * @param objectId
  177.      *            the object id key for the bitmap.
  178.      * @param bitmap
  179.      *            the bitmap
  180.      * @param flags
  181.      *            the flags to be stored with the bitmap
  182.      */
  183.     public void addBitmap(
  184.             AnyObjectId objectId, EWAHCompressedBitmap bitmap, int flags) {
  185.         bitmap.trim();
  186.         StoredBitmap result = new StoredBitmap(objectId, bitmap, null, flags);
  187.         getBitmaps().add(result);
  188.         byAddOrder.add(result);
  189.     }

  190.     /** {@inheritDoc} */
  191.     @Override
  192.     public EWAHCompressedBitmap ofObjectType(
  193.             EWAHCompressedBitmap bitmap, int type) {
  194.         switch (type) {
  195.         case Constants.OBJ_BLOB:
  196.             return getBlobs().and(bitmap);
  197.         case Constants.OBJ_TREE:
  198.             return getTrees().and(bitmap);
  199.         case Constants.OBJ_COMMIT:
  200.             return getCommits().and(bitmap);
  201.         case Constants.OBJ_TAG:
  202.             return getTags().and(bitmap);
  203.         }
  204.         throw new IllegalArgumentException();
  205.     }

  206.     /** {@inheritDoc} */
  207.     @Override
  208.     public int findPosition(AnyObjectId objectId) {
  209.         PositionEntry entry = positionEntries.get(objectId);
  210.         if (entry == null)
  211.             return -1;
  212.         return entry.offsetPosition;
  213.     }

  214.     /** {@inheritDoc} */
  215.     @Override
  216.     public ObjectId getObject(int position) throws IllegalArgumentException {
  217.         ObjectId objectId = byOffset.get(position);
  218.         if (objectId == null)
  219.             throw new IllegalArgumentException();
  220.         return objectId;
  221.     }

  222.     /**
  223.      * Get the commit object bitmap.
  224.      *
  225.      * @return the commit object bitmap.
  226.      */
  227.     public EWAHCompressedBitmap getCommits() {
  228.         return commits;
  229.     }

  230.     /**
  231.      * Get the tree object bitmap.
  232.      *
  233.      * @return the tree object bitmap.
  234.      */
  235.     public EWAHCompressedBitmap getTrees() {
  236.         return trees;
  237.     }

  238.     /**
  239.      * Get the blob object bitmap.
  240.      *
  241.      * @return the blob object bitmap.
  242.      */
  243.     public EWAHCompressedBitmap getBlobs() {
  244.         return blobs;
  245.     }

  246.     /**
  247.      * Get the tag object bitmap.
  248.      *
  249.      * @return the tag object bitmap.
  250.      */
  251.     public EWAHCompressedBitmap getTags() {
  252.         return tags;
  253.     }

  254.     /**
  255.      * Get the index storage options.
  256.      *
  257.      * @return the index storage options.
  258.      */
  259.     public int getOptions() {
  260.         return PackBitmapIndexV1.OPT_FULL;
  261.     }

  262.     /** {@inheritDoc} */
  263.     @Override
  264.     public int getBitmapCount() {
  265.         return getBitmaps().size();
  266.     }

  267.     /**
  268.      * Remove all the bitmaps entries added.
  269.      */
  270.     public void clearBitmaps() {
  271.         byAddOrder.clear();
  272.         getBitmaps().clear();
  273.     }

  274.     /** {@inheritDoc} */
  275.     @Override
  276.     public int getObjectCount() {
  277.         return byOffset.size();
  278.     }

  279.     /**
  280.      * Get an iterator over the xor compressed entries.
  281.      *
  282.      * @return an iterator over the xor compressed entries.
  283.      */
  284.     public Iterable<StoredEntry> getCompressedBitmaps() {
  285.         // Add order is from oldest to newest. The reverse add order is the
  286.         // output order.
  287.         return new Iterable<StoredEntry>() {
  288.             @Override
  289.             public Iterator<StoredEntry> iterator() {
  290.                 return new Iterator<StoredEntry>() {
  291.                     private int index = byAddOrder.size() - 1;

  292.                     @Override
  293.                     public boolean hasNext() {
  294.                         return index >= 0;
  295.                     }

  296.                     @Override
  297.                     public StoredEntry next() {
  298.                         if (!hasNext())
  299.                             throw new NoSuchElementException();
  300.                         StoredBitmap item = byAddOrder.get(index);
  301.                         int bestXorOffset = 0;
  302.                         EWAHCompressedBitmap bestBitmap = item.getBitmap();

  303.                         // Attempt to compress the bitmap with an XOR of the
  304.                         // previously written entries.
  305.                         for (int i = 1; i <= MAX_XOR_OFFSET_SEARCH; i++) {
  306.                             int curr = i + index;
  307.                             if (curr >= byAddOrder.size())
  308.                                 break;

  309.                             StoredBitmap other = byAddOrder.get(curr);
  310.                             EWAHCompressedBitmap bitmap = other.getBitmap()
  311.                                     .xor(item.getBitmap());

  312.                             if (bitmap.sizeInBytes()
  313.                                     < bestBitmap.sizeInBytes()) {
  314.                                 bestBitmap = bitmap;
  315.                                 bestXorOffset = i;
  316.                             }
  317.                         }
  318.                         index--;

  319.                         PositionEntry entry = positionEntries.get(item);
  320.                         if (entry == null)
  321.                             throw new IllegalStateException();
  322.                         bestBitmap.trim();
  323.                         return new StoredEntry(entry.namePosition, bestBitmap,
  324.                                 bestXorOffset, item.getFlags());
  325.                     }

  326.                     @Override
  327.                     public void remove() {
  328.                         throw new UnsupportedOperationException();
  329.                     }
  330.                 };
  331.             }
  332.         };
  333.     }

  334.     /** Data object for the on disk representation of a bitmap entry. */
  335.     public static final class StoredEntry {
  336.         private final long objectId;
  337.         private final EWAHCompressedBitmap bitmap;
  338.         private final int xorOffset;
  339.         private final int flags;

  340.         StoredEntry(long objectId, EWAHCompressedBitmap bitmap,
  341.                 int xorOffset, int flags) {
  342.             this.objectId = objectId;
  343.             this.bitmap = bitmap;
  344.             this.xorOffset = xorOffset;
  345.             this.flags = flags;
  346.         }

  347.         /** @return the bitmap */
  348.         public EWAHCompressedBitmap getBitmap() {
  349.             return bitmap;
  350.         }

  351.         /** @return the xorOffset */
  352.         public int getXorOffset() {
  353.             return xorOffset;
  354.         }

  355.         /** @return the flags */
  356.         public int getFlags() {
  357.             return flags;
  358.         }

  359.         /** @return the ObjectId */
  360.         public long getObjectId() {
  361.             return objectId;
  362.         }
  363.     }

  364.     private static final class PositionEntry extends ObjectIdOwnerMap.Entry {
  365.         final int namePosition;

  366.         int offsetPosition;

  367.         PositionEntry(AnyObjectId objectId, int namePosition) {
  368.             super(objectId);
  369.             this.namePosition = namePosition;
  370.         }
  371.     }
  372. }