7679c6603ae8720e0d8478efcdddf2a4621d5d36
[idea/community.git] / platform / vcs-log / impl / src / com / intellij / vcs / log / data / index / VcsLogPathsIndex.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.vcs.log.data.index;
17
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.util.Couple;
21 import com.intellij.openapi.util.SystemInfo;
22 import com.intellij.openapi.util.io.FileUtil;
23 import com.intellij.openapi.vcs.FilePath;
24 import com.intellij.openapi.vcs.changes.Change;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.util.Consumer;
27 import com.intellij.util.PathUtil;
28 import com.intellij.util.containers.ContainerUtil;
29 import com.intellij.util.indexing.DataIndexer;
30 import com.intellij.util.indexing.StorageException;
31 import com.intellij.util.io.*;
32 import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
33 import com.intellij.vcs.log.VcsFullCommitDetails;
34 import com.intellij.vcs.log.impl.FatalErrorHandler;
35 import com.intellij.vcs.log.impl.VcsChangesLazilyParsedDetails;
36 import com.intellij.vcs.log.util.PersistentUtil;
37 import gnu.trove.THashMap;
38 import gnu.trove.TIntHashSet;
39 import org.jetbrains.annotations.NotNull;
40
41 import java.io.DataInput;
42 import java.io.DataOutput;
43 import java.io.File;
44 import java.io.IOException;
45 import java.util.Arrays;
46 import java.util.Collection;
47 import java.util.Map;
48 import java.util.Set;
49
50 import static com.intellij.util.containers.ContainerUtil.newTroveSet;
51 import static com.intellij.vcs.log.data.index.VcsLogPersistentIndex.getVersion;
52
53 public class VcsLogPathsIndex extends VcsLogFullDetailsIndex<Integer> {
54   private static final Logger LOG = Logger.getInstance(VcsLogPathsIndex.class);
55   public static final String PATHS = "paths";
56   public static final String INDEX_PATHS_IDS = "paths-ids";
57
58   @NotNull private final PathsIndexer myPathsIndexer;
59
60   public VcsLogPathsIndex(@NotNull String logId,
61                           @NotNull Set<VirtualFile> roots,
62                           @NotNull FatalErrorHandler fatalErrorHandler,
63                           @NotNull Disposable disposableParent) throws IOException {
64     super(logId, PATHS, getVersion(), new PathsIndexer(createPathsEnumerator(logId), roots),
65           new NullableIntKeyDescriptor(), fatalErrorHandler, disposableParent);
66
67     myPathsIndexer = (PathsIndexer)myIndexer;
68     myPathsIndexer.setFatalErrorConsumer(e -> fatalErrorHandler.consume(this, e));
69   }
70
71   @NotNull
72   private static PersistentEnumeratorBase<String> createPathsEnumerator(@NotNull String logId) throws IOException {
73     File storageFile = PersistentUtil.getStorageFile(INDEX, INDEX_PATHS_IDS, logId, getVersion(), true);
74     return new PersistentBTreeEnumerator<>(storageFile, SystemInfo.isFileSystemCaseSensitive ? EnumeratorStringDescriptor.INSTANCE
75                                                                                              : new ToLowerCaseStringDescriptor(),
76                                            Page.PAGE_SIZE, null, getVersion());
77   }
78
79   @Override
80   public void flush() throws StorageException {
81     super.flush();
82     myPathsIndexer.getPathsEnumerator().force();
83   }
84
85   public TIntHashSet getCommitsForPaths(@NotNull Collection<FilePath> paths) throws IOException, StorageException {
86     Set<Integer> allPathIds = ContainerUtil.newHashSet();
87     for (FilePath path : paths) {
88       allPathIds.add(myPathsIndexer.myPathsEnumerator.enumerate(path.getPath()));
89     }
90
91     TIntHashSet result = new TIntHashSet();
92     Set<Integer> renames = allPathIds;
93     while (!renames.isEmpty()) {
94       renames = addCommitsAndGetRenames(renames, allPathIds, result);
95       allPathIds.addAll(renames);
96     }
97
98     return result;
99   }
100
101   @NotNull
102   public Set<Integer> addCommitsAndGetRenames(@NotNull Set<Integer> newPathIds,
103                                               @NotNull Set<Integer> allPathIds,
104                                               @NotNull TIntHashSet commits)
105     throws StorageException {
106     Set<Integer> renames = ContainerUtil.newHashSet();
107     for (Integer key : newPathIds) {
108       iterateCommitIdsAndValues(key, (value, commit) -> {
109         commits.add(commit);
110         if (value != null && !allPathIds.contains(value)) {
111           renames.add(value);
112         }
113       });
114     }
115     return renames;
116   }
117
118   @Override
119   public void dispose() {
120     super.dispose();
121     try {
122       myPathsIndexer.getPathsEnumerator().close();
123     }
124     catch (IOException e) {
125       LOG.warn(e);
126     }
127   }
128
129   private static class PathsIndexer implements DataIndexer<Integer, Integer, VcsFullCommitDetails> {
130     @NotNull private final PersistentEnumeratorBase<String> myPathsEnumerator;
131     @NotNull private final Set<String> myRoots;
132     @NotNull private Consumer<Exception> myFatalErrorConsumer = LOG::error;
133
134     private PathsIndexer(@NotNull PersistentEnumeratorBase<String> enumerator, @NotNull Set<VirtualFile> roots) {
135       myPathsEnumerator = enumerator;
136       myRoots = newTroveSet(FileUtil.PATH_HASHING_STRATEGY);
137       for (VirtualFile root : roots) {
138         myRoots.add(root.getPath());
139       }
140     }
141
142     public void setFatalErrorConsumer(@NotNull Consumer<Exception> fatalErrorConsumer) {
143       myFatalErrorConsumer = fatalErrorConsumer;
144     }
145
146     @NotNull
147     @Override
148     public Map<Integer, Integer> map(@NotNull VcsFullCommitDetails inputData) {
149       Map<Integer, Integer> result = new THashMap<>();
150
151
152       Collection<Couple<String>> moves;
153       Collection<String> changedPaths;
154       if (inputData instanceof VcsChangesLazilyParsedDetails) {
155         changedPaths = ((VcsChangesLazilyParsedDetails)inputData).getModifiedPaths();
156         moves = ((VcsChangesLazilyParsedDetails)inputData).getRenamedPaths();
157       }
158       else {
159         moves = ContainerUtil.newHashSet();
160         changedPaths = ContainerUtil.newHashSet();
161         for (Change change : inputData.getChanges()) {
162           if (change.getAfterRevision() != null) changedPaths.add(change.getAfterRevision().getFile().getPath());
163           if (change.getBeforeRevision() != null) changedPaths.add(change.getBeforeRevision().getFile().getPath());
164           if (change.getType().equals(Change.Type.MOVED)) {
165             moves.add(Couple.of(change.getBeforeRevision().getFile().getPath(), change.getAfterRevision().getFile().getPath()));
166           }
167         }
168       }
169
170       getParentPaths(changedPaths).forEach(changedPath -> {
171         try {
172           result.put(myPathsEnumerator.enumerate(changedPath), null);
173         }
174         catch (IOException e) {
175           myFatalErrorConsumer.consume(e);
176         }
177       });
178       moves.forEach(renamedPaths -> {
179         try {
180           int beforeId = myPathsEnumerator.enumerate(renamedPaths.first);
181           int afterId = myPathsEnumerator.enumerate(renamedPaths.second);
182
183           result.put(beforeId, afterId);
184           result.put(afterId, beforeId);
185         }
186         catch (IOException e) {
187           myFatalErrorConsumer.consume(e);
188         }
189       });
190
191       return result;
192     }
193
194     @NotNull
195     private Collection<String> getParentPaths(@NotNull Collection<String> paths) {
196       Set<String> result = ContainerUtil.newHashSet();
197       for (String path : paths) {
198         while (!path.isEmpty() && !result.contains(path)) {
199           result.add(path);
200           if (myRoots.contains(path)) break;
201
202           path = PathUtil.getParentPath(path);
203         }
204       }
205       return result;
206     }
207
208     @NotNull
209     public PersistentEnumeratorBase<String> getPathsEnumerator() {
210       return myPathsEnumerator;
211     }
212   }
213
214   private static class NullableIntKeyDescriptor implements DataExternalizer<Integer> {
215     @Override
216     public void save(@NotNull DataOutput out, Integer value) throws IOException {
217       if (value == null) {
218         out.writeBoolean(false);
219       }
220       else {
221         out.writeBoolean(true);
222         out.writeInt(value);
223       }
224     }
225
226     @Override
227     public Integer read(@NotNull DataInput in) throws IOException {
228       if (in.readBoolean()) {
229         return in.readInt();
230       }
231       return null;
232     }
233   }
234
235   private static class ToLowerCaseStringDescriptor implements KeyDescriptor<String> {
236     @Override
237     public int getHashCode(String value) {
238       return CaseInsensitiveStringHashingStrategy.INSTANCE.computeHashCode(value);
239     }
240
241     @Override
242     public boolean isEqual(String val1, String val2) {
243       return CaseInsensitiveStringHashingStrategy.INSTANCE.equals(val1, val2);
244     }
245
246     @Override
247     public void save(@NotNull DataOutput out, String value) throws IOException {
248       IOUtil.writeUTF(out, value.toLowerCase());
249     }
250
251     @Override
252     public String read(@NotNull DataInput in) throws IOException {
253       return IOUtil.readUTF(in);
254     }
255   }
256 }