BitmapIndexImpl.java
/*
* Copyright (C) 2012, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.internal.storage.file;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.BitmapIndex;
import org.eclipse.jgit.lib.BitmapObject;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdOwnerMap;
import org.eclipse.jgit.util.BlockList;
import com.googlecode.javaewah.EWAHCompressedBitmap;
import com.googlecode.javaewah.IntIterator;
/**
* A compressed bitmap representation of the entire object graph.
*/
public class BitmapIndexImpl implements BitmapIndex {
private static final int EXTRA_BITS = 10 * 1024;
final PackBitmapIndex packIndex;
final MutableBitmapIndex mutableIndex;
final int indexObjectCount;
/**
* Creates a BitmapIndex that is back by Compressed bitmaps.
*
* @param packIndex
* the bitmap index for the pack.
*/
public BitmapIndexImpl(PackBitmapIndex packIndex) {
this.packIndex = packIndex;
mutableIndex = new MutableBitmapIndex();
indexObjectCount = packIndex.getObjectCount();
}
PackBitmapIndex getPackBitmapIndex() {
return packIndex;
}
/** {@inheritDoc} */
@Override
public CompressedBitmap getBitmap(AnyObjectId objectId) {
EWAHCompressedBitmap compressed = packIndex.getBitmap(objectId);
if (compressed == null)
return null;
return new CompressedBitmap(compressed, this);
}
/** {@inheritDoc} */
@Override
public CompressedBitmapBuilder newBitmapBuilder() {
return new CompressedBitmapBuilder(this);
}
int findPosition(AnyObjectId objectId) {
int position = packIndex.findPosition(objectId);
if (position < 0) {
position = mutableIndex.findPosition(objectId);
if (position >= 0)
position += indexObjectCount;
}
return position;
}
int findOrInsert(AnyObjectId objectId, int type) {
int position = findPosition(objectId);
if (position < 0) {
position = mutableIndex.findOrInsert(objectId, type);
position += indexObjectCount;
}
return position;
}
private static final class ComboBitset {
private InflatingBitSet inflatingBitmap;
private BitSet toAdd;
private BitSet toRemove;
ComboBitset() {
this(new EWAHCompressedBitmap());
}
ComboBitset(EWAHCompressedBitmap bitmap) {
this.inflatingBitmap = new InflatingBitSet(bitmap);
}
EWAHCompressedBitmap combine() {
EWAHCompressedBitmap toAddCompressed = null;
if (toAdd != null) {
toAddCompressed = toAdd.toEWAHCompressedBitmap();
toAdd = null;
}
EWAHCompressedBitmap toRemoveCompressed = null;
if (toRemove != null) {
toRemoveCompressed = toRemove.toEWAHCompressedBitmap();
toRemove = null;
}
if (toAddCompressed != null)
or(toAddCompressed);
if (toRemoveCompressed != null)
andNot(toRemoveCompressed);
return inflatingBitmap.getBitmap();
}
void or(EWAHCompressedBitmap inbits) {
if (toRemove != null)
combine();
inflatingBitmap = inflatingBitmap.or(inbits);
}
void andNot(EWAHCompressedBitmap inbits) {
if (toAdd != null || toRemove != null)
combine();
inflatingBitmap = inflatingBitmap.andNot(inbits);
}
void xor(EWAHCompressedBitmap inbits) {
if (toAdd != null || toRemove != null)
combine();
inflatingBitmap = inflatingBitmap.xor(inbits);
}
boolean contains(int position) {
if (toRemove != null && toRemove.get(position))
return false;
if (toAdd != null && toAdd.get(position))
return true;
return inflatingBitmap.contains(position);
}
void remove(int position) {
if (toAdd != null)
toAdd.clear(position);
if (inflatingBitmap.maybeContains(position)) {
if (toRemove == null)
toRemove = new BitSet(position + EXTRA_BITS);
toRemove.set(position);
}
}
void set(int position) {
if (toRemove != null)
toRemove.clear(position);
if (toAdd == null)
toAdd = new BitSet(position + EXTRA_BITS);
toAdd.set(position);
}
}
private static final class CompressedBitmapBuilder implements BitmapBuilder {
private ComboBitset bitset;
private final BitmapIndexImpl bitmapIndex;
CompressedBitmapBuilder(BitmapIndexImpl bitmapIndex) {
this.bitset = new ComboBitset();
this.bitmapIndex = bitmapIndex;
}
@Override
public boolean contains(AnyObjectId objectId) {
int position = bitmapIndex.findPosition(objectId);
return 0 <= position && bitset.contains(position);
}
@Override
public BitmapBuilder addObject(AnyObjectId objectId, int type) {
bitset.set(bitmapIndex.findOrInsert(objectId, type));
return this;
}
@Override
public void remove(AnyObjectId objectId) {
int position = bitmapIndex.findPosition(objectId);
if (0 <= position)
bitset.remove(position);
}
@Override
public CompressedBitmapBuilder or(Bitmap other) {
bitset.or(ewahBitmap(other));
return this;
}
@Override
public CompressedBitmapBuilder andNot(Bitmap other) {
bitset.andNot(ewahBitmap(other));
return this;
}
@Override
public CompressedBitmapBuilder xor(Bitmap other) {
bitset.xor(ewahBitmap(other));
return this;
}
/** @return the fully built immutable bitmap */
@Override
public CompressedBitmap build() {
return new CompressedBitmap(bitset.combine(), bitmapIndex);
}
@Override
public Iterator<BitmapObject> iterator() {
return build().iterator();
}
@Override
public int cardinality() {
return bitset.combine().cardinality();
}
@Override
public boolean removeAllOrNone(PackBitmapIndex index) {
if (!bitmapIndex.packIndex.equals(index))
return false;
EWAHCompressedBitmap curr = bitset.combine()
.xor(ones(bitmapIndex.indexObjectCount));
IntIterator ii = curr.intIterator();
if (ii.hasNext() && ii.next() < bitmapIndex.indexObjectCount)
return false;
bitset = new ComboBitset(curr);
return true;
}
@Override
public BitmapIndexImpl getBitmapIndex() {
return bitmapIndex;
}
private EWAHCompressedBitmap ewahBitmap(Bitmap other) {
if (other instanceof CompressedBitmap) {
CompressedBitmap b = (CompressedBitmap) other;
if (b.bitmapIndex != bitmapIndex) {
throw new IllegalArgumentException();
}
return b.bitmap;
}
if (other instanceof CompressedBitmapBuilder) {
CompressedBitmapBuilder b = (CompressedBitmapBuilder) other;
if (b.bitmapIndex != bitmapIndex) {
throw new IllegalArgumentException();
}
return b.bitset.combine();
}
throw new IllegalArgumentException();
}
}
/**
* Wrapper for a {@link EWAHCompressedBitmap} and {@link PackBitmapIndex}.
* <p>
* For a EWAHCompressedBitmap {@code bitmap} representing a vector of
* bits, {@code new CompressedBitmap(bitmap, bitmapIndex)} represents the
* objects at those positions in {@code bitmapIndex.packIndex}.
*/
public static final class CompressedBitmap implements Bitmap {
final EWAHCompressedBitmap bitmap;
final BitmapIndexImpl bitmapIndex;
/**
* Construct compressed bitmap for given bitmap and bitmap index
*
* @param bitmap
* @param bitmapIndex
*/
public CompressedBitmap(EWAHCompressedBitmap bitmap, BitmapIndexImpl bitmapIndex) {
this.bitmap = bitmap;
this.bitmapIndex = bitmapIndex;
}
@Override
public CompressedBitmap or(Bitmap other) {
return new CompressedBitmap(bitmap.or(ewahBitmap(other)), bitmapIndex);
}
@Override
public CompressedBitmap andNot(Bitmap other) {
return new CompressedBitmap(bitmap.andNot(ewahBitmap(other)), bitmapIndex);
}
@Override
public CompressedBitmap xor(Bitmap other) {
return new CompressedBitmap(bitmap.xor(ewahBitmap(other)), bitmapIndex);
}
private final IntIterator ofObjectType(int type) {
return bitmapIndex.packIndex.ofObjectType(bitmap, type).intIterator();
}
@Override
public Iterator<BitmapObject> iterator() {
final IntIterator dynamic = bitmap.andNot(ones(bitmapIndex.indexObjectCount))
.intIterator();
final IntIterator commits = ofObjectType(Constants.OBJ_COMMIT);
final IntIterator trees = ofObjectType(Constants.OBJ_TREE);
final IntIterator blobs = ofObjectType(Constants.OBJ_BLOB);
final IntIterator tags = ofObjectType(Constants.OBJ_TAG);
return new Iterator<BitmapObject>() {
private final BitmapObjectImpl out = new BitmapObjectImpl();
private int type;
private IntIterator cached = dynamic;
@Override
public boolean hasNext() {
if (!cached.hasNext()) {
if (commits.hasNext()) {
type = Constants.OBJ_COMMIT;
cached = commits;
} else if (trees.hasNext()) {
type = Constants.OBJ_TREE;
cached = trees;
} else if (blobs.hasNext()) {
type = Constants.OBJ_BLOB;
cached = blobs;
} else if (tags.hasNext()) {
type = Constants.OBJ_TAG;
cached = tags;
} else {
return false;
}
}
return true;
}
@Override
public BitmapObject next() {
if (!hasNext())
throw new NoSuchElementException();
int position = cached.next();
if (position < bitmapIndex.indexObjectCount) {
out.type = type;
out.objectId = bitmapIndex.packIndex.getObject(position);
} else {
position -= bitmapIndex.indexObjectCount;
MutableEntry entry = bitmapIndex.mutableIndex.getObject(position);
out.type = entry.type;
out.objectId = entry;
}
return out;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
EWAHCompressedBitmap getEwahCompressedBitmap() {
return bitmap;
}
private EWAHCompressedBitmap ewahBitmap(Bitmap other) {
if (other instanceof CompressedBitmap) {
CompressedBitmap b = (CompressedBitmap) other;
if (b.bitmapIndex != bitmapIndex) {
throw new IllegalArgumentException();
}
return b.bitmap;
}
if (other instanceof CompressedBitmapBuilder) {
CompressedBitmapBuilder b = (CompressedBitmapBuilder) other;
if (b.bitmapIndex != bitmapIndex) {
throw new IllegalArgumentException();
}
return b.bitset.combine();
}
throw new IllegalArgumentException();
}
}
private static final class MutableBitmapIndex {
private final ObjectIdOwnerMap<MutableEntry>
revMap = new ObjectIdOwnerMap<>();
private final BlockList<MutableEntry>
revList = new BlockList<>();
int findPosition(AnyObjectId objectId) {
MutableEntry entry = revMap.get(objectId);
if (entry == null)
return -1;
return entry.position;
}
MutableEntry getObject(int position) {
try {
MutableEntry entry = revList.get(position);
if (entry == null)
throw new IllegalArgumentException(MessageFormat.format(
JGitText.get().objectNotFound,
String.valueOf(position)));
return entry;
} catch (IndexOutOfBoundsException ex) {
throw new IllegalArgumentException(ex);
}
}
int findOrInsert(AnyObjectId objectId, int type) {
MutableEntry entry = new MutableEntry(
objectId, type, revList.size());
revList.add(entry);
revMap.add(entry);
return entry.position;
}
}
private static final class MutableEntry extends ObjectIdOwnerMap.Entry {
final int type;
final int position;
MutableEntry(AnyObjectId objectId, int type, int position) {
super(objectId);
this.type = type;
this.position = position;
}
}
private static final class BitmapObjectImpl extends BitmapObject {
private ObjectId objectId;
private int type;
@Override
public ObjectId getObjectId() {
return objectId;
}
@Override
public int getType() {
return type;
}
}
static final EWAHCompressedBitmap ones(int sizeInBits) {
EWAHCompressedBitmap mask = new EWAHCompressedBitmap();
mask.addStreamOfEmptyWords(
true, sizeInBits / EWAHCompressedBitmap.WORD_IN_BITS);
int remaining = sizeInBits % EWAHCompressedBitmap.WORD_IN_BITS;
if (remaining > 0)
mask.addWord((1L << remaining) - 1, remaining);
return mask;
}
}