revert original fix for IDEA-152628

performing IO in background thread while holding read lock sometimes leads to EDT freeze (when there's a pending write action)
This commit is contained in:
Dmitry Batrak
2016-06-28 10:44:56 +03:00
parent 9a73fd1628
commit c5859f8f77
7 changed files with 55 additions and 199 deletions
@@ -331,7 +331,7 @@ public class JavaDocInfoGenerator {
if (fragment != null && targetElement instanceof PsiClass) {
if (fragment.contains("-") || fragment.contains("(")) {
for (PsiMethod method : ((PsiClass)targetElement).getMethods()) {
Set<String> signatures = JavaDocumentationProvider.getHtmlMethodSignatures(method);
Set<String> signatures = JavaDocumentationProvider.getHtmlMethodSignatures(method, true);
if (signatures.contains(fragment)) {
targetElement = method;
fragment = null;
@@ -27,9 +27,9 @@ import com.intellij.lang.CodeDocumentationAwareCommenter;
import com.intellij.lang.LangBundle;
import com.intellij.lang.LanguageCommenters;
import com.intellij.lang.documentation.CodeDocumentationProvider;
import com.intellij.lang.documentation.CompositeDocumentationProvider;
import com.intellij.lang.documentation.DocumentationProviderEx;
import com.intellij.lang.documentation.ExternalDocumentationProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
@@ -41,8 +41,9 @@ import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileSystem;
import com.intellij.psi.*;
import com.intellij.psi.impl.beanProperties.BeanPropertyElement;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
@@ -57,19 +58,14 @@ import com.intellij.util.SmartList;
import com.intellij.util.Url;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.io.URLUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.builtInWebServer.BuiltInWebBrowserUrlProvider;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Maxim.Mossienko
@@ -614,25 +610,22 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
sb.append("<br>");
}
// this method is closely related to hasUrlFor, both should be updated in sync
@Nullable
public static List<String> getExternalJavaDocUrl(final PsiElement element) {
// this method can be slow, as it can perform IO (potentially via network), so it shouldn't be invoked on EDT
LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || !ApplicationManager.getApplication().isDispatchThread());
List<String> urls = null;
if (element instanceof PsiClass) {
ClassInfo info = findUrlForClass((PsiClass)element);
urls = info == null ? null : info.externalDocUrls;
urls = findUrlForClass((PsiClass)element);
}
else if (element instanceof PsiField) {
PsiField field = (PsiField)element;
PsiClass aClass = field.getContainingClass();
if (aClass != null) {
ClassInfo info = findUrlForClass(aClass);
if (info != null) {
urls = ContainerUtil.map(info.externalDocUrls, (url) -> url + "#" + field.getName());
urls = findUrlForClass(aClass);
if (urls != null) {
for (int i = 0; i < urls.size(); i++) {
urls.set(i, urls.get(i) + "#" + field.getName());
}
}
}
}
@@ -640,17 +633,17 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
PsiMethod method = (PsiMethod)element;
PsiClass aClass = method.getContainingClass();
if (aClass != null) {
ClassInfo info = findUrlForClass(aClass);
if (info != null) {
List<String> classUrls = findUrlForClass(aClass);
if (classUrls != null) {
urls = ContainerUtil.newSmartList();
for (int i = 0; i < info.externalDocUrls.size(); i++) {
// For JDK docs we assume it's always generated with matching javadoc version
LanguageLevel languageLevel = info.isInJdk ? PsiUtil.getLanguageLevel(method) : detectLanguageLevel(info.internalDocUrls.get(i));
String signature = formatMethodSignature(method,
languageLevel.isAtLeast(LanguageLevel.JDK_1_5),
languageLevel.isAtLeast(LanguageLevel.JDK_1_8));
urls.add(info.externalDocUrls.get(i) + "#" + signature);
final boolean useJava8Format = PsiUtil.isLanguageLevel8OrHigher(method);
final Set<String> signatures = getHtmlMethodSignatures(method, useJava8Format);
for (String signature : signatures) {
for (String classUrl : classUrls) {
urls.add(classUrl + "#" + signature);
}
}
}
}
@@ -669,104 +662,20 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
return null;
}
else {
return ContainerUtil.map(urls, FileUtil::toSystemIndependentName);
}
}
// this method is closely related to getExternalJavaDocUrl, both should be updated in sync
public static boolean hasUrlFor(PsiElement element) {
if (element instanceof PsiClass) {
ClassInfo info = findUrlForClass((PsiClass)element);
return info != null;
}
else if (element instanceof PsiField || element instanceof PsiMethod) {
PsiMember member = (PsiMember)element;
PsiClass aClass = member.getContainingClass();
return aClass != null && findUrlForClass(aClass) != null;
}
else if (element instanceof PsiPackage) {
return findUrlForPackage((PsiPackage)element) != null;
}
else if (element instanceof PsiDirectory) {
PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(((PsiDirectory)element));
return aPackage != null && findUrlForPackage(aPackage) != null;
}
else {
return false;
}
}
private static final Pattern GENERATED_SIGNATURE = Pattern.compile("<!-- Generated by javadoc(.*) on .* -->");
private static LanguageLevel detectLanguageLevel(String javadocPageUrl) {
try {
URL url = VfsUtilCore.convertToURL(javadocPageUrl);
if (url == null) {
LOG.warn("Couldn't open URL " + javadocPageUrl);
}
else {
try (InputStream stream = URLUtil.openStream(url);
Reader isr = new InputStreamReader(stream);
BufferedReader reader = new BufferedReader(isr)) {
String line;
while ((line = reader.readLine()) != null) {
Matcher matcher = GENERATED_SIGNATURE.matcher(line);
if (matcher.matches()) {
String version = matcher.group(1);
LanguageLevel level = parseVersion(version);
if (level == null) {
LOG.warn("Couldn't parse javadoc version " + version);
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug("Language level " + level + " detected for " + javadocPageUrl);
}
return level;
}
break;
}
}
}
LOG.warn("Couldn't find javadoc version in " + javadocPageUrl);
for (int i = 0; i < urls.size(); i++) {
urls.set(i, FileUtil.toSystemIndependentName(urls.get(i)));
}
return urls;
}
catch (Exception e) {
LOG.warn("Error reading " + javadocPageUrl, e);
}
return LanguageLevel.HIGHEST; // default, if detection fails
}
@Nullable
private static LanguageLevel parseVersion(@NotNull String javadocVersionString) {
if (javadocVersionString.isEmpty()) return LanguageLevel.JDK_1_3;
javadocVersionString = StringUtil.trim(javadocVersionString);
javadocVersionString = StringUtil.trimStart(javadocVersionString, "(");
javadocVersionString = StringUtil.trimEnd(javadocVersionString, ")");
javadocVersionString = StringUtil.trimStart(javadocVersionString, "version");
javadocVersionString = StringUtil.trimStart(javadocVersionString, "build");
javadocVersionString = StringUtil.trim(javadocVersionString);
String[] parts = javadocVersionString.split("[_.-]");
if (parts.length > 0) {
if (parts[0].equals("9")) return LanguageLevel.JDK_1_9;
if (parts[0].equals("1") && parts.length > 1) {
switch (parts[1]) {
case "8": return LanguageLevel.JDK_1_8;
case "7": return LanguageLevel.JDK_1_7;
case "6": return LanguageLevel.JDK_1_6;
case "5": return LanguageLevel.JDK_1_5;
case "4": return LanguageLevel.JDK_1_4;
}
}
}
return null;
}
public static Set<String> getHtmlMethodSignatures(PsiMethod method) {
public static Set<String> getHtmlMethodSignatures(PsiMethod method, boolean java8FormatFirst) {
final Set<String> signatures = new LinkedHashSet<String>();
signatures.add(formatMethodSignature(method, true, true));
signatures.add(formatMethodSignature(method, false, true));
signatures.add(formatMethodSignature(method, true, false));
signatures.add(formatMethodSignature(method, false, false));
signatures.add(formatMethodSignature(method, true, java8FormatFirst));
signatures.add(formatMethodSignature(method, false, java8FormatFirst));
signatures.add(formatMethodSignature(method, true, !java8FormatFirst));
signatures.add(formatMethodSignature(method, false, !java8FormatFirst));
return signatures;
}
@@ -788,7 +697,7 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
}
@Nullable
private static ClassInfo findUrlForClass(@NotNull PsiClass aClass) {
public static List<String> findUrlForClass(@NotNull PsiClass aClass) {
String qName = aClass.getQualifiedName();
if (qName == null) return null;
@@ -811,7 +720,7 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
}
@Nullable
private static ClassInfo findUrlForVirtualFile(@NotNull Project project, @NotNull VirtualFile virtualFile, @NotNull String relPath) {
public static List<String> findUrlForVirtualFile(@NotNull Project project, @NotNull VirtualFile virtualFile, @NotNull String relPath) {
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
Module module = fileIndex.getModuleForFile(virtualFile);
if (module == null) {
@@ -828,31 +737,28 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
final List<String> httpRoots = PlatformDocumentationUtil.getHttpRoots(javadocPaths, relPath);
// if found nothing and the file is from library classes, fall back to order entries
if (httpRoots != null || !fileIndex.isInLibraryClasses(virtualFile)) {
return ContainerUtil.isEmpty(httpRoots) ? null : new ClassInfo(false, httpRoots);
return httpRoots;
}
}
for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) {
boolean isJdk = orderEntry instanceof JdkOrderEntry;
List<String> internalUrls = new ArrayList<>();
for (VirtualFile root : orderEntry.getFiles(JavadocOrderRootType.getInstance())) {
String internalUrl = PlatformDocumentationUtil.getDocUrl(root, relPath);
if (internalUrl == null) continue;
if (root.getFileSystem() == JarFileSystem.getInstance()) {
VirtualFile file = root.findFileByRelativePath(relPath);
List<Url> urls = file == null ? null : BuiltInWebBrowserUrlProvider.getUrls(file, project, null);
if (!ContainerUtil.isEmpty(urls)) {
List<String> externalUrls = new SmartList<String>();
List<String> result = new SmartList<String>();
for (Url url : urls) {
externalUrls.add(url.toExternalForm());
result.add(url.toExternalForm());
}
return new ClassInfo(isJdk, Collections.nCopies(externalUrls.size(), internalUrl), externalUrls);
return result;
}
}
internalUrls.add(internalUrl);
}
if (!ContainerUtil.isEmpty(internalUrls)) {
return new ClassInfo(isJdk, internalUrls);
List<String> httpRoot = PlatformDocumentationUtil.getHttpRoots(JavadocOrderRootType.getUrls(orderEntry), relPath);
if (httpRoot != null) {
return httpRoot;
}
}
return null;
@@ -863,9 +769,9 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
String qName = aPackage.getQualifiedName();
qName = qName.replace('.', '/') + '/' + PACKAGE_SUMMARY_FILE;
for (PsiDirectory directory : aPackage.getDirectories()) {
ClassInfo urls = findUrlForVirtualFile(aPackage.getProject(), directory.getVirtualFile(), qName);
if (urls != null) {
return urls.externalDocUrls;
List<String> url = findUrlForVirtualFile(aPackage.getProject(), directory.getVirtualFile(), qName);
if (url != null) {
return url;
}
}
return null;
@@ -883,7 +789,7 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
@Override
public boolean hasDocumentationFor(PsiElement element, PsiElement originalElement) {
return hasUrlFor(element);
return CompositeDocumentationProvider.hasUrlsFor(this, element, originalElement);
}
@Override
@@ -929,22 +835,4 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
}
return null;
}
private static class ClassInfo {
private final boolean isInJdk;
/** These should be used to fetch documentation in IDEA code */
private final @NotNull List<String> internalDocUrls;
/** These are for viewing documentation in browser (jar:// urls here are replaced with http:// urls provided by built-in web server) */
private final @NotNull List<String> externalDocUrls;
private ClassInfo(boolean jdk, @NotNull List<String> docUrls) {
this(jdk, docUrls, docUrls);
}
private ClassInfo(boolean jdk, @NotNull List<String> internalUrls, @NotNull List<String> externalUrls) {
isInJdk = jdk;
internalDocUrls = internalUrls;
externalDocUrls = externalUrls;
}
}
}