--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2008, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * 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.IOException;
+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 org.eclipse.core.runtime.IPath;
+
+import org.eclipse.core.resources.IResource;
+
+import org.eclipse.jface.internal.text.html.HTMLPrinter;
+
+import org.eclipse.jdt.core.IBuffer;
+import org.eclipse.jdt.core.IField;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IMember;
+import org.eclipse.jdt.core.IMethod;
+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.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.SimpleName;
+import org.eclipse.jdt.core.dom.TagElement;
+import org.eclipse.jdt.core.dom.TextElement;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
+import org.eclipse.jdt.internal.corext.util.JdtFlags;
+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.ui.SharedASTProvider;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
+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 BLOCK_TAG_START= "<dl>"; //$NON-NLS-1$
+ private static final String BLOCK_TAG_END= "</dl>"; //$NON-NLS-1$
+
+ private static final String BlOCK_TAG_ENTRY_START= "<dd>"; //$NON-NLS-1$
+ private 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<IType>();
+ 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<IType>();
+ IType[] superInterfaces= typeHierarchy.getSuperInterfaces(currentType);
+ for (int i= 0; i < superInterfaces.length; i++) {
+ IType superInterface= superInterfaces[i];
+ 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 (Iterator<IType> iter= toVisitChildren.iterator(); iter.hasNext(); ) {
+ IType child= iter.next();
+ Object result= visitInheritDocInterfaces(visited, child, typeHierarchy);
+ if (result != InheritDocVisitor.CONTINUE)
+ return result;
+ }
+ return InheritDocVisitor.CONTINUE;
+ }
+ }
+
+ 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 static 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<IMethod, JavadocContentAccess2>();
+ }
+
+ /**
+ * For the given method, returns the main description from an overridden method.
+ *
+ * @param method a method
+ * @return the description that replaces the <code>{@inheritDoc}</code> tag,
+ * or <code>null</code> if none could be found
+ */
+ public CharSequence getInheritedMainDescription(IMethod method) {
+ return getInheritedDescription(method, new DescriptionGetter() {
+ public CharSequence getDescription(JavadocContentAccess2 contentAccess) {
+ return contentAccess.getMainDescription();
+ }
+ });
+ }
+
+ /**
+ * 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>{@inheritDoc}</code> tag,
+ * or <code>null</code> if none could be found
+ */
+ public CharSequence getInheritedParamDescription(IMethod method, final int paramIndex) {
+ return getInheritedDescription(method, new DescriptionGetter() {
+ public CharSequence getDescription(JavadocContentAccess2 contentAccess) throws JavaModelException {
+ return 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>{@inheritDoc}</code> tag,
+ * or <code>null</code> if none could be found
+ */
+ public CharSequence getInheritedReturnDescription(IMethod method) {
+ return getInheritedDescription(method, new DescriptionGetter() {
+ public CharSequence getDescription(JavadocContentAccess2 contentAccess) {
+ return contentAccess.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>{@inheritDoc}</code> tag,
+ * or <code>null</code> if none could be found
+ */
+ public CharSequence getInheritedExceptionDescription(IMethod method, final String simpleName) {
+ return getInheritedDescription(method, new DescriptionGetter() {
+ public CharSequence getDescription(JavadocContentAccess2 contentAccess) {
+ return 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);
+ return contentAccess.generated_4672312213436743719(method, JavadocLookup.this);
+ }
+
+ 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;
+ }
+
+ public String generated_7350347271025408250(JavadocContentAccess2 javadoccontentaccess2, List<String> parameterNames, List<String> exceptionNames, TagElement deprecatedTag, TagElement start) {
+ List<TagElement> parameters= new ArrayList<TagElement>();
+ TagElement returnTag= null;
+ List<TagElement> exceptions= new ArrayList<TagElement>();
+ List<TagElement> versions= new ArrayList<TagElement>();
+ List<TagElement> authors= new ArrayList<TagElement>();
+ List<TagElement> sees= new ArrayList<TagElement>();
+ List<TagElement> since= new ArrayList<TagElement>();
+ List<TagElement> rest= new ArrayList<TagElement>();
+
+ List<TagElement> tags= javadoccontentaccess2.fJavadoc.tags();
+ for (Iterator<TagElement> iter= tags.iterator(); iter.hasNext(); ) {
+ TagElement tag= iter.next();
+ String tagName= tag.getTagName();
+ if (tagName == null) {
+ start= tag;
+
+ } else if (TagElement.TAG_PARAM.equals(tagName)) {
+ parameters.add(tag);
+ List<? extends ASTNode> fragments= tag.fragments();
+ if (fragments.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);
+ }
+ }
+ }
+
+ } else if (TagElement.TAG_RETURN.equals(tagName)) {
+ if (returnTag == null)
+ returnTag= tag; // the Javadoc tool only shows the first return tag
+
+ } else if (TagElement.TAG_EXCEPTION.equals(tagName) || TagElement.TAG_THROWS.equals(tagName)) {
+ exceptions.add(tag);
+ 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);
+ int exceptionIndex= exceptionNames.indexOf(name);
+ if (exceptionIndex != -1) {
+ exceptionNames.set(exceptionIndex, null);
+ }
+ }
+ }
+
+ } else if (TagElement.TAG_SINCE.equals(tagName)) {
+ since.add(tag);
+ } else if (TagElement.TAG_VERSION.equals(tagName)) {
+ versions.add(tag);
+ } else if (TagElement.TAG_AUTHOR.equals(tagName)) {
+ authors.add(tag);
+ } else if (TagElement.TAG_SEE.equals(tagName)) {
+ sees.add(tag);
+ } else if (TagElement.TAG_DEPRECATED.equals(tagName)) {
+ if (deprecatedTag == null)
+ deprecatedTag= tag; // the Javadoc tool only shows the first deprecated tag
+ } else {
+ rest.add(tag);
+ }
+ }
+
+ //TODO: @Documented annotations before header
+ if (deprecatedTag != null)
+ javadoccontentaccess2.handleDeprecatedTag(deprecatedTag);
+ if (start != null)
+ javadoccontentaccess2.handleContentElements(start.fragments());
+ else if (javadoccontentaccess2.fMethod != null) {
+ CharSequence inherited= getInheritedMainDescription(javadoccontentaccess2.fMethod);
+ // The Javadoc tool adds "Description copied from class: ..." (only for the main description).
+ // We don't bother doing that.
+ javadoccontentaccess2.handleInherited(inherited);
+ }
+
+ CharSequence[] parameterDescriptions= new CharSequence[parameterNames.size()];
+ boolean hasInheritedParameters= javadoccontentaccess2.inheritParameterDescriptions(parameterNames, parameterDescriptions);
+ boolean hasParameters= parameters.size() > 0 || hasInheritedParameters;
+
+ CharSequence returnDescription= null;
+ if (returnTag == null && javadoccontentaccess2.needsReturnTag())
+ returnDescription= getInheritedReturnDescription(javadoccontentaccess2.fMethod);
+ boolean hasReturnTag= returnTag != null || returnDescription != null;
+
+ CharSequence[] exceptionDescriptions= new CharSequence[exceptionNames.size()];
+ boolean hasInheritedExceptions= javadoccontentaccess2.inheritExceptionDescriptions(exceptionNames, exceptionDescriptions);
+ boolean hasExceptions= exceptions.size() > 0 || hasInheritedExceptions;
+
+ if (hasParameters || hasReturnTag || hasExceptions
+ || versions.size() > 0 || authors.size() > 0 || since.size() > 0 || sees.size() > 0 || rest.size() > 0
+ || (javadoccontentaccess2.fBuf.length() > 0 && (parameterDescriptions.length > 0 || exceptionDescriptions.length > 0))
+ ) {
+ javadoccontentaccess2.handleSuperMethodReferences();
+ javadoccontentaccess2.fBuf.append(JavadocContentAccess2.BLOCK_TAG_START);
+ javadoccontentaccess2.handleParameterTags(parameters, parameterNames, parameterDescriptions);
+ javadoccontentaccess2.handleReturnTag(returnTag, returnDescription);
+ javadoccontentaccess2.handleExceptionTags(exceptions, exceptionNames, exceptionDescriptions);
+ javadoccontentaccess2.handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_since_section, since);
+ javadoccontentaccess2.handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_version_section, versions);
+ javadoccontentaccess2.handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_author_section, authors);
+ javadoccontentaccess2.handleBlockTags(JavaDocMessages.JavaDoc2HTMLTextReader_see_section, sees);
+ javadoccontentaccess2.handleBlockTags(rest);
+ javadoccontentaccess2.fBuf.append(JavadocContentAccess2.BLOCK_TAG_END);
+
+ } else if (javadoccontentaccess2.fBuf.length() > 0) {
+ javadoccontentaccess2.handleSuperMethodReferences();
+ }
+
+ String result= javadoccontentaccess2.fBuf.toString();
+ javadoccontentaccess2.fBuf= null;
+ return result;
+ }
+
+ public boolean generated_8554163332790850784(JavadocContentAccess2 javadoccontentaccess2, TagElement node) {
+ try {
+ if (javadoccontentaccess2.fMethod == null)
+ return false;
+
+ TagElement blockTag= (TagElement) node.getParent();
+ String blockTagName= blockTag.getTagName();
+
+ if (blockTagName == null) {
+ CharSequence inherited= getInheritedMainDescription(javadoccontentaccess2.fMethod);
+ return javadoccontentaccess2.handleInherited(inherited);
+
+ } else if (TagElement.TAG_PARAM.equals(blockTagName)) {
+ List<? extends ASTNode> fragments= blockTag.fragments();
+ if (fragments.size() > 0) {
+ Object first= fragments.get(0);
+ if (first instanceof SimpleName) {
+ String name= ((SimpleName) first).getIdentifier();
+ String[] parameterNames= javadoccontentaccess2.fMethod.getParameterNames();
+ for (int i= 0; i < parameterNames.length; i++) {
+ if (name.equals(parameterNames[i])) {
+ CharSequence inherited= getInheritedParamDescription(javadoccontentaccess2.fMethod, i);
+ return javadoccontentaccess2.handleInherited(inherited);
+ }
+ }
+ }
+ }
+
+ } else if (TagElement.TAG_RETURN.equals(blockTagName)) {
+ CharSequence inherited= getInheritedReturnDescription(javadoccontentaccess2.fMethod);
+ return javadoccontentaccess2.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= getInheritedExceptionDescription(javadoccontentaccess2.fMethod, name);
+ return javadoccontentaccess2.handleInherited(inherited);
+ }
+ }
+ }
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e);
+ }
+ return false;
+ }
+ }
+
+ private final IMember fMember;
+ /**
+ * The method, or <code>null</code> if {@link #fMember} 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[] fParamDescriptions;
+ private HashMap<String, StringBuffer> fExceptionDescriptions;
+
+ private JavadocContentAccess2(IMethod method, Javadoc javadoc, String source, JavadocLookup lookup) {
+ fMember= method;
+ fMethod= method;
+ fJavadoc= javadoc;
+ fSource= source;
+ fJavadocLookup= lookup;
+ }
+
+ private JavadocContentAccess2(IMember member, Javadoc javadoc, String source) {
+ fMember= member;
+ fMethod= null;
+ fJavadoc= javadoc;
+ fSource= source;
+ fJavadocLookup= JavadocLookup.NONE;
+ }
+
+ /**
+ * Gets an IMember's Javadoc comment content from the source or Javadoc attachment
+ * and renders the tags and links in HTML.
+ * Returns <code>null</code> if the member does not contain a Javadoc comment or if no source is available.
+ *
+ * @param member the member 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 member
+ * does not have a Javadoc comment or if no source is available
+ * @throws JavaModelException is thrown when the element's Javadoc can not be accessed
+ */
+ public static String getHTMLContent(IMember member, boolean useAttachedJavadoc) throws JavaModelException {
+ String sourceJavadoc= getHTMLContentFromSource(member);
+ if (sourceJavadoc == null || sourceJavadoc.length() == 0 || sourceJavadoc.trim().equals("{@inheritDoc}")) { //$NON-NLS-1$
+ if (useAttachedJavadoc) {
+ if (member.getOpenable().getBuffer() == null) { // only if no source available
+ return member.getAttachedJavadoc(null);
+ }
+ 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<IMethod>();
+ 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.size() != 0;
+ 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, new Object[] { 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(IMember member) throws JavaModelException {
+ 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. If it finds nothing (in source), return null.
+ String inheritedJavadoc= javadoc2HTML(member, "/***/"); //$NON-NLS-1$
+ return inheritedJavadoc != null && inheritedJavadoc.length() > 0 ? inheritedJavadoc : null;
+ } else {
+ return null;
+ }
+ }
+
+ String rawJavadoc= buf.getText(javadocRange.getOffset(), javadocRange.getLength());
+ return javadoc2HTML(member, rawJavadoc);
+ }
+
+ private static Javadoc getJavadocNode(IMember member, 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
+
+ ASTParser parser= ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
+
+ IJavaProject javaProject= member.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);
+
+ String source= rawJavadoc + "class C{}"; //$NON-NLS-1$
+ parser.setSource(source.toCharArray());
+
+ CompilationUnit root= (CompilationUnit) parser.createAST(null);
+ 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 String javadoc2HTML(IMember member, String rawJavadoc) {
+ Javadoc javadoc= getJavadocNode(member, rawJavadoc);
+
+ if (javadoc == null) {
+ // fall back to JavadocContentAccess:
+ try {
+ Reader contentReader= JavadocContentAccess.getHTMLContentReader(member, false, false);
+ if (contentReader != null)
+ return getString(contentReader);
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e);
+ }
+ return null;
+ }
+
+ if (canInheritJavadoc(member)) {
+ IMethod method= (IMethod) member;
+ return new JavadocContentAccess2(method, javadoc, rawJavadoc, new JavadocLookup(method.getDeclaringType())).toHTML();
+ }
+ return new JavadocContentAccess2(member, 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) {
+ StringBuffer buf= new StringBuffer();
+ 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
+ //TODO: BaseURL for method can be wrong for attached Javadoc from overridden
+ // (e.g. when overridden is from rt.jar). Fix would be to add baseURL here.
+ String attachedJavadoc= overridden.getAttachedJavadoc(null);
+ if (attachedJavadoc != null)
+ return attachedJavadoc;
+ }
+ return CONTINUE;
+ }
+ }.visitInheritDoc(type, hierarchy);
+ }
+
+ private String toHTML() {
+ fBuf= new StringBuffer();
+ fLiteralContent= 0;
+
+ // After first loop, non-null entries in the following two lists are missing and need to be inherited:
+ List<String> parameterNames= initParameterNames();
+ List<String> exceptionNames= initExceptionNames();
+
+ TagElement deprecatedTag= null;
+ TagElement start= null;
+ return fJavadocLookup.generated_7350347271025408250(this, parameterNames, exceptionNames, deprecatedTag, start);
+ }
+
+ 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> initParameterNames() {
+ if (fMethod != null) {
+ try {
+ return new ArrayList<String>(Arrays.asList(fMethod.getParameterNames()));
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e);
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ private List<String> initExceptionNames() {
+ if (fMethod != null) {
+ try {
+ String[] exceptionTypes= fMethod.getExceptionTypes();
+ ArrayList<String> exceptionNames= new ArrayList<String>();
+ for (int i= 0; i < exceptionTypes.length; i++) {
+ exceptionNames.add(Signature.getSimpleName(Signature.toString(exceptionTypes[i])));
+ }
+ 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 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 (Iterator<TagElement> iter= tags.iterator(); iter.hasNext(); ) {
+ TagElement tag= iter.next();
+ 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 (Iterator<TagElement> iter= tags.iterator(); iter.hasNext(); ) {
+ TagElement tag= iter.next();
+ String tagName= tag.getTagName();
+ if (TagElement.TAG_RETURN.equals(tagName)) {
+ handleContentElements(tag.fragments());
+ break;
+ }
+ }
+
+ fBuf= null;
+ }
+ return fReturnDescription.length() > 0 ? fReturnDescription : 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 (Iterator<TagElement> iter= tags.iterator(); iter.hasNext(); ) {
+ TagElement tag= iter.next();
+ 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<String, StringBuffer>();
+ } 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 (Iterator<TagElement> iter= tags.iterator(); iter.hasNext(); ) {
+ TagElement tag= iter.next();
+ 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 (Iterator<? extends ASTNode> iter= nodes.iterator(); iter.hasNext(); ) {
+ ASTNode child= iter.next();
+ 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$
+ + ", member=" + fMember.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$
+ }
+ 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= "&"; //$NON-NLS-1$
+ break;
+ case '"':
+ rep= """; //$NON-NLS-1$
+ break;
+ case '<':
+ rep= "<"; //$NON-NLS-1$
+ break;
+ case '>':
+ rep= ">"; //$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);
+
+ if (isLiteral || isCode)
+ fLiteralContent++;
+ if (isLink || isCode)
+ fBuf.append("<code>"); //$NON-NLS-1$
+
+ if (isLink || isLinkplain)
+ handleLink(node.fragments());
+ else if (isCode || isLiteral)
+ handleContentElements(node.fragments(), true);
+ else if (handleInheritDoc(node)) {
+ // handled
+ } else if (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 (isLiteral || isCode)
+ fLiteralContent--;
+
+ }
+
+ private boolean handleValueTag(TagElement node) {
+
+ List<? extends ASTNode> fragments= node.fragments();
+ try {
+ if (fragments.isEmpty()) {
+ if (fMember instanceof IField && JdtFlags.isStatic(fMember) && JdtFlags.isFinal(fMember)) {
+ IField field= (IField) fMember;
+ return handleConstantValue(field, false);
+ }
+ } else if (fragments.size() == 1) {
+ Object first= fragments.get(0);
+ if (first instanceof MemberRef) {
+ MemberRef memberRef= (MemberRef) first;
+ if (memberRef.getQualifier() == null) {
+ SimpleName name= memberRef.getName();
+ IType type= fMember instanceof IType ? (IType)fMember : fMember.getDeclaringType();
+ 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= SharedASTProvider.getAST(field.getTypeRoot(), SharedASTProvider.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 (fMember.isBinary()) {
+ URL javadocBaseLocation= JavaUI.getJavadocBaseLocation(fMember);
+ if (javadocBaseLocation != null) {
+ url= javadocBaseLocation.toExternalForm();
+ }
+ } else {
+ IPackageFragmentRoot srcRoot= JavaModelUtil.getPackageFragmentRoot(fMember);
+ 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 {@inheritDoc}.
+ *
+ * @param node the node
+ * @return <code>true</code> iff the node was an {@inheritDoc} node and has been handled
+ */
+ private boolean handleInheritDoc(TagElement node) {
+ if (! TagElement.TAG_INHERITDOC.equals(node.getTagName()))
+ return false;
+ return fJavadocLookup.generated_8554163332790850784(this, node);
+ }
+
+ 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 (Iterator<TagElement> iter= tags.iterator(); iter.hasNext(); ) {
+ TagElement tag= iter.next();
+ 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 (Iterator<TagElement> iter= tags.iterator(); iter.hasNext(); ) {
+ TagElement tag= iter.next();
+ 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("<dt>"); //$NON-NLS-1$
+ fBuf.append(title);
+ fBuf.append("</dt>"); //$NON-NLS-1$
+ }
+
+ private void handleSeeTag(TagElement tag) {
+ handleLink(tag.fragments());
+ }
+
+ private void handleExceptionTags(List<TagElement> tags, List<String> exceptionNames, CharSequence[] exceptionDescriptions) {
+ if (tags.size() == 0 && containsOnlyNull(exceptionNames))
+ return;
+
+ handleBlockTagTitle(JavaDocMessages.JavaDoc2HTMLTextReader_throws_section);
+
+ for (Iterator<TagElement> iter= tags.iterator(); iter.hasNext(); ) {
+ TagElement tag= iter.next();
+ 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) {
+ if (tags.size() == 0 && containsOnlyNull(parameterNames))
+ return;
+
+ handleBlockTagTitle(JavaDocMessages.JavaDoc2HTMLTextReader_parameters_section);
+
+ for (Iterator<TagElement> iter= tags.iterator(); iter.hasNext(); ) {
+ TagElement tag= iter.next();
+ 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);
+ fBuf.append(name);
+ 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("<"); //$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(">"); //$NON-NLS-1$
+ i++;
+ }
+ }
+ }
+ }
+ }
+ }
+ fBuf.append(PARAM_NAME_END);
+
+ handleContentElements(fragments.subList(i, fragments.size()));
+ }
+ }
+
+ 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, fMember, 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 (Iterator<String> iter= parameterNames.iterator(); iter.hasNext(); ) {
+ if (iter.next() != null)
+ return false;
+ }
+ return true;
+ }
+
+ public JavadocContentAccess2 generated_4672312213436743719(IMethod method, JavadocLookup javadoclookup) {
+ javadoclookup.fContentAccesses.put(method, this);
+ return this;
+ }
+
+}