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