]> git.uio.no Git - ifi-stolz-refaktor.git/blobdiff - case-study/jdt-after/ui/org/eclipse/jdt/internal/ui/text/javadoc/JavadocContentAccess2.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-after / ui / org / eclipse / jdt / internal / ui / text / javadoc / JavadocContentAccess2.java
diff --git a/case-study/jdt-after/ui/org/eclipse/jdt/internal/ui/text/javadoc/JavadocContentAccess2.java b/case-study/jdt-after/ui/org/eclipse/jdt/internal/ui/text/javadoc/JavadocContentAccess2.java
new file mode 100644 (file)
index 0000000..b158794
--- /dev/null
@@ -0,0 +1,1612 @@
+/*******************************************************************************
+ * 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>{&#64;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>{&#64;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>{&#64;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>{&#64;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= "&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);
+
+               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 {&#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;
+               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("&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 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;
+       }
+
+}