|
|
|
@@ -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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|