1/*
2 * Copyright (c) 1997, 2013, 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.javadoc.main;
27
28import java.util.regex.Matcher;
29import java.util.regex.Pattern;
30import com.sun.javadoc.*;
31import com.sun.tools.javac.util.ListBuffer;
32
33/**
34 * Comment contains all information in comment part.
35 *      It allows users to get first sentence of this comment, get
36 *      comment for different tags...
37 *
38 *  <p><b>This is NOT part of any supported API.
39 *  If you write code that depends on this, you do so at your own risk.
40 *  This code and its internal interfaces are subject to change or
41 *  deletion without notice.</b>
42 *
43 * @author Kaiyang Liu (original)
44 * @author Robert Field (rewrite)
45 * @author Atul M Dambalkar
46 * @author Neal Gafter (rewrite)
47 */
48@Deprecated
49class Comment {
50
51    /**
52     * sorted comments with different tags.
53     */
54    private final ListBuffer<Tag> tagList = new ListBuffer<>();
55
56    /**
57     * text minus any tags.
58     */
59    private String text;
60
61    /**
62     * Doc environment
63     */
64    private final DocEnv docenv;
65
66    /**
67     * constructor of Comment.
68     */
69    Comment(final DocImpl holder, final String commentString) {
70        this.docenv = holder.env;
71
72        /**
73         * Separate the comment into the text part and zero to N tags.
74         * Simple state machine is in one of three states:
75         * <pre>
76         * IN_TEXT: parsing the comment text or tag text.
77         * TAG_NAME: parsing the name of a tag.
78         * TAG_GAP: skipping through the gap between the tag name and
79         * the tag text.
80         * </pre>
81         */
82        @SuppressWarnings("fallthrough")
83        class CommentStringParser {
84            /**
85             * The entry point to the comment string parser
86             */
87            void parseCommentStateMachine() {
88                final int IN_TEXT = 1;
89                final int TAG_GAP = 2;
90                final int TAG_NAME = 3;
91                int state = TAG_GAP;
92                boolean newLine = true;
93                String tagName = null;
94                int tagStart = 0;
95                int textStart = 0;
96                int lastNonWhite = -1;
97                int len = commentString.length();
98                for (int inx = 0; inx < len; ++inx) {
99                    char ch = commentString.charAt(inx);
100                    boolean isWhite = Character.isWhitespace(ch);
101                    switch (state)  {
102                        case TAG_NAME:
103                            if (isWhite) {
104                                tagName = commentString.substring(tagStart, inx);
105                                state = TAG_GAP;
106                            }
107                            break;
108                        case TAG_GAP:
109                            if (isWhite) {
110                                break;
111                            }
112                            textStart = inx;
113                            state = IN_TEXT;
114                            /* fall thru */
115                        case IN_TEXT:
116                            if (newLine && ch == '@') {
117                                parseCommentComponent(tagName, textStart,
118                                                      lastNonWhite+1);
119                                tagStart = inx;
120                                state = TAG_NAME;
121                            }
122                            break;
123                    }
124                    if (ch == '\n') {
125                        newLine = true;
126                    } else if (!isWhite) {
127                        lastNonWhite = inx;
128                        newLine = false;
129                    }
130                }
131                // Finish what's currently being processed
132                switch (state)  {
133                    case TAG_NAME:
134                        tagName = commentString.substring(tagStart, len);
135                        /* fall thru */
136                    case TAG_GAP:
137                        textStart = len;
138                        /* fall thru */
139                    case IN_TEXT:
140                        parseCommentComponent(tagName, textStart, lastNonWhite+1);
141                        break;
142                }
143            }
144
145            /**
146             * Save away the last parsed item.
147             */
148            void parseCommentComponent(String tagName,
149                                       int from, int upto) {
150                String tx = upto <= from ? "" : commentString.substring(from, upto);
151                if (tagName == null) {
152                    text = tx;
153                } else {
154                    TagImpl tag;
155                    switch (tagName) {
156                        case "@exception":
157                        case "@throws":
158                            warnIfEmpty(tagName, tx);
159                            tag = new ThrowsTagImpl(holder, tagName, tx);
160                            break;
161                        case "@param":
162                            warnIfEmpty(tagName, tx);
163                            tag = new ParamTagImpl(holder, tagName, tx);
164                            break;
165                        case "@see":
166                            warnIfEmpty(tagName, tx);
167                            tag = new SeeTagImpl(holder, tagName, tx);
168                            break;
169                        case "@serialField":
170                            warnIfEmpty(tagName, tx);
171                            tag = new SerialFieldTagImpl(holder, tagName, tx);
172                            break;
173                        case "@return":
174                            warnIfEmpty(tagName, tx);
175                            tag = new TagImpl(holder, tagName, tx);
176                            break;
177                        case "@author":
178                            warnIfEmpty(tagName, tx);
179                            tag = new TagImpl(holder, tagName, tx);
180                            break;
181                        case "@version":
182                            warnIfEmpty(tagName, tx);
183                            tag = new TagImpl(holder, tagName, tx);
184                            break;
185                        default:
186                            tag = new TagImpl(holder, tagName, tx);
187                            break;
188                    }
189                    tagList.append(tag);
190                }
191            }
192
193            void warnIfEmpty(String tagName, String tx) {
194                if (tx.length() == 0) {
195                    docenv.warning(holder, "tag.tag_has_no_arguments", tagName);
196                }
197            }
198
199        }
200
201        new CommentStringParser().parseCommentStateMachine();
202    }
203
204    /**
205     * Return the text of the comment.
206     */
207    String commentText() {
208        return text;
209    }
210
211    /**
212     * Return all tags in this comment.
213     */
214    Tag[] tags() {
215        return tagList.toArray(new Tag[tagList.length()]);
216    }
217
218    /**
219     * Return tags of the specified kind in this comment.
220     */
221    Tag[] tags(String tagname) {
222        ListBuffer<Tag> found = new ListBuffer<>();
223        String target = tagname;
224        if (target.charAt(0) != '@') {
225            target = "@" + target;
226        }
227        for (Tag tag : tagList) {
228            if (tag.kind().equals(target)) {
229                found.append(tag);
230            }
231        }
232        return found.toArray(new Tag[found.length()]);
233    }
234
235    /**
236     * Return throws tags in this comment.
237     */
238    ThrowsTag[] throwsTags() {
239        ListBuffer<ThrowsTag> found = new ListBuffer<>();
240        for (Tag next : tagList) {
241            if (next instanceof ThrowsTag) {
242                found.append((ThrowsTag)next);
243            }
244        }
245        return found.toArray(new ThrowsTag[found.length()]);
246    }
247
248    /**
249     * Return param tags (excluding type param tags) in this comment.
250     */
251    ParamTag[] paramTags() {
252        return paramTags(false);
253    }
254
255    /**
256     * Return type param tags in this comment.
257     */
258    ParamTag[] typeParamTags() {
259        return paramTags(true);
260    }
261
262    /**
263     * Return param tags in this comment.  If typeParams is true
264     * include only type param tags, otherwise include only ordinary
265     * param tags.
266     */
267    private ParamTag[] paramTags(boolean typeParams) {
268        ListBuffer<ParamTag> found = new ListBuffer<>();
269        for (Tag next : tagList) {
270            if (next instanceof ParamTag) {
271                ParamTag p = (ParamTag)next;
272                if (typeParams == p.isTypeParameter()) {
273                    found.append(p);
274                }
275            }
276        }
277        return found.toArray(new ParamTag[found.length()]);
278    }
279
280    /**
281     * Return see also tags in this comment.
282     */
283    SeeTag[] seeTags() {
284        ListBuffer<SeeTag> found = new ListBuffer<>();
285        for (Tag next : tagList) {
286            if (next instanceof SeeTag) {
287                found.append((SeeTag)next);
288            }
289        }
290        return found.toArray(new SeeTag[found.length()]);
291    }
292
293    /**
294     * Return serialField tags in this comment.
295     */
296    SerialFieldTag[] serialFieldTags() {
297        ListBuffer<SerialFieldTag> found = new ListBuffer<>();
298        for (Tag next : tagList) {
299            if (next instanceof SerialFieldTag) {
300                found.append((SerialFieldTag)next);
301            }
302        }
303        return found.toArray(new SerialFieldTag[found.length()]);
304    }
305
306    /**
307     * Return array of tags with text and inline See Tags for a Doc comment.
308     */
309    static Tag[] getInlineTags(DocImpl holder, String inlinetext) {
310        ListBuffer<Tag> taglist = new ListBuffer<>();
311        int delimend = 0, textstart = 0, len = inlinetext.length();
312        boolean inPre = false;
313        DocEnv docenv = holder.env;
314
315        if (len == 0) {
316            return taglist.toArray(new Tag[taglist.length()]);
317        }
318        while (true) {
319            int linkstart;
320            if ((linkstart = inlineTagFound(holder, inlinetext,
321                                            textstart)) == -1) {
322                taglist.append(new TagImpl(holder, "Text",
323                                           inlinetext.substring(textstart)));
324                break;
325            } else {
326                inPre = scanForPre(inlinetext, textstart, linkstart, inPre);
327                int seetextstart = linkstart;
328                for (int i = linkstart; i < inlinetext.length(); i++) {
329                    char c = inlinetext.charAt(i);
330                    if (Character.isWhitespace(c) ||
331                        c == '}') {
332                        seetextstart = i;
333                        break;
334                     }
335                }
336                String linkName = inlinetext.substring(linkstart+2, seetextstart);
337                if (!(inPre && (linkName.equals("code") || linkName.equals("literal")))) {
338                    //Move past the white space after the inline tag name.
339                    while (Character.isWhitespace(inlinetext.
340                                                      charAt(seetextstart))) {
341                        if (inlinetext.length() <= seetextstart) {
342                            taglist.append(new TagImpl(holder, "Text",
343                                                       inlinetext.substring(textstart, seetextstart)));
344                            docenv.warning(holder,
345                                           "tag.Improper_Use_Of_Link_Tag",
346                                           inlinetext);
347                            return taglist.toArray(new Tag[taglist.length()]);
348                        } else {
349                            seetextstart++;
350                        }
351                    }
352                }
353                taglist.append(new TagImpl(holder, "Text",
354                                           inlinetext.substring(textstart, linkstart)));
355                textstart = seetextstart;   // this text is actually seetag
356                if ((delimend = findInlineTagDelim(inlinetext, textstart)) == -1) {
357                    //Missing closing '}' character.
358                    // store the text as it is with the {@link.
359                    taglist.append(new TagImpl(holder, "Text",
360                                               inlinetext.substring(textstart)));
361                    docenv.warning(holder,
362                                   "tag.End_delimiter_missing_for_possible_SeeTag",
363                                   inlinetext);
364                    return taglist.toArray(new Tag[taglist.length()]);
365                } else {
366                    //Found closing '}' character.
367                    if (linkName.equals("see")
368                           || linkName.equals("link")
369                           || linkName.equals("linkplain")) {
370                        taglist.append( new SeeTagImpl(holder, "@" + linkName,
371                              inlinetext.substring(textstart, delimend)));
372                    } else {
373                        taglist.append( new TagImpl(holder, "@" + linkName,
374                              inlinetext.substring(textstart, delimend)));
375                    }
376                    textstart = delimend + 1;
377                }
378            }
379            if (textstart == inlinetext.length()) {
380                break;
381            }
382        }
383        return taglist.toArray(new Tag[taglist.length()]);
384    }
385
386    /** regex for case-insensitive match for {@literal <pre> } and  {@literal </pre> }. */
387    private static final Pattern prePat = Pattern.compile("(?i)<(/?)pre>");
388
389    private static boolean scanForPre(String inlinetext, int start, int end, boolean inPre) {
390        Matcher m = prePat.matcher(inlinetext).region(start, end);
391        while (m.find()) {
392            inPre = m.group(1).isEmpty();
393        }
394        return inPre;
395    }
396
397    /**
398     * Recursively find the index of the closing '}' character for an inline tag
399     * and return it.  If it can't be found, return -1.
400     * @param inlineText the text to search in.
401     * @param searchStart the index of the place to start searching at.
402     * @return the index of the closing '}' character for an inline tag.
403     * If it can't be found, return -1.
404     */
405    private static int findInlineTagDelim(String inlineText, int searchStart) {
406        int delimEnd, nestedOpenBrace;
407        if ((delimEnd = inlineText.indexOf("}", searchStart)) == -1) {
408            return -1;
409        } else if (((nestedOpenBrace = inlineText.indexOf("{", searchStart)) != -1) &&
410            nestedOpenBrace < delimEnd){
411            //Found a nested open brace.
412            int nestedCloseBrace = findInlineTagDelim(inlineText, nestedOpenBrace + 1);
413            return (nestedCloseBrace != -1) ?
414                findInlineTagDelim(inlineText, nestedCloseBrace + 1) :
415                -1;
416        } else {
417            return delimEnd;
418        }
419    }
420
421    /**
422     * Recursively search for the characters '{', '@', followed by
423     * name of inline tag and white space,
424     * if found
425     *    return the index of the text following the white space.
426     * else
427     *    return -1.
428     */
429    private static int inlineTagFound(DocImpl holder, String inlinetext, int start) {
430        DocEnv docenv = holder.env;
431        int linkstart = inlinetext.indexOf("{@", start);
432        if (start == inlinetext.length() || linkstart == -1) {
433            return -1;
434        } else if (inlinetext.indexOf('}', linkstart) == -1) {
435            //Missing '}'.
436            docenv.warning(holder, "tag.Improper_Use_Of_Link_Tag",
437                    inlinetext.substring(linkstart, inlinetext.length()));
438            return -1;
439        } else {
440            return linkstart;
441        }
442    }
443
444
445    /**
446     * Return array of tags for the locale specific first sentence in the text.
447     */
448    static Tag[] firstSentenceTags(DocImpl holder, String text) {
449        DocLocale doclocale = holder.env.doclocale;
450        return getInlineTags(holder,
451                             doclocale.localeSpecificFirstSentence(holder, text));
452    }
453
454    /**
455     * Return text for this Doc comment.
456     */
457    @Override
458    public String toString() {
459        return text;
460    }
461}
462