• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.doclava;
18 
19 import java.util.regex.Pattern;
20 import java.util.regex.Matcher;
21 import java.util.ArrayList;
22 
23 public class Comment {
24   static final Pattern FIRST_SENTENCE =
25       Pattern.compile("((.*?)\\.)[ \t\r\n\\<](.*)", Pattern.DOTALL);
26 
27   private static final String[] KNOWN_TAGS = new String[] {
28           "@author",
29           "@since",
30           "@version",
31           "@deprecated",
32           "@undeprecate",
33           "@docRoot",
34           "@sdkCurrent",
35           "@inheritDoc",
36           "@more",
37           "@samplecode",
38           "@sample",
39           "@include",
40           "@serial",
41       };
42 
Comment(String text, ContainerInfo base, SourcePositionInfo sp)43   public Comment(String text, ContainerInfo base, SourcePositionInfo sp) {
44     mText = text;
45     mBase = base;
46     // sp now points to the end of the text, not the beginning!
47     mPosition = SourcePositionInfo.findBeginning(sp, text);
48   }
49 
parseCommentTags(String text)50   private void parseCommentTags(String text) {
51       int i = 0;
52       int length = text.length();
53       while (i < length  && isWhitespaceChar(text.charAt(i++))) {}
54 
55       if (i <=  0) {
56           return;
57       }
58 
59       text = text.substring(i-1);
60       length = text.length();
61 
62       if ("".equals(text)) {
63           return;
64       }
65 
66       int start = 0;
67       int end = findStartOfBlock(text, start);
68 
69 
70       // possible scenarios
71       //    main and block(s)
72       //    main only (end == -1)
73       //    block(s) only (end == 0)
74 
75       switch (end) {
76           case -1: // main only
77               parseMainDescription(text, start, length);
78               return;
79           case 0: // block(s) only
80               break;
81           default: // main and block
82 
83               // find end of main because end is really the beginning of @
84               parseMainDescription(text, start, findEndOfMainOrBlock(text, start, end));
85               break;
86       }
87 
88       // parse blocks
89       for (start = end; start < length; start = end) {
90           end = findStartOfBlock(text, start+1);
91 
92           if (end == -1) {
93               parseBlock(text, start, length);
94               break;
95           } else {
96               parseBlock(text, start, findEndOfMainOrBlock(text, start, end));
97           }
98       }
99 
100       // for each block
101       //    make block parts
102       //        end is either next @ at beginning of line or end of text
103   }
104 
findEndOfMainOrBlock(String text, int start, int end)105   private int findEndOfMainOrBlock(String text, int start, int end) {
106       for (int i = end-1; i >= start; i--) {
107           if (!isWhitespaceChar(text.charAt(i))) {
108               end = i+1;
109               break;
110           }
111       }
112       return end;
113   }
114 
parseMainDescription(String mainDescription, int start, int end)115   private void parseMainDescription(String mainDescription, int start, int end) {
116       if (mainDescription == null) {
117           return;
118       }
119 
120       SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, 0);
121       while (start < end) {
122           int startOfInlineTag = findStartIndexOfInlineTag(mainDescription, start, end);
123 
124           // if there are no more tags
125           if (startOfInlineTag == -1) {
126               tag(null, mainDescription.substring(start, end), true, pos);
127               return;
128           }
129 
130           //int endOfInlineTag = mainDescription.indexOf('}', startOfInlineTag);
131           int endOfInlineTag = findEndIndexOfInlineTag(mainDescription, startOfInlineTag, end);
132 
133           // if there was only beginning tag
134           if (endOfInlineTag == -1) {
135               // parse all of main as one tag
136               tag(null, mainDescription.substring(start, end), true, pos);
137               return;
138           }
139 
140           endOfInlineTag++; // add one to make it a proper ending index
141 
142           // do first part without an inline tag - ie, just plaintext
143           tag(null, mainDescription.substring(start, startOfInlineTag), true, pos);
144 
145           // parse the rest of this section, the inline tag
146           parseInlineTag(mainDescription, startOfInlineTag, endOfInlineTag, pos);
147 
148           // keep going
149           start = endOfInlineTag;
150       }
151   }
152 
findStartIndexOfInlineTag(String text, int fromIndex, int toIndex)153   private int findStartIndexOfInlineTag(String text, int fromIndex, int toIndex) {
154       for (int i = fromIndex; i < (toIndex-3); i++) {
155           if (text.charAt(i) == '{' && text.charAt(i+1) == '@' && !isWhitespaceChar(text.charAt(i+2))) {
156               return i;
157           }
158       }
159 
160       return -1;
161   }
162 
findEndIndexOfInlineTag(String text, int fromIndex, int toIndex)163   private int findEndIndexOfInlineTag(String text, int fromIndex, int toIndex) {
164       for (int i = fromIndex; i < toIndex; i++) {
165           if (text.charAt(i) == '}') {
166               return i;
167           }
168       }
169 
170       return -1;
171   }
172 
parseInlineTag(String text, int start, int end, SourcePositionInfo pos)173   private void parseInlineTag(String text, int start, int end, SourcePositionInfo pos) {
174       int index = start+1;
175       //int len = text.length();
176       char c = text.charAt(index);
177       // find the end of the tag name "@something"
178       // need to do something special if we have '}'
179       while (index < end && !isWhitespaceChar(c)) {
180 
181           // if this tag has no value, just return with tag name only
182           if (c == '}') {
183               // TODO - should value be "" or null?
184               tag(text.substring(start+1, end), null, true, pos);
185               return;
186           }
187           c = text.charAt(index++);
188       }
189 
190       // don't parse things that don't have at least one extra character after @
191       // probably should be plus 3
192       // TODO - remove this - think it's fixed by change in parseMainDescription
193       if (index == start+3) {
194           return;
195       }
196 
197       int endOfFirstPart = index-1;
198 
199       // get to beginning of tag value
200       while (index < end && isWhitespaceChar(text.charAt(index++))) {}
201       int startOfSecondPart = index-1;
202 
203       // +1 to get rid of opening brace and -1 to get rid of closing brace
204       // maybe i wanna make this more elegant
205       String tagName = text.substring(start+1, endOfFirstPart);
206       String tagText = text.substring(startOfSecondPart, end-1);
207       tag(tagName, tagText, true, pos);
208   }
209 
210 
211   /**
212    * Finds the index of the start of a new block comment or -1 if there are
213    * no more starts.
214    * @param text The String to search
215    * @param start the index of the String to start searching
216    * @return The index of the start of a new block comment or -1 if there are
217    * no more starts.
218    */
findStartOfBlock(String text, int start)219   private int findStartOfBlock(String text, int start) {
220       // how to detect we're at a new @
221       //       if the chars to the left of it are \r or \n, we're at one
222       //       if the chars to the left of it are ' ' or \t, keep looking
223       //       otherwise, we're in the middle of a block, keep looking
224       int index = text.indexOf('@', start);
225 
226       // no @ in text or index at first position
227       if (index == -1 ||
228               (index == 0 && text.length() > 1 && !isWhitespaceChar(text.charAt(index+1)))) {
229           return index;
230       }
231 
232       index = getPossibleStartOfBlock(text, index);
233 
234       int i = index-1; // start at the character immediately to the left of @
235       char c;
236       while (i >= 0) {
237           c = text.charAt(i--);
238 
239           // found a new block comment because we're at the beginning of a line
240           if (c == '\r' || c == '\n') {
241               return index;
242           }
243 
244           // there is a non whitespace character to the left of the @
245           // before finding a new line, keep searching
246           if (c != ' ' && c != '\t') {
247               index = getPossibleStartOfBlock(text, index+1);
248               i = index-1;
249           }
250 
251           // some whitespace character, so keep looking, we might be at a new block comment
252       }
253 
254       return -1;
255   }
256 
getPossibleStartOfBlock(String text, int index)257   private int getPossibleStartOfBlock(String text, int index) {
258       while (isWhitespaceChar(text.charAt(index+1)) || !isWhitespaceChar(text.charAt(index-1))) {
259           index = text.indexOf('@', index+1);
260 
261           if (index == -1 || index == text.length()-1) {
262               return -1;
263           }
264       }
265 
266       return index;
267   }
268 
parseBlock(String text, int startOfBlock, int endOfBlock)269   private void parseBlock(String text, int startOfBlock, int endOfBlock) {
270       SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, startOfBlock);
271       int index = startOfBlock;
272 
273       for (char c = text.charAt(index);
274               index < endOfBlock && !isWhitespaceChar(c); c = text.charAt(index++)) {}
275 
276       //
277       if (index == startOfBlock+1) {
278           return;
279       }
280 
281       int endOfFirstPart = index-1;
282       if (index == endOfBlock) {
283           // TODO - should value be null or ""
284           tag(text.substring(startOfBlock,
285                   findEndOfMainOrBlock(text, startOfBlock, index)), "", false, pos);
286           return;
287       }
288 
289 
290       // get to beginning of tag value
291       while (index < endOfBlock && isWhitespaceChar(text.charAt(index++))) {}
292       int startOfSecondPart = index-1;
293 
294       tag(text.substring(startOfBlock, endOfFirstPart),
295               text.substring(startOfSecondPart, endOfBlock), false, pos);
296   }
297 
isWhitespaceChar(char c)298   private boolean isWhitespaceChar(char c) {
299       return c == ' ' || c == '\r' || c == '\t' || c == '\n';
300   }
301 
tag(String name, String text, boolean isInline, SourcePositionInfo pos)302   private void tag(String name, String text, boolean isInline, SourcePositionInfo pos) {
303     /*
304      * String s = isInline ? "inline" : "outofline"; System.out.println("---> " + s + " name=[" +
305      * name + "] text=[" + text + "]");
306      */
307     if (name == null) {
308       mInlineTagsList.add(new TextTagInfo("Text", "Text", text, pos));
309     } else if (name.equals("@param")) {
310       mParamTagsList.add(new ParamTagInfo("@param", "@param", text, mBase, pos));
311     } else if (name.equals("@see")) {
312       mSeeTagsList.add(new SeeTagInfo("@see", "@see", text, mBase, pos));
313     } else if (name.equals("@link")) {
314       mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos));
315     } else if (name.equals("@linkplain")) {
316       mInlineTagsList.add(new SeeTagInfo(name, "@linkplain", text, mBase, pos));
317     } else if (name.equals("@value")) {
318       mInlineTagsList.add(new SeeTagInfo(name, "@value", text, mBase, pos));
319     } else if (name.equals("@throws") || name.equals("@exception")) {
320       mThrowsTagsList.add(new ThrowsTagInfo("@throws", "@throws", text, mBase, pos));
321     } else if (name.equals("@return")) {
322       mReturnTagsList.add(new ParsedTagInfo("@return", "@return", text, mBase, pos));
323     } else if (name.equals("@deprecated")) {
324       if (text.length() == 0) {
325         Errors.error(Errors.MISSING_COMMENT, pos, "@deprecated tag with no explanatory comment");
326         text = "No replacement.";
327       }
328       mDeprecatedTagsList.add(new ParsedTagInfo("@deprecated", "@deprecated", text, mBase, pos));
329     } else if (name.equals("@literal")) {
330       mInlineTagsList.add(new LiteralTagInfo(text, pos));
331     } else if (name.equals("@code")) {
332       mInlineTagsList.add(new CodeTagInfo(text, pos));
333     } else if (name.equals("@hide") || name.equals("@pending") || name.equals("@doconly")) {
334       // nothing
335     } else if (name.equals("@attr")) {
336       AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos);
337       mAttrTagsList.add(tag);
338       Comment c = tag.description();
339       if (c != null) {
340         for (TagInfo t : c.tags()) {
341           mInlineTagsList.add(t);
342         }
343       }
344     } else if (name.equals("@undeprecate")) {
345       mUndeprecateTagsList.add(new TextTagInfo("@undeprecate", "@undeprecate", text, pos));
346     } else if (name.equals("@include") || name.equals("@sample")) {
347       mInlineTagsList.add(new SampleTagInfo(name, "@include", text, mBase, pos));
348     } else {
349       boolean known = false;
350       for (String s : KNOWN_TAGS) {
351         if (s.equals(name)) {
352           known = true;
353           break;
354         }
355       }
356       if (!known) {
357         known = Doclava.knownTags.contains(name);
358       }
359       if (!known) {
360         Errors.error(Errors.UNKNOWN_TAG, pos == null ? null : new SourcePositionInfo(pos),
361             "Unknown tag: " + name);
362       }
363       TagInfo t = new TextTagInfo(name, name, text, pos);
364       if (isInline) {
365         mInlineTagsList.add(t);
366       } else {
367         mTagsList.add(t);
368       }
369     }
370   }
371 
parseBriefTags()372   private void parseBriefTags() {
373     int N = mInlineTagsList.size();
374 
375     // look for "@more" tag, which means that we might go past the first sentence.
376     int more = -1;
377     for (int i = 0; i < N; i++) {
378       if (mInlineTagsList.get(i).name().equals("@more")) {
379         more = i;
380       }
381     }
382     if (more >= 0) {
383       for (int i = 0; i < more; i++) {
384         mBriefTagsList.add(mInlineTagsList.get(i));
385       }
386     } else {
387       for (int i = 0; i < N; i++) {
388         TagInfo t = mInlineTagsList.get(i);
389         if (t.name().equals("Text")) {
390           Matcher m = FIRST_SENTENCE.matcher(t.text());
391           if (m.matches()) {
392             String text = m.group(1);
393             TagInfo firstSentenceTag = new TagInfo(t.name(), t.kind(), text, t.position());
394             mBriefTagsList.add(firstSentenceTag);
395             break;
396           }
397         }
398         mBriefTagsList.add(t);
399 
400       }
401     }
402   }
403 
tags()404   public TagInfo[] tags() {
405     init();
406     return mInlineTags;
407   }
408 
tags(String name)409   public TagInfo[] tags(String name) {
410     init();
411     ArrayList<TagInfo> results = new ArrayList<TagInfo>();
412     int N = mInlineTagsList.size();
413     for (int i = 0; i < N; i++) {
414       TagInfo t = mInlineTagsList.get(i);
415       if (t.name().equals(name)) {
416         results.add(t);
417       }
418     }
419     return results.toArray(new TagInfo[results.size()]);
420   }
421 
paramTags()422   public ParamTagInfo[] paramTags() {
423     init();
424     return mParamTags;
425   }
426 
seeTags()427   public SeeTagInfo[] seeTags() {
428     init();
429     return mSeeTags;
430   }
431 
throwsTags()432   public ThrowsTagInfo[] throwsTags() {
433     init();
434     return mThrowsTags;
435   }
436 
returnTags()437   public TagInfo[] returnTags() {
438     init();
439     return mReturnTags;
440   }
441 
deprecatedTags()442   public TagInfo[] deprecatedTags() {
443     init();
444     return mDeprecatedTags;
445   }
446 
undeprecateTags()447   public TagInfo[] undeprecateTags() {
448     init();
449     return mUndeprecateTags;
450   }
451 
attrTags()452   public AttrTagInfo[] attrTags() {
453     init();
454     return mAttrTags;
455   }
456 
briefTags()457   public TagInfo[] briefTags() {
458     init();
459     return mBriefTags;
460   }
461 
isHidden()462   public boolean isHidden() {
463     if (mHidden == null) {
464       mHidden = !Doclava.checkLevel(Doclava.SHOW_HIDDEN) &&
465           (mText != null) && (mText.indexOf("@hide") >= 0 || mText.indexOf("@pending") >= 0);
466     }
467     return mHidden;
468   }
469 
isRemoved()470   public boolean isRemoved() {
471     if (mRemoved == null) {
472         mRemoved = !Doclava.checkLevel(Doclava.SHOW_HIDDEN) &&
473             (mText != null) && (mText.indexOf("@removed") >= 0);
474     }
475 
476     return mRemoved;
477   }
478 
isDocOnly()479   public boolean isDocOnly() {
480     if (mDocOnly == null) {
481       mDocOnly = (mText != null) && (mText.indexOf("@doconly") >= 0);
482     }
483     return mDocOnly;
484   }
485 
isDeprecated()486   public boolean isDeprecated() {
487     if (mDeprecated == null) {
488       mDeprecated = (mText != null) && (mText.indexOf("@deprecated") >= 0);
489     }
490 
491     return mDeprecated;
492   }
493 
init()494   private void init() {
495     if (!mInitialized) {
496       initImpl();
497     }
498   }
499 
initImpl()500   private void initImpl() {
501     isHidden();
502     isRemoved();
503     isDocOnly();
504     isDeprecated();
505 
506     // Don't bother parsing text if we aren't generating documentation.
507     if (Doclava.parseComments()) {
508         parseCommentTags(mText);
509         parseBriefTags();
510     } else {
511       // Forces methods to be recognized by findOverriddenMethods in MethodInfo.
512       mInlineTagsList.add(new TextTagInfo("Text", "Text", mText,
513           SourcePositionInfo.add(mPosition, mText, 0)));
514     }
515 
516     mText = null;
517     mInitialized = true;
518 
519     mInlineTags = mInlineTagsList.toArray(new TagInfo[mInlineTagsList.size()]);
520     mParamTags = mParamTagsList.toArray(new ParamTagInfo[mParamTagsList.size()]);
521     mSeeTags = mSeeTagsList.toArray(new SeeTagInfo[mSeeTagsList.size()]);
522     mThrowsTags = mThrowsTagsList.toArray(new ThrowsTagInfo[mThrowsTagsList.size()]);
523     mReturnTags =
524         ParsedTagInfo.joinTags(mReturnTagsList.toArray(new ParsedTagInfo[mReturnTagsList.size()]));
525     mDeprecatedTags =
526         ParsedTagInfo.joinTags(mDeprecatedTagsList.toArray(new ParsedTagInfo[mDeprecatedTagsList
527             .size()]));
528     mUndeprecateTags = mUndeprecateTagsList.toArray(new TagInfo[mUndeprecateTagsList.size()]);
529     mAttrTags = mAttrTagsList.toArray(new AttrTagInfo[mAttrTagsList.size()]);
530     mBriefTags = mBriefTagsList.toArray(new TagInfo[mBriefTagsList.size()]);
531 
532     mParamTagsList = null;
533     mSeeTagsList = null;
534     mThrowsTagsList = null;
535     mReturnTagsList = null;
536     mDeprecatedTagsList = null;
537     mUndeprecateTagsList = null;
538     mAttrTagsList = null;
539     mBriefTagsList = null;
540   }
541 
542   boolean mInitialized;
543   Boolean mHidden = null;
544   Boolean mRemoved = null;
545   Boolean mDocOnly = null;
546   Boolean mDeprecated = null;
547   String mText;
548   ContainerInfo mBase;
549   SourcePositionInfo mPosition;
550   int mLine = 1;
551 
552   TagInfo[] mInlineTags;
553   TagInfo[] mTags;
554   ParamTagInfo[] mParamTags;
555   SeeTagInfo[] mSeeTags;
556   ThrowsTagInfo[] mThrowsTags;
557   TagInfo[] mBriefTags;
558   TagInfo[] mReturnTags;
559   TagInfo[] mDeprecatedTags;
560   TagInfo[] mUndeprecateTags;
561   AttrTagInfo[] mAttrTags;
562 
563   ArrayList<TagInfo> mInlineTagsList = new ArrayList<TagInfo>();
564   ArrayList<TagInfo> mTagsList = new ArrayList<TagInfo>();
565   ArrayList<ParamTagInfo> mParamTagsList = new ArrayList<ParamTagInfo>();
566   ArrayList<SeeTagInfo> mSeeTagsList = new ArrayList<SeeTagInfo>();
567   ArrayList<ThrowsTagInfo> mThrowsTagsList = new ArrayList<ThrowsTagInfo>();
568   ArrayList<TagInfo> mBriefTagsList = new ArrayList<TagInfo>();
569   ArrayList<ParsedTagInfo> mReturnTagsList = new ArrayList<ParsedTagInfo>();
570   ArrayList<ParsedTagInfo> mDeprecatedTagsList = new ArrayList<ParsedTagInfo>();
571   ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>();
572   ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>();
573 
574 
575 }
576