blob: 20198eaf26b72b8d4a2992c20f69ec7ffdae78bd [file] [log] [blame]
/*
* Copyright 2000-2013 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.jetbrains.python.debugger;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.FileAttribute;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* @author traff
*/
public class PySignatureCacheManagerImpl extends PySignatureCacheManager {
protected static final Logger LOG = Logger.getInstance(PySignatureCacheManagerImpl.class.getName());
private final static boolean SHOULD_OVERWRITE_TYPES = false;
public static final FileAttribute CALL_SIGNATURES_ATTRIBUTE = new FileAttribute("call.signatures.attribute", 1, true);
private final Project myProject;
private final LoadingCache<VirtualFile, String> mySignatureCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(
new CacheLoader<VirtualFile, String>() {
@Override
public String load(VirtualFile key) throws Exception {
return readAttributeFromFile(key);
}
});
public PySignatureCacheManagerImpl(Project project) {
myProject = project;
}
@Override
public void recordSignature(@NotNull PySignature signature) {
GlobalSearchScope scope = ProjectScope.getProjectScope(myProject);
VirtualFile file = getFile(signature);
if (file != null && scope.contains(file)) {
recordSignature(file, signature);
}
}
private void recordSignature(VirtualFile file, PySignature signature) {
String dataString = readAttribute(file);
String[] lines;
if (dataString != null) {
lines = dataString.split("\n");
}
else {
lines = ArrayUtil.EMPTY_STRING_ARRAY;
}
boolean found = false;
int i = 0;
for (String sign : lines) {
String[] parts = sign.split("\t");
if (parts.length > 0 && parts[0].equals(signature.getFunctionName())) {
found = true;
if (SHOULD_OVERWRITE_TYPES) {
lines[i] = signatureToString(signature);
}
else {
//noinspection ConstantConditions
lines[i] = signatureToString(stringToSignature(file.
getCanonicalPath(), lines[i]).addAllArgs(signature));
}
}
i++;
}
if (!found) {
String[] lines2 = new String[lines.length + 1];
System.arraycopy(lines, 0, lines2, 0, lines.length);
lines2[lines2.length - 1] = signatureToString(signature);
lines = lines2;
}
String attrString = StringUtil.join(lines, "\n");
writeAttribute(file, attrString);
}
private void writeAttribute(@NotNull VirtualFile file, @NotNull String attrString) {
String cachedValue = mySignatureCache.asMap().get(file);
if (!attrString.equals(cachedValue)) {
mySignatureCache.put(file, attrString);
writeAttributeToAFile(file, attrString);
}
}
private static void writeAttributeToAFile(@NotNull VirtualFile file, @NotNull String attrString) {
try {
CALL_SIGNATURES_ATTRIBUTE.writeAttributeBytes(file, attrString.getBytes());
}
catch (IOException e) {
LOG.warn("Can't write attribute " + file.getCanonicalPath() + " " + attrString);
}
}
private static String signatureToString(PySignature signature) {
return signature.getFunctionName() + "\t" + StringUtil.join(arguments(signature), "\t");
}
private static List<String> arguments(PySignature signature) {
List<String> res = Lists.newArrayList();
for (PySignature.NamedParameter param : signature.getArgs()) {
res.add(param.getName() + ":" + param.getTypeQualifiedName());
}
return res;
}
@Nullable
public String findParameterType(@NotNull PyFunction function, @NotNull String name) {
final PySignature signature = findSignature(function);
if (signature != null) {
return signature.getArgTypeQualifiedName(name);
}
return null;
}
@Nullable
public PySignature findSignature(@NotNull PyFunction function) {
VirtualFile file = getFile(function);
if (file != null) {
return readSignatureAttributeFromFile(file, getFunctionName(function));
}
else {
return null;
}
}
private static String getFunctionName(PyFunction function) {
String name = function.getName();
if (name == null) {
return "";
}
PyClass cls = function.getContainingClass();
if (cls != null) {
name = cls.getName() + "." + name;
}
return name;
}
@Nullable
private PySignature readSignatureAttributeFromFile(@NotNull VirtualFile file, @NotNull String name) {
String content = readAttribute(file);
if (content != null) {
String[] lines = content.split("\n");
for (String sign : lines) {
String[] parts = sign.split("\t");
if (parts.length > 0 && parts[0].equals(name)) {
return stringToSignature(file.getCanonicalPath(), sign);
}
}
}
return null;
}
@Nullable
private String readAttribute(@NotNull VirtualFile file) {
try {
String attrContent = mySignatureCache.get(file);
if (!StringUtil.isEmpty(attrContent)) {
return attrContent;
}
}
catch (ExecutionException e) {
//pass
}
return null;
}
@NotNull
private static String readAttributeFromFile(@NotNull VirtualFile file) {
byte[] data;
try {
data = CALL_SIGNATURES_ATTRIBUTE.readAttributeBytes(file);
}
catch (Exception e) {
data = null;
}
String content;
if (data != null && data.length > 0) {
content = new String(data);
}
else {
content = null;
}
return content != null ? content : "";
}
@Nullable
private static PySignature stringToSignature(String path, String string) {
String[] parts = string.split("\t");
if (parts.length > 0) {
PySignature signature = new PySignature(path, parts[0]);
for (int i = 1; i < parts.length; i++) {
String[] var = parts[i].split(":");
if (var.length == 2) {
signature = signature.addArgument(var[0], var[1]);
}
else {
throw new IllegalStateException("Should be <name>:<type> format. " + parts[i] + " instead.");
}
}
return signature;
}
return null;
}
@Nullable
private static VirtualFile getFile(@NotNull PySignature signature) {
return LocalFileSystem.getInstance().findFileByPath(signature.getFile());
}
@Nullable
private static VirtualFile getFile(@NotNull PyFunction function) {
PsiFile file = function.getContainingFile();
return file != null ? file.getOriginalFile().getVirtualFile() : null;
}
@Override
public void clearCache() {
final Ref<Boolean> deleted = Ref.create(false);
ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
@Override
public void run() {
ProjectFileIndex.SERVICE.getInstance(myProject).iterateContent(new ContentIterator() {
@Override
public boolean processFile(VirtualFile fileOrDir) {
if (readAttribute(fileOrDir) != null) {
writeAttribute(fileOrDir, "");
deleted.set(true);
}
if (ProgressManager.getInstance().getProgressIndicator().isCanceled()) {
return false;
}
return true;
}
});
}
}, "Cleaning the cache of dynamically collected types", true, myProject);
String message;
if (deleted.get()) {
message = "Collected signatures were deleted";
}
else {
message = "Nothing to delete";
}
Messages.showInfoMessage(myProject, message, "Delete Cache");
}
}