8008174: DocTree API should provide start and end positions for tree nodes
authorjlahoda
Wed, 17 Apr 2013 15:54:24 +0200
changeset 17273 d7ec6654aac9
parent 16977 aa655625a6b9
child 17274 64fdf967f6a5
8008174: DocTree API should provide start and end positions for tree nodes Summary: Adding DocSourcePositions to allow access to DocTree starting/ending position Reviewed-by: jjg, darcy Contributed-by: Ralph Benjamin Ruijs <ralphbenjamin@netbeans.org>, Jan Lahoda <jlahoda@netbeans.org>
langtools/src/share/classes/com/sun/source/util/DocSourcePositions.java
langtools/src/share/classes/com/sun/source/util/DocTrees.java
langtools/src/share/classes/com/sun/source/util/SourcePositions.java
langtools/src/share/classes/com/sun/tools/javac/api/JavacTrees.java
langtools/src/share/classes/com/sun/tools/javac/parser/DocCommentParser.java
langtools/src/share/classes/com/sun/tools/javac/tree/DCTree.java
langtools/test/tools/javac/doctree/positions/TestPosition.java
langtools/test/tools/javac/doctree/positions/TestPosition.out
langtools/test/tools/javac/doctree/positions/TestPositionSource.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/source/util/DocSourcePositions.java	Wed Apr 17 15:54:24 2013 +0200
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.source.util;
+
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.tree.CompilationUnitTree;
+
+/**
+ * Provides methods to obtain the position of a DocTree within a javadoc comment.
+ * A position is defined as a simple character offset from the start of a
+ * CompilationUnit where the first character is at offset 0.
+ *
+ * @since 1.8
+ */
+@jdk.Supported
+public interface DocSourcePositions extends SourcePositions {
+
+    /**
+     * Gets the starting position of the tree within the comment within the file.  If tree is not found within
+     * file, or if the starting position is not available,
+     * return {@link javax.tools.Diagnostic#NOPOS}.
+     * The given tree should be under the given comment tree, and the given documentation
+     * comment tree should be returned from a {@link DocTrees#getDocCommentTree(com.sun.source.util.TreePath) }
+     * for a tree under the given file.
+     * The returned position must be at the start of the yield of this tree, that
+     * is for any sub-tree of this tree, the following must hold:
+     *
+     * <p>
+     * {@code tree.getStartPosition() <= subtree.getStartPosition()} or <br>
+     * {@code tree.getStartPosition() == NOPOS} or <br>
+     * {@code subtree.getStartPosition() == NOPOS}
+     * </p>
+     *
+     * @param file CompilationUnit in which to find tree.
+     * @param comment the comment tree that encloses the tree for which the
+     *                position is being sought
+     * @param tree tree for which a position is sought.
+     * @return the start position of tree.
+     */
+    long getStartPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree);
+
+    /**
+     * Gets the ending position of the tree within the comment within the file.  If tree is not found within
+     * file, or if the ending position is not available,
+     * return {@link javax.tools.Diagnostic#NOPOS}.
+     * The given tree should be under the given comment tree, and the given documentation
+     * comment tree should be returned from a {@link DocTrees#getDocCommentTree(com.sun.source.util.TreePath) }
+     * for a tree under the given file.
+     * The returned position must be at the end of the yield of this tree,
+     * that is for any sub-tree of this tree, the following must hold:
+     *
+     * <p>
+     * {@code tree.getEndPosition() >= subtree.getEndPosition()} or <br>
+     * {@code tree.getEndPosition() == NOPOS} or <br>
+     * {@code subtree.getEndPosition() == NOPOS}
+     * </p>
+     *
+     * In addition, the following must hold:
+     *
+     * <p>
+     * {@code tree.getStartPosition() <= tree.getEndPosition()}  or <br>
+     * {@code tree.getStartPosition() == NOPOS} or <br>
+     * {@code tree.getEndPosition() == NOPOS}
+     * </p>
+     *
+     * @param file CompilationUnit in which to find tree.
+     * @param comment the comment tree that encloses the tree for which the
+     *                position is being sought
+     * @param tree tree for which a position is sought.
+     * @return the start position of tree.
+     */
+    long getEndPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree);
+
+}
--- a/langtools/src/share/classes/com/sun/source/util/DocTrees.java	Wed Apr 17 10:31:01 2013 -0700
+++ b/langtools/src/share/classes/com/sun/source/util/DocTrees.java	Wed Apr 17 15:54:24 2013 +0200
@@ -72,6 +72,8 @@
      */
     public abstract Element getElement(TreePath path, ReferenceTree reference);
 
+    public abstract DocSourcePositions getSourcePositions();
+
     /**
      * Prints a message of the specified kind at the location of the
      * tree within the provided compilation unit
--- a/langtools/src/share/classes/com/sun/source/util/SourcePositions.java	Wed Apr 17 10:31:01 2013 -0700
+++ b/langtools/src/share/classes/com/sun/source/util/SourcePositions.java	Wed Apr 17 15:54:24 2013 +0200
@@ -59,7 +59,7 @@
 
     /**
      * Gets the ending position of tree within file.  If tree is not found within
-     * file, or if the starting position is not available,
+     * file, or if the ending position is not available,
      * return {@link javax.tools.Diagnostic#NOPOS}.
      * The returned position must be at the end of the yield of this tree,
      * that is for any sub-tree of this tree, the following must hold:
--- a/langtools/src/share/classes/com/sun/tools/javac/api/JavacTrees.java	Wed Apr 17 10:31:01 2013 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/api/JavacTrees.java	Wed Apr 17 15:54:24 2013 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -43,14 +43,16 @@
 import javax.tools.JavaFileObject;
 
 import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
 import com.sun.source.doctree.ReferenceTree;
 import com.sun.source.tree.CatchTree;
 import com.sun.source.tree.CompilationUnitTree;
 import com.sun.source.tree.Scope;
 import com.sun.source.tree.Tree;
+import com.sun.source.util.DocSourcePositions;
+import com.sun.source.util.DocTreeScanner;
 import com.sun.source.util.DocTrees;
 import com.sun.source.util.JavacTask;
-import com.sun.source.util.SourcePositions;
 import com.sun.source.util.TreePath;
 import com.sun.tools.javac.code.Flags;
 import com.sun.tools.javac.code.Kinds;
@@ -76,8 +78,14 @@
 import com.sun.tools.javac.model.JavacElements;
 import com.sun.tools.javac.processing.JavacProcessingEnvironment;
 import com.sun.tools.javac.tree.DCTree;
+import com.sun.tools.javac.tree.DCTree.DCBlockTag;
 import com.sun.tools.javac.tree.DCTree.DCDocComment;
+import com.sun.tools.javac.tree.DCTree.DCEndPosTree;
+import com.sun.tools.javac.tree.DCTree.DCErroneous;
+import com.sun.tools.javac.tree.DCTree.DCIdentifier;
+import com.sun.tools.javac.tree.DCTree.DCParam;
 import com.sun.tools.javac.tree.DCTree.DCReference;
+import com.sun.tools.javac.tree.DCTree.DCText;
 import com.sun.tools.javac.tree.EndPosTable;
 import com.sun.tools.javac.tree.JCTree;
 import com.sun.tools.javac.tree.JCTree.*;
@@ -94,6 +102,7 @@
 import com.sun.tools.javac.util.Name;
 import com.sun.tools.javac.util.Names;
 import com.sun.tools.javac.util.Pair;
+import com.sun.tools.javac.util.Position;
 import static com.sun.tools.javac.code.TypeTag.*;
 
 /**
@@ -166,8 +175,8 @@
             javacTaskImpl = (JavacTaskImpl) t;
     }
 
-    public SourcePositions getSourcePositions() {
-        return new SourcePositions() {
+    public DocSourcePositions getSourcePositions() {
+        return new DocSourcePositions() {
                 public long getStartPosition(CompilationUnitTree file, Tree tree) {
                     return TreeInfo.getStartPos((JCTree) tree);
                 }
@@ -176,9 +185,80 @@
                     EndPosTable endPosTable = ((JCCompilationUnit) file).endPositions;
                     return TreeInfo.getEndPos((JCTree) tree, endPosTable);
                 }
+
+                public long getStartPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) {
+                    return ((DCTree) tree).getSourcePosition((DCDocComment) comment);
+                }
+                @SuppressWarnings("fallthrough")
+                public long getEndPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) {
+                    DCDocComment dcComment = (DCDocComment) comment;
+                    if (tree instanceof DCEndPosTree) {
+                        int endPos = ((DCEndPosTree) tree).getEndPos(dcComment);
+
+                        if (endPos != Position.NOPOS) {
+                            return endPos;
+                        }
+                    }
+                    int correction = 0;
+                    switch (tree.getKind()) {
+                        case TEXT:
+                            DCText text = (DCText) tree;
+
+                            return dcComment.comment.getSourcePos(text.pos + text.text.length());
+                        case ERRONEOUS:
+                            DCErroneous err = (DCErroneous) tree;
+
+                            return dcComment.comment.getSourcePos(err.pos + err.body.length());
+                        case IDENTIFIER:
+                            DCIdentifier ident = (DCIdentifier) tree;
+
+                            return dcComment.comment.getSourcePos(ident.pos + (ident.name != names.error ? ident.name.length() : 0));
+                        case PARAM:
+                            DCParam param = (DCParam) tree;
+
+                            if (param.isTypeParameter && param.getDescription().isEmpty()) {
+                                correction = 1;
+                            }
+                        case AUTHOR: case DEPRECATED: case RETURN: case SEE:
+                        case SERIAL: case SERIAL_DATA: case SERIAL_FIELD: case SINCE:
+                        case THROWS: case UNKNOWN_BLOCK_TAG: case VERSION: {
+                            DocTree last = getLastChild(tree);
+
+                            if (last != null) {
+                                return getEndPosition(file, comment, last) + correction;
+                            }
+
+                            DCBlockTag block = (DCBlockTag) tree;
+
+                            return dcComment.comment.getSourcePos(block.pos + block.getTagName().length() + 1);
+                        }
+                        default:
+                            DocTree last = getLastChild(tree);
+
+                            if (last != null) {
+                                return getEndPosition(file, comment, last);
+                            }
+                            break;
+                    }
+
+                    return Position.NOPOS;
+                }
             };
     }
 
+    private DocTree getLastChild(DocTree tree) {
+        final DocTree[] last = new DocTree[] {null};
+
+        tree.accept(new DocTreeScanner<Void, Void>() {
+            @Override public Void scan(DocTree node, Void p) {
+                if (node != null) last[0] = node;
+                return null;
+            }
+        }, null);
+
+        return last[0];
+    }
+
     public JCClassDecl getTree(TypeElement element) {
         return (JCClassDecl) getTree((Element) element);
     }
--- a/langtools/src/share/classes/com/sun/tools/javac/parser/DocCommentParser.java	Wed Apr 17 10:31:01 2013 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/parser/DocCommentParser.java	Wed Apr 17 15:54:24 2013 +0200
@@ -41,6 +41,7 @@
 import com.sun.tools.javac.tree.DCTree.DCAttribute;
 import com.sun.tools.javac.tree.DCTree.DCDocComment;
 import com.sun.tools.javac.tree.DCTree.DCEndElement;
+import com.sun.tools.javac.tree.DCTree.DCEndPosTree;
 import com.sun.tools.javac.tree.DCTree.DCErroneous;
 import com.sun.tools.javac.tree.DCTree.DCIdentifier;
 import com.sun.tools.javac.tree.DCTree.DCReference;
@@ -336,12 +337,12 @@
                     DCTree text = inlineText();
                     if (text != null) {
                         nextChar();
-                        return m.at(p).UnknownInlineTag(name, List.of(text));
+                        return m.at(p).UnknownInlineTag(name, List.of(text)).setEndPos(bp);
                     }
                 } else if (tp.getKind() == TagParser.Kind.INLINE) {
-                    DCTree tree =  tp.parse(p);
+                    DCEndPosTree<?> tree = (DCEndPosTree<?>) tp.parse(p);
                     if (tree != null) {
-                        return tree;
+                        return tree.setEndPos(bp);
                     }
                 } else {
                     inlineText(); // skip content
@@ -509,7 +510,7 @@
             fac.log.popDiagnosticHandler(deferredDiagnosticHandler);
         }
 
-        return m.at(pos).Reference(sig, qualExpr, member, paramTypes);
+        return m.at(pos).Reference(sig, qualExpr, member, paramTypes).setEndPos(bp);
     }
 
     JCTree parseType(String s) throws ParseException {
@@ -741,7 +742,7 @@
                 }
                 if (ch == '>') {
                     nextChar();
-                    return m.at(p).StartElement(name, attrs, selfClosing);
+                    return m.at(p).StartElement(name, attrs, selfClosing).setEndPos(bp);
                 }
             }
         } else if (ch == '/') {
--- a/langtools/src/share/classes/com/sun/tools/javac/tree/DCTree.java	Wed Apr 17 10:31:01 2013 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/tree/DCTree.java	Wed Apr 17 15:54:24 2013 +0200
@@ -36,6 +36,7 @@
 import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
 import com.sun.tools.javac.util.List;
 import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.Position;
 import java.io.IOException;
 import java.io.StringWriter;
 import javax.tools.JavaFileObject;
@@ -82,8 +83,24 @@
         return s.toString();
     }
 
+    public static abstract class DCEndPosTree<T extends DCEndPosTree<T>> extends DCTree {
+
+        private int endPos = Position.NOPOS;
+
+        public int getEndPos(DCDocComment dc) {
+            return dc.comment.getSourcePos(endPos);
+        }
+
+        @SuppressWarnings("unchecked")
+        public T setEndPos(int endPos) {
+            this.endPos = endPos;
+            return (T) this;
+        }
+
+    }
+
     public static class DCDocComment extends DCTree implements DocCommentTree {
-        final Comment comment; // required for the implicit source pos table
+        public final Comment comment; // required for the implicit source pos table
 
         public final List<DCTree> firstSentence;
         public final List<DCTree> body;
@@ -125,7 +142,7 @@
         }
     }
 
-    public static abstract class DCInlineTag extends DCTree implements InlineTagTree {
+    public static abstract class DCInlineTag extends DCEndPosTree<DCInlineTag> implements InlineTagTree {
         public String getTagName() {
             return getKind().tagName;
         }
@@ -345,6 +362,7 @@
         public int getEndPosition(EndPosTable endPosTable) {
             return pos + body.length();
         }
+
     }
 
     public static class DCIdentifier extends DCTree implements IdentifierTree {
@@ -478,7 +496,7 @@
         }
     }
 
-    public static class DCReference extends DCTree implements ReferenceTree {
+    public static class DCReference extends DCEndPosTree<DCReference> implements ReferenceTree {
         public final String signature;
 
         // The following are not directly exposed through ReferenceTree
@@ -663,7 +681,7 @@
         }
     }
 
-    public static class DCStartElement extends DCTree implements StartElementTree {
+    public static class DCStartElement extends DCEndPosTree<DCStartElement> implements StartElementTree {
         public final Name name;
         public final List<DCTree> attrs;
         public final boolean selfClosing;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/doctree/positions/TestPosition.java	Wed Apr 17 15:54:24 2013 +0200
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8008174
+ * @summary proper source positions for doc comments
+ * @build TestPosition
+ * @compile/ref=TestPosition.out -processor TestPosition -proc:only TestPositionSource.java
+ */
+
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.DocSourcePositions;
+import com.sun.source.util.DocTreeScanner;
+import com.sun.source.util.DocTrees;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import java.io.IOException;
+import java.util.Set;
+import javax.annotation.processing.*;
+import javax.lang.model.*;
+import javax.lang.model.element.*;
+
+@SupportedAnnotationTypes("*")
+public class TestPosition extends AbstractProcessor {
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        TypeElement source = processingEnv.getElementUtils().getTypeElement("TestPositionSource");
+
+        if (source == null) throw new IllegalStateException();
+
+        if (!roundEnv.getRootElements().contains(source)) return false;
+
+        final DocTrees trees = DocTrees.instance(processingEnv);
+        final TreePath testElement = trees.getPath(source);
+
+        if (testElement == null) throw new IllegalStateException();
+
+        String code;
+
+        try {
+            code = testElement.getCompilationUnit().getSourceFile().getCharContent(false).toString();
+        } catch ( IOException ex) {
+            throw new IllegalStateException(ex);
+        }
+
+        new TreePathScanner<Void, Void>() {
+            @Override public Void visitMethod(MethodTree node, Void p) {
+                final DocCommentTree docCommentTree = trees.getDocCommentTree(getCurrentPath());
+
+                if (docCommentTree != null) {
+                    System.out.println(node.getName() + ":");
+                    new DocTreeScanner<Void, Void>() {
+                        @Override public Void scan(DocTree node, Void p) {
+                            if (node != null) {
+                                DocSourcePositions sp = (DocSourcePositions) trees.getSourcePositions(); //XXX: the cast???
+                                int start = (int) sp.getStartPosition(testElement.getCompilationUnit(), docCommentTree, node);
+                                int end   = (int) sp.getEndPosition(testElement.getCompilationUnit(), docCommentTree, node);
+                                String snippet = code.substring(start, end).replace(" \n", "!trailing-whitespace!\n");
+
+                                if (snippet.endsWith(" ")) {
+                                    snippet = snippet.substring(0, snippet.length() - 1) + "!trailing-whitespace!";
+                                }
+                                System.out.println(node.getKind().name() + ":" + snippet);
+                            }
+                            return super.scan(node, p);
+                        }
+                    }.scan(docCommentTree, null);
+                }
+
+                return super.visitMethod(node, p);
+            }
+        }.scan(testElement, null);
+
+        return false;
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.latest();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/doctree/positions/TestPosition.out	Wed Apr 17 15:54:24 2013 +0200
@@ -0,0 +1,99 @@
+valid:
+DOC_COMMENT:First sentence.
+     *
+     * <p>Description with {@link java.io.InputStream link}
+     *
+     * @param first description
+     * @param second description
+     * @return whatever
+     * @throws IllegalStateException why?
+     * @since 1.15
+     * @see java.util.List
+TEXT:First sentence.
+START_ELEMENT:<p>
+TEXT:Description with!trailing-whitespace!
+LINK:{@link java.io.InputStream link}
+REFERENCE:java.io.InputStream
+TEXT:link
+PARAM:@param first description
+IDENTIFIER:first
+TEXT:description
+PARAM:@param second description
+IDENTIFIER:second
+TEXT:description
+RETURN:@return whatever
+TEXT:whatever
+THROWS:@throws IllegalStateException why?
+REFERENCE:IllegalStateException
+TEXT:why?
+SINCE:@since 1.15
+TEXT:1.15
+SEE:@see java.util.List
+REFERENCE:java.util.List
+erroneous:
+DOC_COMMENT:First sentence.
+     *
+     * <p>Description with {@link}, {@link java.util.List}, {@link
+     *
+     * @param
+     * @param second
+     * @return
+     * @throws
+     * @throws IllegalStateException
+     * @since
+     * @see
+TEXT:First sentence.
+START_ELEMENT:<p>
+TEXT:Description with!trailing-whitespace!
+LINK:{@link}
+TEXT:,!trailing-whitespace!
+LINK:{@link java.util.List}
+REFERENCE:java.util.List
+TEXT:,!trailing-whitespace!
+ERRONEOUS:{@link
+ERRONEOUS:@param
+PARAM:@param second
+IDENTIFIER:second
+RETURN:@return
+ERRONEOUS:@throws
+THROWS:@throws IllegalStateException
+REFERENCE:IllegalStateException
+SINCE:@since
+ERRONEOUS:@see
+withWhiteSpaces:
+DOC_COMMENT:First sentence.
+     *
+     * <p>Description with {@link    }, {@link java.util.List#add(   int   )},
+     * {@link java.util.List#add(   int   ) some   text   with   whitespaces}, {@link
+     *
+     * @param     first
+     * @param     second   some   text   with trailing whitespace
+     * @return      some   return
+     * @throws      java.lang.IllegalStateException
+     * @throws   java.lang.IllegalStateException some     text
+TEXT:First sentence.
+START_ELEMENT:<p>
+TEXT:Description with!trailing-whitespace!
+LINK:{@link    }
+TEXT:,!trailing-whitespace!
+LINK:{@link java.util.List#add(   int   )}
+REFERENCE:java.util.List#add(   int   )
+TEXT:,
+     *!trailing-whitespace!
+LINK:{@link java.util.List#add(   int   ) some   text   with   whitespaces}
+REFERENCE:java.util.List#add(   int   )
+TEXT:some   text   with   whitespaces
+TEXT:,!trailing-whitespace!
+ERRONEOUS:{@link
+PARAM:@param     first
+IDENTIFIER:first
+PARAM:@param     second   some   text   with trailing whitespace
+IDENTIFIER:second
+TEXT:some   text   with trailing whitespace
+RETURN:@return      some   return
+TEXT:some   return
+THROWS:@throws      java.lang.IllegalStateException
+REFERENCE:java.lang.IllegalStateException
+THROWS:@throws   java.lang.IllegalStateException some     text
+REFERENCE:java.lang.IllegalStateException
+TEXT:some     text
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/doctree/positions/TestPositionSource.java	Wed Apr 17 15:54:24 2013 +0200
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+public class TestPositionSource {
+
+    /**First sentence.
+     *
+     * <p>Description with {@link java.io.InputStream link}
+     *
+     * @param first description
+     * @param second description
+     * @return whatever
+     * @throws IllegalStateException why?
+     * @since 1.15
+     * @see java.util.List
+     */
+    public boolean valid(int first, int second) throws IllegalStateException {
+        return true;
+    }
+
+    /**First sentence.
+     *
+     * <p>Description with {@link}, {@link java.util.List}, {@link
+     *
+     * @param
+     * @param second
+     * @return
+     * @throws
+     * @throws IllegalStateException
+     * @since
+     * @see
+     */
+    public boolean erroneous(int first, int second) throws IllegalStateException {
+        return true;
+    }
+
+    /**First sentence.
+     *
+     * <p>Description with {@link    }, {@link java.util.List#add(   int   )},
+     * {@link java.util.List#add(   int   ) some   text   with   whitespaces}, {@link
+     *
+     * @param     first
+     * @param     second   some   text   with trailing whitespace
+     * @return      some   return
+     * @throws      java.lang.IllegalStateException
+     * @throws   java.lang.IllegalStateException some     text
+     */
+    public boolean withWhiteSpaces(int first, int second) throws IllegalStateException {
+        return true;
+    }
+
+}