• Home
  • 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") || name.equals("@linkplain")) {
314       mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos));
315     } else if (name.equals("@value")) {
316       mInlineTagsList.add(new SeeTagInfo(name, "@value", text, mBase, pos));
317     } else if (name.equals("@throws") || name.equals("@exception")) {
318       mThrowsTagsList.add(new ThrowsTagInfo("@throws", "@throws", text, mBase, pos));
319     } else if (name.equals("@return")) {
320       mReturnTagsList.add(new ParsedTagInfo("@return", "@return", text, mBase, pos));
321     } else if (name.equals("@deprecated")) {
322       if (text.length() == 0) {
323         Errors.error(Errors.MISSING_COMMENT, pos, "@deprecated tag with no explanatory comment");
324         text = "No replacement.";
325       }
326       mDeprecatedTagsList.add(new ParsedTagInfo("@deprecated", "@deprecated", text, mBase, pos));
327     } else if (name.equals("@literal")) {
328       mInlineTagsList.add(new LiteralTagInfo(text, pos));
329     } else if (name.equals("@code")) {
330       mInlineTagsList.add(new CodeTagInfo(text, pos));
331     } else if (name.equals("@hide") || name.equals("@pending") || name.equals("@doconly")) {
332       // nothing
333     } else if (name.equals("@attr")) {
334       AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos);
335       mAttrTagsList.add(tag);
336       Comment c = tag.description();
337       if (c != null) {
338         for (TagInfo t : c.tags()) {
339           mInlineTagsList.add(t);
340         }
341       }
342     } else if (name.equals("@undeprecate")) {
343       mUndeprecateTagsList.add(new TextTagInfo("@undeprecate", "@undeprecate", text, pos));
344     } else if (name.equals("@include") || name.equals("@sample")) {
345       mInlineTagsList.add(new SampleTagInfo(name, "@include", text, mBase, pos));
346     } else {
347       boolean known = false;
348       for (String s : KNOWN_TAGS) {
349         if (s.equals(name)) {
350           known = true;
351           break;
352         }
353       }
354       if (!known) {
355         known = Doclava.knownTags.contains(name);
356       }
357       if (!known) {
358         Errors.error(Errors.UNKNOWN_TAG, pos == null ? null : new SourcePositionInfo(pos),
359             "Unknown tag: " + name);
360       }
361       TagInfo t = new TextTagInfo(name, name, text, pos);
362       if (isInline) {
363         mInlineTagsList.add(t);
364       } else {
365         mTagsList.add(t);
366       }
367     }
368   }
369 
parseBriefTags()370   private void parseBriefTags() {
371     int N = mInlineTagsList.size();
372 
373     // look for "@more" tag, which means that we might go past the first sentence.
374     int more = -1;
375     for (int i = 0; i < N; i++) {
376       if (mInlineTagsList.get(i).name().equals("@more")) {
377         more = i;
378       }
379     }
380     if (more >= 0) {
381       for (int i = 0; i < more; i++) {
382         mBriefTagsList.add(mInlineTagsList.get(i));
383       }
384     } else {
385       for (int i = 0; i < N; i++) {
386         TagInfo t = mInlineTagsList.get(i);
387         if (t.name().equals("Text")) {
388           Matcher m = FIRST_SENTENCE.matcher(t.text());
389           if (m.matches()) {
390             String text = m.group(1);
391             TagInfo firstSentenceTag = new TagInfo(t.name(), t.kind(), text, t.position());
392             mBriefTagsList.add(firstSentenceTag);
393             break;
394           }
395         }
396         mBriefTagsList.add(t);
397 
398       }
399     }
400   }
401 
tags()402   public TagInfo[] tags() {
403     init();
404     return mInlineTags;
405   }
406 
tags(String name)407   public TagInfo[] tags(String name) {
408     init();
409     ArrayList<TagInfo> results = new ArrayList<TagInfo>();
410     int N = mInlineTagsList.size();
411     for (int i = 0; i < N; i++) {
412       TagInfo t = mInlineTagsList.get(i);
413       if (t.name().equals(name)) {
414         results.add(t);
415       }
416     }
417     return results.toArray(new TagInfo[results.size()]);
418   }
419 
paramTags()420   public ParamTagInfo[] paramTags() {
421     init();
422     return mParamTags;
423   }
424 
seeTags()425   public SeeTagInfo[] seeTags() {
426     init();
427     return mSeeTags;
428   }
429 
throwsTags()430   public ThrowsTagInfo[] throwsTags() {
431     init();
432     return mThrowsTags;
433   }
434 
returnTags()435   public TagInfo[] returnTags() {
436     init();
437     return mReturnTags;
438   }
439 
deprecatedTags()440   public TagInfo[] deprecatedTags() {
441     init();
442     return mDeprecatedTags;
443   }
444 
undeprecateTags()445   public TagInfo[] undeprecateTags() {
446     init();
447     return mUndeprecateTags;
448   }
449 
attrTags()450   public AttrTagInfo[] attrTags() {
451     init();
452     return mAttrTags;
453   }
454 
briefTags()455   public TagInfo[] briefTags() {
456     init();
457     return mBriefTags;
458   }
459 
isHidden()460   public boolean isHidden() {
461     if (mHidden != -1) {
462       return mHidden != 0;
463     } else {
464       if (Doclava.checkLevel(Doclava.SHOW_HIDDEN)) {
465         mHidden = 0;
466         return false;
467       }
468       boolean b = mText.indexOf("@hide") >= 0 || mText.indexOf("@pending") >= 0;
469       mHidden = b ? 1 : 0;
470       return b;
471     }
472   }
473 
isDocOnly()474   public boolean isDocOnly() {
475     if (mDocOnly != -1) {
476       return mDocOnly != 0;
477     } else {
478       boolean b = (mText != null) && (mText.indexOf("@doconly") >= 0);
479       mDocOnly = b ? 1 : 0;
480       return b;
481     }
482   }
483 
isDeprecated()484   public boolean isDeprecated() {
485     if (mDeprecated != -1) {
486       return mDeprecated != 0;
487     } else {
488       boolean b = (mText != null) && (mText.indexOf("@deprecated") >= 0);
489       mDeprecated = b ? 1 : 0;
490       return b;
491     }
492   }
493 
init()494   private void init() {
495     if (!mInitialized) {
496       initImpl();
497     }
498   }
499 
initImpl()500   private void initImpl() {
501     isHidden();
502     isDocOnly();
503     isDeprecated();
504 
505     // Don't bother parsing text if we aren't generating documentation.
506     if (Doclava.parseComments()) {
507         parseCommentTags(mText);
508         parseBriefTags();
509     } else {
510       // Forces methods to be recognized by findOverriddenMethods in MethodInfo.
511       mInlineTagsList.add(new TextTagInfo("Text", "Text", mText,
512           SourcePositionInfo.add(mPosition, mText, 0)));
513     }
514 
515     mText = null;
516     mInitialized = true;
517 
518     mInlineTags = mInlineTagsList.toArray(new TagInfo[mInlineTagsList.size()]);
519     mParamTags = mParamTagsList.toArray(new ParamTagInfo[mParamTagsList.size()]);
520     mSeeTags = mSeeTagsList.toArray(new SeeTagInfo[mSeeTagsList.size()]);
521     mThrowsTags = mThrowsTagsList.toArray(new ThrowsTagInfo[mThrowsTagsList.size()]);
522     mReturnTags =
523         ParsedTagInfo.joinTags(mReturnTagsList.toArray(new ParsedTagInfo[mReturnTagsList.size()]));
524     mDeprecatedTags =
525         ParsedTagInfo.joinTags(mDeprecatedTagsList.toArray(new ParsedTagInfo[mDeprecatedTagsList
526             .size()]));
527     mUndeprecateTags = mUndeprecateTagsList.toArray(new TagInfo[mUndeprecateTagsList.size()]);
528     mAttrTags = mAttrTagsList.toArray(new AttrTagInfo[mAttrTagsList.size()]);
529     mBriefTags = mBriefTagsList.toArray(new TagInfo[mBriefTagsList.size()]);
530 
531     mParamTagsList = null;
532     mSeeTagsList = null;
533     mThrowsTagsList = null;
534     mReturnTagsList = null;
535     mDeprecatedTagsList = null;
536     mUndeprecateTagsList = null;
537     mAttrTagsList = null;
538     mBriefTagsList = null;
539   }
540 
541   boolean mInitialized;
542   int mHidden = -1;
543   int mDocOnly = -1;
544   int mDeprecated = -1;
545   String mText;
546   ContainerInfo mBase;
547   SourcePositionInfo mPosition;
548   int mLine = 1;
549 
550   TagInfo[] mInlineTags;
551   TagInfo[] mTags;
552   ParamTagInfo[] mParamTags;
553   SeeTagInfo[] mSeeTags;
554   ThrowsTagInfo[] mThrowsTags;
555   TagInfo[] mBriefTags;
556   TagInfo[] mReturnTags;
557   TagInfo[] mDeprecatedTags;
558   TagInfo[] mUndeprecateTags;
559   AttrTagInfo[] mAttrTags;
560 
561   ArrayList<TagInfo> mInlineTagsList = new ArrayList<TagInfo>();
562   ArrayList<TagInfo> mTagsList = new ArrayList<TagInfo>();
563   ArrayList<ParamTagInfo> mParamTagsList = new ArrayList<ParamTagInfo>();
564   ArrayList<SeeTagInfo> mSeeTagsList = new ArrayList<SeeTagInfo>();
565   ArrayList<ThrowsTagInfo> mThrowsTagsList = new ArrayList<ThrowsTagInfo>();
566   ArrayList<TagInfo> mBriefTagsList = new ArrayList<TagInfo>();
567   ArrayList<ParsedTagInfo> mReturnTagsList = new ArrayList<ParsedTagInfo>();
568   ArrayList<ParsedTagInfo> mDeprecatedTagsList = new ArrayList<ParsedTagInfo>();
569   ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>();
570   ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>();
571 
572 
573 }
574