blob: 31f60096a193bb2739f42b77e2dfedd2f5e9bd4b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2022 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* This is an implementation of an early-draft specification developed under the Java
* Community Process (JCP) and is made available for testing and evaluation purposes
* only. The code is not compatible with any specification of the JCP.
*
* Contributors:
* IBM Corporation - initial API and implementation
* Tom Hofmann, Google <eclipse@tom.eicher.name> - [hovering] NPE when hovering over @value reference within a type's javadoc - https://bugs.eclipse.org/bugs/show_bug.cgi?id=320084
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.javadoc;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.jface.internal.text.html.HTMLPrinter;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJarEntryResource;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IPackageDeclaration;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MemberRef;
import org.eclipse.jdt.core.dom.MethodRef;
import org.eclipse.jdt.core.dom.MethodRefParameter;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TagProperty;
import org.eclipse.jdt.core.dom.TextElement;
import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
import org.eclipse.jdt.internal.corext.javadoc.JavaDocLocations;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.MethodOverrideTester;
import org.eclipse.jdt.internal.corext.util.SuperTypeHierarchyCache;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.JavadocContentAccess;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks;
/**
* Helper to get the content of a Javadoc comment as HTML.
*
* <p>
* <strong>This is work in progress. Parts of this will later become
* API through {@link JavadocContentAccess}</strong>
* </p>
*
* @since 3.4
*/
public class JavadocContentAccess2 {
private static final String BASE_URL_COMMENT_INTRO= "<!-- baseURL=\""; //$NON-NLS-1$
private static final String BLOCK_TAG_START= "<dl>"; //$NON-NLS-1$
private static final String BLOCK_TAG_END= "</dl>"; //$NON-NLS-1$
public static final String BlOCK_TAG_TITLE_START= "<dt>"; //$NON-NLS-1$
public static final String BlOCK_TAG_TITLE_END= "</dt>"; //$NON-NLS-1$
public static final String BlOCK_TAG_ENTRY_START= "<dd>"; //$NON-NLS-1$
public static final String BlOCK_TAG_ENTRY_END= "</dd>"; //$NON-NLS-1$
private static final String PARAM_NAME_START= "<b>"; //$NON-NLS-1$
private static final String PARAM_NAME_END= "</b> "; //$NON-NLS-1$
/**
* Implements the "Algorithm for Inheriting Method Comments" as specified for <a href=
* "http://download.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#inheritingcomments"
* >1.4.2</a>, <a href=
* "http://download.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#inheritingcomments"
* >1.5</a>, and <a href=
* "http://download.oracle.com/javase/6/docs/technotes/tools/windows/javadoc.html#inheritingcomments"
* >1.6</a>.
*
* <p>
* Unfortunately, the implementation is broken in Javadoc implementations since 1.5, see <a
* href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6376959">Sun's bug</a>.
* </p>
*
* <p>
* We adhere to the spec.
* </p>
*/
private static abstract class InheritDocVisitor {
public static final Object STOP_BRANCH= new Object() {
@Override
public String toString() { return "STOP_BRANCH"; } //$NON-NLS-1$
};
public static final Object CONTINUE= new Object() {
@Override
public String toString() { return "CONTINUE"; } //$NON-NLS-1$
};
/**
* Visits a type and decides how the visitor should proceed.
*
* @param currType the current type
* @return <ul>
* <li>{@link #STOP_BRANCH} to indicate that no Javadoc has been found and visiting
* super types should stop here</li>
* <li>{@link #CONTINUE} to indicate that no Javadoc has been found and visiting
* super types should continue</li>
* <li>an {@link Object} or <code>null</code>, to indicate that visiting should be
* cancelled immediately. The returned value is the result of
* {@link #visitInheritDoc(IType, ITypeHierarchy)}</li>
* </ul>
* @throws JavaModelException unexpected problem
* @see #visitInheritDoc(IType, ITypeHierarchy)
*/
public abstract Object visit(IType currType) throws JavaModelException;
/**
* Visits the super types of the given <code>currentType</code>.
*
* @param currentType the starting type
* @param typeHierarchy a super type hierarchy that contains <code>currentType</code>
* @return the result from a call to {@link #visit(IType)}, or <code>null</code> if none of
* the calls returned a result
* @throws JavaModelException unexpected problem
*/
public Object visitInheritDoc(IType currentType, ITypeHierarchy typeHierarchy) throws JavaModelException {
ArrayList<IType> visited= new ArrayList<>();
visited.add(currentType);
Object result= visitInheritDocInterfaces(visited, currentType, typeHierarchy);
if (result != InheritDocVisitor.CONTINUE)
return result;
IType superClass;
if (currentType.isInterface())
superClass= currentType.getJavaProject().findType("java.lang.Object"); //$NON-NLS-1$
else
superClass= typeHierarchy.getSuperclass(currentType);
while (superClass != null && ! visited.contains(superClass)) {
result= visit(superClass);
if (result == InheritDocVisitor.STOP_BRANCH) {
return null;
} else if (result == InheritDocVisitor.CONTINUE) {
visited.add(superClass);
result= visitInheritDocInterfaces(visited, superClass, typeHierarchy);
if (result != InheritDocVisitor.CONTINUE)
return result;
else
superClass= typeHierarchy.getSuperclass(superClass);
} else {
return result;
}
}
return null;
}
/**
* Visits the super interfaces of the given type in the given hierarchy, thereby skipping already visited types.
*
* @param visited set of visited types
* @param currentType type whose super interfaces should be visited
* @param typeHierarchy type hierarchy (must include <code>currentType</code>)
* @return the result, or {@link #CONTINUE} if no result has been found
* @throws JavaModelException unexpected problem
*/
private Object visitInheritDocInterfaces(ArrayList<IType> visited, IType currentType, ITypeHierarchy typeHierarchy) throws JavaModelException {
ArrayList<IType> toVisitChildren= new ArrayList<>();
for (IType superInterface : typeHierarchy.getSuperInterfaces(currentType)) {
if (visited.contains(superInterface))
continue;
visited.add(superInterface);
Object result= visit(superInterface);
if (result == InheritDocVisitor.STOP_BRANCH) {
//skip
} else if (result == InheritDocVisitor.CONTINUE) {
toVisitChildren.add(superInterface);
} else {
return result;
}
}
for (IType child : toVisitChildren) {
Object result= visitInheritDocInterfaces(visited, child, typeHierarchy);
if (result != InheritDocVisitor.CONTINUE)
return result;
}
return InheritDocVisitor.CONTINUE;
}
}
private static class JavadocLookup {
private static final JavadocLookup NONE= new JavadocLookup(null) {
@Override
public CharSequence getInheritedMainDescription(IMethod method) {
return null;
}
@Override
public CharSequence getInheritedParamDescription(IMethod method, int i) {
return null;
}
@Override
public CharSequence getInheritedReturnDescription(IMethod method) {
return null;
}
@Override
public CharSequence getInheritedExceptionDescription(IMethod method, String name) {
return null;
}
};
private interface DescriptionGetter {
/**
* Returns a Javadoc tag description or <code>null</code>.
*
* @param contentAccess the content access
* @return the description, or <code>null</code> if none
* @throws JavaModelException unexpected problem
*/
CharSequence getDescription(JavadocContentAccess2 contentAccess) throws JavaModelException;
}
private final IType fStartingType;
private final HashMap<IMethod, JavadocContentAccess2> fContentAccesses;
private ITypeHierarchy fTypeHierarchy;
private MethodOverrideTester fOverrideTester;
private JavadocLookup(IType startingType) {
fStartingType= startingType;
fContentAccesses= new HashMap<>();
}
/**
* For the given method, returns the main description from an overridden method.
*
* @param method a method
* @return the description that replaces the <code>{&#64;inheritDoc}</code> tag,
* or <code>null</code> if none could be found
*/
public CharSequence getInheritedMainDescription(IMethod method) {
return getInheritedDescription(method, JavadocContentAccess2::getMainDescription);
}
/**
* For the given method, returns the @param tag description for the given type parameter
* from an overridden method.
*
* @param method a method
* @param typeParamIndex the index of the type parameter
* @return the description that replaces the <code>{&#64;inheritDoc}</code> tag, or
* <code>null</code> if none could be found
*/
public CharSequence getInheritedTypeParamDescription(IMethod method, final int typeParamIndex) {
return getInheritedDescription(method, contentAccess -> contentAccess.getInheritedTypeParamDescription(typeParamIndex));
}
/**
* For the given method, returns the @param tag description for the given parameter from an
* overridden method.
*
* @param method a method
* @param paramIndex the index of the parameter
* @return the description that replaces the <code>{&#64;inheritDoc}</code> tag,
* or <code>null</code> if none could be found
*/
public CharSequence getInheritedParamDescription(IMethod method, final int paramIndex) {
return getInheritedDescription(method, contentAccess -> contentAccess.getInheritedParamDescription(paramIndex));
}
/**
* For the given method, returns the @return tag description from an overridden method.
*
* @param method a method
* @return the description that replaces the <code>{&#64;inheritDoc}</code> tag,
* or <code>null</code> if none could be found
*/
public CharSequence getInheritedReturnDescription(IMethod method) {
return getInheritedDescription(method, JavadocContentAccess2::getReturnDescription);
}
/**
* For the given method, returns the @throws/@exception tag description for the given
* exception from an overridden method.
*
* @param method a method
* @param simpleName the simple name of an exception
* @return the description that replaces the <code>{&#64;inheritDoc}</code> tag,
* or <code>null</code> if none could be found
*/
public CharSequence getInheritedExceptionDescription(IMethod method, final String simpleName) {
return getInheritedDescription(method, contentAccess -> contentAccess.getExceptionDescription(simpleName));
}
private CharSequence getInheritedDescription(final IMethod method, final DescriptionGetter descriptionGetter) {
try {
return (CharSequence) new InheritDocVisitor() {
@Override
public Object visit(IType currType) throws JavaModelException {
IMethod overridden= getOverrideTester().findOverriddenMethodInType(currType, method);
if (overridden == null)
return InheritDocVisitor.CONTINUE;
JavadocContentAccess2 contentAccess= getJavadocContentAccess(overridden);
if (contentAccess == null) {
if (overridden.getOpenable().getBuffer() == null) {
// Don't continue this branch when no source is available.
// We don't extract individual tags from Javadoc attachments,
// and it would be wrong to copy doc from further up the branch,
// thereby skipping doc from this overridden method.
return InheritDocVisitor.STOP_BRANCH;
} else {
return InheritDocVisitor.CONTINUE;
}
}
CharSequence overriddenDescription= descriptionGetter.getDescription(contentAccess);
if (overriddenDescription != null)
return overriddenDescription;
else
return InheritDocVisitor.CONTINUE;
}
}.visitInheritDoc(method.getDeclaringType(), getTypeHierarchy());
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
return null;
}
/**
* @param method the method
* @return the Javadoc content access for the given method, or
* <code>null</code> if no Javadoc could be found in source
* @throws JavaModelException unexpected problem
*/
private JavadocContentAccess2 getJavadocContentAccess(IMethod method) throws JavaModelException {
Object cached= fContentAccesses.get(method);
if (cached != null)
return (JavadocContentAccess2) cached;
if (fContentAccesses.containsKey(method))
return null;
IBuffer buf= method.getOpenable().getBuffer();
if (buf == null) { // no source attachment found
fContentAccesses.put(method, null);
return null;
}
ISourceRange javadocRange= method.getJavadocRange();
if (javadocRange == null) {
fContentAccesses.put(method, null);
return null;
}
String rawJavadoc= buf.getText(javadocRange.getOffset(), javadocRange.getLength());
Javadoc javadoc= getJavadocNode(method, rawJavadoc);
if (javadoc == null) {
fContentAccesses.put(method, null);
return null;
}
JavadocContentAccess2 contentAccess= new JavadocContentAccess2(method, javadoc, rawJavadoc, this);
fContentAccesses.put(method, contentAccess);
return contentAccess;
}
private ITypeHierarchy getTypeHierarchy() throws JavaModelException {
if (fTypeHierarchy == null)
fTypeHierarchy= SuperTypeHierarchyCache.getTypeHierarchy(fStartingType);
return fTypeHierarchy;
}
private MethodOverrideTester getOverrideTester() throws JavaModelException {
if (fOverrideTester == null)
fOverrideTester= SuperTypeHierarchyCache.getMethodOverrideTester(fStartingType);
return fOverrideTester;
}
}
/**
* Either an IMember or an IPackageFragment.
*/
private final IJavaElement fElement;
/**
* The method, or <code>null</code> if {@link #fElement} is not a method where @inheritDoc could
* work.
*/
private final IMethod fMethod;
private final Javadoc fJavadoc;
private final String fSource;
private final JavadocLookup fJavadocLookup;
private StringBuffer fBuf;
private int fLiteralContent;
private StringBuffer fMainDescription;
private StringBuffer fReturnDescription;
private StringBuffer[] fTypeParamDescriptions;
private StringBuffer[] fParamDescriptions;
private HashMap<String, StringBuffer> fExceptionDescriptions;
private JavadocContentAccess2(IJavaElement element, Javadoc javadoc, String source, JavadocLookup lookup) {
Assert.isNotNull(element);
Assert.isTrue(element instanceof IMethod || element instanceof ILocalVariable || element instanceof ITypeParameter);
fElement= element;
fMethod= (IMethod) ((element instanceof ILocalVariable || element instanceof ITypeParameter) ? element.getParent() : element);
fJavadoc= javadoc;
fSource= source;
fJavadocLookup= lookup;
}
private JavadocContentAccess2(IJavaElement element, Javadoc javadoc, String source) {
Assert.isTrue(element instanceof IMember || element instanceof IPackageFragment || element instanceof ILocalVariable || element instanceof ITypeParameter);
fElement= element;
fMethod= null;
fJavadoc= javadoc;
fSource= source;
fJavadocLookup= JavadocLookup.NONE;
}
/**
* Gets an IJavaElement's Javadoc comment content from the source or Javadoc attachment
* and renders the tags and links in HTML.
* Returns <code>null</code> if the element does not have a Javadoc comment or if no source is available.
*
* @param element the element to get the Javadoc of
* @param useAttachedJavadoc if <code>true</code> Javadoc will be extracted from attached Javadoc
* if there's no source
* @return the Javadoc comment content in HTML or <code>null</code> if the element
* does not have a Javadoc comment or if no source is available
* @throws CoreException is thrown when the element's Javadoc cannot be accessed
*/
public static String getHTMLContent(IJavaElement element, boolean useAttachedJavadoc) throws CoreException {
if (element instanceof IPackageFragment) {
return getHTMLContent((IPackageFragment) element);
}
if (element instanceof IPackageDeclaration) {
return getHTMLContent((IPackageDeclaration) element);
}
if (!(element instanceof IMember)
&& !(element instanceof ITypeParameter)
&& (!(element instanceof ILocalVariable) || !(((ILocalVariable) element).isParameter()))) {
return null;
}
String sourceJavadoc= getHTMLContentFromSource(element);
if (sourceJavadoc == null || sourceJavadoc.length() == 0 || "{@inheritDoc}".equals(sourceJavadoc.trim())) { //$NON-NLS-1$
if (useAttachedJavadoc) {
if (element.getOpenable().getBuffer() == null) { // only if no source available
return element.getAttachedJavadoc(null);
}
IMember member= null;
if (element instanceof ILocalVariable) {
member= ((ILocalVariable) element).getDeclaringMember();
} else if (element instanceof ITypeParameter) {
member= ((ITypeParameter) element).getDeclaringMember();
} else if (element instanceof IMember) {
member= (IMember) element;
}
if (canInheritJavadoc(member)) {
IMethod method= (IMethod) member;
String attachedDocInHierarchy= findAttachedDocInHierarchy(method);
// Prepend "Overrides:" / "Specified by:" reference headers to make clear
// that description has been copied from super method.
if (attachedDocInHierarchy == null)
return sourceJavadoc;
StringBuffer superMethodReferences= createSuperMethodReferences(method);
if (superMethodReferences == null)
return attachedDocInHierarchy;
superMethodReferences.append(attachedDocInHierarchy);
return superMethodReferences.toString();
}
}
}
return sourceJavadoc;
}
private static StringBuffer createSuperMethodReferences(final IMethod method) throws JavaModelException {
IType type= method.getDeclaringType();
ITypeHierarchy hierarchy= SuperTypeHierarchyCache.getTypeHierarchy(type);
final MethodOverrideTester tester= SuperTypeHierarchyCache.getMethodOverrideTester(type);
final ArrayList<IMethod> superInterfaceMethods= new ArrayList<>();
final IMethod[] superClassMethod= { null };
new InheritDocVisitor() {
@Override
public Object visit(IType currType) throws JavaModelException {
IMethod overridden= tester.findOverriddenMethodInType(currType, method);
if (overridden == null)
return InheritDocVisitor.CONTINUE;
if (currType.isInterface())
superInterfaceMethods.add(overridden);
else
superClassMethod[0]= overridden;
return STOP_BRANCH;
}
}.visitInheritDoc(type, hierarchy);
boolean hasSuperInterfaceMethods= !superInterfaceMethods.isEmpty();
if (!hasSuperInterfaceMethods && superClassMethod[0] == null)
return null;
StringBuffer buf= new StringBuffer();
buf.append("<div>"); //$NON-NLS-1$
if (hasSuperInterfaceMethods) {
buf.append("<b>"); //$NON-NLS-1$
buf.append(JavaDocMessages.JavaDoc2HTMLTextReader_specified_by_section);
buf.append("</b> "); //$NON-NLS-1$
for (Iterator<IMethod> iter= superInterfaceMethods.iterator(); iter.hasNext(); ) {
IMethod overridden= iter.next();
buf.append(createMethodInTypeLinks(overridden));
if (iter.hasNext())
buf.append(JavaElementLabels.COMMA_STRING);
}
}
if (superClassMethod[0] != null) {
if (hasSuperInterfaceMethods)
buf.append(JavaElementLabels.COMMA_STRING);
buf.append("<b>"); //$NON-NLS-1$
buf.append(JavaDocMessages.JavaDoc2HTMLTextReader_overrides_section);
buf.append("</b> "); //$NON-NLS-1$
buf.append(createMethodInTypeLinks(superClassMethod[0]));
}
buf.append("</div>"); //$NON-NLS-1$
return buf;
}
private static String createMethodInTypeLinks(IMethod overridden) {
CharSequence methodLink= createSimpleMemberLink(overridden);
CharSequence typeLink= createSimpleMemberLink(overridden.getDeclaringType());
String methodInType= MessageFormat.format(JavaDocMessages.JavaDoc2HTMLTextReader_method_in_type, methodLink, typeLink);
return methodInType;
}
private static CharSequence createSimpleMemberLink(IMember member) {
StringBuffer buf= new StringBuffer();
buf.append("<a href='"); //$NON-NLS-1$
try {
String uri= JavaElementLinks.createURI(JavaElementLinks.JAVADOC_SCHEME, member);
buf.append(uri);
} catch (URISyntaxException e) {
JavaPlugin.log(e);
}
buf.append("'>"); //$NON-NLS-1$
JavaElementLabels.getElementLabel(member, 0, buf);
buf.append("</a>"); //$NON-NLS-1$
return buf;
}
private static String getHTMLContentFromSource(IJavaElement element) throws JavaModelException {
IMember member;
if (element instanceof ILocalVariable) {
member= ((ILocalVariable) element).getDeclaringMember();
} else if (element instanceof ITypeParameter) {
member= ((ITypeParameter) element).getDeclaringMember();
} else if (element instanceof IMember) {
member= (IMember) element;
} else {
return null;
}
IBuffer buf= member.getOpenable().getBuffer();
if (buf == null) {
return null; // no source attachment found
}
ISourceRange javadocRange= member.getJavadocRange();
if (javadocRange == null) {
if (canInheritJavadoc(member)) {
// Try to use the inheritDoc algorithm.
String inheritedJavadoc= javadoc2HTML(member, element, "/***/"); //$NON-NLS-1$
if (inheritedJavadoc != null && inheritedJavadoc.length() > 0) {
return inheritedJavadoc;
}
}
return getJavaFxPropertyDoc(member);
}
String rawJavadoc= buf.getText(javadocRange.getOffset(), javadocRange.getLength());
return javadoc2HTML(member, element, rawJavadoc);
}
private static String getJavaFxPropertyDoc(IMember member) throws JavaModelException {
// XXX: should not do this by default (but we don't have settings for Javadoc, see https://bugs.eclipse.org/424283 )
if (member instanceof IMethod) {
String name= member.getElementName();
boolean isGetter= name.startsWith("get") && name.length() > 3; //$NON-NLS-1$
boolean isBooleanGetter= name.startsWith("is") && name.length() > 2; //$NON-NLS-1$
boolean isSetter= name.startsWith("set") && name.length() > 3; //$NON-NLS-1$
if (isGetter || isBooleanGetter || isSetter) {
String propertyName= firstToLower(name.substring(isBooleanGetter ? 2 : 3));
IType type= member.getDeclaringType();
IMethod method= type.getMethod(propertyName + "Property", new String[0]); //$NON-NLS-1$
if (method.exists()) {
String content= getHTMLContentFromSource(method);
if (content != null) {
if (isSetter) {
content= Messages.format(JavaDocMessages.JavadocContentAccess2_setproperty_message, new Object[] { propertyName, content });
} else {
content= Messages.format(JavaDocMessages.JavadocContentAccess2_getproperty_message, new Object[] { propertyName, content });
}
}
return content;
}
} else if (name.endsWith("Property")) { //$NON-NLS-1$
String propertyName= name.substring(0, name.length() - 8);
IType type= member.getDeclaringType();
IField field= type.getField(propertyName);
if (field.exists()) {
return getHTMLContentFromSource(field);
}
}
}
return null;
}
private static String firstToLower(String propertyName) {
char[] c = propertyName.toCharArray();
c[0] = Character.toLowerCase(c[0]);
return String.valueOf(c);
}
private static Javadoc getJavadocNode(IJavaElement element, String rawJavadoc) {
//FIXME: take from SharedASTProvider if available
//Caveat: Javadoc nodes are not available when Javadoc processing has been disabled!
//https://bugs.eclipse.org/bugs/show_bug.cgi?id=212207
String source= rawJavadoc + "class C{}"; //$NON-NLS-1$
CompilationUnit root= createAST(element, source);
if (root == null)
return null;
List<AbstractTypeDeclaration> types= root.types();
if (types.size() != 1)
return null;
AbstractTypeDeclaration type= types.get(0);
return type.getJavadoc();
}
private static Javadoc getPackageJavadocNode(IJavaElement element, String cuSource) {
CompilationUnit cu= createAST(element, cuSource);
if (cu != null) {
PackageDeclaration packDecl= cu.getPackage();
if (packDecl != null) {
return packDecl.getJavadoc();
}
}
return null;
}
private static CompilationUnit createAST(IJavaElement element, String cuSource) {
Assert.isNotNull(element);
ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
IJavaProject javaProject= element.getJavaProject();
parser.setProject(javaProject);
Map<String, String> options= javaProject.getOptions(true);
options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED); // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=212207
parser.setCompilerOptions(options);
parser.setSource(cuSource.toCharArray());
return (CompilationUnit) parser.createAST(null);
}
private static String javadoc2HTML(IMember member, IJavaElement element, String rawJavadoc) {
Javadoc javadoc= getJavadocNode(member, rawJavadoc);
if (javadoc == null) {
Reader contentReader= null;
// fall back to JavadocContentAccess:
try {
contentReader= JavadocContentAccess.getHTMLContentReader(member, false, false);
if (contentReader != null)
return getString(contentReader);
} catch (JavaModelException e) {
JavaPlugin.log(e);
} finally {
if (contentReader != null) {
try {
contentReader.close();
} catch (IOException e) {
//ignore
}
}
}
return null;
}
if (canInheritJavadoc(member)) {
IMethod method= (IMethod) member;
return new JavadocContentAccess2(element, javadoc, rawJavadoc, new JavadocLookup(method.getDeclaringType())).toHTML();
}
return new JavadocContentAccess2(element, javadoc, rawJavadoc).toHTML();
}
private static boolean canInheritJavadoc(IMember member) {
if (member instanceof IMethod && member.getJavaProject().exists()) {
/*
* Exists test catches ExternalJavaProject, in which case no hierarchy can be built.
*/
try {
return ! ((IMethod) member).isConstructor();
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
}
return false;
}
/**
* Gets the reader content as a String
*
* @param reader the reader
* @return the reader content as string
*/
private static String getString(Reader reader) {
StringBuilder buf= new StringBuilder();
char[] buffer= new char[1024];
int count;
try {
while ((count= reader.read(buffer)) != -1)
buf.append(buffer, 0, count);
} catch (IOException e) {
return null;
}
return buf.toString();
}
/**
* Finds the first available attached Javadoc in the hierarchy of the given method.
*
* @param method the method
* @return the inherited Javadoc from the Javadoc attachment, or <code>null</code> if none
* @throws JavaModelException unexpected problem
*/
private static String findAttachedDocInHierarchy(final IMethod method) throws JavaModelException {
IType type= method.getDeclaringType();
ITypeHierarchy hierarchy= SuperTypeHierarchyCache.getTypeHierarchy(type);
final MethodOverrideTester tester= SuperTypeHierarchyCache.getMethodOverrideTester(type);
return (String) new InheritDocVisitor() {
@Override
public Object visit(IType currType) throws JavaModelException {
IMethod overridden= tester.findOverriddenMethodInType(currType, method);
if (overridden == null)
return InheritDocVisitor.CONTINUE;
if (overridden.getOpenable().getBuffer() == null) { // only if no source available
String attachedJavadoc= overridden.getAttachedJavadoc(null);
if (attachedJavadoc != null) {
// BaseURL for the original method can be wrong for attached Javadoc from overridden
// (e.g. when overridden is from rt.jar).
// Fix is to store the baseURL inside the doc content and later fetch it with #extractBaseURL(String).
String baseURL= JavaDocLocations.getBaseURL(overridden, overridden.isBinary());
if (baseURL != null) {
attachedJavadoc= BASE_URL_COMMENT_INTRO + baseURL + "\"--> " + attachedJavadoc; //$NON-NLS-1$
}
return attachedJavadoc;
}
}
return CONTINUE;
}
}.visitInheritDoc(type, hierarchy);
}
private String toHTML() {
fBuf= new StringBuffer();
fLiteralContent= 0;
if (fElement instanceof ILocalVariable || fElement instanceof ITypeParameter) {
parameterToHTML();
} else {
elementToHTML();
}
String result= fBuf.toString();
fBuf= null;
return result;
}
private void parameterToHTML() {
String elementName= fElement.getElementName();
List<TagElement> tags= fJavadoc.tags();
for (TagElement tag : tags) {
String tagName= tag.getTagName();
if (TagElement.TAG_PARAM.equals(tagName)) {
List<? extends ASTNode> fragments= tag.fragments();
int size= fragments.size();
if (size > 0) {
Object first= fragments.get(0);
if (first instanceof SimpleName) {
String name= ((SimpleName) first).getIdentifier();
if (elementName.equals(name)) {
handleContentElements(fragments.subList(1, size));
return;
}
} else if (size > 2 && fElement instanceof ITypeParameter && first instanceof TextElement) {
String firstText= ((TextElement) first).getText();
if ("<".equals(firstText)) { //$NON-NLS-1$
Object second= fragments.get(1);
Object third= fragments.get(2);
if (second instanceof SimpleName && third instanceof TextElement) {
String name= ((SimpleName) second).getIdentifier();
String thirdText= ((TextElement) third).getText();
if (elementName.equals(name) && ">".equals(thirdText)) { //$NON-NLS-1$
handleContentElements(fragments.subList(3, size));
return;
}
}
}
}
}
}
}
if (fElement instanceof ILocalVariable) {
List<String> parameterNames= initParameterNames();
int i= parameterNames.indexOf(elementName);
if (i != -1) {
CharSequence inheritedParamDescription= fJavadocLookup.getInheritedParamDescription(fMethod, i);
handleInherited(inheritedParamDescription);
}
} else if (fElement instanceof ITypeParameter) {
List<String> typeParameterNames= initTypeParameterNames();
int i= typeParameterNames.indexOf(elementName);
if (i != -1) {
CharSequence inheritedTypeParamDescription= fJavadocLookup.getInheritedTypeParamDescription(fMethod, i);
handleInherited(inheritedTypeParamDescription);
}
}
}
private void elementToHTML() {
// After first loop, non-null entries in the following two lists are missing and need to be inherited:
List<String> typeParameterNames= initTypeParameterNames();
List<String> parameterNames= initParameterNames();
List<String> exceptionNames= initExceptionNames();
TagElement deprecatedTag= null;
TagElement start= null;
List<TagElement> typeParameters= new ArrayList<>();
List<TagElement> parameters= new ArrayList<>();
TagElement returnTag= null;
List<TagElement> exceptions= new ArrayList<>();
List<TagElement> provides= new ArrayList<>();
List<TagElement> uses= new ArrayList<>();
List<TagElement> versions= new ArrayList<>();
List<TagElement> authors= new ArrayList<>();
List<TagElement> sees= new ArrayList<>();
List<TagElement> since= new ArrayList<>();
List<TagElement> rest= new ArrayList<>();
List<TagElement> apinote= new ArrayList<>(1);
List<TagElement> implspec= new ArrayList<>(1);
List<TagElement> implnote= new ArrayList<>(1);
List<TagElement> hidden= new ArrayList<>(1);
List<TagElement> tags= fJavadoc.tags();
for (TagElement tag : tags) {
String tagName= tag.getTagName();
if (tagName == null) {
start= tag;
} else {
switch (tagName) {
case TagElement.TAG_PARAM:
List<? extends ASTNode> fragments= tag.fragments();
int size= fragments.size();
if (size > 0) {
Object first= fragments.get(0);
if (first instanceof SimpleName) {
String name= ((SimpleName) first).getIdentifier();
int paramIndex= parameterNames.indexOf(name);
if (paramIndex != -1) {
parameterNames.set(paramIndex, null);
}
parameters.add(tag);
} else if (size > 2 && first instanceof TextElement) {
String firstText= ((TextElement) first).getText();
if ("<".equals(firstText)) { //$NON-NLS-1$
Object second= fragments.get(1);
Object third= fragments.get(2);
if (second instanceof SimpleName && third instanceof TextElement) {
String name= ((SimpleName) second).getIdentifier();
String thirdText= ((TextElement) third).getText();
if (">".equals(thirdText)) { //$NON-NLS-1$
int paramIndex= typeParameterNames.indexOf(name);
if (paramIndex != -1) {
typeParameterNames.set(paramIndex, null);
}
typeParameters.add(tag);
}
}
}
}
}
break;
case TagElement.TAG_RETURN:
if (returnTag == null)
returnTag= tag; // the Javadoc tool only shows the first return tag
break;
case TagElement.TAG_EXCEPTION:
case TagElement.TAG_THROWS:
exceptions.add(tag);
List<? extends ASTNode> fragments2= tag.fragments();
if (fragments2.size() > 0) {
Object first= fragments2.get(0);
if (first instanceof Name) {
String name= ASTNodes.getSimpleNameIdentifier((Name) first);
int exceptionIndex= exceptionNames.indexOf(name);
if (exceptionIndex != -1) {
exceptionNames.set(exceptionIndex, null);
}
}
}
break;
case TagElement.TAG_PROVIDES:
provides.add(tag);
break;
case TagElement.TAG_USES:
uses.add(tag);
break;
case TagElement.TAG_SINCE:
since.add(tag);
break;
case TagElement.TAG_VERSION:
versions.add(tag);
break;
case TagElement.TAG_AUTHOR:
authors.add(tag);
break;
case TagElement.TAG_SEE:
sees.add(tag);
break;
case TagElement.TAG_DEPRECATED:
if (deprecatedTag == null)
deprecatedTag= tag; // the Javadoc tool only shows the first deprecated tag
break;
case TagElement.TAG_API_NOTE:
apinote.add(tag);
break;
case TagElement.TAG_IMPL_SPEC:
implspec.add(tag);
break;
case TagElement.TAG_IMPL_NOTE:
implnote.add(tag);
break;
case TagElement.TAG_HIDDEN:
hidden.add(tag);
break;
default:
rest.add(tag);
break;
}
}
}
//TODO: @Documented annotations before header
if (deprecatedTag != null)
handleDeprecatedTag(deprecatedTag);
if (start != null)
handleContentElements(start.fragments());
else if (fMethod != null) {
CharSequence inherited= fJavadocLookup.getInheritedMainDescription(fMethod);
// The Javadoc tool adds "Description copied from class: ..." (only for the main description).
// We don't bother doing that.
handleInherited(inherited);
}
CharSequence[] typeParameterDescriptions= new CharSequence[typeParameterNames.size()];
boolean hasInheritedTypeParameters= inheritTypeParameterDescriptions(typeParameterNames, typeParameterDescriptions);
boolean hasTypeParameters= typeParameters.size() > 0 || hasInheritedTypeParameters;
CharSequence[] parameterDescriptions= new CharSequence[parameterNames.size()];
boolean hasInheritedParameters= inheritParameterDescriptions(parameterNames, parameterDescriptions);
boolean hasParameters= parameters.size() > 0 || hasInheritedParameters;
CharSequence returnDescription= null;
if (returnTag == null && needsReturnTag())
returnDescription= fJavadocLookup.getInheritedReturnDescription(fMethod);
boolean hasReturnTag= returnTag != null || returnDescription != null;
CharSequence[] exceptionDescriptions= new CharSequence[exceptionNames.size()];
boolean hasInheritedExceptions= inheritExceptionDescriptions(exceptionNames, exceptionDescriptions);
boolean hasExceptions= exceptions.size() > 0 || hasInheritedExceptions;
if (hasParameters || hasTypeParameters || hasReturnTag || hasExceptions
|| versions.size() > 0 || authors.size() > 0 || since.size() > 0 || sees.size() > 0 ||
apinote.size() > 0 || implnote.size() > 0 || implspec.size() > 0 || uses.size() > 0 ||
provides.size() > 0 || hidden.size() > 0 || rest.size() > 0
|| (fBuf.length() > 0 && (parameterDescriptions.length > 0 || exceptionDescriptions.length > 0))
) {
handleSuperMethodReferences();
fBuf.append(BLOCK_TAG_START);
handleParameterTags(typeParameters, typeParameterNames, typeParameterDescriptions, true);
handleParameterTags(parameters, parameterNames, parameterDescriptions, false);
handleReturnTag(returnTag, returnDescription);
handleExceptionTags(exceptions, exceptionNames, exceptionDescriptions);
handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_since_section, since);
handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_version_section, versions);
handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_author_section, authors);
handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_see_section, sees);
handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_api_note, apinote);
handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_impl_spec, implspec);
handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_impl_note, implnote);
handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_uses, uses);
handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_provides, provides);
if (hidden.size() > 0) {
handleBlockTagsHidden();
}
handleBlockTags(rest);
fBuf.append(BLOCK_TAG_END);
} else if (fBuf.length() > 0) {
handleSuperMethodReferences();
}
}
private void handleBlockTagsHidden() {
String replaceAll= fBuf.toString().replaceAll(BLOCK_TAG_START, "<dl hidden>"); //$NON-NLS-1$
replaceAll= replaceAll.replaceAll(BlOCK_TAG_TITLE_START, "<dt hidden>"); //$NON-NLS-1$
replaceAll= replaceAll.replaceAll(BlOCK_TAG_ENTRY_START, "<dd hidden>"); //$NON-NLS-1$
// For tags like deprecated
replaceAll= replaceAll.replaceAll(PARAM_NAME_START, "<b hidden>"); //$NON-NLS-1$
fBuf.setLength(0);
fBuf.append(replaceAll);
}
private void handleDeprecatedTag(TagElement tag) {
fBuf.append("<p><b>"); //$NON-NLS-1$
fBuf.append(JavaDocMessages.JavaDoc2HTMLTextReader_deprecated_section);
fBuf.append("</b> <i>"); //$NON-NLS-1$
handleContentElements(tag.fragments());
fBuf.append("</i><p>"); //$NON-NLS-1$ TODO: Why not </p>? See https://bugs.eclipse.org/bugs/show_bug.cgi?id=243318 .
}
private void handleSuperMethodReferences() {
if (fMethod != null) {
try {
StringBuffer superMethodReferences= createSuperMethodReferences(fMethod);
if (superMethodReferences != null)
fBuf.append(superMethodReferences);
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
}
}
private List<String> initTypeParameterNames() {
if (fMethod != null) {
try {
ArrayList<String> typeParameterNames= new ArrayList<>();
for (ITypeParameter typeParameter : fMethod.getTypeParameters()) {
typeParameterNames.add(typeParameter.getElementName());
}
return typeParameterNames;
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
}
return Collections.emptyList();
}
private List<String> initParameterNames() {
if (fMethod != null) {
try {
return new ArrayList<>(Arrays.asList(fMethod.getParameterNames()));
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
}
return Collections.emptyList();
}
private List<String> initExceptionNames() {
if (fMethod != null) {
try {
ArrayList<String> exceptionNames= new ArrayList<>();
for (String exceptionType : fMethod.getExceptionTypes()) {
exceptionNames.add(Signature.getSimpleName(Signature.toString(exceptionType)));
}
return exceptionNames;
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
}
return Collections.emptyList();
}
private boolean needsReturnTag() {
if (fMethod == null)
return false;
try {
return ! Signature.SIG_VOID.equals(fMethod.getReturnType());
} catch (JavaModelException e) {
JavaPlugin.log(e);
return false;
}
}
private boolean inheritTypeParameterDescriptions(List<String> typeParameterNames, CharSequence[] typeParameterDescriptions) {
boolean hasInheritedTypeParameters= false;
for (int i= 0; i < typeParameterNames.size(); i++) {
String name= typeParameterNames.get(i);
if (name != null) {
typeParameterDescriptions[i]= fJavadocLookup.getInheritedTypeParamDescription(fMethod, i);
if (typeParameterDescriptions[i] != null)
hasInheritedTypeParameters= true;
}
}
return hasInheritedTypeParameters;
}
private boolean inheritParameterDescriptions(List<String> parameterNames, CharSequence[] parameterDescriptions) {
boolean hasInheritedParameters= false;
for (int i= 0; i < parameterNames.size(); i++) {
String name= parameterNames.get(i);
if (name != null) {
parameterDescriptions[i]= fJavadocLookup.getInheritedParamDescription(fMethod, i);
if (parameterDescriptions[i] != null)
hasInheritedParameters= true;
}
}
return hasInheritedParameters;
}
private boolean inheritExceptionDescriptions(List<String> exceptionNames, CharSequence[] exceptionDescriptions) {
boolean hasInheritedExceptions= false;
for (int i= 0; i < exceptionNames.size(); i++) {
String name= exceptionNames.get(i);
if (name != null) {
exceptionDescriptions[i]= fJavadocLookup.getInheritedExceptionDescription(fMethod, name);
if (exceptionDescriptions[i] != null)
hasInheritedExceptions= true;
}
}
return hasInheritedExceptions;
}
CharSequence getMainDescription() {
if (fMainDescription == null) {
fMainDescription= new StringBuffer();
fBuf= fMainDescription;
fLiteralContent= 0;
List<TagElement> tags= fJavadoc.tags();
for (TagElement tag : tags) {
String tagName= tag.getTagName();
if (tagName == null) {
handleContentElements(tag.fragments());
break;
}
}
fBuf= null;
}
return fMainDescription.length() > 0 ? fMainDescription : null;
}
CharSequence getReturnDescription() {
if (fReturnDescription == null) {
fReturnDescription= new StringBuffer();
fBuf= fReturnDescription;
fLiteralContent= 0;
List<TagElement> tags= fJavadoc.tags();
for (TagElement tag : tags) {
String tagName= tag.getTagName();
if (TagElement.TAG_RETURN.equals(tagName)) {
handleContentElements(tag.fragments());
break;
}
}
fBuf= null;
}
return fReturnDescription.length() > 0 ? fReturnDescription : null;
}
CharSequence getInheritedTypeParamDescription(int typeParamIndex) {
if (fMethod != null) {
List<String> typeParameterNames= initTypeParameterNames();
if (fTypeParamDescriptions == null) {
fTypeParamDescriptions= new StringBuffer[typeParameterNames.size()];
} else {
StringBuffer description= fTypeParamDescriptions[typeParamIndex];
if (description != null) {
return description.length() > 0 ? description : null;
}
}
StringBuffer description= new StringBuffer();
fTypeParamDescriptions[typeParamIndex]= description;
fBuf= description;
fLiteralContent= 0;
String typeParamName= typeParameterNames.get(typeParamIndex);
List<TagElement> tags= fJavadoc.tags();
for (TagElement tag : tags) {
String tagName= tag.getTagName();
if (TagElement.TAG_PARAM.equals(tagName)) {
List<? extends ASTNode> fragments= tag.fragments();
if (fragments.size() > 2) {
Object first= fragments.get(0);
Object second= fragments.get(1);
Object third= fragments.get(2);
if (first instanceof TextElement && second instanceof SimpleName && third instanceof TextElement) {
String firstText= ((TextElement) first).getText();
String thirdText= ((TextElement) third).getText();
if ("<".equals(firstText) && ">".equals(thirdText)) { //$NON-NLS-1$ //$NON-NLS-2$
String name= ((SimpleName) second).getIdentifier();
if (name.equals(typeParamName)) {
handleContentElements(fragments.subList(3, fragments.size()));
break;
}
}
}
}
}
}
fBuf= null;
return description.length() > 0 ? description : null;
}
return null;
}
CharSequence getInheritedParamDescription(int paramIndex) throws JavaModelException {
if (fMethod != null) {
String[] parameterNames= fMethod.getParameterNames();
if (fParamDescriptions == null) {
fParamDescriptions= new StringBuffer[parameterNames.length];
} else {
StringBuffer description= fParamDescriptions[paramIndex];
if (description != null) {
return description.length() > 0 ? description : null;
}
}
StringBuffer description= new StringBuffer();
fParamDescriptions[paramIndex]= description;
fBuf= description;
fLiteralContent= 0;
String paramName= parameterNames[paramIndex];
List<TagElement> tags= fJavadoc.tags();
for (TagElement tag : tags) {
String tagName= tag.getTagName();
if (TagElement.TAG_PARAM.equals(tagName)) {
List<? extends ASTNode> fragments= tag.fragments();
if (fragments.size() > 0) {
Object first= fragments.get(0);
if (first instanceof SimpleName) {
String name= ((SimpleName) first).getIdentifier();
if (name.equals(paramName)) {
handleContentElements(fragments.subList(1, fragments.size()));
break;
}
}
}
}
}
fBuf= null;
return description.length() > 0 ? description : null;
}
return null;
}
CharSequence getExceptionDescription(String simpleName) {
if (fMethod != null) {
if (fExceptionDescriptions == null) {
fExceptionDescriptions= new HashMap<>();
} else {
StringBuffer description= fExceptionDescriptions.get(simpleName);
if (description != null) {
return description.length() > 0 ? description : null;
}
}
StringBuffer description= new StringBuffer();
fExceptionDescriptions.put(simpleName, description);
fBuf= description;
fLiteralContent= 0;
List<TagElement> tags= fJavadoc.tags();
for (TagElement tag : tags) {
String tagName= tag.getTagName();
if (TagElement.TAG_THROWS.equals(tagName) || TagElement.TAG_EXCEPTION.equals(tagName)) {
List<? extends ASTNode> fragments= tag.fragments();
if (fragments.size() > 0) {
Object first= fragments.get(0);
if (first instanceof Name) {
String name= ASTNodes.getSimpleNameIdentifier((Name) first);
if (name.equals(simpleName)) {
if (fragments.size() > 1)
handleContentElements(fragments.subList(1, fragments.size()));
break;
}
}
}
}
}
fBuf= null;
return description.length() > 0 ? description : null;
}
return null;
}
private void handleContentElements(List<? extends ASTNode> nodes) {
handleContentElements(nodes, false);
}
private void handleContentElements(List<? extends ASTNode> nodes, boolean skipLeadingWhitespace) {
ASTNode previousNode= null;
for (ASTNode child : nodes) {
if (previousNode != null) {
int previousEnd= previousNode.getStartPosition() + previousNode.getLength();
int childStart= child.getStartPosition();
if (previousEnd > childStart) {
// should never happen, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=304826
Exception exception= new Exception("Illegal ASTNode positions: previousEnd=" + previousEnd //$NON-NLS-1$
+ ", childStart=" + childStart //$NON-NLS-1$
+ ", element=" + fElement.getHandleIdentifier() //$NON-NLS-1$
+ ", Javadoc:\n" + fSource); //$NON-NLS-1$
JavaPlugin.log(exception);
} else if (previousEnd != childStart) {
// Need to preserve whitespace before a node that's not
// directly following the previous node (e.g. on a new line)
// due to https://bugs.eclipse.org/bugs/show_bug.cgi?id=206518 :
String textWithStars= fSource.substring(previousEnd, childStart);
String text= removeDocLineIntros(textWithStars);
fBuf.append(text);
}
}
previousNode= child;
if (child instanceof TextElement) {
String text= ((TextElement) child).getText();
if (skipLeadingWhitespace) {
text= text.replaceFirst("^\\s", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
// workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=233481 :
text= text.replaceAll("(\r\n?|\n)([ \t]*\\*)", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
handleText(text);
} else if (child instanceof TagElement) {
handleInlineTagElement((TagElement) child);
} else {
// This is unexpected. Fail gracefully by just copying the source.
int start= child.getStartPosition();
String text= fSource.substring(start, start + child.getLength());
fBuf.append(removeDocLineIntros(text));
}
}
}
private String removeDocLineIntros(String textWithStars) {
String lineBreakGroup= "(\\r\\n?|\\n)"; //$NON-NLS-1$
String noBreakSpace= "[^\r\n&&\\s]"; //$NON-NLS-1$
return textWithStars.replaceAll(lineBreakGroup + noBreakSpace + "*\\*" /*+ noBreakSpace + '?'*/, "$1"); //$NON-NLS-1$ //$NON-NLS-2$
}
private void handleText(String text) {
if (fLiteralContent == 0) {
fBuf.append(text);
} else {
appendEscaped(fBuf, text);
}
}
private static void appendEscaped(StringBuffer buf, String text) {
int nextToCopy= 0;
int length= text.length();
for (int i= 0; i < length; i++) {
char ch= text.charAt(i);
String rep= null;
switch (ch) {
case '&':
rep= "&amp;"; //$NON-NLS-1$
break;
case '"':
rep= "&quot;"; //$NON-NLS-1$
break;
case '<':
rep= "&lt;"; //$NON-NLS-1$
break;
case '>':
rep= "&gt;"; //$NON-NLS-1$
break;
}
if (rep != null) {
if (nextToCopy < i)
buf.append(text.substring(nextToCopy, i));
buf.append(rep);
nextToCopy= i + 1;
}
}
if (nextToCopy < length)
buf.append(text.substring(nextToCopy));
}
private void handleInlineTagElement(TagElement node) {
String name= node.getTagName();
if (TagElement.TAG_VALUE.equals(name) && handleValueTag(node))
return;
boolean isLink= TagElement.TAG_LINK.equals(name);
boolean isLinkplain= TagElement.TAG_LINKPLAIN.equals(name);
boolean isCode= TagElement.TAG_CODE.equals(name);
boolean isLiteral= TagElement.TAG_LITERAL.equals(name);
boolean isSummary = TagElement.TAG_SUMMARY.equals(name);
boolean isIndex = TagElement.TAG_INDEX.equals(name);
boolean isSnippet = TagElement.TAG_SNIPPET.equals(name);
if (isLiteral || isCode || isSummary || isIndex)
fLiteralContent++;
if (isLink || isCode)
fBuf.append("<code>"); //$NON-NLS-1$
if (isLink || isLinkplain)
handleLink(node.fragments());
else if (isSummary)
handleSummary(node.fragments());
else if (isIndex)
handleIndex(node.fragments());
else if (isCode || isLiteral)
handleContentElements(node.fragments(), true);
else if (isSnippet) {
handleSnippet(node.fragments());
}
else if (handleInheritDoc(node) || handleDocRoot(node)) {
// Handled
} else {
//print uninterpreted source {@tagname ...} for unknown tags
int start= node.getStartPosition();
String text= fSource.substring(start, start + node.getLength());
fBuf.append(removeDocLineIntros(text));
}
if (isLink || isCode)
fBuf.append("</code>"); //$NON-NLS-1$
if (isSnippet)
fBuf.append("</code></pre>"); //$NON-NLS-1$
if (isLiteral || isCode)
fLiteralContent--;
}
private boolean handleValueTag(TagElement node) {
List<? extends ASTNode> fragments= node.fragments();
try {
if (!(fElement instanceof IMember)) {
return false;
}
if (fragments.isEmpty()) {
if (fElement instanceof IField && JdtFlags.isStatic((IField) fElement) && JdtFlags.isFinal((IField) fElement)) {
IField field= (IField) fElement;
return handleConstantValue(field, false);
}
} else if (fragments.size() == 1) {
Object first= fragments.get(0);
if (first instanceof MemberRef) {
MemberRef memberRef= (MemberRef) first;
IType type= fElement instanceof IType ? (IType) fElement : ((IMember) fElement).getDeclaringType();
if (memberRef.getQualifier() != null) {
String[][] qualifierTypes= type.resolveType(memberRef.getQualifier().getFullyQualifiedName());
if (qualifierTypes != null && qualifierTypes.length == 1) {
type= type.getJavaProject().findType(String.join(".", qualifierTypes[0]), (IProgressMonitor)null); //$NON-NLS-1$
}
}
SimpleName name= memberRef.getName();
while (type != null) {
IField field= type.getField(name.getIdentifier());
if (field != null && field.exists()) {
if (JdtFlags.isStatic(field) && JdtFlags.isFinal(field))
return handleConstantValue(field, true);
break;
}
type= type.getDeclaringType();
}
}
}
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
return false;
}
private boolean handleConstantValue(IField field, boolean link) throws JavaModelException {
String text= null;
ISourceRange nameRange= field.getNameRange();
if (SourceRange.isAvailable(nameRange)) {
CompilationUnit cuNode= SharedASTProviderCore.getAST(field.getTypeRoot(), SharedASTProviderCore.WAIT_ACTIVE_ONLY, null);
if (cuNode != null) {
ASTNode nameNode= NodeFinder.perform(cuNode, nameRange);
if (nameNode instanceof SimpleName) {
IBinding binding= ((SimpleName) nameNode).resolveBinding();
if (binding instanceof IVariableBinding) {
IVariableBinding variableBinding= (IVariableBinding) binding;
Object constantValue= variableBinding.getConstantValue();
if (constantValue != null) {
if (constantValue instanceof String) {
text= ASTNodes.getEscapedStringLiteral((String) constantValue);
} else {
text= constantValue.toString(); // Javadoc tool is even worse for chars...
}
}
}
}
}
}
if (text == null) {
Object constant= field.getConstant();
if (constant != null) {
text= constant.toString();
}
}
if (text != null) {
text= HTMLPrinter.convertToHTMLContentWithWhitespace(text);
if (link) {
String uri;
try {
uri= JavaElementLinks.createURI(JavaElementLinks.JAVADOC_SCHEME, field);
fBuf.append(JavaElementLinks.createLink(uri, text));
} catch (URISyntaxException e) {
JavaPlugin.log(e);
return false;
}
} else {
handleText(text);
}
return true;
}
return false;
}
private boolean handleDocRoot(TagElement node) {
if (!TagElement.TAG_DOCROOT.equals(node.getTagName()))
return false;
try {
String url= null;
if (fElement instanceof IMember && ((IMember) fElement).isBinary()) {
URL javadocBaseLocation= JavaUI.getJavadocBaseLocation(fElement);
if (javadocBaseLocation != null) {
url= javadocBaseLocation.toExternalForm();
}
} else {
IPackageFragmentRoot srcRoot= JavaModelUtil.getPackageFragmentRoot(fElement);
if (srcRoot != null) {
IResource resource= srcRoot.getResource();
if (resource != null) {
/*
* Too bad: Browser widget knows nothing about EFS and custom URL handlers,
* so IResource#getLocationURI() does not work in all cases.
* We only support the local file system for now.
* A solution could be https://bugs.eclipse.org/bugs/show_bug.cgi?id=149022 .
*/
IPath location= resource.getLocation();
if (location != null) {
url= location.toFile().toURI().toASCIIString();
}
}
}
}
if (url != null) {
if (url.endsWith("/")) { //$NON-NLS-1$
url= url.substring(0, url.length() -1);
}
fBuf.append(url);
return true;
}
} catch (JavaModelException e) {
}
return false;
}
/**
* Handle {&#64;inheritDoc}.
*
* @param node the node
* @return <code>true</code> iff the node was an {&#64;inheritDoc} node and has been handled
*/
private boolean handleInheritDoc(TagElement node) {
if (! TagElement.TAG_INHERITDOC.equals(node.getTagName()))
return false;
try {
if (fMethod == null)
return false;
TagElement blockTag= (TagElement) node.getParent();
String blockTagName= blockTag.getTagName();
if (blockTagName == null) {
CharSequence inherited= fJavadocLookup.getInheritedMainDescription(fMethod);
return handleInherited(inherited);
} else if (TagElement.TAG_PARAM.equals(blockTagName)) {
List<? extends ASTNode> fragments= blockTag.fragments();
int size= fragments.size();
if (size > 0) {
Object first= fragments.get(0);
if (first instanceof SimpleName) {
String name= ((SimpleName) first).getIdentifier();
String[] parameterNames= fMethod.getParameterNames();
for (int i= 0; i < parameterNames.length; i++) {
if (name.equals(parameterNames[i])) {
CharSequence inherited= fJavadocLookup.getInheritedParamDescription(fMethod, i);
return handleInherited(inherited);
}
}
} else if (size > 2 && first instanceof TextElement) {
String firstText= ((TextElement) first).getText();
if ("<".equals(firstText)) { //$NON-NLS-1$
Object second= fragments.get(1);
Object third= fragments.get(2);
if (second instanceof SimpleName && third instanceof TextElement) {
String thirdText= ((TextElement) third).getText();
if (">".equals(thirdText)) { //$NON-NLS-1$
String name= ((SimpleName) second).getIdentifier();
ITypeParameter[] typeParameters= fMethod.getTypeParameters();
for (int i= 0; i < typeParameters.length; i++) {
ITypeParameter typeParameter= typeParameters[i];
if (name.equals(typeParameter.getElementName())) {
CharSequence inherited= fJavadocLookup.getInheritedTypeParamDescription(fMethod, i);
return handleInherited(inherited);
}
}
}
}
}
}
}
} else if (TagElement.TAG_RETURN.equals(blockTagName)) {
CharSequence inherited= fJavadocLookup.getInheritedReturnDescription(fMethod);
return handleInherited(inherited);
} else if (TagElement.TAG_THROWS.equals(blockTagName) || TagElement.TAG_EXCEPTION.equals(blockTagName)) {
List<? extends ASTNode> fragments= blockTag.fragments();
if (fragments.size() > 0) {
Object first= fragments.get(0);
if (first instanceof Name) {
String name= ASTNodes.getSimpleNameIdentifier((Name) first);
CharSequence inherited= fJavadocLookup.getInheritedExceptionDescription(fMethod, name);
return handleInherited(inherited);
}
}
}
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
return false;
}
private boolean handleInherited(CharSequence inherited) {
if (inherited == null)
return false;
fBuf.append(inherited);
return true;
}
private void handleBlockTags(String title, List<TagElement> tags) {
if (tags.isEmpty())
return;
handleBlockTagTitle(title);
for (TagElement tag : tags) {
fBuf.append(BlOCK_TAG_ENTRY_START);
if (TagElement.TAG_SEE.equals(tag.getTagName())) {
handleSeeTag(tag);
} else {
handleContentElements(tag.fragments());
}
fBuf.append(BlOCK_TAG_ENTRY_END);
}
}
private void handleReturnTag(TagElement tag, CharSequence returnDescription) {
if (tag == null && returnDescription == null)
return;
handleBlockTagTitle(JavaDocMessages.JavaDoc2HTMLTextReader_returns_section);
fBuf.append(BlOCK_TAG_ENTRY_START);
if (tag != null)
handleContentElements(tag.fragments());
else
fBuf.append(returnDescription);
fBuf.append(BlOCK_TAG_ENTRY_END);
}
private void handleBlockTags(List<TagElement> tags) {
for (TagElement tag : tags) {
handleBlockTagTitle(tag.getTagName());
fBuf.append(BlOCK_TAG_ENTRY_START);
handleContentElements(tag.fragments());
fBuf.append(BlOCK_TAG_ENTRY_END);
}
}
private void handleBlockTagTitle(String title) {
fBuf.append(BlOCK_TAG_TITLE_START);
fBuf.append(title);
fBuf.append(BlOCK_TAG_TITLE_END);
}
private void handleSeeTag(TagElement tag) {
handleLink(tag.fragments());
}
private void handleExceptionTags(List<TagElement> tags, List<String> exceptionNames, CharSequence[] exceptionDescriptions) {
if (tags.isEmpty() && containsOnlyNull(exceptionNames))
return;
handleBlockTagTitle(JavaDocMessages.JavaDoc2HTMLTextReader_throws_section);
for (TagElement tag : tags) {
fBuf.append(BlOCK_TAG_ENTRY_START);
handleThrowsTag(tag);
fBuf.append(BlOCK_TAG_ENTRY_END);
}
for (int i= 0; i < exceptionDescriptions.length; i++) {
CharSequence description= exceptionDescriptions[i];
String name= exceptionNames.get(i);
if (name != null) {
fBuf.append(BlOCK_TAG_ENTRY_START);
handleLink(Collections.singletonList(fJavadoc.getAST().newSimpleName(name)));
if (description != null) {
fBuf.append(JavaElementLabels.CONCAT_STRING);
fBuf.append(description);
}
fBuf.append(BlOCK_TAG_ENTRY_END);
}
}
}
private void handleThrowsTag(TagElement tag) {
List<? extends ASTNode> fragments= tag.fragments();
int size= fragments.size();
if (size > 0) {
handleLink(fragments.subList(0, 1));
if (size > 1) {
fBuf.append(JavaElementLabels.CONCAT_STRING);
handleContentElements(fragments.subList(1, size));
}
}
}
private void handleParameterTags(List<TagElement> tags, List<String> parameterNames, CharSequence[] parameterDescriptions, boolean isTypeParameters) {
if (tags.isEmpty() && containsOnlyNull(parameterNames))
return;
String tagTitle= isTypeParameters ? JavaDocMessages.JavaDoc2HTMLTextReader_type_parameters_section : JavaDocMessages.JavaDoc2HTMLTextReader_parameters_section;
handleBlockTagTitle(tagTitle);
for (TagElement tag : tags) {
fBuf.append(BlOCK_TAG_ENTRY_START);
handleParamTag(tag);
fBuf.append(BlOCK_TAG_ENTRY_END);
}
for (int i= 0; i < parameterDescriptions.length; i++) {
CharSequence description= parameterDescriptions[i];
String name= parameterNames.get(i);
if (name != null) {
fBuf.append(BlOCK_TAG_ENTRY_START);
fBuf.append(PARAM_NAME_START);
if (isTypeParameters) {
fBuf.append("&lt;"); //$NON-NLS-1$
}
fBuf.append(name);
if (isTypeParameters) {
fBuf.append("&gt;"); //$NON-NLS-1$
}
fBuf.append(PARAM_NAME_END);
if (description != null)
fBuf.append(description);
fBuf.append(BlOCK_TAG_ENTRY_END);
}
}
}
private void handleParamTag(TagElement tag) {
List<? extends ASTNode> fragments= tag.fragments();
int i= 0;
int size= fragments.size();
if (size > 0) {
Object first= fragments.get(0);
fBuf.append(PARAM_NAME_START);
if (first instanceof SimpleName) {
String name= ((SimpleName) first).getIdentifier();
fBuf.append(name);
i++;
} else if (first instanceof TextElement) {
String firstText= ((TextElement) first).getText();
if ("<".equals(firstText)) { //$NON-NLS-1$
fBuf.append("&lt;"); //$NON-NLS-1$
i++;
if (size > 1) {
Object second= fragments.get(1);
if (second instanceof SimpleName) {
String name= ((SimpleName) second).getIdentifier();
fBuf.append(name);
i++;
if (size > 2) {
Object third= fragments.get(2);
String thirdText= ((TextElement) third).getText();
if (">".equals(thirdText)) { //$NON-NLS-1$
fBuf.append("&gt;"); //$NON-NLS-1$
i++;
}
}
}
}
}
}
fBuf.append(PARAM_NAME_END);
handleContentElements(fragments.subList(i, fragments.size()));
}
}
private void handleSummary(List<? extends ASTNode> fragments) {
int fs= fragments.size();
if (fs > 0) {
Object first= fragments.get(0);
if (first instanceof TextElement) {
TextElement memberRef= (TextElement) first;
fBuf.append(BlOCK_TAG_TITLE_START + "Summary: " + memberRef.getText() + BlOCK_TAG_TITLE_END); //$NON-NLS-1$
return;
}
}
}
private void handleSnippet(List<? extends ASTNode> fragments) {
int fs= fragments.size();
if (fs > 0) {
fBuf.append("<pre><code>"); //$NON-NLS-1$
fBuf.append(BlOCK_TAG_ENTRY_START);
for(ASTNode fragment : fragments) {
if (fragment instanceof TextElement) {
TextElement memberRef= (TextElement) fragment;
fBuf.append(memberRef.getText());
} else if (fragment instanceof TagElement) {
TagElement tagElem= (TagElement) fragment;
String name= tagElem.getTagName();
boolean isHighlight = TagElement.TAG_HIGHLIGHT.equals(name);
if (isHighlight) {
handleSnippetHighlight(tagElem.fragments(), tagElem.tagProperties());
}
}
}
fBuf.append(BlOCK_TAG_ENTRY_END);
}
}
private void handleSnippetHighlight(List<? extends ASTNode> fragments, List<? extends ASTNode> tagProperties) {
int fs= fragments.size();
String defHighlight= getHighlightHtmlTag(tagProperties);
String startDefHighlight = '<' + defHighlight + '>';
String endDefHighlight = "</" + defHighlight + '>'; //$NON-NLS-1$
boolean process = true;
if (defHighlight.length() == 0 || !arePropertyValuesStringLiterals(tagProperties)) {
process = false;
}
String regExValue = getHighlightPropertyValue("regex", tagProperties); //$NON-NLS-1$
String subStringValue = getHighlightPropertyValue("substring", tagProperties); //$NON-NLS-1$
int additionalLength = startDefHighlight.length() + endDefHighlight.length();
Pattern regexPattern = null;
if (regExValue != null) {
regexPattern = Pattern.compile(regExValue);
}
if (fs > 0) {
for(ASTNode fragment : fragments) {
if (fragment instanceof TextElement) {
TextElement memberRef= (TextElement) fragment;
String modifiedText = memberRef.getText();
if (process) {
if (regexPattern != null && process) {
Matcher matcher = regexPattern.matcher(modifiedText);
StringBuilder strBuild= new StringBuilder();
int finalMatchIndex = 0;
while (matcher.find()) {
finalMatchIndex = matcher.end();
String replacementStr= startDefHighlight + modifiedText.substring(matcher.start(), matcher.end()) + endDefHighlight;
matcher.appendReplacement(strBuild, replacementStr);
}
modifiedText = strBuild.toString() + modifiedText.substring(finalMatchIndex);
} else if (subStringValue != null) {
int startIndex = 0;
while (true) {
startIndex = modifiedText.indexOf(subStringValue, startIndex);
if (startIndex == -1) {
break;
} else {
modifiedText = modifiedText.substring(0, startIndex) + startDefHighlight + subStringValue + endDefHighlight + modifiedText.substring(startIndex + subStringValue.length());
startIndex = startIndex + subStringValue.length() + additionalLength;
}
}
} else {
modifiedText = startDefHighlight + modifiedText + endDefHighlight;
}
}
fBuf.append(modifiedText);
}
}
}
}
private String getHighlightHtmlTag(List<? extends ASTNode> tagProperties) {
String defaultTag= "b"; //$NON-NLS-1$
if (tagProperties != null) {
for (ASTNode node : tagProperties) {
if (node instanceof TagProperty) {
TagProperty tagProp = (TagProperty) node;
if ("type".equals(tagProp.getName())) { //$NON-NLS-1$
String tagValue = stripQuotes(tagProp.getValue());
switch (tagValue) {
case "bold" : //$NON-NLS-1$
defaultTag= "b"; //$NON-NLS-1$
break;
case "italic" : //$NON-NLS-1$
defaultTag= "i"; //$NON-NLS-1$
break;
case "highlighted" : //$NON-NLS-1$
defaultTag= "mark"; //$NON-NLS-1$
break;
default :
defaultTag= ""; //$NON-NLS-1$
break;
}
break;
}
}
}
}
return defaultTag;
}
private boolean arePropertyValuesStringLiterals(List<? extends ASTNode> tagProperties) {
boolean val= true;
if (tagProperties != null) {
final String SUBSTRING = "substring"; //$NON-NLS-1$
final String REGEX = "regex"; //$NON-NLS-1$
final String TYPE = "type"; //$NON-NLS-1$
for (ASTNode node : tagProperties) {
if (node instanceof TagProperty) {
TagProperty tagProp = (TagProperty) node;
String propName = tagProp.getName();
if (SUBSTRING.equals(propName)
|| REGEX.equals(propName)
|| TYPE.equals(propName)) {
String value= tagProp.getValue();
String changed= stripQuotes(value);
if (changed.equals(value)) {
val= false;
break;
}
}
}
}
}
return val;
}
private String getHighlightPropertyValue(String property, List<? extends ASTNode> tagProperties) {
String defaultTag= null;
if (tagProperties != null && property!= null) {
for (ASTNode node : tagProperties) {
if (node instanceof TagProperty) {
TagProperty tagProp = (TagProperty) node;
if (property.equals(tagProp.getName())) {
defaultTag= stripQuotes(tagProp.getValue());
break;
}
}
}
}
return defaultTag;
}
private String stripQuotes (String str) {
String newStr = str;
if (str != null && str.length() > 2) {
int length = str.length();
if (str.charAt(0) == '"' && str.charAt(length-1) == '"') {
newStr = str.substring(1, length-1);
}
}
return newStr;
}
private void handleIndex(List<? extends ASTNode> fragments) {
int fs= fragments.size();
if (fs > 0) {
Object first= fragments.get(0);
if (first instanceof TextElement) {
TextElement memberRef= (TextElement) first;
fBuf.append( memberRef.getText() );
return;
}
}
}
private void handleLink(List<? extends ASTNode> fragments) {
//TODO: Javadoc shortens type names to minimal length according to context
int fs= fragments.size();
if (fs > 0) {
Object first= fragments.get(0);
String refTypeName= null;
String refMemberName= null;
String[] refMethodParamTypes= null;
String[] refMethodParamNames= null;
if (first instanceof Name) {
Name name = (Name) first;
refTypeName= name.getFullyQualifiedName();
} else if (first instanceof MemberRef) {
MemberRef memberRef= (MemberRef) first;
Name qualifier= memberRef.getQualifier();
refTypeName= qualifier == null ? "" : qualifier.getFullyQualifiedName(); //$NON-NLS-1$
refMemberName= memberRef.getName().getIdentifier();
} else if (first instanceof MethodRef) {
MethodRef methodRef= (MethodRef) first;
Name qualifier= methodRef.getQualifier();
refTypeName= qualifier == null ? "" : qualifier.getFullyQualifiedName(); //$NON-NLS-1$
refMemberName= methodRef.getName().getIdentifier();
List<MethodRefParameter> params= methodRef.parameters();
int ps= params.size();
refMethodParamTypes= new String[ps];
refMethodParamNames= new String[ps];
for (int i= 0; i < ps; i++) {
MethodRefParameter param= params.get(i);
refMethodParamTypes[i]= ASTNodes.asString(param.getType());
SimpleName paramName= param.getName();
if (paramName != null)
refMethodParamNames[i]= paramName.getIdentifier();
}
}
if (refTypeName != null) {
fBuf.append("<a href='"); //$NON-NLS-1$
try {
String scheme= JavaElementLinks.JAVADOC_SCHEME;
String uri= JavaElementLinks.createURI(scheme, fElement, refTypeName, refMemberName, refMethodParamTypes);
fBuf.append(uri);
} catch (URISyntaxException e) {
JavaPlugin.log(e);
}
fBuf.append("'>"); //$NON-NLS-1$
if (fs > 1 && ((fs != 2) || !isWhitespaceTextElement(fragments.get(1)))) {
handleContentElements(fragments.subList(1, fs), true);
} else {
fBuf.append(refTypeName);
if (refMemberName != null) {
if (refTypeName.length() > 0) {
fBuf.append('.');
}
fBuf.append(refMemberName);
if (refMethodParamTypes != null) {
fBuf.append('(');
for (int i= 0; i < refMethodParamTypes.length; i++) {
String pType= refMethodParamTypes[i];
fBuf.append(pType);
String pName= refMethodParamNames[i];
if (pName != null) {
fBuf.append(' ').append(pName);
}
if (i < refMethodParamTypes.length - 1) {
fBuf.append(", "); //$NON-NLS-1$
}
}
fBuf.append(')');
}
}
}
fBuf.append("</a>"); //$NON-NLS-1$
} else {
handleContentElements(fragments);
}
}
}
private static boolean isWhitespaceTextElement(Object fragment) {
if (!(fragment instanceof TextElement))
return false;
TextElement textElement= (TextElement) fragment;
return textElement.getText().trim().length() == 0;
}
private boolean containsOnlyNull(List<String> parameterNames) {
for (String string : parameterNames) {
if (string != null)
return false;
}
return true;
}
/**
* @param content HTML content produced by <code>getHTMLContent(...)</code>
* @return the baseURL to use for the given content, or <code>null</code> if none
* @since 3.10
*/
public static String extractBaseURL(String content) {
int introStart= content.indexOf(BASE_URL_COMMENT_INTRO);
if (introStart != -1) {
int introLength= BASE_URL_COMMENT_INTRO.length();
int endIndex= content.indexOf('"', introStart + introLength);
if (endIndex != -1) {
return content.substring(introStart + introLength, endIndex);
}
}
return null;
}
/**
* Returns the Javadoc for a PackageDeclaration.
*
* @param packageDeclaration the Java element whose Javadoc has to be retrieved
* @return the package documentation in HTML format or <code>null</code> if there is no
* associated Javadoc
* @throws CoreException if the Java element does not exists or an exception occurs while
* accessing the file containing the package Javadoc
* @since 3.9
*/
public static String getHTMLContent(IPackageDeclaration packageDeclaration) throws CoreException {
IJavaElement element= packageDeclaration.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
if (element instanceof IPackageFragment) {
return getHTMLContent((IPackageFragment) element);
}
return null;
}
/**
* Returns the Javadoc for a package which could be present in package.html, package-info.java
* or from an attached Javadoc.
*
* @param packageFragment the package which is requesting for the document
* @return the document content in HTML format or <code>null</code> if there is no associated
* Javadoc
* @throws CoreException if the Java element does not exists or an exception occurs while
* accessing the file containing the package Javadoc
* @since 3.9
*/
public static String getHTMLContent(IPackageFragment packageFragment) throws CoreException {
IPackageFragmentRoot root= (IPackageFragmentRoot) packageFragment.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
//1==> Handle the case when the documentation is present in package-info.java or package-info.class file
ITypeRoot packageInfo;
boolean isBinary= root.getKind() == IPackageFragmentRoot.K_BINARY;
if (isBinary) {
packageInfo= packageFragment.getClassFile(JavaModelUtil.PACKAGE_INFO_CLASS);
} else {
packageInfo= packageFragment.getCompilationUnit(JavaModelUtil.PACKAGE_INFO_JAVA);
}
if (packageInfo != null && packageInfo.exists()) {
String cuSource= packageInfo.getSource();
//the source can be null for some of the class files
if (cuSource != null) {
Javadoc packageJavadocNode= getPackageJavadocNode(packageFragment, cuSource);
if (packageJavadocNode != null) {
IJavaElement element;
if (isBinary) {
element= ((IOrdinaryClassFile) packageInfo).getType();
} else {
element= packageInfo.getParent(); // parent is the IPackageFragment
}
return new JavadocContentAccess2(element, packageJavadocNode, cuSource).toHTML();
}
}
}
// 2==> Handle the case when the documentation is done in package.html file. The file can be either in normal source folder or coming from a jar file
else {
Object[] nonJavaResources= packageFragment.getNonJavaResources();
// 2.1 ==>If the package.html file is present in the source or directly in the binary jar
for (Object nonJavaResource : nonJavaResources) {
if (nonJavaResource instanceof IFile) {
IFile iFile= (IFile) nonJavaResource;
if (iFile.exists() && JavaModelUtil.PACKAGE_HTML.equals(iFile.getName())) {
return getIFileContent(iFile);
}
}
}
// 2.2==>The file is present in a binary container
if (isBinary) {
for (Object nonJavaResource : nonJavaResources) {
// The content is from an external binary class folder
if (nonJavaResource instanceof IJarEntryResource) {
IJarEntryResource jarEntryResource= (IJarEntryResource) nonJavaResource;
String encoding= getSourceAttachmentEncoding(root);
if (JavaModelUtil.PACKAGE_HTML.equals(jarEntryResource.getName()) && jarEntryResource.isFile()) {
return getHTMLContent(jarEntryResource, encoding);
}
}
}
//2.3 ==>The file is present in the source attachment path.
String contents= getHTMLContentFromAttachedSource(root, packageFragment);
if (contents != null)
return contents;
}
}
//3==> Handle the case when the documentation is coming from the attached Javadoc
if ((root.isArchive() || root.isExternal())) {
return packageFragment.getAttachedJavadoc(null);
}
return null;
}
private static String getHTMLContent(IJarEntryResource jarEntryResource, String encoding) throws CoreException {
InputStream in= jarEntryResource.getContents();
try {
return getContentsFromInputStream(in, encoding);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
//ignore
}
}
}
}
private static String getHTMLContentFromAttachedSource(IPackageFragmentRoot root, IPackageFragment packageFragment) throws CoreException {
String filePath= packageFragment.getElementName().replace('.', '/') + '/' + JavaModelUtil.PACKAGE_INFO_JAVA;
String contents= getFileContentFromAttachedSource(root, filePath);
if (contents != null) {
Javadoc packageJavadocNode= getPackageJavadocNode(packageFragment, contents);
if (packageJavadocNode != null)
return new JavadocContentAccess2(packageFragment, packageJavadocNode, contents).toHTML();
}
filePath= packageFragment.getElementName().replace('.', '/') + '/' + JavaModelUtil.PACKAGE_HTML;
return getFileContentFromAttachedSource(root, filePath);
}
@SuppressWarnings("resource")
private static String getFileContentFromAttachedSource(IPackageFragmentRoot root, String filePath) throws CoreException {
IPath sourceAttachmentPath= root.getSourceAttachmentPath();
if (sourceAttachmentPath != null) {
File file= null ;
String encoding= null;
if (sourceAttachmentPath.getDevice() == null) {
//the path could be a workspace relative path to a zip or to the source folder
IWorkspaceRoot wsRoot= ResourcesPlugin.getWorkspace().getRoot();
IResource res= wsRoot.findMember(sourceAttachmentPath);
if (res instanceof IFile) {
// zip in the workspace
IPath location= res.getLocation();
if (location == null)
return null;
file= location.toFile();
encoding= ((IFile) res).getCharset(false);
} else if (res instanceof IContainer) {
// folder in the workspace
res= ((IContainer) res).findMember(filePath);
if (!(res instanceof IFile))
return null;
encoding= ((IFile) res).getCharset(false);
if (encoding == null)
encoding= getSourceAttachmentEncoding(root);
return getContentsFromInputStream(((IFile) res).getContents(), encoding);
}
}
if (file == null || !file.exists())
file= sourceAttachmentPath.toFile();
if (file.isDirectory()) {
//the path is an absolute filesystem path to the source folder
IPath packagedocPath= sourceAttachmentPath.append(filePath);
if (packagedocPath.toFile().exists())
return getFileContent(packagedocPath.toFile());
} else if (file.exists()) {
//the package documentation is in a Jar/Zip
IPath sourceAttachmentRootPath= root.getSourceAttachmentRootPath();
String packagedocPath;
//consider the root path also in the search path if it exists
if (sourceAttachmentRootPath != null) {
packagedocPath= sourceAttachmentRootPath.append(filePath).toString();
} else {
packagedocPath= filePath;
}
ZipFile zipFile= null;
InputStream in= null;
try {
zipFile= new ZipFile(file, ZipFile.OPEN_READ);
ZipEntry packagedocFile= zipFile.getEntry(packagedocPath);
if (packagedocFile != null) {
in= zipFile.getInputStream(packagedocFile);
if (encoding == null)
encoding= getSourceAttachmentEncoding(root);
return getContentsFromInputStream(in, encoding);
}
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), e.getMessage(), e));
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
//ignore
}
try {
if (zipFile != null) {
zipFile.close();//this will close the InputStream also
}
} catch (IOException e) {
//ignore
}
}
}
}
return null;
}
private static String getContentsFromInputStream(InputStream in, String encoding) throws CoreException {
final int defaultFileSize= 15 * 1024;
StringBuilder buffer= new StringBuilder(defaultFileSize);
Reader reader= null;
try {
reader= new BufferedReader(new InputStreamReader(in, encoding), defaultFileSize);
char[] readBuffer= new char[2048];
int charCount= reader.read(readBuffer);
while (charCount > 0) {
buffer.append(readBuffer, 0, charCount);
charCount= reader.read(readBuffer);
}
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), e.getMessage(), e));
} finally {
try {
if (reader != null) {
reader.close();//this will also close the InputStream wrapped in the reader
}
} catch (IOException e) {
//ignore
}
}
return buffer.toString();
}
private static String getSourceAttachmentEncoding(IPackageFragmentRoot root) throws JavaModelException {
String encoding= ResourcesPlugin.getEncoding();
IClasspathEntry entry= root.getRawClasspathEntry();
if (entry != null) {
int kind= entry.getEntryKind();
if (kind == IClasspathEntry.CPE_LIBRARY || kind == IClasspathEntry.CPE_VARIABLE) {
for (IClasspathAttribute attrib : entry.getExtraAttributes()) {
if (IClasspathAttribute.SOURCE_ATTACHMENT_ENCODING.equals(attrib.getName())) {
return attrib.getValue();
}
}
}
}
return encoding;
}
/**
* Reads the content of the IFile.
*
* @param file the file whose content has to be read
* @return the content of the file
* @throws CoreException if the file could not be successfully connected or disconnected
*/
private static String getIFileContent(IFile file) throws CoreException {
String content= null;
ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
IPath fullPath= file.getFullPath();
manager.connect(fullPath, LocationKind.IFILE, null);
try {
ITextFileBuffer buffer= manager.getTextFileBuffer(fullPath, LocationKind.IFILE);
if (buffer != null) {
content= buffer.getDocument().get();
}
} finally {
manager.disconnect(fullPath, LocationKind.IFILE, null);
}
return content;
}
/**
* Reads the content of the java.io.File.
*
* @param file the file whose content has to be read
* @return the content of the file
* @throws CoreException if the file could not be successfully connected or disconnected
*/
private static String getFileContent(File file) throws CoreException {
String content= null;
ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
IPath fullPath= new Path(file.getAbsolutePath());
manager.connect(fullPath, LocationKind.LOCATION, null);
try {
ITextFileBuffer buffer= manager.getTextFileBuffer(fullPath, LocationKind.LOCATION);
if (buffer != null) {
content= buffer.getDocument().get();
}
} finally {
manager.disconnect(fullPath, LocationKind.LOCATION, null);
}
return content;
}
}