/*
* Copyright 2000-2016 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.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileTypes.FileTypeEvent;
import com.intellij.openapi.fileTypes.FileTypeListener;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.VirtualFileWithId;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ConcurrentIntObjectMap;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* This is a light version of DirectoryIndexImpl
*
* @author gregsh
*/
public final class LightDirectoryIndex<T> {
private final ConcurrentIntObjectMap<T> myInfoCache = ContainerUtil.createConcurrentIntObjectMap();
private final T myDefValue;
private final Consumer<LightDirectoryIndex<T>> myInitializer;
public LightDirectoryIndex(@NotNull Disposable parentDisposable, @NotNull T defValue, @NotNull Consumer<LightDirectoryIndex<T>> initializer) {
myDefValue = defValue;
myInitializer = initializer;
resetIndex();
MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect(parentDisposable);
connection.subscribe(FileTypeManager.TOPIC, new FileTypeListener() {
@Override
public void fileTypesChanged(@NotNull FileTypeEvent event) {
resetIndex();
}
});
connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
@Override
public void before(@NotNull List<? extends VFileEvent> events) {
}
@Override
public void after(@NotNull List<? extends VFileEvent> events) {
for (VFileEvent event : events) {
VirtualFile file = event.getFile();
if (file == null || file.isDirectory()) {
resetIndex();
break;
}
}
}
});
}
public void resetIndex() {
myInfoCache.clear();
myInitializer.consume(this);
}
public void putInfo(@Nullable VirtualFile file, @NotNull T value) {
if (!(file instanceof VirtualFileWithId)) return;
cacheInfo(file, value);
}
@NotNull
public T getInfoForFile(@Nullable VirtualFile file) {
if (!(file instanceof VirtualFileWithId)) return myDefValue;
VirtualFile dir;
if (!file.isDirectory()) {
T info = getCachedInfo(file);
if (info != null) {
return info;
}
dir = file.getParent();
}
else {
dir = file;
}
int count = 0;
for (VirtualFile root = dir; root != null; root = root.getParent()) {
if (++count > 1000) {
throw new IllegalStateException("Possible loop in tree, started at " + dir.getName());
}
T info = getCachedInfo(root);
if (info != null) {
if (!dir.equals(root)) {
cacheInfos(dir, root, info);
}
return info;
}
}
return cacheInfos(dir, null, myDefValue);
}
@NotNull
private T cacheInfos(VirtualFile dir, @Nullable VirtualFile stopAt, @NotNull T info) {
while (dir != null) {
cacheInfo(dir, info);
if (dir.equals(stopAt)) {
break;
}
dir = dir.getParent();
}
return info;
}
private void cacheInfo(VirtualFile file, T info) {
myInfoCache.put(((VirtualFileWithId)file).getId(), info);
}
private T getCachedInfo(VirtualFile file) {
return myInfoCache.get(((VirtualFileWithId)file).getId());
}
}