/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.doc;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.ExternalAnnotationsManager;
import com.intellij.codeInsight.documentation.DocumentationManager;
import com.intellij.codeInsight.javadoc.ColorUtil;
import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator;
import com.intellij.codeInsight.javadoc.JavaDocUtil;
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.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaDirectoryService;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiAnonymousClass;
import com.intellij.psi.PsiArrayType;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiDisjunctionType;
import com.intellij.psi.PsiDocCommentOwner;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiEllipsisType;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiMember;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiNameValuePair;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeParameter;
import com.intellij.psi.PsiTypeParameterListOwner;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.PsiWildcardType;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
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.PsiTreeUtil;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import gw.plugin.ij.lang.psi.api.statements.typedef.IGosuEnhancementDefinition;
import gw.plugin.ij.lang.psi.api.statements.typedef.IGosuTypeDefinition;
import gw.plugin.ij.lang.psi.custom.CustomGosuClass;
import gw.plugin.ij.lang.psi.custom.GosuXMLField;
import gw.plugin.ij.lang.psi.impl.AbstractGosuClassFileImpl;
import gw.plugin.ij.lang.psi.impl.expressions.GosuAnnotationExpressionImpl;
import gw.plugin.ij.lang.psi.impl.statements.GosuFieldImpl;
import gw.plugin.ij.lang.psi.impl.statements.GosuFieldPropertyImpl;
import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuSyntheticClassDefinitionImpl;
import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuTypeDefinitionImpl;
import gw.plugin.ij.lang.psi.impl.statements.typedef.members.GosuMethodImpl;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Copied from JavaDocInfoGenerator.
*/
public class GosuDocInfoGenerator {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.javadoc.JavaDocInfoGenerator");
private static final @NonNls
Pattern ourNotDot = Pattern.compile("[^.]");
private static final @NonNls Pattern ourWhitespaces = Pattern.compile("[ \\n\\r\\t]+");
private static final @NonNls
Matcher ourNotDotMatcher = ourNotDot.matcher("");
private static final @NonNls Matcher ourWhitespacesMatcher = ourWhitespaces.matcher("");
public static final String FUNCTION = "function ";
private final Project myProject;
private final PsiElement myElement;
private static final @NonNls String THROWS_KEYWORD = "throws";
private static final @NonNls String BR_TAG = "<br>";
private static final @NonNls String LINK_TAG = "link";
private static final @NonNls String LITERAL_TAG = "literal";
private static final @NonNls String CODE_TAG = "code";
private static final @NonNls String LINKPLAIN_TAG = "linkplain";
private static final @NonNls String INHERITDOC_TAG = "inheritDoc";
private static final @NonNls String DOCROOT_TAG = "docRoot";
private static final @NonNls String VALUE_TAG = "value";
private static final String PROPERTY_SET = "property set ";
private static final String PROPERTY_GET = "property get ";
interface InheritDocProvider <T> {
@Nullable
Pair<T, InheritDocProvider<T>> getInheritDoc();
@Nullable
PsiClass getElement();
}
@Nullable
private static final InheritDocProvider<PsiDocTag> ourEmptyProvider = new InheritDocProvider<PsiDocTag>() {
@Nullable
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return null;
}
@Nullable
public PsiClass getElement() {
return null;
}
};
@Nullable
private static final InheritDocProvider<PsiElement[]> ourEmptyElementsProvider = mapProvider(ourEmptyProvider, false);
@Nullable
private static InheritDocProvider<PsiElement[]> mapProvider(@NotNull final InheritDocProvider<PsiDocTag> i,
final boolean dropFirst) {
return new InheritDocProvider<PsiElement[]>() {
@Nullable
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 new Pair<>(elements, mapProvider(pair.second, dropFirst));
}
@Nullable
public PsiClass getElement() {
return i.getElement();
}
};
}
interface DocTagLocator <T> {
@Nullable
T find(PsiDocComment comment);
}
@Nullable
private static DocTagLocator<PsiDocTag> parameterLocator(final String name) {
return new DocTagLocator<PsiDocTag>() {
@Nullable
public PsiDocTag find(@Nullable 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;
}
};
}
@Nullable
private static DocTagLocator<PsiDocTag> exceptionLocator(@NotNull final String name) {
return new DocTagLocator<PsiDocTag>() {
@Nullable
public PsiDocTag find(@Nullable 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 GosuDocInfoGenerator(Project project, PsiElement element) {
myProject = project;
myElement = element;
}
@Nullable
public String generateFileInfo() {
StringBuilder buffer = new StringBuilder();
if (myElement instanceof PsiFile) {
generateFileJavaDoc(buffer, (PsiFile) myElement); //used for Ctrl-Click
}
return fixupDoc(buffer);
}
@Nullable
private static String fixupDoc(@NotNull final StringBuilder buffer) {
String text = buffer.toString();
if (text.length() == 0) {
return null;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Generated JavaDoc:");
LOG.debug(text);
}
return StringUtil.replace(text, "/>", ">");
}
@Nullable
public String generateDocInfo(@Nullable List<String> docURLs) {
StringBuilder buffer = new StringBuilder();
if (myElement instanceof PsiClass) {
generateClassJavaDoc(buffer, (PsiClass) myElement);
}
else if (myElement instanceof GosuMethodImpl) {
generateMethodJavaDoc(buffer, (GosuMethodImpl)myElement);
} else if (myElement instanceof PsiParameter) {
generateMethodParameterJavaDoc(buffer, (PsiParameter)myElement);
}
else if (myElement instanceof PsiField) {
generateFieldJavaDoc(buffer, (PsiField)myElement);
}
else if (myElement instanceof PsiVariable) {
generateVariableJavaDoc(buffer, (PsiVariable)myElement);
}
else if (myElement instanceof PsiDirectory) {
final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage((PsiDirectory)myElement);
if (aPackage == null) return null;
generatePackageJavaDoc(buffer, aPackage);
}
else if (myElement instanceof PsiPackage) {
generatePackageJavaDoc(buffer, (PsiPackage) myElement);
} else {
return null;
}
if (docURLs != null) {
final StringBuilder sb = new StringBuilder("<p id=\"error\">Following external urls were checked:<br> <i>");
sb.append(StringUtil.join(docURLs, "</i><br> <i>"));
sb.append("</i><br>The documentation for this element is not found. Please add all the needed paths to API docs in ");
sb.append("<a href=\"open://Project Settings\">Project Settings.</a></p>");
buffer.insert(buffer.indexOf("<body>"), sb.toString());
}
return fixupDoc(buffer);
}
private void generateClassJavaDoc(@NotNull @NonNls StringBuilder buffer, @NotNull PsiClass aClass) {
if (aClass instanceof PsiAnonymousClass) return;
PsiManager manager = aClass.getManager();
generatePrologue(buffer);
PsiFile file = aClass.getContainingFile();
if (file instanceof AbstractGosuClassFileImpl) {
String packageName = ((AbstractGosuClassFileImpl)file).getPackageName();
if (packageName.length() > 0) {
buffer.append("<small><b>");
buffer.append(packageName);
buffer.append("</b></small>");
//buffer.append("<br>");
}
}
buffer.append("<PRE>");
generateAnnotations(buffer, aClass);
String modifiers = PsiFormatUtil.formatModifiers(aClass, PsiFormatUtil.JAVADOC_MODIFIERS_ONLY);
if (modifiers.length() > 0) {
buffer.append(modifiers);
buffer.append(" ");
}
buffer.append(getClassType(aClass));
buffer.append(" ");
String refText = JavaDocUtil.getReferenceText(myProject, aClass);
if (refText == null) {
buffer.setLength(0);
return;
}
String labelText = JavaDocUtil.getLabelText(myProject, manager, refText, aClass);
buffer.append("<b>");
buffer.append(labelText);
buffer.append("</b>");
buffer.append(generateTypeParameters(aClass));
if (isEnhancement(aClass)) {
buffer.append(" : ");
PsiElement enhancedType = ((IGosuEnhancementDefinition) aClass).getEnhancedType();
generateLink(buffer, getQualifiedName(enhancedType), null, aClass, false);
buffer.append("\n");
}
buffer.append("\n");
PsiClassType[] refs = aClass.getExtendsListTypes();
String qName = aClass.getQualifiedName();
if (!isEnhancement(aClass) && !(aClass instanceof GosuSyntheticClassDefinitionImpl) &&
(refs.length > 0 || !aClass.isInterface() && (qName == null || !qName.equals("java.lang.Object")))) {
buffer.append("extends ");
if (refs.length == 0) {
generateLink(buffer, "java.lang.Object", null, aClass, false);
}
else {
for (int i = 0; i < refs.length; i++) {
generateType(buffer, refs[i], aClass);
if (i < refs.length - 1) {
buffer.append(", ");
}
}
}
buffer.append("\n");
}
refs = aClass.getImplementsListTypes();
if (refs.length > 0) {
boolean firstInterface = true;
for (int i = 0; i < refs.length; i++) {
if (!refs[i].getCanonicalText().equals("gosu.lang.GosuObject")) {
if (firstInterface) {
buffer.append("implements ");
firstInterface = false;
}
generateType(buffer, refs[i], aClass);
if (i < refs.length - 1) {
buffer.append(", ");
}
}
}
buffer.append("\n");
}
if (buffer.charAt(buffer.length() - 1) == '\n') {
buffer.setLength(buffer.length() - 1);
}
buffer.append("</PRE>");
//buffer.append("<br>");
PsiDocComment comment = getDocComment(aClass);
if (comment != null) {
generateCommonSection(buffer, comment);
generateTypeParametersSection(buffer, aClass);
}
generateEpilogue(buffer);
}
private boolean isEnhancement(PsiClass aClass) {
return aClass instanceof GosuTypeDefinitionImpl && ((IGosuTypeDefinition)aClass).isEnhancement();
}
@Nullable
private String getQualifiedName(@NotNull PsiElement type) {
if (type instanceof PsiClass) {
return ((PsiClass) type).getQualifiedName();
} else {
return type.toString();
}
}
private String getClassType(@NotNull PsiClass aClass) {
if (aClass.isInterface()) {
return LangBundle.message("java.terms.interface");
} else if (isEnhancement(aClass)) {
return "enhancement";
} else if (aClass instanceof GosuSyntheticClassDefinitionImpl) {
return "template";
} else {
return LangBundle.message("java.terms.class");
}
}
private void generateTypeParametersSection(@NotNull final StringBuilder buffer, @NotNull final PsiClass aClass) {
final PsiDocComment docComment = aClass.getDocComment();
if (docComment == null) return;
final LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>> result =
new LinkedList<>();
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(@NotNull PsiClass psiClass, @NotNull 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;
}
@Nullable
private static Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> findInClassComment(@NotNull final PsiClass psiClass, @NotNull final DocTagLocator<PsiDocTag> locator) {
final PsiDocTag tag = locator.find(getDocComment(psiClass));
if (tag != null) {
return new Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>(tag, new InheritDocProvider<PsiDocTag>() {
@Nullable
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return findInHierarchy(psiClass, locator);
}
@NotNull
public PsiClass getElement() {
return psiClass;
}
});
}
return null;
}
@Nullable
private static PsiDocComment getDocComment(@NotNull PsiDocCommentOwner docOwner) {
if (docOwner instanceof CustomGosuClass || docOwner instanceof GosuXMLField) {
return docOwner.getDocComment();
}
if (docOwner instanceof GosuFieldPropertyImpl && docOwner.getParent() instanceof GosuFieldImpl) {
docOwner = (PsiDocCommentOwner) docOwner.getParent();
}
PsiDocComment comment = ((PsiDocCommentOwner)docOwner.getNavigationElement()).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) {
// return ((PsiDocCommentOwner)parent.getNavigationElement()).getDocComment();
// }
// }
// }
return comment;
}
private void generateFieldJavaDoc(@NotNull @NonNls StringBuilder buffer, @NotNull PsiField field) {
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>");
generateAnnotations(buffer, field);
String modifiers = PsiFormatUtil.formatModifiers(field, PsiFormatUtil.JAVADOC_MODIFIERS_ONLY);
if (modifiers.length() > 0) {
buffer.append(modifiers);
buffer.append(" ");
}
buffer.append("var ");
// name
buffer.append("<b>");
buffer.append(field.getName());
appendInitializer(buffer, field);
buffer.append("</b>");
// type
buffer.append(": ");
generateType(buffer, field.getType(), field);
buffer.append(" ");
buffer.append("</PRE>");
ColorUtil.appendColorPreview(field, buffer);
PsiDocComment comment = getDocComment(field);
if (comment != null) {
generateCommonSection(buffer, comment);
}
generateEpilogue(buffer);
}
// not a javadoc in fact..
private void generateVariableJavaDoc(@NotNull @NonNls StringBuilder buffer, @NotNull PsiVariable variable) {
generatePrologue(buffer);
buffer.append("<PRE>");
String modifiers = PsiFormatUtil.formatModifiers(variable, PsiFormatUtil.JAVADOC_MODIFIERS_ONLY);
if (modifiers.length() > 0) {
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);
generateEpilogue(buffer);
}
// not a javadoc in fact..
private static void generateFileJavaDoc(@NotNull StringBuilder buffer, @NotNull PsiFile file) {
generatePrologue(buffer);
final VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile != null) {
buffer.append(virtualFile.getPresentableUrl());
}
generateEpilogue(buffer);
}
private void generatePackageJavaDoc(@NotNull final StringBuilder buffer, @NotNull final PsiPackage psiPackage) {
for(PsiDirectory directory: psiPackage.getDirectories()) {
final PsiFile packageInfoFile = directory.findFile("package-info.java");
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();
generatePrologue(buffer);
generateCommonSection(buffer, docComment);
generateEpilogue(buffer);
break;
}
}
}
PsiFile packageHtmlFile = directory.findFile("package.html");
if (packageHtmlFile != null) {
generatePackageHtmlJavaDoc(buffer, packageHtmlFile);
break;
}
}
}
private void generateCommonSection(@NotNull StringBuilder buffer, @NotNull PsiDocComment docComment) {
generateDescription(buffer, docComment);
generateDeprecatedSection(buffer, docComment);
generateSinceSection(buffer, docComment);
generateSeeAlsoSection(buffer, docComment);
}
private void generatePackageHtmlJavaDoc(@NotNull final StringBuilder buffer, @NotNull final PsiFile packageHtmlFile) {
String htmlText;
XmlFile packageXmlFile = (XmlFile) packageHtmlFile;
final XmlTag rootTag = packageXmlFile.getDocument().getRootTag();
if (rootTag != null) {
final XmlTag subTag = rootTag.findFirstSubTag("body");
if (subTag != null) {
htmlText = subTag.getValue().getText();
}
else {
htmlText = packageHtmlFile.getText();
}
}
else {
htmlText = packageHtmlFile.getText();
}
htmlText = StringUtil.replace(htmlText, "*/", "*/");
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;
}
generatePrologue(buffer);
generateCommonSection(buffer, docComment);
generateEpilogue(buffer);
}
private static void appendInitializer(@NotNull StringBuilder buffer, @NotNull PsiVariable variable) {
PsiExpression initializer = variable.getInitializer();
if (initializer != null) {
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();
text = text.substring(0, index);
buffer.append(" = ");
text = StringUtil.replace(text, "<", "<");
text = StringUtil.replace(text, ">", ">");
buffer.append(text);
if (trunc) {
buffer.append("...");
}
}
}
private void generateAnnotations (@NotNull @NonNls StringBuilder buffer, @NotNull PsiModifierListOwner owner) {
final PsiModifierList ownerModifierList = owner.getModifierList();
if (ownerModifierList == null) return;
PsiAnnotation[] annotations = ownerModifierList.getAnnotations();
final PsiAnnotation[] externalAnnotations = ExternalAnnotationsManager.getInstance(owner.getProject()).findExternalAnnotations(owner);
if (externalAnnotations != null) {
annotations = ArrayUtil.mergeArrays(annotations, externalAnnotations, PsiAnnotation.ARRAY_FACTORY);
}
PsiManager manager = owner.getManager();
for (PsiAnnotation annotation : annotations) {
// final PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
// if (nameReferenceElement == null) continue;
// final PsiElement resolved = nameReferenceElement.resolve();
PsiClass annotationType = ((GosuAnnotationExpressionImpl) annotation).resolve();
if (annotationType!=null && AnnotationUtil.isAnnotated(annotationType, "java.lang.annotation.Documented", false)) {
final PsiClassType type = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createType(annotationType, PsiSubstitutor.EMPTY);
buffer.append("@");
generateType(buffer, type, owner);
final PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
if (attributes.length > 0) {
boolean first = true;
buffer.append("(");
for (PsiNameValuePair pair : attributes) {
if (!first) buffer.append(" ");
first = false;
final String name = pair.getName();
if (name != null) {
buffer.append(name);
buffer.append(" = ");
}
final PsiAnnotationMemberValue value = pair.getValue();
if (value != null) {
buffer.append(value.getText());
}
}
buffer.append(")");
}
buffer.append(" ");
}
}
}
private void generateMethodParameterJavaDoc(@NotNull @NonNls StringBuilder buffer, @NotNull PsiParameter parameter) {
generatePrologue(buffer);
buffer.append("<PRE>");
String modifiers = PsiFormatUtil.formatModifiers(parameter, PsiFormatUtil.JAVADOC_MODIFIERS_ONLY);
if (modifiers.length() > 0) {
buffer.append(modifiers);
buffer.append(" ");
}
generateAnnotations(buffer, parameter);
buffer.append("<b>");
buffer.append(parameter.getName());
buffer.append("</b>");
buffer.append(": ");
generateType(buffer, parameter.getType(), parameter);
appendInitializer(buffer, parameter);
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);
}
}
}
generateEpilogue(buffer);
}
private void generateMethodJavaDoc(@NotNull @NonNls StringBuilder buffer, @NotNull GosuMethodImpl method) {
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>");
int indent = 0;
generateAnnotations(buffer, method);
String modifiers = PsiFormatUtil.formatModifiers(method, PsiFormatUtil.JAVADOC_MODIFIERS_ONLY);
if (modifiers.length() > 0) {
buffer.append(modifiers);
buffer.append(" ");
indent += modifiers.length() + 1;
}
// type params
final String typeParamsString = generateTypeParameters(method);
indent += typeParamsString.length();
if (typeParamsString.length() > 0) {
buffer.append(typeParamsString);
buffer.append(" ");
indent++;
}
// name
String name = method.getName();
if (method.isForPropertySetter()) {
buffer.append(PROPERTY_SET);
indent += PROPERTY_SET.length() + name.length();
} else if (method.isForPropertyGetter()) {
buffer.append(PROPERTY_GET);
indent += PROPERTY_GET.length() + name.length();
} else if (method.isConstructor()) {
name = "construct";
} else {
buffer.append(FUNCTION);
indent += FUNCTION.length() + name.length();
}
buffer.append("<b>");
buffer.append(name);
buffer.append("</b>");
buffer.append("(");
// params
PsiParameter[] parms = method.getParameterList().getParameters();
for (int i = 0; i < parms.length; i++) {
PsiParameter parm = parms[i];
generateAnnotations(buffer, parm);
// buffer.append(" ");
if (parm.getName() != null) {
buffer.append(parm.getName());
buffer.append(": ");
}
generateType(buffer, parm.getType(), method);
if (i < parms.length - 1) {
buffer.append(",\n ");
for (int j = 0; j < indent; j++) {
buffer.append(" ");
}
}
}
buffer.append(")");
//return
PsiType returnType = method.getReturnType();
if (returnType != null && !returnType.getCanonicalText().equals("void")) {
buffer.append(": ");
indent += generateType(buffer, returnType, method);
buffer.append(" ");
indent++;
}
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(" ");
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(" ");
}
}
}
}
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);
}
generateEpilogue(buffer);
}
@Nullable
@SuppressWarnings({"HardCodedStringLiteral"})
private PsiDocComment getMethodDocComment(@NotNull final PsiMethod method) {
final PsiClass parentClass = method.getContainingClass();
if (parentClass != null && parentClass.isEnum()) {
if (method.getName().equals("values") && method.getParameterList().getParametersCount() == 0) {
return loadSyntheticDocComment(method, "/javadoc/EnumValues.java.template");
}
if (method.getName().equals("valueOf") && method.getParameterList().getParametersCount() == 1) {
return loadSyntheticDocComment(method, "/javadoc/EnumValueOf.java.template");
}
}
return getDocComment(method);
}
@Nullable
private PsiDocComment loadSyntheticDocComment(@NotNull 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(@NotNull 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(@NotNull 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(@NotNull StringBuilder buffer, @NotNull PsiDocComment comment) {
PsiElement[] elements = comment.getDescriptionElements();
generateValue(buffer, elements, ourEmptyElementsProvider);
}
private static boolean isEmptyDescription(@NotNull PsiDocComment comment) {
PsiElement[] descriptionElements = comment.getDescriptionElements();
for (PsiElement description : descriptionElements) {
String text = description.getText();
if (text != null) {
if (ourWhitespacesMatcher.reset(text).replaceAll("").length() != 0) {
return false;
}
}
}
return true;
}
private void generateMethodDescription(@NotNull @NonNls StringBuilder buffer, @NotNull final PsiMethod method, @Nullable final PsiDocComment comment) {
final DocTagLocator<PsiElement[]> descriptionLocator = new DocTagLocator<PsiElement[]>() {
@Nullable
public PsiElement[] find(@Nullable 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[]>() {
@Nullable
public Pair<PsiElement[], InheritDocProvider<PsiElement[]>> getInheritDoc() {
return findInheritDocTag(method, descriptionLocator);
}
@Nullable
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> ");
generateLink(buffer, extendee, JavaDocUtil.getShortestClassName(extendee, method));
buffer.append(BR_TAG);
generateValue(buffer, elements, pair.second);
buffer.append("</DD></DL></DD>");
}
}
}
private void generateValue(@NotNull StringBuilder buffer, @NotNull PsiElement[] elements, @NotNull InheritDocProvider<PsiElement[]> provider) {
generateValue(buffer, elements, 0, provider);
}
@NotNull
private String getDocRoot() {
PsiClass aClass = null;
if (myElement instanceof PsiClass) {
aClass = (PsiClass)myElement;
}
else if (myElement instanceof PsiMember) {
aClass = ((PsiMember)myElement).getContainingClass();
}
else {
LOG.error("Class or member expected but found " + myElement.getClass().getName());
}
String qName = aClass.getQualifiedName();
if (qName == null) {
return "";
}
return "../" + ourNotDotMatcher.reset(qName).replaceAll("").replaceAll(".", "../");
}
private void generateValue(@NotNull StringBuilder buffer,
@NotNull PsiElement[] elements,
int startIndex,
@NotNull 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 == null) {
buffer.append(element.getText());
}
else if (tagName.equals(LINK_TAG)) {
generateLinkValue(tag, buffer, false);
}
else if (tagName.equals(LITERAL_TAG)) {
generateLiteralValue(tag, buffer);
}
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(@NotNull PsiInlineDocTag tag, @NotNull StringBuilder buffer) {
buffer.append("<code>");
generateLiteralValue(tag, buffer);
buffer.append("</code>");
}
private static void generateLiteralValue(@NotNull PsiInlineDocTag tag, @NotNull StringBuilder buffer) {
PsiElement[] elements = tag.getDataElements();
for (PsiElement element : elements) {
appendPlainText(element.getText(), buffer);
}
}
private static void appendPlainText(@NonNls String text, @NotNull final StringBuilder buffer) {
text = text.replaceAll("<", "<");
text = text.replaceAll(">", ">");
buffer.append(text);
}
private void generateLinkValue(@NotNull PsiInlineDocTag tag, @NotNull StringBuilder buffer, boolean plainLink) {
PsiElement[] tagElements = tag.getDataElements();
String text = createLinkText(tagElements);
if (text.length() > 0) {
int index = JavaDocUtil.extractReference(text);
String refText = text.substring(0, index).trim();
String label = text.substring(index).trim();
if (label.length() == 0) {
label = null;
}
generateLink(buffer, refText, label, tagElements[0], plainLink);
}
}
private void generateValueValue(@NotNull final PsiInlineDocTag tag, @NotNull final StringBuilder buffer, @NotNull final PsiElement element) {
String text = createLinkText(tag.getDataElements());
PsiField valueField = null;
if (text.length() == 0) {
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.toString());
}
else {
buffer.append(element.getText());
}
}
private static String createLinkText(@NotNull 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(@NotNull StringBuilder buffer, @NotNull PsiDocComment comment) {
PsiDocTag tag = comment.findTagByName("deprecated");
if (tag != null) {
buffer.append("<DD><DL>");
buffer.append("<B>").append(CodeInsightBundle.message("javadoc.deprecated")).append("</B> ");
buffer.append("<I>");
generateValue(buffer, tag.getDataElements(), ourEmptyElementsProvider);
buffer.append("</I>");
buffer.append("</DL></DD>");
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
private void generateSinceSection(@NotNull StringBuilder buffer, @NotNull 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 void generateSeeAlsoSection(@NotNull StringBuilder buffer, @NotNull 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.length() == 0) {
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(@NotNull StringBuilder buffer, @NotNull final PsiMethod method, @Nullable 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<>();
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.size() > 0) {
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(@NotNull final StringBuilder buffer, @NotNull 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<>();
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(@NotNull final StringBuilder buffer, @NotNull final LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>> collectedTags) {
if (collectedTags.size() > 0) {
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(@NotNull final PsiDocTag[] localTags,
final String paramName,
@NotNull 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>() {
@Nullable
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return findInheritDocTag(method, parameterLocator(paramName));
}
@Nullable
public PsiClass getElement() {
return method.getContainingClass();
}
});
break;
}
}
}
if (parmTag == null) {
parmTag = findInheritDocTag(method, parameterLocator(paramName));
}
return parmTag;
}
@SuppressWarnings({"HardCodedStringLiteral"})
private void generateOneParameter(@NotNull final PsiElement[] elements,
@NotNull final StringBuilder buffer,
@NotNull 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(@NotNull StringBuilder buffer, @NotNull final PsiMethod method, @Nullable 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>(){
@Nullable
public Pair<PsiDocTag, InheritDocProvider<PsiDocTag>> getInheritDoc() {
return findInheritDocTag(method, new ReturnTagLocator());
}
@Nullable
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(@Nullable 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, PsiDocTag.class);
}
private static boolean areWeakEqual(@NotNull String one, @NotNull String two) {
return one.equals(two) || one.endsWith("." + two) || two.endsWith("." + one);
}
@SuppressWarnings({"HardCodedStringLiteral"})
private void generateThrowsSection(@NotNull StringBuilder buffer, @NotNull PsiMethod method, final PsiDocComment comment) {
PsiDocTag[] localTags = getThrowsTags(comment);
LinkedList<Pair<PsiDocTag, InheritDocProvider<PsiDocTag>>> collectedTags =
new LinkedList<>();
final 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;
}
}
collectedTags.addFirst(new Pair<>(localTags[i], ourEmptyProvider));
}
}
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 = new Pair<>(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(new Pair<>(tag, ourEmptyProvider));
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
}
if (collectedTags.size() > 0) {
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.length() > 0 || elements.length > 1) buffer.append(" - ");
buffer.append(rest);
generateValue(buffer, elements, 1, mapProvider(tag.second, true));
}
buffer.append("</DD></DL></DD>");
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
private void generateSuperMethodsSection(@NotNull StringBuilder buffer, @NotNull 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 (!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());
StringBuilder classBuffer = new StringBuilder();
generateLink(classBuffer, superClass, superClass.getName());
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 void generateLink(StringBuilder buffer, PsiElement element, String label) {
String refText = JavaDocUtil.getReferenceText(myProject, element);
if (refText != null) {
DocumentationManager.createHyperlink(buffer, refText, label, false);
//return generateLink(buffer, refText, label, context, false);
}
}
/**
* @return Length of the generated label.
*/
@SuppressWarnings({"HardCodedStringLiteral"})
private static int generateLink(@NotNull StringBuilder buffer, @Nullable String refText, @Nullable 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 label.length();
}
DocumentationManager.createHyperlink(buffer, JavaDocUtil.getReferenceText(context.getProject(), target), label, plainLink);
return label.length();
}
/**
* @return Length of the generated label.
*/
@SuppressWarnings({"HardCodedStringLiteral"})
public static int generateType(@NotNull StringBuilder buffer, @NotNull PsiType type, @NotNull PsiElement context) {
return generateType(buffer, type, context, true);
}
/**
* @return Length of the generated label.
*/
@SuppressWarnings({"HardCodedStringLiteral"})
public static int generateType(@NotNull StringBuilder buffer, @NotNull PsiType type, @NotNull PsiElement context, boolean generateLink) {
if (type instanceof PsiPrimitiveType) {
String text = 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 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 text = "<font color=red>" + type.getCanonicalText() + "</font>";
buffer.append(text);
return text.length();
}
String qName = psiClass.getQualifiedName();
if (qName == null || psiClass instanceof PsiTypeParameter) {
String text = 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();
boolean goodSubst = true;
PsiTypeParameter[] params = psiClass.getTypeParameters();
subst.append("<");
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(", ");
}
}
if (goodSubst) {
subst.append(">");
String text = subst.toString();
buffer.append(text);
}
}
return length;
}
if (type instanceof PsiDisjunctionType) {
if (!generateLink) {
final String text = type.getCanonicalText();
buffer.append(text);
return text.length();
}
else {
int length = 0;
for (PsiType psiType : ((PsiDisjunctionType)type).getDisjunctions()) {
if (length > 0) {
buffer.append(" | ");
length += 3;
}
length += generateType(buffer, psiType, context, generateLink);
}
return length;
}
}
return 0;
}
@SuppressWarnings({"HardCodedStringLiteral"})
private String generateTypeParameters(@NotNull PsiTypeParameterListOwner owner) {
if (owner.hasTypeParameters()) {
PsiTypeParameter[] parms = owner.getTypeParameters();
StringBuilder buffer = new StringBuilder();
buffer.append("<");
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(">");
return buffer.toString();
}
return "";
}
@Nullable
private <T> Pair<T, InheritDocProvider<T>> searchDocTagInOverridenMethod(@NotNull PsiMethod method,
@Nullable final PsiClass aSuper,
@NotNull 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>() {
@Nullable
public Pair<T, InheritDocProvider<T>> getInheritDoc() {
return findInheritDocTag(overriden, loc);
}
@Nullable
public PsiClass getElement() {
return aSuper;
}
});
}
}
}
return null;
}
@Nullable
private <T> PsiMethod findMethodInSuperClass(@NotNull PsiMethod method, @NotNull PsiClass aSuper) {
PsiMethod overriden = null;
for (PsiMethod superMethod : method.findDeepestSuperMethods()) {
overriden = aSuper.findMethodBySignature(superMethod, false);
if (overriden != null) {
return overriden;
}
}
return overriden;
}
@Nullable private <T> Pair<T, InheritDocProvider<T>> searchDocTagInSupers(@NotNull PsiClassType[] supers,
@NotNull PsiMethod method,
@NotNull DocTagLocator<T> loc) {
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) {
Pair<T, InheritDocProvider<T>> tag = findInheritDocTagInClass(method, aSuper, loc);
if (tag != null) {
return tag;
}
}
}
return null;
}
@Nullable
private <T> Pair<T, InheritDocProvider<T>> findInheritDocTagInClass(@NotNull PsiMethod aMethod,
@Nullable PsiClass aClass,
@NotNull DocTagLocator<T> loc) {
if (aClass == null) {
return null;
}
PsiClassType[] implementsTypes = aClass.getImplementsListTypes();
Pair<T, InheritDocProvider<T>> tag = searchDocTagInSupers(implementsTypes, aMethod, loc);
if (tag != null) {
return tag;
}
PsiClassType[] extendsTypes = aClass.getExtendsListTypes();
return searchDocTagInSupers(extendsTypes, aMethod, loc);
}
@Nullable private <T> Pair<T, InheritDocProvider<T>> findInheritDocTag(@NotNull PsiMethod method, @NotNull DocTagLocator<T> loc) {
PsiClass aClass = method.getContainingClass();
if (aClass == null) return null;
return findInheritDocTagInClass(method, aClass, loc);
}
private static class ReturnTagLocator implements DocTagLocator<PsiDocTag> {
@Nullable
public PsiDocTag find(@Nullable PsiDocComment comment) {
if (comment == null) {
return null;
}
return comment.findTagByName("return");
}
}
}