blob: 90f29dccb4d1f854b2e33bb40a32badfbb5ce8cb [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;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.ThreadLocalCachedIntArray;
import com.intellij.util.SmartList;
import com.intellij.util.containers.EmptyIterator;
import com.intellij.util.indexing.containers.ChangeBufferingList;
import com.intellij.util.indexing.containers.IdSet;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.DataInputOutputUtil;
import gnu.trove.THashMap;
import gnu.trove.TObjectObjectProcedure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;
/**
* @author Eugene Zhuravlev
* Date: Dec 20, 2007
*/
class ValueContainerImpl<Value> extends UpdatableValueContainer<Value> implements Cloneable{
private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.ValueContainerImpl");
private final static Object myNullValue = new Object();
// there is no volatile as we modify under write lock and read under read lock
// Most often (80%) we store 0 or one mapping, then we store them in two fields: myInputIdMapping, myInputIdMappingValue
// when there are several value mapped, myInputIdMapping is THashMap<Value, Data>, myInputIdMappingValue = null
private Object myInputIdMapping;
private Object myInputIdMappingValue;
@Override
public void addValue(int inputId, Value value) {
final Object fileSetObject = getFileSetObject(value);
if (fileSetObject == null) {
attachFileSetForNewValue(value, inputId);
}
else if (fileSetObject instanceof Integer) {
ChangeBufferingList list = new ChangeBufferingList();
list.add(((Integer)fileSetObject).intValue());
list.add(inputId);
resetFileSetForValue(value, list);
}
else {
((ChangeBufferingList)fileSetObject).add(inputId);
}
}
private void resetFileSetForValue(Value value, Object fileSet) {
if (!(myInputIdMapping instanceof THashMap)) myInputIdMappingValue = fileSet;
else ((THashMap<Value, Object>)myInputIdMapping).put(value, fileSet);
}
@Override
public int size() {
return myInputIdMapping != null ? myInputIdMapping instanceof THashMap ? ((THashMap)myInputIdMapping).size(): 1 : 0;
}
static final ThreadLocal<ID> ourDebugIndexInfo = new ThreadLocal<ID>();
@Override
public void removeAssociatedValue(int inputId) {
if (myInputIdMapping == null) return;
List<Object> fileSetObjects = null;
List<Value> valueObjects = null;
for (final ValueIterator<Value> valueIterator = getValueIterator(); valueIterator.hasNext();) {
final Value value = valueIterator.next();
if (valueIterator.getValueAssociationPredicate().contains(inputId)) {
if (fileSetObjects == null) {
fileSetObjects = new SmartList<Object>();
valueObjects = new SmartList<Value>();
}
else if (DebugAssertions.DEBUG) {
LOG.error("Expected only one value per-inputId for " + ourDebugIndexInfo.get(), String.valueOf(fileSetObjects.get(0)), String.valueOf(value));
}
fileSetObjects.add(valueIterator.getFileSetObject());
valueObjects.add(value);
}
}
if (fileSetObjects != null) {
for (int i = 0, len = valueObjects.size(); i < len; ++i) {
removeValue(inputId, fileSetObjects.get(i), valueObjects.get(i));
}
}
}
void removeValue(int inputId, Value value) {
removeValue(inputId, getFileSetObject(value), value);
}
private void removeValue(int inputId, Object fileSet, Value value) {
if (fileSet == null) {
return;
}
if (fileSet instanceof ChangeBufferingList) {
final ChangeBufferingList changesList = (ChangeBufferingList)fileSet;
changesList.remove(inputId);
if (!changesList.isEmpty()) return;
}
else if (fileSet instanceof Integer) {
if (((Integer)fileSet).intValue() != inputId) {
return;
}
}
if (!(myInputIdMapping instanceof THashMap)) {
myInputIdMapping = null;
myInputIdMappingValue = null;
} else {
THashMap<Value, Object> mapping = (THashMap<Value, Object>)myInputIdMapping;
mapping.remove(value);
if (mapping.size() == 1) {
myInputIdMapping = mapping.keySet().iterator().next();
myInputIdMappingValue = mapping.get((Value)myInputIdMapping);
}
}
}
@NotNull
@Override
public ValueIterator<Value> getValueIterator() {
if (myInputIdMapping != null) {
if (!(myInputIdMapping instanceof THashMap)) {
return new ValueIterator<Value>() {
private Value value = (Value)myInputIdMapping;
@NotNull
@Override
public IntIterator getInputIdsIterator() {
return getIntIteratorOutOfFileSetObject(getFileSetObject());
}
@NotNull
@Override
public IntPredicate getValueAssociationPredicate() {
return getPredicateOutOfFileSetObject(getFileSetObject());
}
@Override
public Object getFileSetObject() {
return myInputIdMappingValue;
}
@Override
public boolean hasNext() {
return value != null;
}
@Override
public Value next() {
Value next = value;
if (next == myNullValue) next = null;
value = null;
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
} else {
return new ValueIterator<Value>() {
private Map.Entry<Value, Object> current;
final Iterator<Map.Entry<Value, Object>> iterator = ((THashMap<Value, Object>)myInputIdMapping).entrySet().iterator();
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Value next() {
Value next = (current = iterator.next()).getKey();
if (next == myNullValue) next = null;
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public IntIterator getInputIdsIterator() {
return getIntIteratorOutOfFileSetObject(getFileSetObject());
}
@NotNull
@Override
public IntPredicate getValueAssociationPredicate() {
return getPredicateOutOfFileSetObject(getFileSetObject());
}
@Override
public Object getFileSetObject() {
if (current == null) throw new IllegalStateException();
return current.getValue();
}
};
}
} else {
return emptyIterator;
}
}
static class EmptyValueIterator<Value> extends EmptyIterator<Value> implements ValueIterator<Value> {
@NotNull
@Override
public IntIterator getInputIdsIterator() {
throw new IllegalStateException();
}
@NotNull
@Override
public IntPredicate getValueAssociationPredicate() {
throw new IllegalStateException();
}
@Override
public Object getFileSetObject() {
throw new IllegalStateException();
}
}
private static final EmptyValueIterator emptyIterator = new EmptyValueIterator();
@NotNull
@Override
public List<Value> toValueList() {
if (myInputIdMapping == null) {
return Collections.emptyList();
} else if (myInputIdMapping instanceof THashMap) {
return new ArrayList<Value>(((THashMap<Value, Object>)myInputIdMapping).keySet());
} else {
return new SmartList<Value>((Value)myInputIdMapping);
}
}
@NotNull
@Override
public IntPredicate getValueAssociationPredicate(Value value) {
return getPredicateOutOfFileSetObject(getFileSetObject(value));
}
private static @NotNull IntPredicate getPredicateOutOfFileSetObject(@Nullable Object input) {
if (input == null) return EMPTY_PREDICATE;
if (input instanceof Integer) {
final int singleId = (Integer)input;
return new IntPredicate() {
@Override
public boolean contains(int id) {
return id == singleId;
}
};
}
return ((ChangeBufferingList)input).intPredicate();
}
@NotNull
@Override
public IntIterator getInputIdsIterator(Value value) {
return getIntIteratorOutOfFileSetObject(getFileSetObject(value));
}
private static @NotNull IntIterator getIntIteratorOutOfFileSetObject(@Nullable Object input) {
if (input == null) return EMPTY_ITERATOR;
if (input instanceof Integer){
return new SingleValueIterator(((Integer)input).intValue());
} else {
return ((ChangeBufferingList)input).intIterator();
}
}
private Object getFileSetObject(Value value) {
if (myInputIdMapping == null) return null;
value = value != null ? value:(Value)myNullValue;
if (myInputIdMapping == value || // myNullValue is Object
myInputIdMapping.equals(value)
) {
return myInputIdMappingValue;
}
if (!(myInputIdMapping instanceof THashMap)) return null;
return ((THashMap<Value, Object>)myInputIdMapping).get(value);
}
@Override
public ValueContainerImpl<Value> clone() {
try {
final ValueContainerImpl clone = (ValueContainerImpl)super.clone();
if (myInputIdMapping instanceof THashMap) {
clone.myInputIdMapping = mapCopy((THashMap<Value, Object>)myInputIdMapping);
} else if (myInputIdMappingValue instanceof ChangeBufferingList) {
clone.myInputIdMappingValue = ((ChangeBufferingList)myInputIdMappingValue).clone();
}
return clone;
}
catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
private static final IntIterator EMPTY_ITERATOR = new IntIterator() {
@Override
public boolean hasNext() {
return false;
}
@Override
public int next() {
return 0;
}
@Override
public int size() {
return 0;
}
@Override
public boolean hasAscendingOrder() {
return true;
}
@Override
public IntIterator createCopyInInitialState() {
return this;
}
};
@NotNull
public ValueContainerImpl<Value> copy() {
ValueContainerImpl<Value> container = new ValueContainerImpl<Value>();
if (myInputIdMapping instanceof THashMap) {
final THashMap<Value, Object> mapping = (THashMap<Value, Object>)myInputIdMapping;
final THashMap<Value, Object> newMapping = new THashMap<Value, Object>(mapping.size());
container.myInputIdMapping = newMapping;
mapping.forEachEntry(new TObjectObjectProcedure<Value, Object>() {
@Override
public boolean execute(Value key, Object val) {
if (val instanceof ChangeBufferingList) {
newMapping.put(key, ((ChangeBufferingList)val).clone());
} else {
newMapping.put(key, val);
}
return true;
}
});
} else {
container.myInputIdMapping = myInputIdMapping;
container.myInputIdMappingValue = myInputIdMappingValue instanceof ChangeBufferingList ?
((ChangeBufferingList)myInputIdMappingValue).clone():
myInputIdMappingValue;
}
return container;
}
private void ensureFileSetCapacityForValue(Value value, int count) {
if (count <= 1) return;
Object fileSetObject = getFileSetObject(value);
if (fileSetObject != null) {
if (fileSetObject instanceof Integer) {
ChangeBufferingList list = new ChangeBufferingList(count + 1);
list.add(((Integer)fileSetObject).intValue());
resetFileSetForValue(value, list);
} else if (fileSetObject instanceof ChangeBufferingList) {
ChangeBufferingList list = (ChangeBufferingList)fileSetObject;
list.ensureCapacity(count);
}
return;
}
final Object fileSet = new ChangeBufferingList(count);
attachFileSetForNewValue(value, fileSet);
}
private void attachFileSetForNewValue(Value value, Object fileSet) {
value = value != null ? value:(Value)myNullValue;
if (myInputIdMapping != null) {
if (!(myInputIdMapping instanceof THashMap)) {
Object oldMapping = myInputIdMapping;
myInputIdMapping = new THashMap<Value, Object>(2);
((THashMap<Value, Object>)myInputIdMapping).put((Value)oldMapping, myInputIdMappingValue);
myInputIdMappingValue = null;
}
((THashMap<Value, Object>)myInputIdMapping).put(value, fileSet);
} else {
myInputIdMapping = value;
myInputIdMappingValue = fileSet;
}
}
private static final ThreadLocalCachedIntArray ourSpareBuffer = new ThreadLocalCachedIntArray();
private static final int INT_BITS_SHIFT = 5;
private static int nextSetBit(int bitIndex, int[] bits, int bitsLength) {
int wordIndex = bitIndex >> INT_BITS_SHIFT;
if (wordIndex >= bitsLength) {
return -1;
}
int word = bits[wordIndex] & (-1 << bitIndex);
while (true) {
if (word != 0) {
return (wordIndex << INT_BITS_SHIFT) + Long.numberOfTrailingZeros(word);
}
if (++wordIndex == bitsLength) {
return -1;
}
word = bits[wordIndex];
}
}
@Override
public void saveTo(DataOutput out, DataExternalizer<Value> externalizer) throws IOException {
DataInputOutputUtil.writeINT(out, size());
for (final ValueIterator<Value> valueIterator = getValueIterator(); valueIterator.hasNext();) {
final Value value = valueIterator.next();
externalizer.save(out, value);
Object fileSetObject = valueIterator.getFileSetObject();
if (fileSetObject instanceof Integer) {
DataInputOutputUtil.writeINT(out, (Integer)fileSetObject); // most common 90% case during index building
} else {
// serialize positive file ids with delta encoding
ChangeBufferingList originalInput = (ChangeBufferingList)fileSetObject;
IntIterator intIterator = originalInput.rawIntIterator();
DataInputOutputUtil.writeINT(out, -intIterator.size());
if (intIterator.hasAscendingOrder()) {
IdSet checkSet = originalInput.getCheckSet();
if (checkSet != null && checkSet.size() != intIterator.size()) { // debug code
int a = 1; assert false;
}
int prev = 0;
while (intIterator.hasNext()) {
int fileId = intIterator.next();
if (checkSet != null && !checkSet.contains(fileId)) { // debug code
int a = 1; assert false;
}
DataInputOutputUtil.writeINT(out, fileId - prev);
prev = fileId;
}
} else {
// sorting numbers via bitset before writing deltas
int max = 0, min = Integer.MAX_VALUE;
while(intIterator.hasNext()) {
int nextInt = intIterator.next();
max = Math.max(max, nextInt);
min = Math.min(min, nextInt);
}
assert min > 0;
final int offset = (min >> INT_BITS_SHIFT) << INT_BITS_SHIFT;
final int bitsLength = ((max - offset) >> INT_BITS_SHIFT) + 1;
final int[] bits = ourSpareBuffer.getBuffer(bitsLength);
for(int i = 0; i < bitsLength; ++i) bits[i] = 0;
intIterator = originalInput.rawIntIterator();
while(intIterator.hasNext()) {
final int id = intIterator.next() - offset;
bits[id >> INT_BITS_SHIFT] |= (1 << (id));
}
int pos = nextSetBit(0, bits, bitsLength);
int prev = 0;
while (pos != -1) {
DataInputOutputUtil.writeINT(out, pos + offset - prev);
prev = pos + offset;
pos = nextSetBit(pos + 1, bits, bitsLength);
}
}
}
}
}
static final int NUMBER_OF_VALUES_THRESHOLD = 20;
public void readFrom(DataInputStream stream, DataExternalizer<Value> externalizer) throws IOException {
FileId2ValueMapping<Value> mapping = null;
while (stream.available() > 0) {
final int valueCount = DataInputOutputUtil.readINT(stream);
if (valueCount < 0) {
// ChangeTrackingValueContainer marked inputId as invalidated, see ChangeTrackingValueContainer.saveTo
final int inputId = -valueCount;
if (mapping == null && size() > NUMBER_OF_VALUES_THRESHOLD) { // avoid O(NumberOfValues)
mapping = new FileId2ValueMapping<Value>(this);
}
boolean doCompact;
if(mapping != null) {
doCompact = mapping.removeFileId(inputId);
} else {
removeAssociatedValue(inputId);
doCompact = true;
}
if (doCompact) setNeedsCompacting(true);
}
else {
for (int valueIdx = 0; valueIdx < valueCount; valueIdx++) {
final Value value = externalizer.read(stream);
int idCountOrSingleValue = DataInputOutputUtil.readINT(stream);
if (idCountOrSingleValue > 0) {
addValue(idCountOrSingleValue, value);
if (mapping != null) mapping.associateFileIdToValue(idCountOrSingleValue, value);
} else {
idCountOrSingleValue = -idCountOrSingleValue;
ensureFileSetCapacityForValue(value, idCountOrSingleValue);
int prev = 0;
for (int i = 0; i < idCountOrSingleValue; i++) {
final int id = DataInputOutputUtil.readINT(stream);
addValue(prev + id, value);
if (mapping != null) mapping.associateFileIdToValue(prev + id, value);
prev += id;
}
}
}
}
}
}
private static class SingleValueIterator implements IntIterator {
private final int myValue;
private boolean myValueRead = false;
private SingleValueIterator(int value) {
myValue = value;
}
@Override
public boolean hasNext() {
return !myValueRead;
}
@Override
public int next() {
int next = myValue;
myValueRead = true;
return next;
}
@Override
public int size() {
return 1;
}
@Override
public boolean hasAscendingOrder() {
return true;
}
@Override
public IntIterator createCopyInInitialState() {
return new SingleValueIterator(myValue);
}
}
private THashMap<Value, Object> mapCopy(final THashMap<Value, Object> map) {
if (map == null) {
return null;
}
final THashMap<Value, Object> cloned = map.clone();
cloned.forEachEntry(new TObjectObjectProcedure<Value, Object>() {
@Override
public boolean execute(Value key, Object val) {
if (val instanceof ChangeBufferingList) {
cloned.put(key, ((ChangeBufferingList)val).clone());
}
return true;
}
});
return cloned;
}
private static final IntPredicate EMPTY_PREDICATE = new IntPredicate() {
@Override
public boolean contains(int id) {
return false;
}
};
}