diff --git a/platform/lang-impl/src/com/intellij/util/indexing/ChangeTrackingValueContainer.java b/platform/lang-impl/src/com/intellij/util/indexing/ChangeTrackingValueContainer.java index f41bc79ec6e3..660d759fcf48 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/ChangeTrackingValueContainer.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/ChangeTrackingValueContainer.java @@ -19,6 +19,7 @@ package com.intellij.util.indexing; import com.intellij.openapi.util.Computable; import gnu.trove.TIntHashSet; import gnu.trove.TIntProcedure; +import org.jetbrains.annotations.Nullable; import java.util.Iterator; import java.util.List; @@ -28,11 +29,11 @@ import java.util.List; * Date: Dec 20, 2007 */ class ChangeTrackingValueContainer extends UpdatableValueContainer{ - private final ValueContainerImpl myAdded; - private final ValueContainerImpl myRemoved; - private final TIntHashSet myInvalidated; + // there is no volatile as we modify under write lock and read under read lock + private ValueContainerImpl myAdded; + private TIntHashSet myInvalidated; + private volatile ValueContainerImpl myMerged; private final Initializer myInitializer; - private volatile ValueContainerImpl myMerged = null; public interface Initializer extends Computable> { Object getLock(); @@ -40,51 +41,48 @@ class ChangeTrackingValueContainer extends UpdatableValueContainer public ChangeTrackingValueContainer(Initializer initializer) { myInitializer = initializer; - myAdded = new ValueContainerImpl(); - myRemoved = new ValueContainerImpl(); - myInvalidated = new TIntHashSet(1); } - //public void log(String op, int id, final Value value) { - // System.out.print("@" + mcount + ": "); - // System.out.print(op); - // System.out.print("(" + id + ")"); - // System.out.print(" value=" + value + " "); - // System.out.print("+[" + myAdded.dumpInputIdMapping() + "], "); - // System.out.print("-[" + myRemoved.dumpInputIdMapping() + "], "); - // System.out.println("*[" + (myMerged != null ? myMerged.dumpInputIdMapping() : "null") + "] "); - //} - @Override public void addValue(int inputId, Value value) { - if (myMerged != null) { - myMerged.addValue(inputId, value); + ValueContainerImpl merged = myMerged; + if (merged != null) { + merged.addValue(inputId, value); } - if (!myRemoved.removeValue(inputId, value)) { - myAdded.addValue(inputId, value); + ValueContainerImpl added = myAdded; + if (added == null) { + myAdded = added = new ValueContainerImpl(); } + added.addValue(inputId, value); // will flush the changes & caller should ensure exclusiveness to avoid intermediate visibility issues } @Override public void removeAssociatedValue(int inputId) { - if (myMerged != null) { - myMerged.removeAssociatedValue(inputId); + ValueContainerImpl merged = myMerged; + if (merged != null) { + merged.removeAssociatedValue(inputId); } - myAdded.removeAssociatedValue(inputId); - myRemoved.removeAssociatedValue(inputId); - myInvalidated.add(inputId); + + ValueContainerImpl added = myAdded; + if (added != null) added.removeAssociatedValue(inputId); + + TIntHashSet invalidated = myInvalidated; + if (invalidated == null) { + invalidated = new TIntHashSet(1); + } + invalidated.add(inputId); + myInvalidated = invalidated; // volatile write } @Override public boolean removeValue(int inputId, Value value) { - if (myMerged != null) { - myMerged.removeValue(inputId, value); - } - if (!myAdded.removeValue(inputId, value)) { - if (!myInvalidated.contains(inputId)) { - myRemoved.addValue(inputId, value); - } + ValueContainerImpl merged = myMerged; + if (merged != null) { + merged.removeValue(inputId, value); } + ValueContainerImpl added = myAdded; + if (added != null) added.removeValue(inputId, value); + return true; } @@ -143,28 +141,29 @@ class ChangeTrackingValueContainer extends UpdatableValueContainer } else { newMerged = ((ChangeTrackingValueContainer)fromDisk).getMergedData().copy(); } - myInvalidated.forEach(new TIntProcedure() { - @Override - public boolean execute(int inputId) { - newMerged.removeAssociatedValue(inputId); - return true; - } - }); - myRemoved.forEach(new ContainerAction() { - @Override - public boolean perform(final int id, final Value value) { - newMerged.removeValue(id, value); - return true; - } - }); - myAdded.forEach(new ContainerAction() { - @Override - public boolean perform(final int id, final Value value) { - newMerged.removeAssociatedValue(id); // enforcing "one-value-per-file for particular key" invariant - newMerged.addValue(id, value); - return true; - } - }); + + TIntHashSet invalidated = myInvalidated; + if (invalidated != null) { + invalidated.forEach(new TIntProcedure() { + @Override + public boolean execute(int inputId) { + newMerged.removeAssociatedValue(inputId); + return true; + } + }); + } + + ValueContainerImpl added = myAdded; + if (added != null) { + added.forEach(new ContainerAction() { + @Override + public boolean perform(final int id, final Value value) { + newMerged.removeAssociatedValue(id); // enforcing "one-value-per-file for particular key" invariant + newMerged.addValue(id, value); + return true; + } + }); + } setNeedsCompacting(fromDisk.needsCompacting()); myMerged = newMerged; @@ -173,18 +172,16 @@ class ChangeTrackingValueContainer extends UpdatableValueContainer } public boolean isDirty() { - return myAdded.size() > 0 || myRemoved.size() > 0 || !myInvalidated.isEmpty() || needsCompacting(); + return (myAdded != null && myAdded.size() > 0) || + (myInvalidated != null && !myInvalidated.isEmpty()) || + needsCompacting(); } - public ValueContainer getAddedDelta() { + public @Nullable ValueContainer getAddedDelta() { return myAdded; } - - public ValueContainer getRemovedDelta() { - return myRemoved; - } - public TIntHashSet getInvalidated() { + public @Nullable TIntHashSet getInvalidated() { return myInvalidated; } } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/IndexStorage.java b/platform/lang-impl/src/com/intellij/util/indexing/IndexStorage.java index 0eefa0bf5bf1..5b0f2aa3f447 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/IndexStorage.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/IndexStorage.java @@ -31,8 +31,6 @@ public interface IndexStorage extends Flushable { void addValue(Key key, int inputId, Value value) throws StorageException; - void removeValue(Key key, int inputId, Value value) throws StorageException; - void removeAllValues(Key key, int inputId) throws StorageException; void clear() throws StorageException; diff --git a/platform/lang-impl/src/com/intellij/util/indexing/MapIndexStorage.java b/platform/lang-impl/src/com/intellij/util/indexing/MapIndexStorage.java index d0c60582c640..9379f64369bf 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/MapIndexStorage.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/MapIndexStorage.java @@ -237,17 +237,6 @@ public final class MapIndexStorage implements IndexStorage implements IndexStorage myBackendStorage.addValue(key, inputId, value); } - @Override - public void removeValue(final Key key, final int inputId, final Value value) throws StorageException { - if (myBufferingEnabled.get()) { - getMemValueContainer(key).removeValue(inputId, value); - return; - } - final ChangeTrackingValueContainer valueContainer = myMap.get(key); - if (valueContainer != null) { - valueContainer.dropMergedData(); - } - myBackendStorage.removeValue(key, inputId, value); - } - @Override public void removeAllValues(Key key, int inputId) throws StorageException { if (myBufferingEnabled.get()) { diff --git a/platform/lang-impl/src/com/intellij/util/indexing/ValueContainerImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/ValueContainerImpl.java index 89e2429ff1ae..36ef41459f42 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/ValueContainerImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/ValueContainerImpl.java @@ -33,27 +33,37 @@ import java.util.*; class ValueContainerImpl extends UpdatableValueContainer implements Cloneable{ private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.ValueContainerImpl"); private final static Object myNullValue = new Object(); - private THashMap myInputIdMapping; - - public ValueContainerImpl() { - // per statistic most maps (80%) has one value - myInputIdMapping = new THashMap(1); - } + // 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, myInputIdMappingValue = null + private Object myInputIdMapping; + private Object myInputIdMappingValue; @Override public void addValue(int inputId, Value value) { - value = maskNull(value); - final Object input = myInputIdMapping.get(value); + final Object input = getInput(value); + if (input == null) { - //idSet = new TIntHashSet(3, 0.98f); - myInputIdMapping.put(value, inputId); + if (myInputIdMapping != null) { + if (!(myInputIdMapping instanceof THashMap)) { + Object oldMapping = myInputIdMapping; + myInputIdMapping = new THashMap(2); + ((THashMap)myInputIdMapping).put((Value)oldMapping, myInputIdMappingValue); + myInputIdMappingValue = null; + } + ((THashMap)myInputIdMapping).put(value, inputId); + } else { + myInputIdMapping = value != null ? value:(Value)myNullValue; + myInputIdMappingValue = inputId; + } } else { final TIntHashSet idSet; if (input instanceof Integer) { idSet = new IdSet(3, 0.98f); idSet.add(((Integer)input).intValue()); - myInputIdMapping.put(value, idSet); + if (!(myInputIdMapping instanceof THashMap)) myInputIdMappingValue = idSet; + else ((THashMap)myInputIdMapping).put(value, idSet); } else { idSet = (TIntHashSet)input; @@ -64,21 +74,19 @@ class ValueContainerImpl extends UpdatableValueContainer implement @Override public int size() { - return myInputIdMapping.size(); + return myInputIdMapping != null ? myInputIdMapping instanceof THashMap ? ((THashMap)myInputIdMapping).size(): 1 : 0; } @Override public void removeAssociatedValue(int inputId) { - if (myInputIdMapping.isEmpty()) return; + if (myInputIdMapping == null) return; List toRemove = null; for (final Iterator valueIterator = getValueIterator(); valueIterator.hasNext();) { final Value value = valueIterator.next(); if (isAssociated(value, inputId)) { - if (toRemove == null) toRemove = new SmartList(value); - else { - LOG.error("Expected only one value per-inputId"); - toRemove.add(value); - } + if (toRemove == null) toRemove = new SmartList(); + else LOG.error("Expected only one value per-inputId"); + toRemove.add(value); } } @@ -91,12 +99,11 @@ class ValueContainerImpl extends UpdatableValueContainer implement @Override public boolean removeValue(int inputId, Value value) { - if (myInputIdMapping.isEmpty()) return false; // skipping hash code for value - value = maskNull(value); - final Object input = myInputIdMapping.get(value); + final Object input = getInput(value); if (input == null) { return false; } + if (input instanceof TIntHashSet) { final TIntHashSet idSet = (TIntHashSet)input; final boolean reallyRemoved = idSet.remove(inputId); @@ -112,57 +119,87 @@ class ValueContainerImpl extends UpdatableValueContainer implement return false; } } - myInputIdMapping.remove(value); - return true; - } - private Value maskNull(Value value) { - if (value == null) { - return (Value)myNullValue; + if (!(myInputIdMapping instanceof THashMap)) { + myInputIdMapping = null; + myInputIdMappingValue = null; + } else { + THashMap mapping = (THashMap)myInputIdMapping; + mapping.remove(value); + if (mapping.size() == 1) { + myInputIdMapping = mapping.keySet().iterator().next(); + myInputIdMappingValue = mapping.get((Value)myInputIdMapping); + } } - return value; + + return true; } @Override public Iterator getValueIterator() { - if (myInputIdMapping.isEmpty()) { + if (myInputIdMapping != null) { + if (!(myInputIdMapping instanceof THashMap)) { + return new Iterator() { + private Value value = (Value)myInputIdMapping; + @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 Iterator() { + final Iterator iterator = ((THashMap)myInputIdMapping).keySet().iterator(); + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Value next() { + Value next = iterator.next(); + if (next == myNullValue) next = null; + return next; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + } else { return EmptyIterator.getInstance(); } - - return new Iterator() { - final Iterator iterator = myInputIdMapping.keySet().iterator(); - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public Value next() { - Value next = iterator.next(); - if (next == myNullValue) next = null; - return next; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; } @Override public List toValueList() { - if (myInputIdMapping.isEmpty()) { + if (myInputIdMapping == null) { return Collections.emptyList(); + } else if (myInputIdMapping instanceof THashMap) { + return new ArrayList(((THashMap)myInputIdMapping).keySet()); + } else { + return new SmartList((Value)myInputIdMapping); } - return new ArrayList(myInputIdMapping.keySet()); } @Override public boolean isAssociated(Value value, final int inputId) { - value = maskNull(value); - final Object input = myInputIdMapping.get(value); + final Object input = getInput(value); if (input instanceof TIntHashSet) { return ((TIntHashSet)input).contains(inputId); } @@ -174,7 +211,7 @@ class ValueContainerImpl extends UpdatableValueContainer implement @Override public IntPredicate getValueAssociationPredicate(Value value) { - final Object input = myInputIdMapping.get(value); + final Object input = getInput(value); if (input == null) return EMPTY_PREDICATE; if (input instanceof Integer) { return new IntPredicate() { @@ -196,8 +233,7 @@ class ValueContainerImpl extends UpdatableValueContainer implement @Override public IntIterator getInputIdsIterator(Value value) { - value = maskNull(value); - final Object input = myInputIdMapping.get(value); + final Object input = getInput(value); final IntIterator it; if (input instanceof TIntHashSet) { it = new IntSetIterator((TIntHashSet)input); @@ -211,11 +247,30 @@ class ValueContainerImpl extends UpdatableValueContainer implement return it; } + private Object getInput(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)myInputIdMapping).get(value); + } + @Override public ValueContainerImpl clone() { try { final ValueContainerImpl clone = (ValueContainerImpl)super.clone(); - clone.myInputIdMapping = mapCopy(myInputIdMapping); + if (myInputIdMapping instanceof THashMap) { + clone.myInputIdMapping = mapCopy((THashMap)myInputIdMapping); + } else if (myInputIdMappingValue instanceof TIntHashSet) { + clone.myInputIdMappingValue = ((TIntHashSet)myInputIdMappingValue).clone(); + } return clone; } catch (CloneNotSupportedException e) { @@ -241,18 +296,30 @@ class ValueContainerImpl extends UpdatableValueContainer implement }; public ValueContainerImpl copy() { - final ValueContainerImpl container = new ValueContainerImpl(); - myInputIdMapping.forEachEntry(new TObjectObjectProcedure() { - @Override - public boolean execute(Value key, Object val) { - if (val instanceof TIntHashSet) { - container.myInputIdMapping.put(key, ((TIntHashSet)val).clone()); - } else { - container.myInputIdMapping.put(key, val); + ValueContainerImpl container = new ValueContainerImpl(); + + if (myInputIdMapping instanceof THashMap) { + final THashMap mapping = (THashMap)myInputIdMapping; + final THashMap newMapping = new THashMap(mapping.size()); + container.myInputIdMapping = newMapping; + + mapping.forEachEntry(new TObjectObjectProcedure() { + @Override + public boolean execute(Value key, Object val) { + if (val instanceof TIntHashSet) { + newMapping.put(key, ((TIntHashSet)val).clone()); + } + else { + newMapping.put(key, val); + } + return true; } - return true; - } - }); + }); + } else { + container.myInputIdMapping = myInputIdMapping; + container.myInputIdMappingValue = myInputIdMappingValue instanceof TIntHashSet ? + ((TIntHashSet)myInputIdMappingValue).clone():myInputIdMappingValue; + } return container; } @@ -271,12 +338,9 @@ class ValueContainerImpl extends UpdatableValueContainer implement @Override public int next() { - try { - return myValue; - } - finally { - myValueRead = true; - } + int next = myValue; + myValueRead = true; + return next; } @Override diff --git a/platform/lang-impl/src/com/intellij/util/indexing/ValueContainerMap.java b/platform/lang-impl/src/com/intellij/util/indexing/ValueContainerMap.java index cb26b39d4994..ba3d72501fba 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/ValueContainerMap.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/ValueContainerMap.java @@ -38,18 +38,14 @@ class ValueContainerMap extends PersistentHashMap 0) { + if (set != null && set.size() > 0) { for (int inputId : set.toArray()) { ValueContainerExternalizer.saveInvalidateCommand(_out, inputId); } } - final ValueContainer toRemove = valueContainer.getRemovedDelta(); - if (toRemove.size() > 0) { - myValueContainerExternalizer.saveAsRemoved(_out, toRemove); - } final ValueContainer toAppend = valueContainer.getAddedDelta(); - if (toAppend.size() > 0) { + if (toAppend != null && toAppend.size() > 0) { myValueContainerExternalizer.save(_out, toAppend); } @@ -76,18 +72,14 @@ class ValueContainerMap extends PersistentHashMap container) throws IOException { - saveImpl(out, container, false); - } - - public void saveAsRemoved(final DataOutput out, @NotNull final ValueContainer container) throws IOException { - saveImpl(out, container, true); + saveImpl(out, container); } public static void saveInvalidateCommand(final DataOutput out, int inputId) throws IOException { DataInputOutputUtil.writeSINT(out, -inputId); } - private void saveImpl(final DataOutput out, @NotNull final ValueContainer container, final boolean asRemovedData) throws IOException { + private void saveImpl(final DataOutput out, @NotNull final ValueContainer container) throws IOException { DataInputOutputUtil.writeSINT(out, container.size()); for (final Iterator valueIterator = container.getValueIterator(); valueIterator.hasNext();) { final T value = valueIterator.next(); @@ -98,7 +90,7 @@ class ValueContainerMap extends PersistentHashMap