blob: 67b1ef813de87fc2a08b85e6c4e9a70fa1c9f057 [file] [log] [blame]
/*
* Copyright 2000-2014 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.DocumentationManagerUtil;
import com.intellij.lang.ASTNode;
import com.intellij.lang.LangBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
import com.intellij.psi.impl.source.javadoc.PsiInlineDocTagImpl;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.javadoc.PsiInlineDocTag;
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.Function;
import com.intellij.util.IncorrectOperationException;
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.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.regex.Pattern;
public class JavaDocInfoGenerator {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.javadoc.JavaDocInfoGenerator");
@NonNls private static final Pattern ourNotDot = Pattern.compile("[^.]");
@NonNls private static final Pattern ourWhitespaces = Pattern.compile("[ \\n\\r\\t]+");
@NonNls private static final String THROWS_KEYWORD = "throws";
@NonNls private static final String BR_TAG = "<br>";
@NonNls private static final String LINK_TAG = "link";
@NonNls private static final String LITERAL_TAG = "literal";
@NonNls private static final String CODE_TAG = "code";
@NonNls private static final String LINKPLAIN_TAG = "linkplain";
@NonNls private static final String INHERITDOC_TAG = "inheritDoc";
@NonNls private static final String DOCROOT_TAG = "docRoot";
@NonNls private static final String VALUE_TAG = "value";
private static final String LT = "&lt;";
private static final String GT = "&gt;";
private final Project myProject;
private final PsiElement myElement;
interface InheritDocProvider<T> {
Pair<T, InheritDocProvider<T>> getInheritDoc();
PsiClass getElement();
}
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 static InheritDocProvider<PsiElement[]> mapProvider(final InheritDocProvider<PsiDocTag> i,
final 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();
}
};
}
interface DocTagLocator <T> {
T find(PsiDocComment comment);
}
private static DocTagLocator<PsiDocTag> parameterLocator(final String name) {
return new DocTagLocator<PsiDocTag>() {
@Override
public PsiDocTag find(PsiDocComment comment) {
if (comment == null) {
return null;
}
PsiDocTag[] tags = comment.findTagsByName("param");
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(final String name) {
return new DocTagLocator<PsiDocTag>() {
@Override
public PsiDocTag find(PsiDocComment comment) {
if (comment == null) {
return null;
}
PsiDocTag[] tags = getThrowsTags(comment);
for (PsiDocTag tag : tags) {
PsiDocTagValue value = tag.getValueElement();
if (value != null) {
String text = value.getText();
if (text != null && areWeakEqual(text, name)) {
return tag;
}
}
}
return null;
}
};
}
public JavaDocInfoGenerator(Project project, PsiElement element) {
myProject = project;
myElement = element;
}
@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 static String fixupDoc(@NotNull final StringBuilder buffer) {
String text = buffer.toString();
if (text.isEmpty()) {
return null;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Generated JavaDoc:");
LOG.debug(text);
}
text = StringUtil.replaceIgnoreCase(text, "<p/>", "<p></p>");
return StringUtil.replace(text, "/>", ">");
}
public boolean generateDocInfoCore (final StringBuilder buffer, final 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) {
final 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 {
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);
}
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) {
buffer.append("<html><body></body></html>");
}
String errorSection = "<p id=\"error\">Following external urls were checked:<br>&nbsp;&nbsp;&nbsp;<i>" +
StringUtil.join(docURLs, new Function<String, String>() {
@Override
public String fun(String url) {
return XmlStringUtil.escapeString(url);
}
}, "</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 void generateClassJavaDoc(@NonNls 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("<br>");
}
}
buffer.append("<PRE>");
if (generateClassSignature(buffer, aClass, true)) return;
buffer.append("</PRE>");
//buffer.append("<br>");
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);
String modifiers = PsiFormatUtil.formatModifiers(aClass, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
if (!modifiers.isEmpty()) {
buffer.append(modifiers);
buffer.append(" ");
}
buffer.append(aClass.isInterface() ? LangBundle.message("java.terms.interface") : LangBundle.message("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));
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 PsiDocComment docComment = aClass.getDocComment();
if (docComment == null) return;
final LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>> result =
new LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>>();
final PsiTypeParameter[] typeParameters = aClass.getTypeParameters();
for (PsiTypeParameter typeParameter : typeParameters) {
final DocTagLocator<PsiDocTag> locator = parameterLocator("<" + typeParameter.getName() + ">");
final Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> inClassComment = findInClassComment(aClass, locator);
if (inClassComment != null) {
result.add(inClassComment);
}
else {
final Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> pair = findInHierarchy(aClass, locator);
if (pair != null) {
result.add(pair);
}
}
}
generateTypeParametersSection(buffer, 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(getDocComment(psiClass));
if (tag != null) {
return new Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>(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(@NonNls StringBuilder buffer, PsiField field, boolean generatePrologueAndEpilogue) {
if (generatePrologueAndEpilogue)
generatePrologue(buffer);
PsiClass parentClass = field.getContainingClass();
if (parentClass != null) {
String qName = parentClass.getQualifiedName();
if (qName != null) {
buffer.append("<small><b>");
//buffer.append(qName);
generateLink(buffer, qName, qName, field, false);
buffer.append("</b></small>");
//buffer.append("<br>");
}
}
buffer.append("<PRE>");
generateFieldSignature(buffer, field, true);
buffer.append("</PRE>");
//buffer.append("<br>");
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);
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 static void generateVariableJavaDoc(@NonNls 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>");
//buffer.append("<br>");
ColorUtil.appendColorPreview(variable, buffer);
if (generatePrologueAndEpilogue)
generateEpilogue(buffer);
}
// not a javadoc in fact..
private static void generateFileJavaDoc(StringBuilder buffer, PsiFile file, boolean generatePrologueAndEpilogue) {
if (generatePrologueAndEpilogue)
generatePrologue(buffer);
final 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()) {
final PsiFile packageInfoFile = directory.findFile(PsiPackage.PACKAGE_INFO_FILE);
if (packageInfoFile != null) {
final ASTNode node = packageInfoFile.getNode();
if (node != null) {
final ASTNode docCommentNode = node.findChildByType(JavaDocElementType.DOC_COMMENT);
if (docCommentNode != null) {
final PsiDocComment docComment = (PsiDocComment)docCommentNode.getPsi();
if (generatePrologueAndEpilogue)
generatePrologue(buffer);
generateCommonSection(buffer, docComment);
if (generatePrologueAndEpilogue)
generateEpilogue(buffer);
break;
}
}
}
PsiFile packageHtmlFile = directory.findFile("package.html");
if (packageHtmlFile != null) {
generatePackageHtmlJavaDoc(buffer, packageHtmlFile, generatePrologueAndEpilogue);
break;
}
}
}
public void generateCommonSection(StringBuilder buffer, PsiDocComment docComment) {
generateDescription(buffer, docComment);
generateDeprecatedSection(buffer, docComment);
generateSinceSection(buffer, docComment);
generateSeeAlsoSection(buffer, docComment);
}
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();
if (rootTag != null) {
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.escapeLineBreak(StringUtil.shortenPathWithEllipsis(text, 120)) + "\"";
}
else if (type.equalsToText("char")) text = "'" + text + "'";
try {
return instance.getElementFactory().createExpressionFromText(text, variable);
} catch (IncorrectOperationException ex) {
LOG.error(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(@NonNls @NotNull StringBuilder buffer,
@NotNull PsiModifierListOwner owner,
boolean generateLink,
boolean splitAnnotations) {
final PsiModifierList ownerModifierList = owner.getModifierList();
if (ownerModifierList == null) return;
generateAnnotations(buffer, owner, ownerModifierList.getAnnotations(), false, generateLink, splitAnnotations);
PsiAnnotation[] externalAnnotations = ExternalAnnotationsManager.getInstance(owner.getProject()).findExternalAnnotations(owner);
if (externalAnnotations == null) {
externalAnnotations = new PsiAnnotation[]{};
}
PsiAnnotation[] inferredAnnotations = InferredAnnotationsManager.getInstance(owner.getProject()).findInferredAnnotations(owner);
externalAnnotations = ArrayUtil.mergeArrays(externalAnnotations, inferredAnnotations, PsiAnnotation.ARRAY_FACTORY);
generateAnnotations(buffer, owner, externalAnnotations, true, generateLink, splitAnnotations);
}
private static void generateAnnotations(StringBuilder buffer,
PsiModifierListOwner owner,
PsiAnnotation[] annotations,
boolean external,
boolean generateLink, boolean splitAnnotations) {
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);
if (!(shownAnnotations.add(annotation.getQualifiedName()) || isRepeatableAnnotationType(resolved))) {
continue;
}
if (resolved instanceof PsiClass) {
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("@");
generateType(buffer, type, owner, generateLink);
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>");
buffer.append(XmlStringUtil.escapeString(annotation.getText()));
buffer.append("&nbsp;");
if (inferred) buffer.append("</i>");
}
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(@NonNls StringBuilder buffer, PsiParameter parameter, boolean generatePrologueAndEpilogue) {
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);
generateType(buffer, parameter.getType(), parameter);
buffer.append(" ");
buffer.append("<b>");
buffer.append(parameter.getName());
appendInitializer(buffer, parameter);
buffer.append("</b>");
buffer.append("</PRE>");
final PsiMethod method = PsiTreeUtil.getParentOfType(parameter, PsiMethod.class);
if (method != null) {
final PsiDocComment docComment = getDocComment(method);
if (docComment != null) {
final Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> tagInfoProvider =
findDocTag(docComment.getTags(), parameter.getName(), method);
if (tagInfoProvider != null) {
PsiElement[] elements = tagInfoProvider.first.getDataElements();
if (elements.length != 0) generateOneParameter(elements, buffer, tagInfoProvider);
}
}
}
if (generatePrologueAndEpilogue)
generateEpilogue(buffer);
}
private void generateMethodJavaDoc(@NonNls StringBuilder buffer, PsiMethod method, boolean generatePrologueAndEpilogue) {
if (generatePrologueAndEpilogue)
generatePrologue(buffer);
PsiClass parentClass = method.getContainingClass();
if (parentClass != null) {
String qName = parentClass.getQualifiedName();
if (qName != null) {
buffer.append("<small><b>");
generateLink(buffer, qName, qName, method, false);
//buffer.append(qName);
buffer.append("</b></small>");
//buffer.append("<br>");
}
}
buffer.append("<PRE>");
generateMethodSignature(buffer, method, true);
buffer.append("</PRE>");
//buffer.append("<br>");
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);
generateReturnsSection(buffer, method, comment);
generateThrowsSection(buffer, method, comment);
if (comment != null) {
generateSinceSection(buffer, comment);
generateSeeAlsoSection(buffer, comment);
}
if (generatePrologueAndEpilogue)
generateEpilogue(buffer);
}
private static void generateMethodSignature(StringBuilder buffer, PsiMethod method, boolean generateLink) {
generateAnnotations(buffer, method, generateLink, true);
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);
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);
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);
generateType(buffer, parm.getType(), method, generateLink);
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, refs[i].getCanonicalText(), null, method, false);
if (i < refs.length - 1) {
buffer.append(",\n");
for (int j = 0; j < indent; j++) {
buffer.append(" ");
}
}
}
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
private PsiDocComment getMethodDocComment(final PsiMethod method) {
final PsiClass parentClass = method.getContainingClass();
if (parentClass != null && parentClass.isEnum()) {
final 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) {
final PsiType psiType = parameterList.getParameters()[0].getType();
if (psiType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
return loadSyntheticDocComment(method, "/javadoc/EnumValueOf.java.template");
}
}
}
return getDocComment(method);
}
private PsiDocComment loadSyntheticDocComment(final PsiMethod method, final String resourceName) {
final InputStream commentStream = JavaDocInfoGenerator.class.getResourceAsStream(resourceName);
if (commentStream == null) {
return null;
}
final StringBuilder buffer;
try {
buffer = new StringBuilder();
try {
for (int ch; (ch = commentStream.read()) != -1;) {
buffer.append((char)ch);
}
}
catch (IOException e) {
return null;
}
}
finally {
try {
commentStream.close();
}
catch (IOException e) {
LOG.error(e);
}
}
String s = buffer.toString();
s = StringUtil.replace(s, "<ClassName>", method.getContainingClass().getName());
final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(myProject).getElementFactory();
try {
return elementFactory.createDocCommentFromText(s);
}
catch (IncorrectOperationException e) {
return null;
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
private static void generatePrologue(StringBuilder buffer) {
buffer.append("<html><head>" +
" <style type=\"text/css\">" +
" #error {" +
" background-color: #eeeeee;" +
" margin-bottom: 10px;" +
" }" +
" p {" +
" margin: 5px 0;" +
" }" +
" </style>" +
"</head><body>");
}
@SuppressWarnings({"HardCodedStringLiteral"})
private static 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;
}
PsiElement[] descriptionElements = comment.getDescriptionElements();
for (PsiElement description : descriptionElements) {
String text = description.getText();
if (text != null) {
if (!ourWhitespaces.matcher(text).replaceAll("").isEmpty()) {
return false;
}
}
}
return true;
}
private void generateMethodDescription(@NonNls StringBuilder buffer, final PsiMethod method, final PsiDocComment comment) {
final DocTagLocator<PsiElement[]> descriptionLocator = new DocTagLocator<PsiElement[]>() {
@Override
public PsiElement[] find(PsiDocComment comment) {
if (comment == null) {
return null;
}
if (isEmptyDescription(comment)) {
return null;
}
return comment.getDescriptionElements();
}
};
if (comment != null) {
if (!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;
PsiClass extendee = pair.second.getElement();
if (elements != null) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>");
buffer.append(extendee.isInterface() ?
CodeInsightBundle.message("javadoc.description.copied.from.interface") :
CodeInsightBundle .message("javadoc.description.copied.from.class"));
buffer.append("</b>&nbsp;");
generateLink(buffer, extendee, JavaDocUtil.getShortestClassName(extendee, 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) {
return "";
}
String qName = aClass.getQualifiedName();
if (qName == null) {
return "";
}
return "../" + ourNotDot.matcher(qName).replaceAll("").replaceAll(".", "../");
}
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)) {
final PsiElement[] dataElements = tag instanceof PsiInlineDocTagImpl ?((PsiInlineDocTagImpl)tag).getDataElementsIgnoreWhitespaces()
: tag.getDataElements();
generateLiteralValue(buffer, dataElements);
}
else if (tagName.equals(CODE_TAG)) {
generateCodeValue(tag, buffer);
}
else if (tagName.equals(LINKPLAIN_TAG)) {
generateLinkValue(tag, buffer, true);
}
else if (tagName.equals(INHERITDOC_TAG)) {
Pair<PsiElement[], InheritDocProvider<PsiElement[]>> inheritInfo = provider.getInheritDoc();
if (inheritInfo != null) {
generateValue(buffer, inheritInfo.first, inheritInfo.second);
}
}
else if (tagName.equals(DOCROOT_TAG)) {
buffer.append(getDocRoot());
}
else if (tagName.equals(VALUE_TAG)) {
generateValueValue(tag, buffer, element);
}
else {
buffer.append(element.getText());
}
}
else {
buffer.append(element.getText());
}
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
private static void generateCodeValue(PsiInlineDocTag tag, StringBuilder buffer) {
buffer.append("<code>");
generateLiteralValue(buffer, tag.getDataElements());
buffer.append("</code>");
}
private static void generateLiteralValue(StringBuilder buffer, final PsiElement[] dataElements) {
for (PsiElement element : dataElements) {
appendPlainText(element.getText(), buffer);
}
}
private static void appendPlainText(@NonNls String text, final StringBuilder buffer) {
text = text.replaceAll("<", LT);
text = text.replaceAll(">", GT);
buffer.append(text);
}
private static 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 {
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) {
buffer.append(value);
}
else {
buffer.append(element.getText());
}
}
private static String createLinkText(final PsiElement[] tagElements) {
int predictOffset = tagElements.length > 0
? tagElements[0].getTextOffset() + tagElements[0].getText().length()
: 0;
StringBuilder buffer1 = new StringBuilder();
for (int j = 0; j < tagElements.length; j++) {
PsiElement tagElement = tagElements[j];
if (tagElement.getTextOffset() > predictOffset) buffer1.append(" ");
predictOffset = tagElement.getTextOffset() + tagElement.getText().length();
buffer1.append(tagElement.getText());
if (j < tagElements.length - 1) {
buffer1.append(" ");
}
}
return buffer1.toString().trim();
}
@SuppressWarnings({"HardCodedStringLiteral"})
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>");
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
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>");
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
private static 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>");
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
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<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>> collectedTags =
new LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>>();
for (PsiParameter param : params) {
final String paramName = param.getName();
Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> parmTag = findDocTag(localTags, paramName, method);
if (parmTag != null) {
collectedTags.addLast(parmTag);
}
}
if (!collectedTags.isEmpty()) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>").append(CodeInsightBundle.message("javadoc.parameters")).append("</b>");
for (Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> tag : collectedTags) {
PsiElement[] elements = tag.first.getDataElements();
if (elements.length == 0) continue;
generateOneParameter(elements, buffer, tag);
}
buffer.append("</DD></DL></DD>");
}
}
private void generateTypeParametersSection(final StringBuilder buffer, final PsiMethod method) {
final PsiDocComment docComment = method.getDocComment();
if (docComment == null) return;
final PsiDocTag[] localTags = docComment.findTagsByName("param");
final PsiTypeParameter[] typeParameters = method.getTypeParameters();
final LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>> collectedTags = new LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>>();
for (PsiTypeParameter typeParameter : typeParameters) {
final String paramName = "<" + typeParameter.getName() + ">";
Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> parmTag = findDocTag(localTags, paramName, method);
if (parmTag != null) {
collectedTags.addLast(parmTag);
}
}
generateTypeParametersSection(buffer, collectedTags);
}
private void generateTypeParametersSection(final StringBuilder buffer, final LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>> collectedTags) {
if (!collectedTags.isEmpty()) {
buffer.append("<DD><DL>");
buffer.append("<DT><b>").append(CodeInsightBundle.message("javadoc.type.parameters")).append("</b>");
for (Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> tag : collectedTags) {
PsiElement[] elements = tag.first.getDataElements();
if (elements.length == 0) continue;
generateOneParameter(elements, buffer, tag);
}
buffer.append("</DD></DL></DD>");
}
}
@Nullable private Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> findDocTag(final PsiDocTag[] localTags,
final String paramName,
final PsiMethod method) {
Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> parmTag = null;
for (PsiDocTag localTag : localTags) {
PsiDocTagValue value = localTag.getValueElement();
if (value != null) {
String tagName = value.getText();
if (tagName != null && tagName.equals(paramName)) {
parmTag =
new Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>
(localTag,
new InheritDocProvider<PsiDocTag>() {
@Override
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return findInheritDocTag(method, parameterLocator(paramName));
}
@Override
public PsiClass getElement() {
return method.getContainingClass();
}
});
break;
}
}
}
if (parmTag == null) {
parmTag = findInheritDocTag(method, parameterLocator(paramName));
}
return parmTag;
}
@SuppressWarnings({"HardCodedStringLiteral"})
private void generateOneParameter(final PsiElement[] elements,
final StringBuilder buffer,
final Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> tag) {
String text = elements[0].getText();
buffer.append("<DD>");
int spaceIndex = text.indexOf(' ');
if (spaceIndex < 0) {
spaceIndex = text.length();
}
String parmName = text.substring(0, spaceIndex);
buffer.append("<code>");
buffer.append(StringUtil.escapeXml(parmName));
buffer.append("</code>");
buffer.append(" - ");
buffer.append(text.substring(spaceIndex));
generateValue(buffer, elements, 1, mapProvider(tag.second, true));
}
@SuppressWarnings({"HardCodedStringLiteral"})
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<PsiDocTag, InheritDocProvider<PsiDocTag>>(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);
}
@SuppressWarnings({"HardCodedStringLiteral"})
private void generateThrowsSection(StringBuilder buffer, final PsiMethod method, final PsiDocComment comment) {
final PsiDocTag[] localTags = getThrowsTags(comment);
LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>> collectedTags =
new LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>>();
final List<PsiClassType> declaredThrows = new ArrayList<PsiClassType>(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;
}
}
final Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> tag = findInheritDocTag(method, exceptionLocator(valueElement.getText()));
collectedTags.addFirst(new Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>(localTags[i], new InheritDocProvider<PsiDocTag>() {
@Override
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return tag;
}
@Override
public PsiClass getElement() {
return method.getContainingClass();
}
}));
}
}
PsiClassType[] trousers = declaredThrows.toArray(new PsiClassType[declaredThrows.size()]);
for (PsiClassType trouser : trousers) {
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 {
final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory();
final PsiDocTag tag = elementFactory.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>");
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
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 : 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(overrides ?
CodeInsightBundle.message("javadoc.method.overrides") :
CodeInsightBundle .message("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 generateLink(buffer, refText, label, context, false);
}
}
/**
* @return Length of the generated label.
*/
@SuppressWarnings({"HardCodedStringLiteral"})
private static int generateLink(StringBuilder buffer, String refText, String label, @NotNull PsiElement context, boolean plainLink) {
if (label == null) {
final PsiManager manager = context.getManager();
label = JavaDocUtil.getLabelText(manager.getProject(), manager, refText, context);
}
LOG.assertTrue(refText != null, "refText appears to be null.");
final PsiElement target = JavaDocUtil.findReferenceTarget(context.getManager(), refText, context);
boolean isBrokenLink = target == null;
if (isBrokenLink) {
buffer.append("<font color=red>");
buffer.append(label);
buffer.append("</font>");
return StringUtil.stripHtml(label, true).length();
}
generateLink(buffer, target, label, plainLink);
return StringUtil.stripHtml(label, true).length();
}
/**
* @return Length of the generated label.
*/
@SuppressWarnings({"HardCodedStringLiteral"})
public static int generateType(StringBuilder buffer, PsiType type, PsiElement context) {
return generateType(buffer, type, context, true);
}
/**
* @return Length of the generated label.
*/
@SuppressWarnings({"HardCodedStringLiteral"})
public static int generateType(StringBuilder buffer, PsiType type, PsiElement context, boolean generateLink) {
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);
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){
final String keyword = wt.isExtends() ? " extends " : " super ";
buffer.append(keyword);
return generateType(buffer, bound, context, generateLink) + 1 + keyword.length();
}
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(type.getCanonicalText());
buffer.append(text);
return text.length();
}
int length;
if (generateLink) {
length = generateLink(buffer, qName, null, context, false);
}
else {
buffer.append(qName);
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);
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 = 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, generateLink);
}
return length;
}
}
return 0;
}
@SuppressWarnings({"HardCodedStringLiteral"})
private static String generateTypeParameters(PsiTypeParameterListOwner owner) {
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);
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>> searchDocTagInOverridenMethod(PsiMethod method,
final PsiClass aSuper,
final DocTagLocator<T> loc) {
if (aSuper != null) {
final PsiMethod overriden = findMethodInSuperClass(method, aSuper);
if (overriden != null) {
T tag = loc.find(getDocComment(overriden));
if (tag != null) {
return new Pair<T, InheritDocProvider<T>>
(tag,
new InheritDocProvider<T>() {
@Override
public Pair<T, InheritDocProvider<T>> getInheritDoc() {
return findInheritDocTag(overriden, loc);
}
@Override
public PsiClass getElement() {
return aSuper;
}
});
}
}
}
return null;
}
@Nullable
private static PsiMethod findMethodInSuperClass(PsiMethod method, PsiClass aSuper) {
for (PsiMethod superMethod : method.findDeepestSuperMethods()) {
PsiMethod overriden = aSuper.findMethodBySignature(superMethod, false);
if (overriden != null) {
return overriden;
}
}
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 = searchDocTagInOverridenMethod(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;
}
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>> findInheritDocTag(PsiMethod method, DocTagLocator<T> loc) {
PsiClass aClass = method.getContainingClass();
if (aClass == null) return null;
return findInheritDocTagInClass(method, aClass, loc, new HashSet<PsiClass>());
}
private static class ReturnTagLocator implements DocTagLocator<PsiDocTag> {
@Override
public PsiDocTag find(PsiDocComment comment) {
if (comment == null) {
return null;
}
return comment.findTagByName("return");
}
}
private static class MyVisitor extends JavaElementVisitor {
@NotNull 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);
}
myBuffer.append("(");
expression.acceptChildren(this);
myBuffer.append(")");
}
@Override
public void visitExpressionList(PsiExpressionList list) {
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());
}
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
myBuffer.append(expression.getMethodExpression().getText()).append("(");
expression.getArgumentList().acceptChildren(this);
myBuffer.append(")");
}
@Override
public void visitLiteralExpression(PsiLiteralExpression expression) {
myBuffer.append(StringUtil.escapeXml(expression.getText()));
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
myBuffer.append(StringUtil.escapeXml(expression.getText()));
}
}
}