1 /*
2  * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.javac.tree;
27 
28 import java.text.BreakIterator;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.EnumSet;
33 import java.util.List;
34 import java.util.ListIterator;
35 
36 import javax.lang.model.element.Name;
37 import javax.tools.Diagnostic;
38 import javax.tools.JavaFileObject;
39 
40 import com.sun.source.doctree.AttributeTree.ValueKind;
41 import com.sun.source.doctree.DocCommentTree;
42 import com.sun.source.doctree.DocTree;
43 import com.sun.source.doctree.DocTree.Kind;
44 import com.sun.source.doctree.EndElementTree;
45 import com.sun.source.doctree.IdentifierTree;
46 import com.sun.source.doctree.ReferenceTree;
47 import com.sun.source.doctree.StartElementTree;
48 import com.sun.source.doctree.TextTree;
49 import com.sun.source.doctree.ProvidesTree;
50 import com.sun.source.doctree.UsesTree;
51 import com.sun.source.util.DocTreeFactory;
52 import com.sun.tools.doclint.HtmlTag;
53 import com.sun.tools.javac.api.JavacTrees;
54 import com.sun.tools.javac.parser.ParserFactory;
55 import com.sun.tools.javac.parser.ReferenceParser;
56 import com.sun.tools.javac.parser.Tokens.Comment;
57 import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
58 import com.sun.tools.javac.tree.DCTree.DCAttribute;
59 import com.sun.tools.javac.tree.DCTree.DCAuthor;
60 import com.sun.tools.javac.tree.DCTree.DCComment;
61 import com.sun.tools.javac.tree.DCTree.DCDeprecated;
62 import com.sun.tools.javac.tree.DCTree.DCDocComment;
63 import com.sun.tools.javac.tree.DCTree.DCDocRoot;
64 import com.sun.tools.javac.tree.DCTree.DCDocType;
65 import com.sun.tools.javac.tree.DCTree.DCEndElement;
66 import com.sun.tools.javac.tree.DCTree.DCEntity;
67 import com.sun.tools.javac.tree.DCTree.DCErroneous;
68 import com.sun.tools.javac.tree.DCTree.DCHidden;
69 import com.sun.tools.javac.tree.DCTree.DCIdentifier;
70 import com.sun.tools.javac.tree.DCTree.DCIndex;
71 import com.sun.tools.javac.tree.DCTree.DCInheritDoc;
72 import com.sun.tools.javac.tree.DCTree.DCLink;
73 import com.sun.tools.javac.tree.DCTree.DCLiteral;
74 import com.sun.tools.javac.tree.DCTree.DCParam;
75 import com.sun.tools.javac.tree.DCTree.DCProvides;
76 import com.sun.tools.javac.tree.DCTree.DCReference;
77 import com.sun.tools.javac.tree.DCTree.DCReturn;
78 import com.sun.tools.javac.tree.DCTree.DCSee;
79 import com.sun.tools.javac.tree.DCTree.DCSerial;
80 import com.sun.tools.javac.tree.DCTree.DCSerialData;
81 import com.sun.tools.javac.tree.DCTree.DCSerialField;
82 import com.sun.tools.javac.tree.DCTree.DCSince;
83 import com.sun.tools.javac.tree.DCTree.DCStartElement;
84 import com.sun.tools.javac.tree.DCTree.DCSummary;
85 import com.sun.tools.javac.tree.DCTree.DCText;
86 import com.sun.tools.javac.tree.DCTree.DCThrows;
87 import com.sun.tools.javac.tree.DCTree.DCUnknownBlockTag;
88 import com.sun.tools.javac.tree.DCTree.DCUnknownInlineTag;
89 import com.sun.tools.javac.tree.DCTree.DCUses;
90 import com.sun.tools.javac.tree.DCTree.DCValue;
91 import com.sun.tools.javac.tree.DCTree.DCVersion;
92 import com.sun.tools.javac.util.Context;
93 import com.sun.tools.javac.util.DefinedBy;
94 import com.sun.tools.javac.util.DefinedBy.Api;
95 import com.sun.tools.javac.util.DiagnosticSource;
96 import com.sun.tools.javac.util.JCDiagnostic;
97 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
98 import com.sun.tools.javac.util.ListBuffer;
99 import com.sun.tools.javac.util.Pair;
100 import com.sun.tools.javac.util.Position;
101 
102 import static com.sun.tools.doclint.HtmlTag.*;
103 
104 /**
105  *
106  *  <p><b>This is NOT part of any supported API.
107  *  If you write code that depends on this, you do so at your own risk.
108  *  This code and its internal interfaces are subject to change or
109  *  deletion without notice.</b>
110  */
111 public class DocTreeMaker implements DocTreeFactory {
112 
113     /** The context key for the tree factory. */
114     protected static final Context.Key<DocTreeMaker> treeMakerKey = new Context.Key<>();
115 
116     // A subset of block tags, which acts as sentence breakers, appearing
117     // anywhere but the zero'th position in the first sentence.
118     final EnumSet<HtmlTag> sentenceBreakTags;
119 
120     /** Get the TreeMaker instance. */
instance(Context context)121     public static DocTreeMaker instance(Context context) {
122         DocTreeMaker instance = context.get(treeMakerKey);
123         if (instance == null)
124             instance = new DocTreeMaker(context);
125         return instance;
126     }
127 
128     /** The position at which subsequent trees will be created.
129      */
130     public int pos = Position.NOPOS;
131 
132     /** Access to diag factory for ErroneousTrees. */
133     private final JCDiagnostic.Factory diags;
134 
135     private final JavacTrees trees;
136 
137     /** Utility class to parse reference signatures. */
138     private final ReferenceParser referenceParser;
139 
140     /** Create a tree maker with NOPOS as initial position.
141      */
DocTreeMaker(Context context)142     protected DocTreeMaker(Context context) {
143         context.put(treeMakerKey, this);
144         diags = JCDiagnostic.Factory.instance(context);
145         this.pos = Position.NOPOS;
146         trees = JavacTrees.instance(context);
147         referenceParser = new ReferenceParser(ParserFactory.instance(context));
148         sentenceBreakTags = EnumSet.of(H1, H2, H3, H4, H5, H6, PRE, P);
149     }
150 
151     /** Reassign current position.
152      */
153     @Override @DefinedBy(Api.COMPILER_TREE)
at(int pos)154     public DocTreeMaker at(int pos) {
155         this.pos = pos;
156         return this;
157     }
158 
159     /** Reassign current position.
160      */
at(DiagnosticPosition pos)161     public DocTreeMaker at(DiagnosticPosition pos) {
162         this.pos = (pos == null ? Position.NOPOS : pos.getStartPosition());
163         return this;
164     }
165 
166     @Override @DefinedBy(Api.COMPILER_TREE)
newAttributeTree(javax.lang.model.element.Name name, ValueKind vkind, java.util.List<? extends DocTree> value)167     public DCAttribute newAttributeTree(javax.lang.model.element.Name name, ValueKind vkind, java.util.List<? extends DocTree> value) {
168         DCAttribute tree = new DCAttribute(name, vkind, cast(value));
169         tree.pos = pos;
170         return tree;
171     }
172 
173     @Override @DefinedBy(Api.COMPILER_TREE)
newAuthorTree(java.util.List<? extends DocTree> name)174     public DCAuthor newAuthorTree(java.util.List<? extends DocTree> name) {
175         DCAuthor tree = new DCAuthor(cast(name));
176         tree.pos = pos;
177         return tree;
178     }
179 
180     @Override @DefinedBy(Api.COMPILER_TREE)
newCodeTree(TextTree text)181     public DCLiteral newCodeTree(TextTree text) {
182         DCLiteral tree = new DCLiteral(Kind.CODE, (DCText) text);
183         tree.pos = pos;
184         return tree;
185     }
186 
187     @Override @DefinedBy(Api.COMPILER_TREE)
newCommentTree(String text)188     public DCComment newCommentTree(String text) {
189         DCComment tree = new DCComment(text);
190         tree.pos = pos;
191         return tree;
192     }
193 
194     @Override @DefinedBy(Api.COMPILER_TREE)
newDeprecatedTree(List<? extends DocTree> text)195     public DCDeprecated newDeprecatedTree(List<? extends DocTree> text) {
196         DCDeprecated tree = new DCDeprecated(cast(text));
197         tree.pos = pos;
198         return tree;
199     }
200 
201     @Override @DefinedBy(Api.COMPILER_TREE)
newDocCommentTree(List<? extends DocTree> fullBody, List<? extends DocTree> tags)202     public DCDocComment newDocCommentTree(List<? extends DocTree> fullBody, List<? extends DocTree> tags) {
203         Pair<List<DCTree>, List<DCTree>> pair = splitBody(fullBody);
204         List<DCTree> preamble = Collections.emptyList();
205         List<DCTree> postamble = Collections.emptyList();
206 
207         return newDocCommentTree(fullBody, tags, preamble, postamble);
208     }
209 
newDocCommentTree(Comment comment, List<? extends DocTree> fullBody, List<? extends DocTree> tags, List<? extends DocTree> preamble, List<? extends DocTree> postamble)210     public DCDocComment newDocCommentTree(Comment comment,
211                                           List<? extends DocTree> fullBody,
212                                           List<? extends DocTree> tags,
213                                           List<? extends DocTree> preamble,
214                                           List<? extends DocTree> postamble) {
215         Pair<List<DCTree>, List<DCTree>> pair = splitBody(fullBody);
216         DCDocComment tree = new DCDocComment(comment, cast(fullBody), pair.fst, pair.snd,
217                 cast(tags), cast(preamble), cast(postamble));
218         tree.pos = pos;
219         return tree;
220     }
221 
222     /*
223      * Primarily to produce a DocCommenTree when given a
224      * first sentence and a body, this is useful, in cases
225      * where the trees are being synthesized by a tool.
226      */
227     @Override @DefinedBy(Api.COMPILER_TREE)
newDocCommentTree(List<? extends DocTree> fullBody, List<? extends DocTree> tags, List<? extends DocTree> preamble, List<? extends DocTree> postamble)228     public DCDocComment newDocCommentTree(List<? extends DocTree> fullBody,
229                                           List<? extends DocTree> tags,
230                                           List<? extends DocTree> preamble,
231                                           List<? extends DocTree> postamble) {
232         ListBuffer<DCTree> lb = new ListBuffer<>();
233         lb.addAll(cast(fullBody));
234         List<DCTree> fBody = lb.toList();
235 
236         // A dummy comment to keep the diagnostics logic happy.
237         Comment c = new Comment() {
238             @Override
239             public String getText() {
240                 return null;
241             }
242 
243             @Override
244             public int getSourcePos(int index) {
245                 return Position.NOPOS;
246             }
247 
248             @Override
249             public CommentStyle getStyle() {
250                 return CommentStyle.JAVADOC;
251             }
252 
253             @Override
254             public boolean isDeprecated() {
255                 return false;
256             }
257         };
258         Pair<List<DCTree>, List<DCTree>> pair = splitBody(fullBody);
259         DCDocComment tree = new DCDocComment(c, fBody, pair.fst, pair.snd, cast(tags),
260                                              cast(preamble), cast(postamble));
261         return tree;
262     }
263 
264     @Override @DefinedBy(Api.COMPILER_TREE)
newDocRootTree()265     public DCDocRoot newDocRootTree() {
266         DCDocRoot tree = new DCDocRoot();
267         tree.pos = pos;
268         return tree;
269     }
270 
271     @Override @DefinedBy(Api.COMPILER_TREE)
newDocTypeTree(String text)272     public DCDocType newDocTypeTree(String text) {
273         DCDocType tree = new DCDocType(text);
274         tree.pos = pos;
275         return tree;
276     }
277 
278     @Override @DefinedBy(Api.COMPILER_TREE)
newEndElementTree(Name name)279     public DCEndElement newEndElementTree(Name name) {
280         DCEndElement tree = new DCEndElement(name);
281         tree.pos = pos;
282         return tree;
283     }
284 
285     @Override @DefinedBy(Api.COMPILER_TREE)
newEntityTree(Name name)286     public DCEntity newEntityTree(Name name) {
287         DCEntity tree = new DCEntity(name);
288         tree.pos = pos;
289         return tree;
290     }
291 
292     @Override @DefinedBy(Api.COMPILER_TREE)
newErroneousTree(String text, Diagnostic<JavaFileObject> diag)293     public DCErroneous newErroneousTree(String text, Diagnostic<JavaFileObject> diag) {
294         DCErroneous tree = new DCErroneous(text, (JCDiagnostic) diag);
295         tree.pos = pos;
296         return tree;
297     }
298 
newErroneousTree(String text, DiagnosticSource diagSource, String code, Object... args)299     public DCErroneous newErroneousTree(String text, DiagnosticSource diagSource, String code, Object... args) {
300         DCErroneous tree = new DCErroneous(text, diags, diagSource, code, args);
301         tree.pos = pos;
302         return tree;
303     }
304 
305     @Override @DefinedBy(Api.COMPILER_TREE)
newExceptionTree(ReferenceTree name, List<? extends DocTree> description)306     public DCThrows newExceptionTree(ReferenceTree name, List<? extends DocTree> description) {
307         // TODO: verify the reference is just to a type (not a field or method)
308         DCThrows tree = new DCThrows(Kind.EXCEPTION, (DCReference) name, cast(description));
309         tree.pos = pos;
310         return tree;
311     }
312 
313     @Override @DefinedBy(Api.COMPILER_TREE)
newHiddenTree(List<? extends DocTree> text)314     public DCHidden newHiddenTree(List<? extends DocTree> text) {
315         DCHidden tree = new DCHidden(cast(text));
316         tree.pos = pos;
317         return tree;
318     }
319 
320     @Override @DefinedBy(Api.COMPILER_TREE)
newIdentifierTree(Name name)321     public DCIdentifier newIdentifierTree(Name name) {
322         DCIdentifier tree = new DCIdentifier(name);
323         tree.pos = pos;
324         return tree;
325     }
326 
327     @Override @DefinedBy(Api.COMPILER_TREE)
newIndexTree(DocTree term, List<? extends DocTree> description)328     public DCIndex newIndexTree(DocTree term, List<? extends DocTree> description) {
329         DCIndex tree = new DCIndex((DCTree) term, cast(description));
330         tree.pos = pos;
331         return tree;
332     }
333 
334     @Override @DefinedBy(Api.COMPILER_TREE)
newInheritDocTree()335     public DCInheritDoc newInheritDocTree() {
336         DCInheritDoc tree = new DCInheritDoc();
337         tree.pos = pos;
338         return tree;
339     }
340 
341     @Override @DefinedBy(Api.COMPILER_TREE)
newLinkTree(ReferenceTree ref, List<? extends DocTree> label)342     public DCLink newLinkTree(ReferenceTree ref, List<? extends DocTree> label) {
343         DCLink tree = new DCLink(Kind.LINK, (DCReference) ref, cast(label));
344         tree.pos = pos;
345         return tree;
346     }
347 
348     @Override @DefinedBy(Api.COMPILER_TREE)
newLinkPlainTree(ReferenceTree ref, List<? extends DocTree> label)349     public DCLink newLinkPlainTree(ReferenceTree ref, List<? extends DocTree> label) {
350         DCLink tree = new DCLink(Kind.LINK_PLAIN, (DCReference) ref, cast(label));
351         tree.pos = pos;
352         return tree;
353     }
354 
355     @Override @DefinedBy(Api.COMPILER_TREE)
newLiteralTree(TextTree text)356     public DCLiteral newLiteralTree(TextTree text) {
357         DCLiteral tree = new DCLiteral(Kind.LITERAL, (DCText) text);
358         tree.pos = pos;
359         return tree;
360     }
361 
362     @Override @DefinedBy(Api.COMPILER_TREE)
newParamTree(boolean isTypeParameter, IdentifierTree name, List<? extends DocTree> description)363     public DCParam newParamTree(boolean isTypeParameter, IdentifierTree name, List<? extends DocTree> description) {
364         DCParam tree = new DCParam(isTypeParameter, (DCIdentifier) name, cast(description));
365         tree.pos = pos;
366         return tree;
367     }
368 
369     @Override @DefinedBy(Api.COMPILER_TREE)
newProvidesTree(ReferenceTree name, List<? extends DocTree> description)370     public DCProvides newProvidesTree(ReferenceTree name, List<? extends DocTree> description) {
371         DCProvides tree = new DCProvides((DCReference) name, cast(description));
372         tree.pos = pos;
373         return tree;
374     }
375 
376     @Override @DefinedBy(Api.COMPILER_TREE)
newReferenceTree(String signature)377     public DCReference newReferenceTree(String signature) {
378         try {
379             ReferenceParser.Reference ref = referenceParser.parse(signature);
380             DCReference tree = new DCReference(signature, ref.qualExpr, ref.member, ref.paramTypes);
381             tree.pos = pos;
382             return tree;
383         } catch (ReferenceParser.ParseException e) {
384             throw new IllegalArgumentException("invalid signature", e);
385         }
386     }
387 
newReferenceTree(String signature, JCTree qualExpr, Name member, List<JCTree> paramTypes)388     public DCReference newReferenceTree(String signature, JCTree qualExpr, Name member, List<JCTree> paramTypes) {
389         DCReference tree = new DCReference(signature, qualExpr, member, paramTypes);
390         tree.pos = pos;
391         return tree;
392     }
393 
394     @Override @DefinedBy(Api.COMPILER_TREE)
newReturnTree(List<? extends DocTree> description)395     public DCReturn newReturnTree(List<? extends DocTree> description) {
396         DCReturn tree = new DCReturn(cast(description));
397         tree.pos = pos;
398         return tree;
399     }
400 
401     @Override @DefinedBy(Api.COMPILER_TREE)
newSeeTree(List<? extends DocTree> reference)402     public DCSee newSeeTree(List<? extends DocTree> reference) {
403         DCSee tree = new DCSee(cast(reference));
404         tree.pos = pos;
405         return tree;
406     }
407 
408     @Override @DefinedBy(Api.COMPILER_TREE)
newSerialTree(List<? extends DocTree> description)409     public DCSerial newSerialTree(List<? extends DocTree> description) {
410         DCSerial tree = new DCSerial(cast(description));
411         tree.pos = pos;
412         return tree;
413     }
414 
415     @Override @DefinedBy(Api.COMPILER_TREE)
newSerialDataTree(List<? extends DocTree> description)416     public DCSerialData newSerialDataTree(List<? extends DocTree> description) {
417         DCSerialData tree = new DCSerialData(cast(description));
418         tree.pos = pos;
419         return tree;
420     }
421 
422     @Override @DefinedBy(Api.COMPILER_TREE)
newSerialFieldTree(IdentifierTree name, ReferenceTree type, List<? extends DocTree> description)423     public DCSerialField newSerialFieldTree(IdentifierTree name, ReferenceTree type, List<? extends DocTree> description) {
424         DCSerialField tree = new DCSerialField((DCIdentifier) name, (DCReference) type, cast(description));
425         tree.pos = pos;
426         return tree;
427     }
428 
429     @Override @DefinedBy(Api.COMPILER_TREE)
newSinceTree(List<? extends DocTree> text)430     public DCSince newSinceTree(List<? extends DocTree> text) {
431         DCSince tree = new DCSince(cast(text));
432         tree.pos = pos;
433         return tree;
434     }
435 
436     @Override @DefinedBy(Api.COMPILER_TREE)
newStartElementTree(Name name, List<? extends DocTree> attrs, boolean selfClosing)437     public DCStartElement newStartElementTree(Name name, List<? extends DocTree> attrs, boolean selfClosing) {
438         DCStartElement tree = new DCStartElement(name, cast(attrs), selfClosing);
439         tree.pos = pos;
440         return tree;
441     }
442 
443     @Override @DefinedBy(Api.COMPILER_TREE)
newSummaryTree(List<? extends DocTree> text)444     public DCSummary newSummaryTree(List<? extends DocTree> text) {
445         DCSummary tree = new DCSummary(cast(text));
446         tree.pos = pos;
447         return tree;
448     }
449 
450     @Override @DefinedBy(Api.COMPILER_TREE)
newTextTree(String text)451     public DCText newTextTree(String text) {
452         DCText tree = new DCText(text);
453         tree.pos = pos;
454         return tree;
455     }
456 
457     @Override @DefinedBy(Api.COMPILER_TREE)
newThrowsTree(ReferenceTree name, List<? extends DocTree> description)458     public DCThrows newThrowsTree(ReferenceTree name, List<? extends DocTree> description) {
459         // TODO: verify the reference is just to a type (not a field or method)
460         DCThrows tree = new DCThrows(Kind.THROWS, (DCReference) name, cast(description));
461         tree.pos = pos;
462         return tree;
463     }
464 
465     @Override @DefinedBy(Api.COMPILER_TREE)
newUnknownBlockTagTree(Name name, List<? extends DocTree> content)466     public DCUnknownBlockTag newUnknownBlockTagTree(Name name, List<? extends DocTree> content) {
467         DCUnknownBlockTag tree = new DCUnknownBlockTag(name, cast(content));
468         tree.pos = pos;
469         return tree;
470     }
471 
472     @Override @DefinedBy(Api.COMPILER_TREE)
newUnknownInlineTagTree(Name name, List<? extends DocTree> content)473     public DCUnknownInlineTag newUnknownInlineTagTree(Name name, List<? extends DocTree> content) {
474         DCUnknownInlineTag tree = new DCUnknownInlineTag(name, cast(content));
475         tree.pos = pos;
476         return tree;
477     }
478 
479     @Override @DefinedBy(Api.COMPILER_TREE)
newUsesTree(ReferenceTree name, List<? extends DocTree> description)480     public DCUses newUsesTree(ReferenceTree name, List<? extends DocTree> description) {
481         DCUses tree = new DCUses((DCReference) name, cast(description));
482         tree.pos = pos;
483         return tree;
484     }
485 
486     @Override @DefinedBy(Api.COMPILER_TREE)
newValueTree(ReferenceTree ref)487     public DCValue newValueTree(ReferenceTree ref) {
488         // TODO: verify the reference is to a constant value
489         DCValue tree = new DCValue((DCReference) ref);
490         tree.pos = pos;
491         return tree;
492     }
493 
494     @Override @DefinedBy(Api.COMPILER_TREE)
newVersionTree(List<? extends DocTree> text)495     public DCVersion newVersionTree(List<? extends DocTree> text) {
496         DCVersion tree = new DCVersion(cast(text));
497         tree.pos = pos;
498         return tree;
499     }
500 
501     @Override @DefinedBy(Api.COMPILER_TREE)
getFirstSentence(java.util.List<? extends DocTree> list)502     public java.util.List<DocTree> getFirstSentence(java.util.List<? extends DocTree> list) {
503         Pair<List<DCTree>, List<DCTree>> pair = splitBody(list);
504         return new ArrayList<>(pair.fst);
505     }
506 
507     /*
508      * Breaks up the body tags into the first sentence and its successors.
509      * The first sentence is determined with the presence of a period,
510      * block tag, or a sentence break, as returned by the BreakIterator.
511      * Trailing whitespaces are trimmed.
512      */
splitBody(Collection<? extends DocTree> list)513     private Pair<List<DCTree>, List<DCTree>> splitBody(Collection<? extends DocTree> list) {
514         // pos is modified as we create trees, therefore
515         // we save the pos and restore it later.
516         final int savedpos = this.pos;
517         try {
518             ListBuffer<DCTree> body = new ListBuffer<>();
519             // split body into first sentence and body
520             ListBuffer<DCTree> fs = new ListBuffer<>();
521             if (list.isEmpty()) {
522                 return new Pair<>(fs.toList(), body.toList());
523             }
524             boolean foundFirstSentence = false;
525             ArrayList<DocTree> alist = new ArrayList<>(list);
526             ListIterator<DocTree> itr = alist.listIterator();
527             while (itr.hasNext()) {
528                 boolean isFirst = !itr.hasPrevious();
529                 DocTree dt = itr.next();
530                 int spos = ((DCTree) dt).pos;
531                 if (foundFirstSentence) {
532                     body.add((DCTree) dt);
533                     continue;
534                 }
535                 switch (dt.getKind()) {
536                     case SUMMARY:
537                         foundFirstSentence = true;
538                         break;
539                     case TEXT:
540                         DCText tt = (DCText) dt;
541                         String s = tt.getBody();
542                         DocTree peekedNext = itr.hasNext()
543                                 ? alist.get(itr.nextIndex())
544                                 : null;
545                         int sbreak = getSentenceBreak(s, peekedNext);
546                         if (sbreak > 0) {
547                             s = removeTrailingWhitespace(s.substring(0, sbreak));
548                             DCText text = this.at(spos).newTextTree(s);
549                             fs.add(text);
550                             foundFirstSentence = true;
551                             int nwPos = skipWhiteSpace(tt.getBody(), sbreak);
552                             if (nwPos > 0) {
553                                 DCText text2 = this.at(spos + nwPos).newTextTree(tt.getBody().substring(nwPos));
554                                 body.add(text2);
555                             }
556                             continue;
557                         } else if (itr.hasNext()) {
558                             // if the next doctree is a break, remove trailing spaces
559                             peekedNext = alist.get(itr.nextIndex());
560                             boolean sbrk = isSentenceBreak(peekedNext, false);
561                             if (sbrk) {
562                                 DocTree next = itr.next();
563                                 s = removeTrailingWhitespace(s);
564                                 DCText text = this.at(spos).newTextTree(s);
565                                 fs.add(text);
566                                 body.add((DCTree) next);
567                                 foundFirstSentence = true;
568                                 continue;
569                             }
570                         }
571                         break;
572                     default:
573                         if (isSentenceBreak(dt, isFirst)) {
574                             body.add((DCTree) dt);
575                             foundFirstSentence = true;
576                             continue;
577                         }
578                         break;
579                 }
580                 fs.add((DCTree) dt);
581             }
582             return new Pair<>(fs.toList(), body.toList());
583         } finally {
584             this.pos = savedpos;
585         }
586     }
587 
isTextTree(DocTree tree)588     private boolean isTextTree(DocTree tree) {
589         return tree.getKind() == Kind.TEXT;
590     }
591 
592     /*
593      * Computes the first sentence break, a simple dot-space algorithm.
594      */
defaultSentenceBreak(String s)595     private int defaultSentenceBreak(String s) {
596         // scan for period followed by whitespace
597         int period = -1;
598         for (int i = 0; i < s.length(); i++) {
599             switch (s.charAt(i)) {
600                 case '.':
601                     period = i;
602                     break;
603 
604                 case ' ':
605                 case '\f':
606                 case '\n':
607                 case '\r':
608                 case '\t':
609                     if (period >= 0) {
610                         return i;
611                     }
612                     break;
613 
614                 default:
615                     period = -1;
616                     break;
617             }
618         }
619         return -1;
620     }
621 
622     /*
623      * Computes the first sentence, if using a default breaker,
624      * the break is returned, if not then a -1, indicating that
625      * more doctree elements are required to be examined.
626      *
627      * BreakIterator.next points to the the start of the following sentence,
628      * and does not provide an easy way to disambiguate between "sentence break",
629      * "possible sentence break" and "not a sentence break" at the end of the input.
630      * For example, BreakIterator.next returns the index for the end
631      * of the string for all of these examples,
632      * using vertical bars to delimit the bounds of the example text
633      * |Abc|        (not a valid end of sentence break, if followed by more text)
634      * |Abc.|       (maybe a valid end of sentence break, depending on the following text)
635      * |Abc. |      (maybe a valid end of sentence break, depending on the following text)
636      * |"Abc." |    (maybe a valid end of sentence break, depending on the following text)
637      * |Abc.  |     (definitely a valid end of sentence break)
638      * |"Abc."  |   (definitely a valid end of sentence break)
639      * Therefore, we have to probe further to determine whether
640      * there really is a sentence break or not at the end of this run of text.
641      */
getSentenceBreak(String s, DocTree dt)642     private int getSentenceBreak(String s, DocTree dt) {
643         BreakIterator breakIterator = trees.getBreakIterator();
644         if (breakIterator == null) {
645             return defaultSentenceBreak(s);
646         }
647         breakIterator.setText(s);
648         final int sbrk = breakIterator.next();
649         // This is the last doctree, found the droid we are looking for
650         if (dt == null) {
651             return sbrk;
652         }
653 
654         // If the break is well within the span of the string ie. not
655         // at EOL, then we have a clear break.
656         if (sbrk < s.length() - 1) {
657             return sbrk;
658         }
659 
660         if (isTextTree(dt)) {
661             // Two adjacent text trees, a corner case, perhaps
662             // produced by a tool synthesizing a doctree. In
663             // this case, does the break lie within the first span,
664             // then we have the droid, otherwise allow the callers
665             // logic to handle the break in the adjacent doctree.
666             TextTree ttnext = (TextTree) dt;
667             String combined = s + ttnext.getBody();
668             breakIterator.setText(combined);
669             int sbrk2 = breakIterator.next();
670             if (sbrk < sbrk2) {
671                 return sbrk;
672             }
673         }
674 
675         // Is the adjacent tree a sentence breaker ?
676         if (isSentenceBreak(dt, false)) {
677             return sbrk;
678         }
679 
680         // At this point the adjacent tree is either a javadoc tag ({@..),
681         // html tag (<..) or an entity (&..). Perform a litmus test, by
682         // concatenating a sentence, to validate the break earlier identified.
683         String combined = s + "Dummy Sentence.";
684         breakIterator.setText(combined);
685         int sbrk2 = breakIterator.next();
686         if (sbrk2 <= sbrk) {
687             return sbrk2;
688         }
689         return -1; // indeterminate at this time
690     }
691 
isSentenceBreak(javax.lang.model.element.Name tagName)692     private boolean isSentenceBreak(javax.lang.model.element.Name tagName) {
693         return sentenceBreakTags.contains(get(tagName));
694     }
695 
isSentenceBreak(DocTree dt, boolean isFirstDocTree)696     private boolean isSentenceBreak(DocTree dt, boolean isFirstDocTree) {
697         switch (dt.getKind()) {
698             case START_ELEMENT:
699                     StartElementTree set = (StartElementTree)dt;
700                     return !isFirstDocTree && ((DCTree) dt).pos > 1 && isSentenceBreak(set.getName());
701             case END_ELEMENT:
702                     EndElementTree eet = (EndElementTree)dt;
703                     return !isFirstDocTree && ((DCTree) dt).pos > 1 && isSentenceBreak(eet.getName());
704             default:
705                 return false;
706         }
707     }
708 
709     /*
710      * Returns the position of the the first non-white space
711      */
skipWhiteSpace(String s, int start)712     private int skipWhiteSpace(String s, int start) {
713         for (int i = start; i < s.length(); i++) {
714             char c = s.charAt(i);
715             if (!Character.isWhitespace(c)) {
716                 return i;
717             }
718         }
719         return -1;
720     }
721 
removeTrailingWhitespace(String s)722     private String removeTrailingWhitespace(String s) {
723         for (int i = s.length() - 1 ; i >= 0 ; i--) {
724             char ch = s.charAt(i);
725             if (!Character.isWhitespace(ch)) {
726                 return s.substring(0, i + 1);
727             }
728         }
729         return s;
730     }
731 
732     @SuppressWarnings("unchecked")
cast(List<? extends DocTree> list)733     private List<DCTree> cast(List<? extends DocTree> list) {
734         return (List<DCTree>) list;
735     }
736 }
737