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