blob: a1373c06a23f452b2a7719f0d4d06221891a57c2 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.indexing.containers;
import com.intellij.util.indexing.DebugAssertions;
import com.intellij.util.indexing.ValueContainer;
import gnu.trove.TIntProcedure;
import static com.intellij.util.indexing.DebugAssertions.DEBUG;
/**
* Class buffers changes in 2 modes:
* - Accumulating up to MAX_FILES changes appending them *sequentially* to changes array
* - Adding changes to randomAccessContainer once it is available: later happens if we accumulated many changes or external client queried
* state of the changes: asked for predicate, iterator, isEmpty, etc. We are trying hard to delay transformation of state upon 2nd reason for
* performance reasons.
* It is assumed that add / remove operations as well as read only operations are externally synchronized, the only synchronization is
* performed upon transforming changes array into randomAccessContainer because it can be done during read only operations in several threads
*/
public class ChangeBufferingList implements Cloneable {
static final int MAX_FILES = 20000; // less than Short.MAX_VALUE
//static final int MAX_FILES = 100;
private volatile int[] changes;
private short length;
private boolean hasRemovals;
private volatile boolean mayHaveDupes;
private volatile RandomAccessIntContainer randomAccessContainer;
private IdSet checkSet;
public ChangeBufferingList() { this(3); }
public ChangeBufferingList(int length) {
if (length > MAX_FILES) {
randomAccessContainer = new IdBitSet(length);
} else {
changes = new int[length];
}
checkSet = DEBUG ? new IdSet(length) : null;
}
static int[] calcMinMax(int[] set, int length) {
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for(int i = 0; i < length; ++i) {
max = Math.max(max, set[i]);
min = Math.min(min, set[i]);
}
return new int[] {min, max};
}
public void add(int value) {
ensureCapacity(1);
if (DEBUG) checkSet.add(value);
RandomAccessIntContainer intContainer = randomAccessContainer;
if (intContainer == null) {
addChange(value);
} else {
intContainer.add(value);
}
}
private void addChange(int value) {
changes[length++] = value;
if (value < 0 && !hasRemovals) hasRemovals = true;
if(!mayHaveDupes) mayHaveDupes = true;
}
public void remove(int value) {
if (DEBUG) checkSet.remove(value);
RandomAccessIntContainer intContainer = randomAccessContainer;
if (intContainer == null) {
ensureCapacity(1);
addChange(-value);
}
else {
boolean removed = intContainer.remove(value);
if (removed) intContainer.compact();
}
}
@Override
public Object clone() {
try {
ChangeBufferingList clone = (ChangeBufferingList)super.clone();
if (changes != null) clone.changes = changes.clone();
if (randomAccessContainer != null) {
clone.randomAccessContainer = (RandomAccessIntContainer)randomAccessContainer.clone();
}
if (checkSet != null) clone.checkSet = (IdSet)checkSet.clone();
return clone;
}
catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
private RandomAccessIntContainer getRandomAccessContainer() {
int[] currentChanges = changes;
if (currentChanges == null) return randomAccessContainer;
synchronized (currentChanges) {
currentChanges = changes;
if (currentChanges == null) return randomAccessContainer;
boolean copyChanges = true;
RandomAccessIntContainer idSet;
if (randomAccessContainer == null) {
int someElementsNumberEstimation = length;
int[] minMax = calcMinMax(changes, length);
// todo we can check these lengths instead of only relying upon reaching MAX_FILES
//int lengthOfBitSet = IdBitSet.sizeInBytes(minMax[1], minMax[0]);
//int lengthOfIntSet = 4 * length;
if (someElementsNumberEstimation < MAX_FILES) {
if (!hasRemovals) {
if (mayHaveDupes) {
mergeChangesRemovingDupes();
}
idSet = new SortedIdSet(currentChanges, length);
copyChanges = false;
} else {
idSet = new SortedIdSet(Math.max(someElementsNumberEstimation, 3));
}
}
else if (!hasRemovals) {
idSet = new IdBitSet(changes, length, 0);
copyChanges = false;
} else {
idSet = new IdBitSet(minMax, 0);
}
} else if (DEBUG) {
idSet = (RandomAccessIntContainer)randomAccessContainer.clone();
} else {
idSet = randomAccessContainer;
}
assert idSet != null;
if (copyChanges) {
for(int i = 0, len = length; i < len; ++i) {
int id = currentChanges[i];
if (id > 0) {
idSet.add(id);
} else {
idSet.remove(-id);
}
}
}
if (DEBUG) {
DebugAssertions.assertTrue(checkSet.size() == idSet.size());
final RandomAccessIntContainer finalIdSet = idSet;
checkSet.forEach(new TIntProcedure() {
@Override
public boolean execute(int value) {
DebugAssertions.assertTrue(finalIdSet.contains(value));
return true;
}
});
}
length = 0;
hasRemovals = false;
mayHaveDupes = false;
randomAccessContainer = idSet;
changes = null;
return randomAccessContainer;
}
}
private void mergeChangesRemovingDupes() { // duplicated ids can be present for some index due to cancellation of indexing for next index
int[] currentChanges = changes;
ValueContainer.IntIterator sorted = SortedFileIdSetIterator.getTransientIterator(new ChangesIterator(currentChanges, length));
int lastIndex = 0;
while(sorted.hasNext()) {
currentChanges[lastIndex++] = sorted.next();
}
length = (short)lastIndex;
mayHaveDupes = false;
}
public void ensureCapacity(int diff) {
RandomAccessIntContainer intContainer = randomAccessContainer;
if (length == MAX_FILES) {
intContainer = getRandomAccessContainer(); // transform into more compact storage
}
if (intContainer != null) {
randomAccessContainer = intContainer.ensureContainerCapacity(diff);
return;
}
if (changes == null) {
changes = new int[Math.max(3, diff)];
} else if (length + diff > changes.length) {
int[] newChanges = new int[calcNextArraySize(changes.length, length + diff)];
System.arraycopy(changes, 0, newChanges, 0, length);
changes = newChanges;
}
}
static int calcNextArraySize(int currentSize, int wantedSize) {
return Math.min(
Math.max(currentSize < 1024 ? currentSize << 1 : currentSize + currentSize / 5, wantedSize),
MAX_FILES
);
}
public boolean isEmpty() {
if (randomAccessContainer == null) {
if (changes == null) {
if (DEBUG) DebugAssertions.assertTrue(checkSet.isEmpty());
return true;
}
if (!hasRemovals) {
boolean b = length == 0;
if (DEBUG) DebugAssertions.assertTrue(b == checkSet.isEmpty());
return b;
}
}
// todo we can calculate isEmpty in more cases (without container)
RandomAccessIntContainer intContainer = getRandomAccessContainer();
return intContainer.size() == 0;
}
public ValueContainer.IntPredicate intPredicate() {
final ValueContainer.IntPredicate predicate = getRandomAccessContainer().intPredicate();
if (DEBUG) {
return new ValueContainer.IntPredicate() {
@Override
public boolean contains(int id) {
boolean answer = predicate.contains(id);
DebugAssertions.assertTrue(answer == checkSet.contains(id));
return answer;
}
};
}
return predicate;
}
public ValueContainer.IntIterator intIterator() {
RandomAccessIntContainer intContainer = randomAccessContainer;
if (intContainer == null && !hasRemovals) {
int[] currentChanges = changes;
if (currentChanges != null) {
if (mayHaveDupes) {
synchronized (currentChanges) {
if (mayHaveDupes) mergeChangesRemovingDupes();
}
}
return new ChangesIterator(currentChanges, length);
}
}
return getRandomAccessContainer().intIterator();
}
public ValueContainer.IntIterator rawIntIterator() {
RandomAccessIntContainer intContainer = randomAccessContainer;
if (intContainer == null && !hasRemovals) {
return new ChangesIterator(changes, length); // dupes are possible
}
return getRandomAccessContainer().intIterator();
}
public IdSet getCheckSet() {
return checkSet;
}
private static class ChangesIterator implements ValueContainer.IntIterator {
private int cursor;
private final int length;
private final int[] changes;
ChangesIterator(int[] _changes, int _length) {
changes = _changes;
length = _length;
}
@Override
public boolean hasNext() {
return cursor < length;
}
@Override
public int next() {
int current = cursor;
++cursor;
return changes[current];
}
@Override
public int size() {
return length;
}
@Override
public boolean hasAscendingOrder() {
return false;
}
@Override
public ValueContainer.IntIterator createCopyInInitialState() {
return new ChangesIterator(changes, length);
}
}
}