| /* |
| * 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.util.Computable; |
| import com.intellij.util.io.DataExternalizer; |
| import com.intellij.util.io.DataInputOutputUtil; |
| import gnu.trove.TIntHashSet; |
| import gnu.trove.TIntProcedure; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.util.List; |
| |
| /** |
| * @author Eugene Zhuravlev |
| * Date: Dec 20, 2007 |
| */ |
| class ChangeTrackingValueContainer<Value> extends UpdatableValueContainer<Value>{ |
| // there is no volatile as we modify under write lock and read under read lock |
| private ValueContainerImpl<Value> myAdded; |
| private TIntHashSet myInvalidated; |
| private volatile ValueContainerImpl<Value> myMerged; |
| private final Initializer<Value> myInitializer; |
| |
| public interface Initializer<T> extends Computable<ValueContainer<T>> { |
| Object getLock(); |
| } |
| |
| public ChangeTrackingValueContainer(Initializer<Value> initializer) { |
| myInitializer = initializer; |
| } |
| |
| @Override |
| public void addValue(int inputId, Value value) { |
| ValueContainerImpl<Value> merged = myMerged; |
| if (merged != null) { |
| merged.addValue(inputId, value); |
| } |
| |
| if (myAdded == null) myAdded = new ValueContainerImpl<Value>(); |
| myAdded.addValue(inputId, value); |
| } |
| |
| @Override |
| public void removeAssociatedValue(int inputId) { |
| ValueContainerImpl<Value> merged = myMerged; |
| if (merged != null) { |
| merged.removeAssociatedValue(inputId); |
| } |
| |
| if (myAdded != null) myAdded.removeAssociatedValue(inputId); |
| |
| if (myInvalidated == null) myInvalidated = new TIntHashSet(1); |
| myInvalidated.add(inputId); |
| } |
| |
| @Override |
| public int size() { |
| return getMergedData().size(); |
| } |
| |
| @NotNull |
| @Override |
| public ValueIterator<Value> getValueIterator() { |
| return getMergedData().getValueIterator(); |
| } |
| |
| @NotNull |
| @Override |
| public List<Value> toValueList() { |
| return getMergedData().toValueList(); |
| } |
| |
| @NotNull |
| @Override |
| public IntPredicate getValueAssociationPredicate(Value value) { |
| return getMergedData().getValueAssociationPredicate(value); |
| } |
| |
| @NotNull |
| @Override |
| public IntIterator getInputIdsIterator(final Value value) { |
| return getMergedData().getInputIdsIterator(value); |
| } |
| |
| public void dropMergedData() { |
| myMerged = null; |
| } |
| |
| // need 'synchronized' to ensure atomic initialization of merged data |
| // because several threads that acquired read lock may simultaneously execute the method |
| private ValueContainerImpl<Value> getMergedData() { |
| ValueContainerImpl<Value> merged = myMerged; |
| if (merged != null) { |
| return merged; |
| } |
| synchronized (myInitializer.getLock()) { |
| merged = myMerged; |
| if (merged != null) { |
| return merged; |
| } |
| |
| FileId2ValueMapping<Value> fileId2ValueMapping = null; |
| final ValueContainer<Value> fromDisk = myInitializer.compute(); |
| final ValueContainerImpl<Value> newMerged; |
| |
| if (fromDisk instanceof ValueContainerImpl) { |
| newMerged = ((ValueContainerImpl<Value>)fromDisk).copy(); |
| } else { |
| newMerged = ((ChangeTrackingValueContainer<Value>)fromDisk).getMergedData().copy(); |
| } |
| |
| if (myAdded != null && newMerged.size() > ValueContainerImpl.NUMBER_OF_VALUES_THRESHOLD) { |
| // Calculate file ids that have Value mapped to avoid O(NumberOfValuesInMerged) during removal |
| fileId2ValueMapping = new FileId2ValueMapping<Value>(newMerged); |
| } |
| final FileId2ValueMapping<Value> finalFileId2ValueMapping = fileId2ValueMapping; |
| if (myInvalidated != null) { |
| myInvalidated.forEach(new TIntProcedure() { |
| @Override |
| public boolean execute(int inputId) { |
| if (finalFileId2ValueMapping != null) finalFileId2ValueMapping.removeFileId(inputId); |
| else newMerged.removeAssociatedValue(inputId); |
| return true; |
| } |
| }); |
| } |
| |
| if (myAdded != null) { |
| myAdded.forEach(new ContainerAction<Value>() { |
| @Override |
| public boolean perform(final int inputId, final Value value) { |
| // enforcing "one-value-per-file for particular key" invariant |
| if (finalFileId2ValueMapping != null) finalFileId2ValueMapping.removeFileId(inputId); |
| else newMerged.removeAssociatedValue(inputId); |
| |
| newMerged.addValue(inputId, value); |
| if (finalFileId2ValueMapping != null) finalFileId2ValueMapping.associateFileIdToValue(inputId, value); |
| return true; |
| } |
| }); |
| } |
| setNeedsCompacting(fromDisk.needsCompacting()); |
| |
| myMerged = newMerged; |
| return newMerged; |
| } |
| } |
| |
| public boolean isDirty() { |
| return (myAdded != null && myAdded.size() > 0) || |
| (myInvalidated != null && !myInvalidated.isEmpty()) || |
| needsCompacting(); |
| } |
| |
| public @Nullable ValueContainer<Value> getAddedDelta() { |
| return myAdded; |
| } |
| |
| @Override |
| public void saveTo(DataOutput out, DataExternalizer<Value> externalizer) throws IOException { |
| if (needsCompacting()) { |
| getMergedData().saveTo(out, externalizer); |
| } else { |
| final TIntHashSet set = myInvalidated; |
| if (set != null && set.size() > 0) { |
| for (int inputId : set.toArray()) { |
| DataInputOutputUtil.writeINT(out, -inputId); // mark inputId as invalid, to be processed on load in ValueContainerImpl.readFrom |
| } |
| } |
| |
| final ValueContainer<Value> toAppend = getAddedDelta(); |
| if (toAppend != null && toAppend.size() > 0) { |
| toAppend.saveTo(out, externalizer); |
| } |
| } |
| } |
| |
| } |