Files
openide/java/java-impl/src/com/intellij/codeInsight/javadoc/JavaDocInfoGenerator.java
Dmitry Batrak e6a6f609ea IDEA-164610 Quick documentation should escape angle brackets at the value of a String field referred via @value (javadoc tag)
also add quotes to string value, and convert value into a link to corresponding field's javadoc, like javadoc tool does
2016-11-30 18:01:44 +03:00

2228 lines
83 KiB
Java

/*
* 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.codeInsight.javadoc;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.ExternalAnnotationsManager;
import com.intellij.codeInsight.InferredAnnotationsManager;
import com.intellij.codeInsight.documentation.DocumentationManagerProtocol;
import com.intellij.codeInsight.documentation.DocumentationManagerUtil;
import com.intellij.javadoc.JavadocGeneratorRunProfile;
import com.intellij.lang.ASTNode;
import com.intellij.lang.LangBundle;
import com.intellij.lang.java.JavaDocumentationProvider;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.JDOMUtil;
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.CharsetToolkit;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.javadoc.*;
import com.intellij.psi.search.EverythingGlobalScope;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.util.PsiFormatUtilBase;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.util.XmlStringUtil;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JavaDocInfoGenerator {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.javadoc.JavaDocInfoGenerator");
private interface InheritDocProvider<T> {
Pair<T, InheritDocProvider<T>> getInheritDoc();
PsiClass getElement();
}
private interface DocTagLocator <T> {
T find(PsiDocCommentOwner owner, PsiDocComment comment);
}
private static final String THROWS_KEYWORD = "throws";
private static final String BR_TAG = "<br>";
private static final String LINK_TAG = "link";
private static final String LITERAL_TAG = "literal";
private static final String CODE_TAG = "code";
private static final String LINKPLAIN_TAG = "linkplain";
private static final String INHERIT_DOC_TAG = "inheritDoc";
private static final String DOC_ROOT_TAG = "docRoot";
private static final String VALUE_TAG = "value";
private static final String LT = "&lt;";
private static final String GT = "&gt;";
private static final Pattern ourWhitespaces = Pattern.compile("[ \\n\\r\\t]+");
private static final Pattern ourRelativeHtmlLinks = Pattern.compile("<A.*?HREF=\"([^\":]*)\"", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
private static final InheritDocProvider<PsiDocTag> ourEmptyProvider = new InheritDocProvider<PsiDocTag>() {
@Override
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return null;
}
@Override
public PsiClass getElement() {
return null;
}
};
private static final InheritDocProvider<PsiElement[]> ourEmptyElementsProvider = mapProvider(ourEmptyProvider, false);
private final Project myProject;
private final PsiElement myElement;
private final JavaSdkVersion mySdkVersion;
public JavaDocInfoGenerator(Project project, PsiElement element) {
myProject = project;
myElement = element;
Sdk jdk = JavadocGeneratorRunProfile.getSdk(myProject);
mySdkVersion = jdk == null ? null : JavaSdk.getInstance().getVersion(jdk);
}
private static InheritDocProvider<PsiElement[]> mapProvider(InheritDocProvider<PsiDocTag> i, boolean dropFirst) {
return new InheritDocProvider<PsiElement[]>() {
@Override
public Pair<PsiElement[], InheritDocProvider<PsiElement[]>> getInheritDoc() {
Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> pair = i.getInheritDoc();
if (pair == null) return null;
PsiElement[] elements;
PsiElement[] rawElements = pair.first.getDataElements();
if (dropFirst && rawElements != null && rawElements.length > 0) {
elements = new PsiElement[rawElements.length - 1];
System.arraycopy(rawElements, 1, elements, 0, elements.length);
}
else {
elements = rawElements;
}
return Pair.create(elements, mapProvider(pair.second, dropFirst));
}
@Override
public PsiClass getElement() {
return i.getElement();
}
};
}
private static DocTagLocator<PsiDocTag> parameterLocator(final int parameterIndex) {
return new DocTagLocator<PsiDocTag>() {
@Override
public PsiDocTag find(PsiDocCommentOwner owner, PsiDocComment comment) {
if (parameterIndex < 0 || comment == null || !(owner instanceof PsiMethod)) return null;
PsiParameter[] parameters = ((PsiMethod)owner).getParameterList().getParameters();
if (parameterIndex >= parameters.length) return null;
String name = parameters[parameterIndex].getName();
return getParamTagByName(comment, name);
}
};
}
private static DocTagLocator<PsiDocTag> typeParameterLocator(final int parameterIndex) {
return new DocTagLocator<PsiDocTag>() {
@Override
public PsiDocTag find(PsiDocCommentOwner owner, PsiDocComment comment) {
if (parameterIndex < 0 || comment == null || !(owner instanceof PsiTypeParameterListOwner)) return null;
PsiTypeParameter[] parameters = ((PsiTypeParameterListOwner)owner).getTypeParameters();
if (parameterIndex >= parameters.length) return null;
String rawName = parameters[parameterIndex].getName();
if (rawName == null) return null;
String name = "<" + rawName + ">";
return getParamTagByName(comment, name);
}
};
}
private static PsiDocTag getParamTagByName(@NotNull PsiDocComment comment, String name) {
PsiDocTag[] tags = comment.findTagsByName("param");
return getTagByName(tags, name);
}
private static PsiDocTag getTagByName(@NotNull PsiDocTag[] tags, String name) {
for (PsiDocTag tag : tags) {
PsiDocTagValue value = tag.getValueElement();
if (value != null) {
String text = value.getText();
if (text != null && text.equals(name)) {
return tag;
}
}
}
return null;
}
private static DocTagLocator<PsiDocTag> exceptionLocator(String name) {
return new DocTagLocator<PsiDocTag>() {
@Override
public PsiDocTag find(PsiDocCommentOwner owner, PsiDocComment comment) {
if (comment == null) return null;
for (PsiDocTag tag : getThrowsTags(comment)) {
PsiDocTagValue value = tag.getValueElement();
if (value != null) {
String text = value.getText();
if (text != null && areWeakEqual(text, name)) {
return tag;
}
}
}
return null;
}
};
}
@Nullable
public String generateFileInfo() {
StringBuilder buffer = new StringBuilder();
if (myElement instanceof PsiFile) {
generateFileJavaDoc(buffer, (PsiFile)myElement, true); //used for Ctrl-Click
}
return fixupDoc(buffer);
}
@Nullable
private String fixupDoc(@NotNull final StringBuilder buffer) {
String text = buffer.toString();
if (text.isEmpty()) return null;
text = convertHtmlLinks(text);
if (LOG.isDebugEnabled()) {
LOG.debug("Generated JavaDoc:");
LOG.debug(text);
}
text = StringUtil.replaceIgnoreCase(text, "<p/>", "<p></p>");
return StringUtil.replace(text, "/>", ">");
}
private String convertHtmlLinks(String text) {
if (myElement == null) return text; // we are resolving links in a context, without context, don't change links
StringBuilder result = new StringBuilder();
int prev = 0;
Matcher matcher = ourRelativeHtmlLinks.matcher(text);
while (matcher.find()) {
int groupStart = matcher.start(1);
int groupEnd = matcher.end(1);
result.append(text, prev, groupStart);
result.append(convertReference(text.substring(groupStart, groupEnd)));
prev = groupEnd;
}
if (result.length() == 0) return text; // don't copy text over, if there are no matches
result.append(text, prev, text.length());
return result.toString();
}
protected String convertReference(String href) {
return ObjectUtils.notNull(createReferenceForRelativeLink(href, myElement), href);
}
/**
* Converts a relative link into {@link DocumentationManagerProtocol#PSI_ELEMENT_PROTOCOL PSI_ELEMENT_PROTOCOL}-type link if possible
*/
@Nullable
static String createReferenceForRelativeLink(@NotNull String relativeLink, @NotNull PsiElement contextElement) {
String fragment = null;
int hashPosition = relativeLink.indexOf('#');
if (hashPosition >= 0) {
fragment = relativeLink.substring(hashPosition + 1);
relativeLink = relativeLink.substring(0, hashPosition);
}
PsiElement targetElement;
if (relativeLink.isEmpty()) {
targetElement = (contextElement instanceof PsiField || contextElement instanceof PsiMethod) ?
((PsiMember)contextElement).getContainingClass() : contextElement;
}
else {
if (!relativeLink.toLowerCase(Locale.US).endsWith(".htm") && !relativeLink.toLowerCase(Locale.US).endsWith(".html")) {
return null;
}
relativeLink = relativeLink.substring(0, relativeLink.lastIndexOf('.'));
String packageName = getPackageName(contextElement);
if (packageName == null) return null;
Couple<String> pathWithPackage = removeParentReferences(Couple.of(relativeLink, packageName));
if (pathWithPackage == null) return null;
relativeLink = pathWithPackage.first;
packageName = pathWithPackage.second;
relativeLink = relativeLink.replace('/', '.');
String qualifiedTargetName = packageName.isEmpty() ? relativeLink : packageName + "." + relativeLink;
JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(contextElement.getProject());
targetElement = "package-summary".equals(StringUtil.getShortName(qualifiedTargetName))
? javaPsiFacade.findPackage(StringUtil.getPackageName(qualifiedTargetName))
: javaPsiFacade.findClass(qualifiedTargetName, contextElement.getResolveScope());
}
if (targetElement == null) return null;
if (fragment != null && targetElement instanceof PsiClass) {
if (fragment.contains("-") || fragment.contains("(")) {
for (PsiMethod method : ((PsiClass)targetElement).getMethods()) {
Set<String> signatures = JavaDocumentationProvider.getHtmlMethodSignatures(method, true);
if (signatures.contains(fragment)) {
targetElement = method;
fragment = null;
break;
}
}
}
else {
for (PsiField field : ((PsiClass)targetElement).getFields()) {
if (fragment.equals(field.getName())) {
targetElement = field;
fragment = null;
break;
}
}
}
}
return DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL + JavaDocUtil.getReferenceText(targetElement.getProject(), targetElement) +
(fragment == null ? "" : DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL_REF_SEPARATOR + fragment);
}
/**
* Takes a pair of strings representing a relative path and a package name, and returns corresponding pair, where path is stripped of
* leading ../ elements, and package name adjusted correspondingly. Returns <code>null</code> if there are more ../ elements than package
* components.
*/
@Nullable
static Couple<String> removeParentReferences(Couple<String> pathWithContextPackage) {
String path = pathWithContextPackage.first;
String packageName = pathWithContextPackage.second;
while (path.startsWith("../")) {
if (packageName.isEmpty()) return null;
int dotPos = packageName.lastIndexOf('.');
packageName = dotPos < 0 ? "" : packageName.substring(0, dotPos);
path = path.substring(3);
}
return Couple.of(path, packageName);
}
static String getPackageName(PsiElement element) {
String packageName = null;
if (element instanceof PsiPackage) {
packageName = ((PsiPackage)element).getQualifiedName();
}
else {
PsiFile file = element.getContainingFile();
if (file instanceof PsiClassOwner) {
packageName = ((PsiClassOwner)file).getPackageName();
}
}
return packageName;
}
public boolean generateDocInfoCore(StringBuilder buffer, boolean generatePrologueAndEpilogue) {
if (myElement instanceof PsiClass) {
generateClassJavaDoc(buffer, (PsiClass)myElement, generatePrologueAndEpilogue);
}
else if (myElement instanceof PsiMethod) {
generateMethodJavaDoc(buffer, (PsiMethod)myElement, generatePrologueAndEpilogue);
}
else if (myElement instanceof PsiParameter) {
generateMethodParameterJavaDoc(buffer, (PsiParameter)myElement, generatePrologueAndEpilogue);
}
else if (myElement instanceof PsiField) {
generateFieldJavaDoc(buffer, (PsiField)myElement, generatePrologueAndEpilogue);
}
else if (myElement instanceof PsiVariable) {
generateVariableJavaDoc(buffer, (PsiVariable)myElement, generatePrologueAndEpilogue);
}
else if (myElement instanceof PsiDirectory) {
PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage((PsiDirectory)myElement);
if (aPackage == null) return false;
generatePackageJavaDoc(buffer, aPackage, generatePrologueAndEpilogue);
}
else if (myElement instanceof PsiPackage) {
generatePackageJavaDoc(buffer, (PsiPackage)myElement, generatePrologueAndEpilogue);
}
else if (myElement instanceof PsiJavaModule) {
generateModuleJavaDoc(buffer, (PsiJavaModule)myElement, generatePrologueAndEpilogue);
}
else {
return false;
}
return true;
}
public static String generateSignature(PsiElement element) {
StringBuilder buf = new StringBuilder();
if (element instanceof PsiClass) {
if (generateClassSignature(buf, (PsiClass)element, false)) return null;
}
else if (element instanceof PsiField) {
generateFieldSignature(buf, (PsiField)element, false);
}
else if (element instanceof PsiMethod) {
generateMethodSignature(buf, (PsiMethod)element, false, true);
}
return buf.toString();
}
@Nullable
public String generateDocInfo(List<String> docURLs) {
StringBuilder buffer = new StringBuilder();
if (!generateDocInfoCore(buffer, true)) {
return null;
}
if (docURLs != null) {
if (buffer.length() > 0 && elementHasSourceCode()) {
LOG.debug("Documentation for " + myElement + " was generated from source code, it wasn't found at following URLs: ", docURLs);
}
else {
if (buffer.length() == 0) {
buffer.append("<html><body></body></html>");
}
String errorSection = "<p id=\"error\">Following external urls were checked:<br>&nbsp;&nbsp;&nbsp;<i>" +
StringUtil.join(docURLs, XmlStringUtil::escapeString, "</i><br>&nbsp;&nbsp;&nbsp;<i>") +
"</i><br>The documentation for this element is not found. Please add all the needed paths to API docs in " +
"<a href=\"open://Project Settings\">Project Settings.</a></p>";
buffer.insert(buffer.indexOf("<body>"), errorSection);
}
}
return fixupDoc(buffer);
}
private boolean elementHasSourceCode() {
PsiFileSystemItem[] items;
if (myElement instanceof PsiDirectory) {
final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage((PsiDirectory)myElement);
if (aPackage == null) return false;
items = aPackage.getDirectories(new EverythingGlobalScope(myProject));
}
else if (myElement instanceof PsiPackage) {
items = ((PsiPackage)myElement).getDirectories(new EverythingGlobalScope(myProject));
}
else {
PsiFile containingFile = myElement.getNavigationElement().getContainingFile();
if (containingFile == null) return false;
items = new PsiFileSystemItem[] {containingFile};
}
ProjectFileIndex projectFileIndex = ProjectFileIndex.SERVICE.getInstance(myProject);
for (PsiFileSystemItem item : items) {
VirtualFile file = item.getVirtualFile();
if (file != null && projectFileIndex.isInSource(file)) return true;
}
return false;
}
private void generateClassJavaDoc(StringBuilder buffer, PsiClass aClass, boolean generatePrologueAndEpilogue) {
if (aClass instanceof PsiAnonymousClass) return;
if (generatePrologueAndEpilogue) generatePrologue(buffer);
PsiFile file = aClass.getContainingFile();
if (file instanceof PsiJavaFile) {
String packageName = ((PsiJavaFile)file).getPackageName();
if (!packageName.isEmpty()) {
buffer.append("<small><b>");
buffer.append(packageName);
buffer.append("</b></small>");
}
}
buffer.append("<PRE>");
if (generateClassSignature(buffer, aClass, true)) return;
buffer.append("</PRE>");
PsiDocComment comment = getDocComment(aClass);
if (comment != null) {
generateCommonSection(buffer, comment);
generateTypeParametersSection(buffer, aClass);
}
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
private static boolean generateClassSignature(StringBuilder buffer, PsiClass aClass, boolean generateLink) {
generateAnnotations(buffer, aClass, generateLink, true, false);
String modifiers = PsiFormatUtil.formatModifiers(aClass, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
if (!modifiers.isEmpty()) {
buffer.append(modifiers);
buffer.append(" ");
}
buffer.append(LangBundle.message(aClass.isInterface() ? "java.terms.interface" : "java.terms.class"));
buffer.append(" ");
String refText = JavaDocUtil.getReferenceText(aClass.getProject(), aClass);
if (refText == null) {
buffer.setLength(0);
return true;
}
String labelText = JavaDocUtil.getLabelText(aClass.getProject(), aClass.getManager(), refText, aClass);
buffer.append("<b>");
buffer.append(labelText);
buffer.append("</b>");
buffer.append(generateTypeParameters(aClass, false));
buffer.append("\n");
PsiClassType[] refs = aClass.getExtendsListTypes();
String qName = aClass.getQualifiedName();
if (refs.length > 0 || !aClass.isInterface() && (qName == null || !qName.equals(CommonClassNames.JAVA_LANG_OBJECT))) {
buffer.append("extends ");
if (refs.length == 0) {
generateLink(buffer, CommonClassNames.JAVA_LANG_OBJECT, null, aClass, false);
}
else {
for (int i = 0; i < refs.length; i++) {
generateType(buffer, refs[i], aClass, generateLink);
if (i < refs.length - 1) {
buffer.append(",&nbsp;");
}
}
}
buffer.append("\n");
}
refs = aClass.getImplementsListTypes();
if (refs.length > 0) {
buffer.append("implements ");
for (int i = 0; i < refs.length; i++) {
generateType(buffer, refs[i], aClass, generateLink);
if (i < refs.length - 1) {
buffer.append(",&nbsp;");
}
}
buffer.append("\n");
}
if (buffer.charAt(buffer.length() - 1) == '\n') {
buffer.setLength(buffer.length() - 1);
}
return false;
}
private void generateTypeParametersSection(final StringBuilder buffer, final PsiClass aClass) {
final LinkedList<ParamInfo> result = new LinkedList<>();
final PsiTypeParameter[] typeParameters = aClass.getTypeParameters();
for (int i = 0; i < typeParameters.length; i++) {
PsiTypeParameter typeParameter = typeParameters[i];
String name = "<" + typeParameter.getName() + ">";
final DocTagLocator<PsiDocTag> locator = typeParameterLocator(i);
final Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> inClassComment = findInClassComment(aClass, locator);
if (inClassComment != null) {
result.add(new ParamInfo(name, inClassComment));
}
else {
final Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> pair = findInHierarchy(aClass, locator);
if (pair != null) {
result.add(new ParamInfo(name, pair));
}
}
}
generateParametersSection(buffer, CodeInsightBundle.message("javadoc.type.parameters"), result);
}
@Nullable
private static Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> findInHierarchy(PsiClass psiClass, final DocTagLocator<PsiDocTag> locator) {
for (final PsiClass superClass : psiClass.getSupers()) {
final Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> pair = findInClassComment(superClass, locator);
if (pair != null) return pair;
}
for (PsiClass superInterface : psiClass.getInterfaces()) {
final Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> pair = findInClassComment(superInterface, locator);
if (pair != null) return pair;
}
return null;
}
private static Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> findInClassComment(final PsiClass psiClass, final DocTagLocator<PsiDocTag> locator) {
final PsiDocTag tag = locator.find(psiClass, getDocComment(psiClass));
if (tag != null) {
return new Pair<>(tag, new InheritDocProvider<PsiDocTag>() {
@Override
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return findInHierarchy(psiClass, locator);
}
@Override
public PsiClass getElement() {
return psiClass;
}
});
}
return null;
}
@Nullable
private static PsiDocComment getDocComment(final PsiDocCommentOwner docOwner) {
PsiElement navElement = docOwner.getNavigationElement();
if (!(navElement instanceof PsiDocCommentOwner)) {
LOG.info("Wrong navElement: " + navElement + "; original = " + docOwner + " of class " + docOwner.getClass());
return null;
}
PsiDocComment comment = ((PsiDocCommentOwner)navElement).getDocComment();
if (comment == null) { //check for non-normalized fields
final PsiModifierList modifierList = docOwner.getModifierList();
if (modifierList != null) {
final PsiElement parent = modifierList.getParent();
if (parent instanceof PsiDocCommentOwner && parent.getNavigationElement() instanceof PsiDocCommentOwner) {
return ((PsiDocCommentOwner)parent.getNavigationElement()).getDocComment();
}
}
}
return comment;
}
private void generateFieldJavaDoc(StringBuilder buffer, PsiField field, boolean generatePrologueAndEpilogue) {
if (generatePrologueAndEpilogue) generatePrologue(buffer);
generateLinkToParentIfNeeded(buffer, field);
buffer.append("<PRE>");
generateFieldSignature(buffer, field, true);
buffer.append("</PRE>");
ColorUtil.appendColorPreview(field, buffer);
PsiDocComment comment = getDocComment(field);
if (comment != null) {
generateCommonSection(buffer, comment);
}
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
private static void generateFieldSignature(StringBuilder buffer, PsiField field, boolean generateLink) {
generateAnnotations(buffer, field, generateLink, true, false);
String modifiers = PsiFormatUtil.formatModifiers(field, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
if (!modifiers.isEmpty()) {
buffer.append(modifiers);
buffer.append(" ");
}
generateType(buffer, field.getType(), field, generateLink);
buffer.append(" ");
buffer.append("<b>");
buffer.append(field.getName());
appendInitializer(buffer, field);
enumConstantOrdinal(buffer, field, field.getContainingClass(), "\n");
buffer.append("</b>");
}
public static void enumConstantOrdinal(StringBuilder buffer, PsiField field, PsiClass parentClass, final String newLine) {
if (parentClass != null && field instanceof PsiEnumConstant) {
final PsiField[] fields = parentClass.getFields();
final int idx = ArrayUtilRt.find(fields, field);
if (idx >= 0) {
buffer.append(newLine);
buffer.append("Enum constant ordinal: ").append(idx);
}
}
}
// not a javadoc in fact..
private void generateVariableJavaDoc(StringBuilder buffer, PsiVariable variable, boolean generatePrologueAndEpilogue) {
if (generatePrologueAndEpilogue) generatePrologue(buffer);
buffer.append("<PRE>");
String modifiers = PsiFormatUtil.formatModifiers(variable, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
if (!modifiers.isEmpty()) {
buffer.append(modifiers);
buffer.append(" ");
}
generateType(buffer, variable.getType(), variable);
buffer.append(" ");
buffer.append("<b>");
buffer.append(variable.getName());
appendInitializer(buffer, variable);
buffer.append("</b>");
buffer.append("</PRE>");
ColorUtil.appendColorPreview(variable, buffer);
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
// not a javadoc in fact..
private void generateFileJavaDoc(StringBuilder buffer, PsiFile file, boolean generatePrologueAndEpilogue) {
if (generatePrologueAndEpilogue) generatePrologue(buffer);
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile != null) buffer.append(virtualFile.getPresentableUrl());
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
private void generatePackageJavaDoc(final StringBuilder buffer, final PsiPackage psiPackage, boolean generatePrologueAndEpilogue) {
for (PsiDirectory directory : psiPackage.getDirectories(new EverythingGlobalScope(myProject))) {
final PsiFile packageInfoFile = directory.findFile(PsiPackage.PACKAGE_INFO_FILE);
if (packageInfoFile != null) {
final ASTNode node = packageInfoFile.getNode();
if (node != null) {
final ASTNode docCommentNode = findRelevantCommentNode(node);
if (docCommentNode != null) {
if (generatePrologueAndEpilogue) generatePrologue(buffer);
generateCommonSection(buffer, (PsiDocComment)docCommentNode.getPsi());
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
break;
}
}
}
PsiFile packageHtmlFile = directory.findFile("package.html");
if (packageHtmlFile != null) {
generatePackageHtmlJavaDoc(buffer, packageHtmlFile, generatePrologueAndEpilogue);
break;
}
}
}
private void generateModuleJavaDoc(StringBuilder buffer, PsiJavaModule module, boolean generatePrologueAndEpilogue) {
if (generatePrologueAndEpilogue) generatePrologue(buffer);
buffer.append("<pre>module <b>").append(module.getModuleName()).append("</b></pre>");
PsiDocComment comment = module.getDocComment();
if (comment != null) {
generateCommonSection(buffer, comment);
}
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
/**
* Finds doc comment immediately preceding package statement
*/
@Nullable
private static ASTNode findRelevantCommentNode(@NotNull ASTNode fileNode) {
ASTNode node = fileNode.findChildByType(JavaElementType.PACKAGE_STATEMENT);
if (node == null) node = fileNode.getLastChildNode();
while (node != null && node.getElementType() != JavaDocElementType.DOC_COMMENT) {
node = node.getTreePrev();
}
return node;
}
public void generateCommonSection(StringBuilder buffer, PsiDocComment docComment) {
generateDescription(buffer, docComment);
generateApiSection(buffer, docComment);
generateDeprecatedSection(buffer, docComment);
generateSinceSection(buffer, docComment);
generateSeeAlsoSection(buffer, docComment);
}
private void generateApiSection(StringBuilder buffer, PsiDocComment comment) {
final String[] tagNames = {"apiNote", "implSpec", "implNote"};
for (String tagName : tagNames) {
PsiDocTag tag = comment.findTagByName(tagName);
if (tag != null) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>").append(tagName).append("</b>");
buffer.append("<DD>");
generateValue(buffer, tag.getDataElements(), ourEmptyElementsProvider);
buffer.append("</DD></DL></DD>");
}
}
}
private void generatePackageHtmlJavaDoc(final StringBuilder buffer, final PsiFile packageHtmlFile, boolean generatePrologueAndEpilogue) {
String htmlText = packageHtmlFile.getText();
try {
final Document document = JDOMUtil.loadDocument(new ByteArrayInputStream(htmlText.getBytes(CharsetToolkit.UTF8_CHARSET)));
final Element rootTag = document.getRootElement();
final Element subTag = rootTag.getChild("body");
if (subTag != null) {
htmlText = subTag.getValue();
}
}
catch (JDOMException ignore) {}
catch (IOException ignore) {}
htmlText = StringUtil.replace(htmlText, "*/", "&#42;&#47;");
final String fileText = "/** " + htmlText + " */";
final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(packageHtmlFile.getProject()).getElementFactory();
final PsiDocComment docComment;
try {
docComment = elementFactory.createDocCommentFromText(fileText);
}
catch (IncorrectOperationException e) {
LOG.error(e);
return;
}
if (generatePrologueAndEpilogue) generatePrologue(buffer);
generateCommonSection(buffer, docComment);
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
public static @Nullable PsiExpression calcInitializerExpression(PsiVariable variable) {
PsiExpression initializer = variable.getInitializer();
if (initializer != null) {
PsiModifierList modifierList = variable.getModifierList();
if (modifierList != null && modifierList.hasModifierProperty(PsiModifier.FINAL) && !(initializer instanceof PsiLiteralExpression)) {
JavaPsiFacade instance = JavaPsiFacade.getInstance(variable.getProject());
Object o = instance.getConstantEvaluationHelper().computeConstantExpression(initializer);
if (o != null) {
String text = o.toString();
PsiType type = variable.getType();
if (type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
text = "\"" + StringUtil.escapeStringCharacters(StringUtil.shortenPathWithEllipsis(text, 120)) + "\"";
}
else if (type.equalsToText("char")) {
text = "'" + text + "'";
}
try {
return instance.getElementFactory().createExpressionFromText(text, variable);
} catch (IncorrectOperationException ex) {
LOG.info("type:" + type.getCanonicalText() + "; text: " + text, ex);
}
}
}
}
return null;
}
public static boolean appendExpressionValue(StringBuilder buffer, PsiExpression initializer, String label) {
String text = initializer.getText().trim();
int index1 = text.indexOf('\n');
if (index1 < 0) index1 = text.length();
int index2 = text.indexOf('\r');
if (index2 < 0) index2 = text.length();
int index = Math.min(index1, index2);
boolean trunc = index < text.length();
text = text.substring(0, index);
buffer.append(label);
buffer.append(StringUtil.escapeXml(text));
if (trunc) {
buffer.append("...");
}
return trunc;
}
private static void appendInitializer(StringBuilder buffer, PsiVariable variable) {
PsiExpression initializer = variable.getInitializer();
if (initializer != null) {
buffer.append(" = ");
String text = initializer.getText();
text = text.trim();
int index1 = text.indexOf('\n');
if (index1 < 0) index1 = text.length();
int index2 = text.indexOf('\r');
if (index2 < 0) index2 = text.length();
int index = Math.min(index1, index2);
boolean trunc = index < text.length();
if (trunc) {
text = text.substring(0, index);
buffer.append(StringUtil.escapeXml(text));
buffer.append("...");
}
else {
initializer.accept(new MyVisitor(buffer));
}
PsiExpression constantInitializer = calcInitializerExpression(variable);
if (constantInitializer != null) {
buffer.append("\n");
appendExpressionValue(buffer, constantInitializer, CodeInsightBundle.message("javadoc.resolved.value"));
}
}
}
private static void generateAnnotations(@NotNull StringBuilder buffer,
@NotNull PsiModifierListOwner owner,
boolean generateLink,
boolean splitAnnotations,
boolean useShortNames) {
final PsiModifierList ownerModifierList = owner.getModifierList();
if (ownerModifierList == null) return;
generateAnnotations(buffer, owner, ownerModifierList.getAnnotations(), false, generateLink, splitAnnotations, useShortNames);
PsiAnnotation[] externalAnnotations = ExternalAnnotationsManager.getInstance(owner.getProject()).findExternalAnnotations(owner);
if (externalAnnotations == null) {
externalAnnotations = PsiAnnotation.EMPTY_ARRAY;
}
PsiAnnotation[] inferredAnnotations = InferredAnnotationsManager.getInstance(owner.getProject()).findInferredAnnotations(owner);
externalAnnotations = ArrayUtil.mergeArrays(externalAnnotations, inferredAnnotations, PsiAnnotation.ARRAY_FACTORY);
generateAnnotations(buffer, owner, externalAnnotations, true, generateLink, splitAnnotations, useShortNames);
}
private static void generateAnnotations(StringBuilder buffer,
PsiModifierListOwner owner,
PsiAnnotation[] annotations,
boolean external,
boolean generateLink, boolean splitAnnotations, boolean useShortNames) {
PsiManager manager = owner.getManager();
Set<String> shownAnnotations = ContainerUtil.newHashSet();
for (PsiAnnotation annotation : annotations) {
final PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
if (nameReferenceElement == null) continue;
final PsiElement resolved = nameReferenceElement.resolve();
boolean inferred = AnnotationUtil.isInferredAnnotation(annotation);
String qualifiedName = annotation.getQualifiedName();
if (!(shownAnnotations.add(qualifiedName) || isRepeatableAnnotationType(resolved))) {
continue;
}
if (resolved instanceof PsiClass &&
qualifiedName != null && JavaDocUtil.findReferenceTarget(owner.getManager(), qualifiedName, owner) != null) {
final PsiClass annotationType = (PsiClass)resolved;
if (isDocumentedAnnotationType(annotationType)) {
if (inferred) buffer.append("<i>");
final PsiClassType type = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createType(annotationType, PsiSubstitutor.EMPTY);
buffer.append("@");
if (inferred && !generateLink) {
buffer.append(type.getPresentableText());
}
else {
generateType(buffer, type, owner, generateLink, useShortNames && !external);
}
final PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
if (attributes.length > 0) {
buffer.append("(");
boolean first = true;
for (PsiNameValuePair pair : attributes) {
if (!first) buffer.append(",&nbsp;");
first = false;
final String name = pair.getName();
if (name != null) {
buffer.append(name);
buffer.append(" = ");
}
final PsiAnnotationMemberValue value = pair.getValue();
if (value != null) {
if (value instanceof PsiArrayInitializerMemberValue) {
buffer.append("{");
boolean firstMember = true;
for(PsiAnnotationMemberValue memberValue:((PsiArrayInitializerMemberValue)value).getInitializers()) {
if (!firstMember) buffer.append(",");
firstMember = false;
appendLinkOrText(buffer, memberValue, generateLink);
}
buffer.append("}");
}
else {
appendLinkOrText(buffer, value, generateLink);
}
}
}
buffer.append(")");
}
if (inferred) buffer.append("</i>");
buffer.append("&nbsp;");
}
}
else if (external) {
if (inferred) buffer.append("<i>");
String annoText = inferred ? "@" + annotation.getNameReferenceElement().getReferenceName() + annotation.getParameterList().getText()
: annotation.getText();
buffer.append(XmlStringUtil.escapeString(annoText));
if (inferred) buffer.append("</i>");
buffer.append("&nbsp;");
}
else {
buffer.append("<font color=red>");
buffer.append(XmlStringUtil.escapeString(annotation.getText()));
buffer.append("</font>");
buffer.append("&nbsp;");
}
if (splitAnnotations) buffer.append("\n");
}
}
private static void appendLinkOrText(StringBuilder buffer,
PsiAnnotationMemberValue memberValue,
boolean generateLink) {
if (generateLink && memberValue instanceof PsiQualifiedReferenceElement) {
String text = ((PsiQualifiedReferenceElement)memberValue).getCanonicalText();
PsiElement resolve = ((PsiQualifiedReferenceElement)memberValue).resolve();
if (resolve instanceof PsiField) {
PsiField field = (PsiField)resolve;
PsiClass aClass = field.getContainingClass();
int startOfPropertyNamePosition = text.lastIndexOf('.');
if (startOfPropertyNamePosition != -1) {
text = text.substring(0, startOfPropertyNamePosition) + '#' + text.substring(startOfPropertyNamePosition + 1);
}
else {
if (aClass != null) text = aClass.getQualifiedName() + '#' + field.getName();
}
generateLink(buffer, text, aClass != null? aClass.getName() + '.' + field.getName():null, memberValue, false);
return;
}
}
buffer.append(XmlStringUtil.escapeString(memberValue.getText()));
}
public static boolean isDocumentedAnnotationType(@Nullable PsiElement annotationType) {
return annotationType instanceof PsiClass && AnnotationUtil.isAnnotated((PsiClass)annotationType, "java.lang.annotation.Documented", false);
}
public static boolean isRepeatableAnnotationType(@Nullable PsiElement annotationType) {
return annotationType instanceof PsiClass && AnnotationUtil.isAnnotated((PsiClass)annotationType, CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE, false, true);
}
private void generateMethodParameterJavaDoc(StringBuilder buffer, PsiParameter parameter, boolean generatePrologueAndEpilogue) {
String parameterName = parameter.getName();
if (generatePrologueAndEpilogue) generatePrologue(buffer);
buffer.append("<PRE>");
String modifiers = PsiFormatUtil.formatModifiers(parameter, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
if (!modifiers.isEmpty()) {
buffer.append(modifiers);
buffer.append(" ");
}
generateAnnotations(buffer, parameter, true, true, false);
generateType(buffer, parameter.getType(), parameter);
buffer.append(" ");
buffer.append("<b>");
buffer.append(parameterName);
appendInitializer(buffer, parameter);
buffer.append("</b>");
buffer.append("</PRE>");
final PsiElement method = PsiTreeUtil.getParentOfType(parameter, PsiMethod.class, PsiLambdaExpression.class);
if (method instanceof PsiMethod) {
PsiMethod psiMethod = (PsiMethod)method;
PsiParameterList parameterList = psiMethod.getParameterList();
if (parameter.getParent() == parameterList) { // this can also be a parameter in foreach statement or in catch clause
final PsiDocComment docComment = getDocComment(psiMethod);
final PsiDocTag[] localTags = docComment != null ? docComment.getTags() : PsiDocTag.EMPTY_ARRAY;
int parameterIndex = parameterList.getParameterIndex(parameter);
final ParamInfo tagInfoProvider = findDocTag(localTags, parameterName, psiMethod, parameterLocator(parameterIndex));
if (tagInfoProvider != null) {
generateOneParameter(buffer, tagInfoProvider);
}
}
}
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
private void generateMethodJavaDoc(StringBuilder buffer, PsiMethod method, boolean generatePrologueAndEpilogue) {
if (generatePrologueAndEpilogue) generatePrologue(buffer);
generateLinkToParentIfNeeded(buffer, method);
buffer.append("<PRE>");
generateMethodSignature(buffer, method, true, false);
buffer.append("</PRE>");
PsiDocComment comment = getMethodDocComment(method);
generateMethodDescription(buffer, method, comment);
generateSuperMethodsSection(buffer, method, false);
generateSuperMethodsSection(buffer, method, true);
if (comment != null) {
generateDeprecatedSection(buffer, comment);
}
generateParametersSection(buffer, method, comment);
generateTypeParametersSection(buffer, method, comment);
generateReturnsSection(buffer, method, comment);
generateThrowsSection(buffer, method, comment);
if (comment != null) {
generateApiSection(buffer, comment);
generateSinceSection(buffer, comment);
generateSeeAlsoSection(buffer, comment);
}
if (generatePrologueAndEpilogue) generateEpilogue(buffer);
}
private static void generateLinkToParentIfNeeded(StringBuilder buffer, PsiMember member) {
PsiClass parentClass = member.getContainingClass();
if (parentClass != null) {
String qName = parentClass.getQualifiedName();
if (qName != null) {
buffer.append("<small><b>");
generateLink(buffer, qName, qName, member, false);
buffer.append("</b></small>");
}
}
}
private static void generateMethodSignature(StringBuilder buffer, PsiMethod method, boolean generateLink, boolean useShortNames) {
generateAnnotations(buffer, method, generateLink, true, useShortNames);
String modifiers = PsiFormatUtil.formatModifiers(method, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
int indent = 0;
if (!modifiers.isEmpty()) {
buffer.append(modifiers);
buffer.append("&nbsp;");
indent += modifiers.length() + 1;
}
final String typeParamsString = generateTypeParameters(method, useShortNames);
indent += StringUtil.unescapeXml(StringUtil.stripHtml(typeParamsString, true)).length();
if (!typeParamsString.isEmpty()) {
buffer.append(typeParamsString);
buffer.append("&nbsp;");
indent++;
}
if (method.getReturnType() != null) {
indent += generateType(buffer, method.getReturnType(), method, generateLink, useShortNames);
buffer.append("&nbsp;");
indent++;
}
buffer.append("<b>");
String name = method.getName();
buffer.append(name);
buffer.append("</b>");
indent += name.length();
buffer.append("(");
PsiParameter[] parms = method.getParameterList().getParameters();
for (int i = 0; i < parms.length; i++) {
PsiParameter parm = parms[i];
generateAnnotations(buffer, parm, generateLink, false, useShortNames);
generateType(buffer, parm.getType(), method, generateLink, useShortNames);
buffer.append("&nbsp;");
if (parm.getName() != null) {
buffer.append(parm.getName());
}
if (i < parms.length - 1) {
buffer.append(",\n ");
for (int j = 0; j < indent; j++) {
buffer.append(" ");
}
}
}
buffer.append(")");
PsiClassType[] refs = method.getThrowsList().getReferencedTypes();
if (refs.length > 0) {
buffer.append("\n");
indent -= THROWS_KEYWORD.length() + 1;
for (int i = 0; i < indent; i++) {
buffer.append(" ");
}
indent += THROWS_KEYWORD.length() + 1;
buffer.append(THROWS_KEYWORD);
buffer.append("&nbsp;");
for (int i = 0; i < refs.length; i++) {
generateLink(buffer, useShortNames ? refs[i].getPresentableText() : refs[i].getCanonicalText(), null, method, false);
if (i < refs.length - 1) {
buffer.append(",\n");
for (int j = 0; j < indent; j++) {
buffer.append(" ");
}
}
}
}
}
private PsiDocComment getMethodDocComment(PsiMethod method) {
PsiClass parentClass = method.getContainingClass();
if (parentClass != null && parentClass.isEnum()) {
PsiParameterList parameterList = method.getParameterList();
if (method.getName().equals("values") && parameterList.getParametersCount() == 0) {
return loadSyntheticDocComment(method, "/javadoc/EnumValues.java.template");
}
if (method.getName().equals("valueOf") &&
parameterList.getParametersCount() == 1 &&
parameterList.getParameters()[0].getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
return loadSyntheticDocComment(method, "/javadoc/EnumValueOf.java.template");
}
}
return getDocComment(method);
}
private PsiDocComment loadSyntheticDocComment(PsiMethod method, String resourceName) {
PsiClass containingClass = method.getContainingClass();
assert containingClass != null : method;
String containingClassName = containingClass.getName();
assert containingClassName != null : containingClass;
try {
String text;
try (InputStream commentStream = JavaDocInfoGenerator.class.getResourceAsStream(resourceName)) {
if (commentStream == null) return null;
byte[] bytes = FileUtil.loadBytes(commentStream);
text = new String(bytes, CharsetToolkit.UTF8_CHARSET);
}
text = StringUtil.replace(text, "<ClassName>", containingClassName);
return JavaPsiFacade.getInstance(myProject).getElementFactory().createDocCommentFromText(text);
}
catch (IOException | IncorrectOperationException e) {
LOG.info(e);
return null;
}
}
protected void generatePrologue(StringBuilder buffer) {
URL baseUrl = getBaseUrl();
buffer.append("<html><head>");
if (baseUrl != null) {
buffer.append("<base href=\"").append(baseUrl).append("\">");
}
buffer.append(" <style type=\"text/css\">" +
" #error {" +
" background-color: #eeeeee;" +
" margin-bottom: 10px;" +
" }" +
" p {" +
" margin: 5px 0;" +
" }" +
" </style>" +
"</head><body>");
}
private URL getBaseUrl() {
if (myElement == null) return null;
PsiElement element = myElement.getNavigationElement();
if (element == null) return null;
PsiFile file = element.getContainingFile();
if (file == null) return null;
VirtualFile vFile = file.getVirtualFile();
if (vFile == null) return null;
return VfsUtilCore.convertToURL(vFile.getUrl());
}
protected void generateEpilogue(StringBuilder buffer) {
while (true) {
if (buffer.length() < BR_TAG.length()) break;
char c = buffer.charAt(buffer.length() - 1);
if (c == '\n' || c == '\r' || c == ' ' || c == '\t') {
buffer.setLength(buffer.length() - 1);
continue;
}
String tail = buffer.substring(buffer.length() - BR_TAG.length());
if (tail.equalsIgnoreCase(BR_TAG)) {
buffer.setLength(buffer.length() - BR_TAG.length());
continue;
}
break;
}
buffer.append("</body></html>");
}
private void generateDescription(StringBuilder buffer, PsiDocComment comment) {
PsiElement[] elements = comment.getDescriptionElements();
generateValue(buffer, elements, ourEmptyElementsProvider);
}
private static boolean isEmptyDescription(PsiDocComment comment) {
if (comment == null) return true;
for (PsiElement description : comment.getDescriptionElements()) {
String text = description.getText();
if (text != null && !ourWhitespaces.matcher(text).replaceAll("").isEmpty()) {
return false;
}
}
return true;
}
private void generateMethodDescription(StringBuilder buffer, PsiMethod method, PsiDocComment comment) {
final DocTagLocator<PsiElement[]> descriptionLocator = new DocTagLocator<PsiElement[]>() {
@Override
public PsiElement[] find(PsiDocCommentOwner owner, PsiDocComment comment) {
return comment != null && !isEmptyDescription(comment) ? comment.getDescriptionElements() : null;
}
};
if (comment != null && !isEmptyDescription(comment)) {
generateValue(buffer, comment.getDescriptionElements(), new InheritDocProvider<PsiElement[]>() {
@Override
public Pair<PsiElement[], InheritDocProvider<PsiElement[]>> getInheritDoc() {
return findInheritDocTag(method, descriptionLocator);
}
@Override
public PsiClass getElement() {
return method.getContainingClass();
}
});
return;
}
Pair<PsiElement[], InheritDocProvider<PsiElement[]>> pair = findInheritDocTag(method, descriptionLocator);
if (pair != null) {
PsiElement[] elements = pair.first;
if (elements != null) {
PsiClass aClass = pair.second.getElement();
buffer.append("<DD><DL>");
buffer.append("<DT><b>");
buffer.append(CodeInsightBundle.message(aClass.isInterface() ? "javadoc.description.copied.from.interface"
: "javadoc.description.copied.from.class"));
buffer.append("</b>&nbsp;");
generateLink(buffer, aClass, JavaDocUtil.getShortestClassName(aClass, method), false);
buffer.append(BR_TAG);
generateValue(buffer, elements, pair.second);
buffer.append("</DD></DL></DD>");
}
}
}
private void generateValue(StringBuilder buffer, PsiElement[] elements, InheritDocProvider<PsiElement[]> provider) {
generateValue(buffer, elements, 0, provider);
}
private String getDocRoot() {
PsiClass aClass;
if (myElement instanceof PsiClass) {
aClass = (PsiClass)myElement;
}
else if (myElement instanceof PsiMember) {
aClass = ((PsiMember)myElement).getContainingClass();
}
else {
aClass = PsiTreeUtil.getParentOfType(myElement, PsiClass.class);
}
if (aClass != null) {
String qName = aClass.getQualifiedName();
if (qName != null) {
return StringUtil.repeat("../", StringUtil.countChars(qName, '.') + 1);
}
}
return "";
}
private void generateValue(StringBuilder buffer,
PsiElement[] elements,
int startIndex,
InheritDocProvider<PsiElement[]> provider) {
int predictOffset = startIndex < elements.length ? elements[startIndex].getTextOffset() + elements[startIndex].getText().length() : 0;
for (int i = startIndex; i < elements.length; i++) {
if (elements[i].getTextOffset() > predictOffset) buffer.append(" ");
predictOffset = elements[i].getTextOffset() + elements[i].getText().length();
PsiElement element = elements[i];
if (element instanceof PsiInlineDocTag) {
PsiInlineDocTag tag = (PsiInlineDocTag)element;
final String tagName = tag.getName();
if (tagName.equals(LINK_TAG)) {
generateLinkValue(tag, buffer, false);
}
else if (tagName.equals(LITERAL_TAG)) {
generateLiteralValue(buffer, tag);
}
else if (tagName.equals(CODE_TAG)) {
generateCodeValue(tag, buffer);
}
else if (tagName.equals(LINKPLAIN_TAG)) {
generateLinkValue(tag, buffer, true);
}
else if (tagName.equals(INHERIT_DOC_TAG)) {
Pair<PsiElement[], InheritDocProvider<PsiElement[]>> inheritInfo = provider.getInheritDoc();
if (inheritInfo != null) {
generateValue(buffer, inheritInfo.first, inheritInfo.second);
}
}
else if (tagName.equals(DOC_ROOT_TAG)) {
buffer.append(getDocRoot());
}
else if (tagName.equals(VALUE_TAG)) {
generateValueValue(tag, buffer, element);
}
}
else {
buffer.append(StringUtil.replaceUnicodeEscapeSequences(element.getText()));
}
}
}
private void generateCodeValue(PsiInlineDocTag tag, StringBuilder buffer) {
buffer.append("<code>");
generateLiteralValue(buffer, tag);
buffer.append("</code>");
}
private void generateLiteralValue(StringBuilder buffer, PsiDocTag tag) {
StringBuilder tmpBuffer = new StringBuilder();
for (PsiElement element : tag.getDataElements()) {
appendPlainText(StringUtil.escapeXml(element.getText()), tmpBuffer);
}
if ((mySdkVersion == null || mySdkVersion.isAtLeast(JavaSdkVersion.JDK_1_8)) && isInPre(tag)) {
buffer.append(tmpBuffer);
}
else {
buffer.append(StringUtil.trimLeading(tmpBuffer));
}
}
private static boolean isInPre(PsiDocTag tag) {
PsiElement sibling = tag.getPrevSibling();
while (sibling != null) {
if (sibling instanceof PsiDocToken) {
String text = sibling.getText().toLowerCase();
int pos = text.lastIndexOf("pre>");
if (pos > 0) {
switch (text.charAt(pos - 1)) {
case '<' : return true;
case '/' : return false;
}
}
}
sibling = sibling.getPrevSibling();
}
return false;
}
private static void appendPlainText(String text, final StringBuilder buffer) {
buffer.append(StringUtil.replaceUnicodeEscapeSequences(text));
}
protected void generateLinkValue(PsiInlineDocTag tag, StringBuilder buffer, boolean plainLink) {
PsiElement[] tagElements = tag.getDataElements();
String text = createLinkText(tagElements);
if (!text.isEmpty()) {
int index = JavaDocUtil.extractReference(text);
String refText = text.substring(0, index).trim();
String label = text.substring(index).trim();
if (label.isEmpty()) {
label = null;
}
generateLink(buffer, refText, label, tagElements[0], plainLink);
}
}
private void generateValueValue(final PsiInlineDocTag tag, final StringBuilder buffer, final PsiElement element) {
String text = createLinkText(tag.getDataElements());
PsiField valueField = null;
if (text.isEmpty()) {
if (myElement instanceof PsiField) valueField = (PsiField) myElement;
}
else {
if (text.indexOf('#') == -1) {
text = "#" + text;
}
PsiElement target = JavaDocUtil.findReferenceTarget(PsiManager.getInstance(myProject), text, myElement);
if (target instanceof PsiField) {
valueField = (PsiField) target;
}
}
Object value = null;
if (valueField != null) {
PsiExpression initializer = valueField.getInitializer();
value = JavaConstantExpressionEvaluator.computeConstantExpression(initializer, false);
}
if (value != null) {
String valueText = StringUtil.escapeXml(value.toString());
if (value instanceof String) valueText = '"' + valueText + '"';
if (valueField.equals(myElement)) buffer.append(valueText); // don't generate link to itself
else generateLink(buffer, valueField, valueText, true);
}
else {
buffer.append(element.getText());
}
}
protected String createLinkText(final PsiElement[] tagElements) {
int predictOffset = tagElements.length > 0 ? tagElements[0].getTextOffset() + tagElements[0].getText().length() : 0;
StringBuilder buffer = new StringBuilder();
for (int j = 0; j < tagElements.length; j++) {
PsiElement tagElement = tagElements[j];
if (tagElement.getTextOffset() > predictOffset) buffer.append(" ");
predictOffset = tagElement.getTextOffset() + tagElement.getText().length();
collectElementText(buffer, tagElement);
if (j < tagElements.length - 1) {
buffer.append(" ");
}
}
return buffer.toString().trim();
}
protected void collectElementText(final StringBuilder buffer, PsiElement element) {
element.accept(new PsiRecursiveElementWalkingVisitor() {
@Override
public void visitElement(PsiElement element) {
super.visitElement(element);
if (element instanceof PsiWhiteSpace ||
element instanceof PsiJavaToken ||
element instanceof PsiDocToken && ((PsiDocToken)element).getTokenType() != JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) {
buffer.append(element.getText());
}
}
});
}
private void generateDeprecatedSection(StringBuilder buffer, PsiDocComment comment) {
PsiDocTag tag = comment.findTagByName("deprecated");
if (tag != null) {
buffer.append("<DD><DL>");
buffer.append("<B>").append(CodeInsightBundle.message("javadoc.deprecated")).append("</B>&nbsp;");
buffer.append("<I>");
generateValue(buffer, tag.getDataElements(), ourEmptyElementsProvider);
buffer.append("</I>");
buffer.append("</DL></DD>");
}
}
private void generateSinceSection(StringBuilder buffer, PsiDocComment comment) {
PsiDocTag tag = comment.findTagByName("since");
if (tag != null) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>").append(CodeInsightBundle.message("javadoc.since")).append("</b>");
buffer.append("<DD>");
generateValue(buffer, tag.getDataElements(), ourEmptyElementsProvider);
buffer.append("</DD></DL></DD>");
}
}
protected void generateSeeAlsoSection(StringBuilder buffer, PsiDocComment comment) {
PsiDocTag[] tags = comment.findTagsByName("see");
if (tags.length > 0) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>").append(CodeInsightBundle.message("javadoc.see.also")).append("</b>");
buffer.append("<DD>");
for (int i = 0; i < tags.length; i++) {
PsiDocTag tag = tags[i];
PsiElement[] elements = tag.getDataElements();
if (elements.length > 0) {
String text = createLinkText(elements);
if (text.startsWith("<")) {
buffer.append(text);
}
else if (text.startsWith("\"")) {
appendPlainText(text, buffer);
}
else {
int index = JavaDocUtil.extractReference(text);
String refText = text.substring(0, index).trim();
String label = text.substring(index).trim();
if (label.isEmpty()) {
label = null;
}
generateLink(buffer, refText, label, comment, false);
}
}
if (i < tags.length - 1) {
buffer.append(",\n");
}
}
buffer.append("</DD></DL></DD>");
}
}
private void generateParametersSection(StringBuilder buffer, final PsiMethod method, final PsiDocComment comment) {
PsiParameter[] params = method.getParameterList().getParameters();
PsiDocTag[] localTags = comment != null ? comment.findTagsByName("param") : PsiDocTag.EMPTY_ARRAY;
LinkedList<ParamInfo> collectedTags = new LinkedList<>();
for (int i = 0; i < params.length; i++) {
PsiParameter param = params[i];
String paramName = param.getName();
DocTagLocator<PsiDocTag> tagLocator = parameterLocator(i);
ParamInfo parmTag = findDocTag(localTags, paramName, method, tagLocator);
if (parmTag != null) {
collectedTags.addLast(parmTag);
}
}
generateParametersSection(buffer, CodeInsightBundle.message("javadoc.parameters"), collectedTags);
}
private void generateTypeParametersSection(final StringBuilder buffer, final PsiMethod method, PsiDocComment comment) {
final PsiDocTag[] localTags = comment == null ? PsiDocTag.EMPTY_ARRAY : comment.findTagsByName("param");
final PsiTypeParameter[] typeParameters = method.getTypeParameters();
final LinkedList<ParamInfo> collectedTags = new LinkedList<>();
for (int i = 0; i < typeParameters.length; i++) {
PsiTypeParameter typeParameter = typeParameters[i];
final String paramName = "<" + typeParameter.getName() + ">";
DocTagLocator<PsiDocTag> tagLocator = typeParameterLocator(i);
ParamInfo parmTag = findDocTag(localTags, paramName, method, tagLocator);
if (parmTag != null) {
collectedTags.addLast(parmTag);
}
}
generateParametersSection(buffer, CodeInsightBundle.message("javadoc.type.parameters"), collectedTags);
}
private void generateParametersSection(StringBuilder buffer, String titleMessage, LinkedList<ParamInfo> collectedTags) {
if (!collectedTags.isEmpty()) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>").append(titleMessage).append("</b>");
for (ParamInfo tag : collectedTags) {
generateOneParameter(buffer, tag);
}
buffer.append("</DD></DL></DD>");
}
}
private @Nullable ParamInfo findDocTag(PsiDocTag[] localTags, String paramName, PsiMethod method, DocTagLocator<PsiDocTag> tagLocator) {
PsiDocTag localTag = getTagByName(localTags, paramName);
if (localTag != null) {
return new ParamInfo(paramName, localTag, new InheritDocProvider<PsiDocTag>() {
@Override
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return findInheritDocTag(method, tagLocator);
}
@Override
public PsiClass getElement() {
return method.getContainingClass();
}
});
}
Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> tag = findInheritDocTag(method, tagLocator);
return tag == null ? null : new ParamInfo(paramName, tag);
}
private void generateOneParameter(StringBuilder buffer, ParamInfo tag) {
PsiElement[] elements = tag.docTag.getDataElements();
if (elements.length == 0) return;
String text = elements[0].getText();
buffer.append("<DD>");
int spaceIndex = text.indexOf(' ');
if (spaceIndex < 0) {
spaceIndex = text.length();
}
buffer.append("<code>");
buffer.append(StringUtil.escapeXml(tag.name));
buffer.append("</code>");
buffer.append(" - ");
buffer.append(text.substring(spaceIndex));
generateValue(buffer, elements, 1, mapProvider(tag.inheritDocTagProvider, true));
}
private void generateReturnsSection(StringBuilder buffer, final PsiMethod method, final PsiDocComment comment) {
PsiDocTag tag = comment == null ? null : comment.findTagByName("return");
Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> pair = tag == null ? null : new Pair<>(tag, new InheritDocProvider<PsiDocTag>() {
@Override
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return findInheritDocTag(method, new ReturnTagLocator());
}
@Override
public PsiClass getElement() {
return method.getContainingClass();
}
});
if (pair == null && myElement instanceof PsiMethod) {
pair = findInheritDocTag((PsiMethod)myElement, new ReturnTagLocator());
}
if (pair != null) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>").append(CodeInsightBundle.message("javadoc.returns")).append("</b>");
buffer.append("<DD>");
generateValue(buffer, pair.first.getDataElements(), mapProvider(pair.second, false));
buffer.append("</DD></DL></DD>");
}
}
private static PsiDocTag[] getThrowsTags(PsiDocComment comment) {
if (comment == null) return PsiDocTag.EMPTY_ARRAY;
PsiDocTag[] tags1 = comment.findTagsByName(THROWS_KEYWORD);
PsiDocTag[] tags2 = comment.findTagsByName("exception");
return ArrayUtil.mergeArrays(tags1, tags2);
}
private static boolean areWeakEqual(String one, String two) {
return one.equals(two) || one.endsWith("." + two) || two.endsWith("." + one);
}
private void generateThrowsSection(StringBuilder buffer, PsiMethod method, PsiDocComment comment) {
PsiDocTag[] localTags = getThrowsTags(comment);
LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>> collectedTags = new LinkedList<>();
List<PsiClassType> declaredThrows = new ArrayList<>(Arrays.asList(method.getThrowsList().getReferencedTypes()));
for (int i = localTags.length - 1; i > -1; i--) {
PsiDocTagValue valueElement = localTags[i].getValueElement();
if (valueElement != null) {
for (Iterator<PsiClassType> iterator = declaredThrows.iterator(); iterator.hasNext();) {
PsiClassType classType = iterator.next();
if (Comparing.strEqual(valueElement.getText(), classType.getClassName()) ||
Comparing.strEqual(valueElement.getText(), classType.getCanonicalText())) {
iterator.remove();
break;
}
}
Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> tag = findInheritDocTag(method, exceptionLocator(valueElement.getText()));
collectedTags.addFirst(new Pair<>(localTags[i], new InheritDocProvider<PsiDocTag>() {
@Override
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return tag;
}
@Override
public PsiClass getElement() {
return method.getContainingClass();
}
}));
}
}
for (PsiClassType trouser : declaredThrows) {
if (trouser != null) {
String paramName = trouser.getCanonicalText();
Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> parmTag = null;
for (PsiDocTag localTag : localTags) {
PsiDocTagValue value = localTag.getValueElement();
if (value != null) {
String tagName = value.getText();
if (tagName != null && areWeakEqual(tagName, paramName)) {
parmTag = Pair.create(localTag, ourEmptyProvider);
break;
}
}
}
if (parmTag == null) {
parmTag = findInheritDocTag(method, exceptionLocator(paramName));
}
if (parmTag != null) {
collectedTags.addLast(parmTag);
}
else {
try {
PsiDocTag tag = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createDocTagFromText("@exception " + paramName);
collectedTags.addLast(Pair.create(tag, ourEmptyProvider));
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
}
if (!collectedTags.isEmpty()) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>").append(CodeInsightBundle.message("javadoc.throws")).append("</b>");
for (Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> tag : collectedTags) {
PsiElement[] elements = tag.first.getDataElements();
if (elements.length == 0) continue;
buffer.append("<DD>");
String text = elements[0].getText();
int index = JavaDocUtil.extractReference(text);
String refText = text.substring(0, index).trim();
generateLink(buffer, refText, null, method, false);
String rest = text.substring(index);
if (!rest.isEmpty() || elements.length > 1) buffer.append(" - ");
buffer.append(rest);
generateValue(buffer, elements, 1, mapProvider(tag.second, true));
}
buffer.append("</DD></DL></DD>");
}
}
private static void generateSuperMethodsSection(StringBuilder buffer, PsiMethod method, boolean overrides) {
PsiClass parentClass = method.getContainingClass();
if (parentClass == null) return;
if (parentClass.isInterface() && !overrides) return;
PsiMethod[] supers = method.findSuperMethods();
if (supers.length == 0) return;
boolean headerGenerated = false;
for (PsiMethod superMethod : supers) {
boolean isAbstract = superMethod.hasModifierProperty(PsiModifier.ABSTRACT);
if (overrides) {
if (parentClass.isInterface() != isAbstract) continue;
}
else {
if (!isAbstract) continue;
}
PsiClass superClass = superMethod.getContainingClass();
if (superClass == null) continue;
if (!headerGenerated) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>");
buffer.append(CodeInsightBundle.message(overrides ? "javadoc.method.overrides" : "javadoc.method.specified.by"));
buffer.append("</b>");
headerGenerated = true;
}
buffer.append("<DD>");
StringBuilder methodBuffer = new StringBuilder();
generateLink(methodBuffer, superMethod, superMethod.getName(), false);
StringBuilder classBuffer = new StringBuilder();
generateLink(classBuffer, superClass, superClass.getName(), false);
if (superClass.isInterface()) {
buffer.append(CodeInsightBundle.message("javadoc.method.in.interface", methodBuffer.toString(), classBuffer.toString()));
}
else {
buffer.append(CodeInsightBundle.message("javadoc.method.in.class", methodBuffer.toString(), classBuffer.toString()));
}
}
if (headerGenerated) {
buffer.append("</DD></DL></DD>");
}
}
private static void generateLink(StringBuilder buffer, PsiElement element, String label, boolean plainLink) {
String refText = JavaDocUtil.getReferenceText(element.getProject(), element);
if (refText != null) {
DocumentationManagerUtil.createHyperlink(buffer, element, refText, label, plainLink);
}
}
/**
* @return Length of the generated label.
*/
private static int generateLink(StringBuilder buffer, String refText, String label, @NotNull PsiElement context, boolean plainLink) {
if (label == null) {
PsiManager manager = context.getManager();
label = JavaDocUtil.getLabelText(manager.getProject(), manager, refText, context);
}
LOG.assertTrue(refText != null, "refText appears to be null.");
PsiElement target = JavaDocUtil.findReferenceTarget(context.getManager(), refText, context);
if (target == null) {
buffer.append("<font color=red>").append(label).append("</font>");
}
else {
generateLink(buffer, target, label, plainLink);
}
return StringUtil.stripHtml(label, true).length();
}
/**
* @return Length of the generated label.
*/
public static int generateType(StringBuilder buffer, PsiType type, PsiElement context) {
return generateType(buffer, type, context, true);
}
/**
* @return Length of the generated label.
*/
public static int generateType(StringBuilder buffer, PsiType type, PsiElement context, boolean generateLink) {
return generateType(buffer, type, context, generateLink, false);
}
/**
* @return Length of the generated label.
*/
public static int generateType(StringBuilder buffer, PsiType type, PsiElement context, boolean generateLink, boolean useShortNames) {
if (type instanceof PsiPrimitiveType) {
String text = StringUtil.escapeXml(type.getCanonicalText());
buffer.append(text);
return text.length();
}
if (type instanceof PsiArrayType) {
int rest = generateType(buffer, ((PsiArrayType)type).getComponentType(), context, generateLink, useShortNames);
if (type instanceof PsiEllipsisType) {
buffer.append("...");
return rest + 3;
}
else {
buffer.append("[]");
return rest + 2;
}
}
if (type instanceof PsiCapturedWildcardType) {
type = ((PsiCapturedWildcardType)type).getWildcard();
}
if (type instanceof PsiWildcardType) {
PsiWildcardType wt = (PsiWildcardType)type;
buffer.append("?");
PsiType bound = wt.getBound();
if (bound != null) {
String keyword = wt.isExtends() ? " extends " : " super ";
buffer.append(keyword);
return generateType(buffer, bound, context, generateLink, useShortNames) + 1 + keyword.length();
}
else {
return 1;
}
}
if (type instanceof PsiClassType) {
PsiClassType.ClassResolveResult result = ((PsiClassType)type).resolveGenerics();
PsiClass psiClass = result.getElement();
PsiSubstitutor psiSubst = result.getSubstitutor();
if (psiClass == null) {
String canonicalText = type.getCanonicalText();
String text = "<font color=red>" + StringUtil.escapeXml(canonicalText) + "</font>";
buffer.append(text);
return canonicalText.length();
}
String qName = psiClass.getQualifiedName();
if (qName == null || psiClass instanceof PsiTypeParameter) {
String text = StringUtil.escapeXml(useShortNames ? type.getPresentableText() : type.getCanonicalText());
buffer.append(text);
return text.length();
}
String name = useShortNames ? ((PsiClassType)type).rawType().getPresentableText() : qName;
int length;
if (generateLink) {
length = generateLink(buffer, name, null, context, false);
}
else {
buffer.append(name);
length = buffer.length();
}
if (psiClass.hasTypeParameters()) {
StringBuilder subst = new StringBuilder();
PsiTypeParameter[] params = psiClass.getTypeParameters();
subst.append(LT);
length += 1;
boolean goodSubst = true;
for (int i = 0; i < params.length; i++) {
PsiType t = psiSubst.substitute(params[i]);
if (t == null) {
goodSubst = false;
break;
}
length += generateType(subst, t, context, generateLink, useShortNames);
if (i < params.length - 1) {
subst.append(", ");
}
}
subst.append(GT);
length += 1;
if (goodSubst) {
String text = subst.toString();
buffer.append(text);
}
}
return length;
}
if (type instanceof PsiDisjunctionType || type instanceof PsiIntersectionType) {
if (!generateLink) {
String canonicalText = useShortNames ? type.getPresentableText() : type.getCanonicalText();
final String text = StringUtil.escapeXml(canonicalText);
buffer.append(text);
return canonicalText.length();
}
else {
final String separator = type instanceof PsiDisjunctionType ? " | " : " & ";
final List<PsiType> componentTypes;
if (type instanceof PsiIntersectionType) {
componentTypes = Arrays.asList(((PsiIntersectionType)type).getConjuncts());
}
else {
componentTypes = ((PsiDisjunctionType)type).getDisjunctions();
}
int length = 0;
for (PsiType psiType : componentTypes) {
if (length > 0) {
buffer.append(separator);
length += 3;
}
length += generateType(buffer, psiType, context, true, useShortNames);
}
return length;
}
}
return 0;
}
private static String generateTypeParameters(PsiTypeParameterListOwner owner, boolean useShortNames) {
if (owner.hasTypeParameters()) {
PsiTypeParameter[] parms = owner.getTypeParameters();
StringBuilder buffer = new StringBuilder();
buffer.append(LT);
for (int i = 0; i < parms.length; i++) {
PsiTypeParameter p = parms[i];
buffer.append(p.getName());
PsiClassType[] refs = JavaDocUtil.getExtendsList(p);
if (refs.length > 0) {
buffer.append(" extends ");
for (int j = 0; j < refs.length; j++) {
generateType(buffer, refs[j], owner, true, useShortNames);
if (j < refs.length - 1) {
buffer.append(" & ");
}
}
}
if (i < parms.length - 1) {
buffer.append(", ");
}
}
buffer.append(GT);
return buffer.toString();
}
return "";
}
private <T> Pair<T, InheritDocProvider<T>> searchDocTagInOverriddenMethod(PsiMethod method, PsiClass aSuper, DocTagLocator<T> loc) {
if (aSuper != null) {
PsiMethod overridden = findMethodInSuperClass(method, aSuper);
if (overridden != null) {
T tag = loc.find(overridden, getDocComment(overridden));
if (tag != null) {
return new Pair<>(tag, new InheritDocProvider<T>() {
@Override
public Pair<T, InheritDocProvider<T>> getInheritDoc() {
return findInheritDocTag(overridden, loc);
}
@Override
public PsiClass getElement() {
return aSuper;
}
});
}
}
}
return null;
}
@Nullable
private static PsiMethod findMethodInSuperClass(PsiMethod method, PsiClass aSuper) {
for (PsiMethod superMethod : method.findDeepestSuperMethods()) {
PsiMethod overridden = aSuper.findMethodBySignature(superMethod, false);
if (overridden != null) return overridden;
}
return null;
}
@Nullable
private <T> Pair<T, InheritDocProvider<T>> searchDocTagInSupers(PsiClassType[] supers,
PsiMethod method,
DocTagLocator<T> loc,
Set<PsiClass> visitedClasses) {
for (PsiClassType superType : supers) {
PsiClass aSuper = superType.resolve();
if (aSuper != null) {
Pair<T, InheritDocProvider<T>> tag = searchDocTagInOverriddenMethod(method, aSuper, loc);
if (tag != null) return tag;
}
}
for (PsiClassType superType : supers) {
PsiClass aSuper = superType.resolve();
if (aSuper != null && visitedClasses.add(aSuper)) {
Pair<T, InheritDocProvider<T>> tag = findInheritDocTagInClass(method, aSuper, loc, visitedClasses);
if (tag != null) {
return tag;
}
}
}
return null;
}
private <T> Pair<T, InheritDocProvider<T>> findInheritDocTagInClass(PsiMethod aMethod,
PsiClass aClass,
DocTagLocator<T> loc,
Set<PsiClass> visitedClasses) {
if (aClass == null) return null;
Pair<T, InheritDocProvider<T>> delegate = findInheritDocTagInDelegate(aMethod, loc);
if (delegate != null) return delegate;
if (aClass instanceof PsiAnonymousClass) {
return searchDocTagInSupers(new PsiClassType[]{((PsiAnonymousClass)aClass).getBaseClassType()}, aMethod, loc, visitedClasses);
}
PsiClassType[] implementsTypes = aClass.getImplementsListTypes();
Pair<T, InheritDocProvider<T>> tag = searchDocTagInSupers(implementsTypes, aMethod, loc, visitedClasses);
if (tag != null) return tag;
PsiClassType[] extendsTypes = aClass.getExtendsListTypes();
return searchDocTagInSupers(extendsTypes, aMethod, loc, visitedClasses);
}
@Nullable
private <T> Pair<T, InheritDocProvider<T>> findInheritDocTagInDelegate(PsiMethod method, DocTagLocator<T> loc) {
PsiMethod delegateMethod = findDelegateMethod(method);
if (delegateMethod == null) return null;
PsiClass containingClass = delegateMethod.getContainingClass();
if (containingClass == null) return null;
T tag = loc.find(delegateMethod, getDocComment(delegateMethod));
if (tag == null) return null;
return Pair.create(tag, new InheritDocProvider<T>() {
@Override
public Pair<T, InheritDocProvider<T>> getInheritDoc() {
return findInheritDocTag(delegateMethod, loc);
}
@Override
public PsiClass getElement() {
return containingClass;
}
});
}
@Nullable
private static PsiMethod findDelegateMethod(@NotNull PsiMethod method) {
PsiDocCommentOwner delegate = DocumentationDelegateProvider.findDocumentationDelegate(method);
return delegate instanceof PsiMethod ? (PsiMethod)delegate : null;
}
@Nullable
private <T> Pair<T, InheritDocProvider<T>> findInheritDocTag(PsiMethod method, DocTagLocator<T> loc) {
PsiClass aClass = method.getContainingClass();
return aClass != null ? findInheritDocTagInClass(method, aClass, loc, new HashSet<>()) : null;
}
private static class ParamInfo {
private final String name;
private final PsiDocTag docTag;
private final InheritDocProvider<PsiDocTag> inheritDocTagProvider;
private ParamInfo(String paramName, PsiDocTag tag, InheritDocProvider<PsiDocTag> provider) {
name = paramName;
docTag = tag;
inheritDocTagProvider = provider;
}
private ParamInfo(String paramName, @NotNull Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> tagWithInheritProvider) {
this(paramName, tagWithInheritProvider.first, tagWithInheritProvider.second);
}
}
private static class ReturnTagLocator implements DocTagLocator<PsiDocTag> {
@Override
public PsiDocTag find(PsiDocCommentOwner owner, PsiDocComment comment) {
return comment != null ? comment.findTagByName("return") : null;
}
}
private static class MyVisitor extends JavaElementVisitor {
private final StringBuilder myBuffer;
MyVisitor(@NotNull StringBuilder buffer) {
myBuffer = buffer;
}
@Override
public void visitNewExpression(PsiNewExpression expression) {
myBuffer.append("new ");
PsiType type = expression.getType();
if (type != null) {
generateType(myBuffer, type, expression);
}
PsiExpression[] dimensions = expression.getArrayDimensions();
if (dimensions.length > 0) {
LOG.assertTrue(myBuffer.charAt(myBuffer.length() - 1) == ']');
myBuffer.setLength(myBuffer.length() - 1);
for (PsiExpression dimension : dimensions) {
dimension.accept(this);
myBuffer.append(", ");
}
myBuffer.setLength(myBuffer.length() - 2);
myBuffer.append(']');
}
else {
expression.acceptChildren(this);
}
}
@Override
public void visitExpressionList(PsiExpressionList list) {
myBuffer.append("(");
String separator = ", ";
PsiExpression[] expressions = list.getExpressions();
for (PsiExpression expression : expressions) {
expression.accept(this);
myBuffer.append(separator);
}
if (expressions.length > 0) {
myBuffer.setLength(myBuffer.length() - separator.length());
}
myBuffer.append(")");
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
myBuffer.append(StringUtil.escapeXml(expression.getMethodExpression().getText()));
expression.getArgumentList().accept(this);
}
@Override
public void visitExpression(PsiExpression expression) {
myBuffer.append(StringUtil.escapeXml(expression.getText()));
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
myBuffer.append(StringUtil.escapeXml(expression.getText()));
}
}
}