9deea465db77b77e399f85d1c79511661661142f
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / stubs / StubCumulativeInputDiffBuilder.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.psi.stubs;
3
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.psi.impl.DebugUtil;
6 import com.intellij.util.containers.ContainerUtil;
7 import com.intellij.util.indexing.FileBasedIndexImpl;
8 import com.intellij.util.indexing.StorageException;
9 import com.intellij.util.indexing.impl.DirectInputDataDiffBuilder;
10 import com.intellij.util.indexing.impl.IndexDebugProperties;
11 import com.intellij.util.indexing.impl.KeyValueUpdateProcessor;
12 import com.intellij.util.indexing.impl.RemovedKeyProcessor;
13 import one.util.streamex.IntStreamEx;
14 import org.jetbrains.annotations.NotNull;
15 import org.jetbrains.annotations.Nullable;
16
17 import java.util.*;
18
19 class StubCumulativeInputDiffBuilder extends DirectInputDataDiffBuilder<Integer, SerializedStubTree> {
20   private static final Logger LOG = Logger.getInstance(SerializedStubTree.class);
21   private final int myInputId;
22   @Nullable
23   private final SerializedStubTree myCurrentTree;
24
25   StubCumulativeInputDiffBuilder(int inputId, @Nullable SerializedStubTree currentTree) {
26     super(inputId);
27     myInputId = inputId;
28     myCurrentTree = currentTree;
29   }
30
31   @Override
32   public boolean differentiate(@NotNull Map<Integer, SerializedStubTree> newData,
33                                @NotNull KeyValueUpdateProcessor<? super Integer, ? super SerializedStubTree> addProcessor,
34                                @NotNull KeyValueUpdateProcessor<? super Integer, ? super SerializedStubTree> updateProcessor,
35                                @NotNull RemovedKeyProcessor<? super Integer> removeProcessor) throws StorageException {
36     if (!newData.isEmpty()) {
37       SerializedStubTree newSerializedStubTree = newData.values().iterator().next();
38       if (myCurrentTree != null) {
39         if (treesAreEqual(newSerializedStubTree, myCurrentTree)) return false;
40         removeProcessor.process(myInputId, myInputId);
41       }
42       addProcessor.process(myInputId, newSerializedStubTree, myInputId);
43       updateStubIndices(newSerializedStubTree);
44     }
45     else {
46       removeProcessor.process(myInputId, myInputId);
47       updateStubIndices(null);
48     }
49     return true;
50   }
51
52   @Override
53   public @NotNull Collection<Integer> getKeys() {
54     return myCurrentTree != null ? Collections.singleton(myInputId) : Collections.emptySet();
55   }
56
57   private static boolean treesAreEqual(@NotNull SerializedStubTree newSerializedStubTree,
58                                        @NotNull SerializedStubTree currentTree) {
59     return Arrays.equals(currentTree.getTreeHash(), newSerializedStubTree.getTreeHash()) &&
60            treesAreReallyEqual(newSerializedStubTree, currentTree);
61   }
62
63   private static boolean treesAreReallyEqual(@NotNull SerializedStubTree newSerializedStubTree,
64                                              @NotNull SerializedStubTree currentTree) {
65     if (newSerializedStubTree.equals(currentTree)) {
66       return true;
67     }
68     if (IndexDebugProperties.DEBUG) {
69       reportStubTreeHashCollision(newSerializedStubTree, currentTree);
70     }
71     return false;
72   }
73
74   private void updateStubIndices(@Nullable SerializedStubTree newTree) {
75     Map<StubIndexKey<?, ?>, Map<Object, StubIdList>> oldForwardIndex =
76       myCurrentTree == null ? Collections.emptyMap() : myCurrentTree.getStubIndicesValueMap();
77
78     Map<StubIndexKey<?, ?>, Map<Object, StubIdList>> newForwardIndex =
79       newTree == null ? Collections.emptyMap() : newTree.getStubIndicesValueMap();
80
81     Collection<StubIndexKey<?, ?>> affectedIndexes =
82       ContainerUtil.union(oldForwardIndex.keySet(), newForwardIndex.keySet());
83
84     if (FileBasedIndexImpl.DO_TRACE_STUB_INDEX_UPDATE) {
85       StubIndexImpl.LOG.info("stub indexes" + (newTree == null ? "deletion" : "update") + ": file = " + myInputId + " indexes " + affectedIndexes);
86     }
87
88     StubIndexImpl stubIndex = (StubIndexImpl)StubIndex.getInstance();
89     //noinspection rawtypes
90     for (StubIndexKey key : affectedIndexes) {
91       // StubIdList-s are ignored.
92       Set<Object> oldKeys = oldForwardIndex.getOrDefault(key, Collections.emptyMap()).keySet();
93       Set<Object> newKeys = newForwardIndex.getOrDefault(key, Collections.emptyMap()).keySet();
94
95       //noinspection unchecked
96       stubIndex.updateIndex(key, myInputId, oldKeys, newKeys);
97     }
98   }
99
100   private static void reportStubTreeHashCollision(@NotNull SerializedStubTree newTree,
101                                                   @NotNull SerializedStubTree existingTree) {
102     String oldTreeDump = "\nexisting tree " + dumpStub(existingTree);
103     String newTreeDump = "\nnew tree " + dumpStub(newTree);
104     byte[] hash = newTree.getTreeHash();
105     LOG.info("Stub tree hashing collision. " +
106              "Different trees have the same hash = " + toHexString(hash, hash.length) + ". " +
107              oldTreeDump + newTreeDump, new Exception());
108   }
109
110   private static String toHexString(byte[] hash, int length) {
111     return IntStreamEx.of(hash).limit(length).mapToObj(b -> String.format("%02x", b & 0xFF)).joining();
112   }
113
114   @NotNull
115   private static String dumpStub(@NotNull SerializedStubTree tree) {
116     String deserialized;
117     try {
118       deserialized = "stub: " + DebugUtil.stubTreeToString(tree.getStub());
119     }
120     catch (SerializerNotFoundException e) {
121       LOG.error(e);
122       deserialized = "error while stub deserialization: " + e.getMessage();
123     }
124     return deserialized + "\n bytes: " + toHexString(tree.myTreeBytes, tree.myTreeByteLength);
125   }
126 }