1 /* gnu.classpath.tools.gjdoc.DocImpl
2    Copyright (C) 2001, 2012 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 package gnu.classpath.tools.gjdoc;
39 
40 import com.sun.javadoc.*;
41 import java.util.*;
42 import java.text.*;
43 import java.io.File;
44 import javax.swing.text.Segment;
45 
46 /**
47  *  Represents the least common denominator of all Javadoc
48  *  comment classes.
49  */
50 public abstract class DocImpl implements Doc, TagContainer {
51 
52    protected static Tag[] seeTagEmptyArr = new SeeTagImpl[0];
53    protected static Tag[] linkTagEmptyArr = new LinkTagImpl[0];
54    protected static Tag[] paramTagEmptyArr = new ParamTagImpl[0];
55    protected static Tag[] throwsTagEmptyArr = new ThrowsTagImpl[0];
56    protected SourcePosition position;
57    private String boilerplateComment;
58 
59    // Return the text of the comment for this doc item.
commentText()60    public String commentText() {
61 
62       StringBuffer rc=new StringBuffer();
63 
64       Tag[] textTags=(Tag[])tagMap.get("text");
65       if (textTags!=null) {
66          for (int i=0; i<textTags.length; ++i) {
67             rc.append(textTags[i].text());
68          }
69       }
70       return rc.toString();
71    }
72 
73    // Compares this Object with the specified Object for order.
compareTo(Doc d)74    public int compareTo(Doc d) {
75       return Main.getInstance().getCollator().compare(name(), d.name());
76    }
77 
78    // Return the first sentence of the comment as tags.
firstSentenceTags()79    public Tag[] firstSentenceTags() {
80 
81       Tag[] rc=(Tag[])tagMap.get("first");
82       if (rc==null) rc=new Tag[0];
83       return rc;
84    }
85 
86    // Return the full unprocessed text of the comment.
getRawCommentText()87    public String getRawCommentText() {
88       if (rawDocumentation!=null)
89           return rawDocumentation;
90       else if (rawDocOffset>=0)
91          return Main.getRootDoc().readRawComment(rawDocOffset);
92       else
93          return null;
94    }
95 
96    // Return comment as tags.
inlineTags()97    public Tag[] inlineTags() {
98 
99       Tag[] rc=(Tag[])tagMap.get("inline");
100       if (rc==null) rc=new Tag[0];
101       return rc;
102    }
103 
104    // Is this Doc item a class.
isClass()105    public boolean isClass() {
106       return false;
107    }
108 
109    // Is this Doc item a constructor? False until overridden.
isConstructor()110    public boolean isConstructor() {
111       return false;
112    }
113 
114    // Is this Doc item a error class? False until overridden.
isError()115    public boolean isError() {
116       return false;
117    }
118 
119    // Is this Doc item a exception class? False until overridden.
isException()120    public boolean isException() {
121       return false;
122    }
123 
124    // Is this Doc item a field? False until overridden.
isField()125    public boolean isField() {
126       return false;
127    }
128 
129    // return true if this Doc is include in the active set.
isIncluded()130    public boolean isIncluded() {
131       return false;
132    }
133 
134    // Is this Doc item a interface? False until overridden.
isInterface()135    public boolean isInterface() {
136       return false;
137    }
138 
139    // Is this Doc item a simple method (i.e.
isMethod()140    public boolean isMethod() {
141       return false;
142    }
143 
isPackage()144    public boolean isPackage() {
145       return false;
146    }
147 
148    // Is this Doc item a ordinary class (i.e.
isOrdinaryClass()149    public boolean isOrdinaryClass() {
150       return false;
151    }
152 
153    // Return the see also tags in this Doc item.
seeTags()154    public SeeTag[] seeTags() {
155       return (SeeTag[])getTagArr("see", seeTagEmptyArr);
156    }
157 
getTagArr(String kindOfTag, Tag[] defaultRc)158    protected Tag[] getTagArr(String kindOfTag, Tag[] defaultRc) {
159       Tag[] rc=(Tag[])tagMap.get(kindOfTag);
160       if (rc==null) rc=defaultRc;
161       return rc;
162    }
163 
164    // Set the full unprocessed text of the comment.
setRawCommentText(String rawDocumentation)165    public void setRawCommentText(String rawDocumentation) {
166       this.rawDocumentation=rawDocumentation;
167    }
168 
resolveComments()169    public void resolveComments() {
170 
171       if (rawDocumentation!=null && tagMap.isEmpty()) {
172          char[] charArray = rawDocumentation.toCharArray();
173          int length = rawDocumentation.length();
174          int startOffset = 0;
175          int endOffset = 0;
176          if (charArray[0] == '/'
177              && charArray[1] == '*'
178              && charArray[2] == '*'
179              && charArray[length - 2] == '*'
180              && charArray[length - 1] == '/') {
181 
182             startOffset = 3;
183             endOffset = 2;
184          }
185 
186          this.tagMap=parseCommentTags(charArray,
187                                       startOffset,
188                                       length - endOffset,
189                                       getContextClass(),
190                                       getContextMember(),
191                                       null,
192                                       boilerplateComment);
193 
194          if (Main.getInstance().isCacheRawComments()) {
195             rawDocOffset=Main.getRootDoc().writeRawComment(rawDocumentation);
196             rawDocumentation=null;
197          }
198 
199          resolveTags();
200       }
201       else if (tagMap.isEmpty() && null != boilerplateComment) {
202          tagMap.put("all", new Tag[] { new TagImpl("@boilerplate", boilerplateComment,getContextClass(),null) });
203          tagMap.put("@boilerplate", new Tag[] { new TagImpl("@boilerplate", boilerplateComment,getContextClass(),null) });
204       }
205    }
206 
skipHtmlWhitespace(char[] buffer, int startIndex)207    public static int skipHtmlWhitespace(char[] buffer, int startIndex) {
208       while (startIndex < buffer.length) {
209          char c=buffer[startIndex];
210          if (!Parser.isWhitespace(c)) {
211             break;
212          }
213          else {
214             ++ startIndex;
215          }
216       }
217       return startIndex;
218    }
219 
220    /**
221     *  Looks for an end-of-sentence marker in <code>text</code>,
222     *  starting at <code>startIndex</code> and stopping at
223     *  <code>endIndex</code>.
224     *
225     *  @param text  the text to be searched
226     *  @param startIndex  index in <code>text</code> at which to start
227     *  @param endIndex  index in <code>text</code> at which to stop
228     *
229     *  @return the index of the character following the end-of-sentence
230     *    marker, <code>endIndex</code> if no end-of-sentence
231     *    marker could be found, or -1 if not implemented.
232     */
findEndOfSentence(char[] text, int startIndex, int endIndex)233    private static int findEndOfSentence(char[] text, int startIndex,
234                                         int endIndex)
235    {
236       if (Main.getInstance().isUseBreakIterator()) {
237          Segment segment = new Segment(text, startIndex, endIndex - startIndex);
238          BreakIterator breakIterator = BreakIterator.getSentenceInstance(Main.getInstance().getLocale());
239          breakIterator.setText(segment);
240          int result = breakIterator.next();
241          if (BreakIterator.DONE == result) {
242             return endIndex;
243          }
244          else {
245             return result;
246          }
247       }
248       else {
249          while (startIndex < endIndex) {
250             if (text[startIndex] == '.'
251                 && (startIndex+1 == endIndex
252                     || Character.isWhitespace(text[startIndex+1])
253                     || isHTMLBreakTag(text, startIndex+1, endIndex)
254                     )) {
255                return startIndex;
256             }
257 
258             startIndex++;
259          }
260          return endIndex;
261       }
262    }
263 
264    /**
265     * Returns true is the text from start to end begins with a 'p' or 'br' tag.
266     */
isHTMLBreakTag(char[] text, int start, int end)267    private static boolean isHTMLBreakTag(char[] text, int start, int end)
268    {
269       String[] breakTags = {
270          "p>", "/p>", "h1>", "h2>", "h3>", "h4>", "h5>", "h6>", "hr>",
271          "pre>", "/pre>"
272       };
273 
274       if (text[start] == '<') {
275 
276       outer:
277          for (int i=0; i<breakTags.length; ++i) {
278             String tag = breakTags[i];
279             int len = tag.length();
280             if (start + len < end) {
281                for (int j=0; j<len; ++j) {
282                   char c = tag.charAt(j);
283                   if (Character.toLowerCase(text[start + 1 + j]) != c) {
284                      continue outer;
285                   }
286                }
287                return true;
288             }
289          }
290       }
291       return false;
292    }
293 
294    //private static final StringBuffer buf=new StringBuffer(32768);
295    private static final StringBuffer whitespaceBuf=new StringBuffer();
296    private static char[] charBuf = new char[60000];
297    private static int bufPos = 0;
298 
appendToBuf(char c)299    private static void appendToBuf(char c)
300    {
301       if (bufPos < charBuf.length) {
302          charBuf[bufPos++] = c;
303       }
304       else {
305          //
306       }
307    }
308 
appendToBuf(StringBuffer s)309    private static void appendToBuf(StringBuffer s)
310    {
311       if (bufPos + s.length() <= charBuf.length) {
312          s.getChars(0, s.length(), charBuf, bufPos);
313          bufPos += s.length();
314       }
315       else {
316          //
317       }
318    }
319 
setBufLength(int length)320    private static void setBufLength(int length)
321    {
322       bufPos = 0;
323    }
324 
bufToString()325    private static String bufToString()
326    {
327       return new String(charBuf, 0, bufPos);
328    }
329 
bufLength()330    private static int bufLength()
331    {
332       return bufPos;
333    }
334 
parseCommentTags(char[] comment, int startIndex, int endIndex, ClassDocImpl contextClass, MemberDocImpl contextMember, AbstractTagImpl contextTag, String boilerplateComment)335    public static Map parseCommentTags(char[] comment, int startIndex, int endIndex,
336                                       ClassDocImpl contextClass, MemberDocImpl contextMember,
337                                       AbstractTagImpl contextTag, String boilerplateComment) {
338 
339       int rawDocStart=skipHtmlWhitespace(comment, startIndex);
340 
341       int firstSentenceEnd = 0;
342 
343       if (comment.length>rawDocStart) {
344 
345          firstSentenceEnd = findEndOfSentence(comment, rawDocStart, comment.length);
346 
347          if (firstSentenceEnd < 0) {
348             BreakIterator boundary = BreakIterator.getSentenceInstance(Locale.ENGLISH);
349             boundary.setText(new ArrayCharacterIterator(comment, rawDocStart));
350             boundary.first();
351             boundary.next();
352             firstSentenceEnd = boundary.current();
353          }
354 
355          // Always include period at end of sentence if there is one.
356          if (firstSentenceEnd < comment.length
357                          && '.' == comment[firstSentenceEnd]) {
358             ++ firstSentenceEnd;
359          }
360       }
361 
362       final int STATE_BEGOFLINE            = 1;
363       final int STATE_TEXT                 = 2;
364       final int STATE_PARAM                = 3;
365       final int STATE_PARAMVALUE           = 4;
366       final int STATE_PARAMWRAP            = 5;
367       final int STATE_INLINEPARAM          = 6;
368       final int STATE_INLINEPARAMVALUE     = 7;
369       final int STATE_WHITESPACE           = 8;
370       final int STATE_INLINEPARAMVALUE_BOL = 9;
371       final int STATE_IPV_WHITESPACE       = 10;
372 
373       int state=STATE_BEGOFLINE;
374       int prevState=STATE_TEXT;
375 
376       setBufLength(0);
377       whitespaceBuf.setLength(0);
378 
379       String paramName="", paramValue="";
380 
381       Map tags=new HashMap();
382       tags.put("inline", new LinkedList());
383       tags.put("first", new LinkedList());
384       tags.put("all", new LinkedList());
385 
386       final char EOL=(char)-1;
387 
388       for (int i=rawDocStart; i<=endIndex; ++i) {
389          char c=(i<endIndex)?comment[i]:EOL;
390          char peek=(i<endIndex-1)?comment[i+1]:EOL;
391 
392          switch (state){
393 
394          case STATE_BEGOFLINE:
395             if (i==firstSentenceEnd) {
396                AbstractTagImpl newTag = addTag(tags, "text", bufToString(), true, contextClass, contextMember, contextTag, false);
397                if (null != newTag) {
398                   contextTag = newTag;
399                }
400                setBufLength(0);
401             }
402 
403             if (Parser.isWhitespace(c)) {
404                // ignore
405             }
406             else if (c=='*') {
407                // ignore, but go to STATE_TEXT
408                if (peek!='*' && peek!='@' && peek!=EOL) {
409                   state=STATE_WHITESPACE;
410                }
411             }
412             else if (c=='@' || (c=='{' && peek=='@') || c==EOL) {
413                if (bufLength()>0) {
414                   addTag(tags, "text", bufToString(), i<firstSentenceEnd, contextClass, contextMember, contextTag, false);
415                   setBufLength(0);
416                }
417                if (c=='{') {
418                   ++i;
419                   state=STATE_INLINEPARAM;
420                }
421                else {
422                   state=STATE_PARAM;
423                }
424             }
425             else {
426                state=STATE_TEXT;
427                appendToBuf(whitespaceBuf);
428                whitespaceBuf.setLength(0);
429                appendToBuf(c);
430             }
431             break;
432 
433          case STATE_WHITESPACE:
434             if (i==firstSentenceEnd) {
435                AbstractTagImpl newTag = addTag(tags, "text", bufToString(), true, contextClass, contextMember, contextTag, false);
436                if (null != newTag) {
437                   contextTag = newTag;
438                }
439                setBufLength(0);
440             }
441 
442             if (c=='\n') {
443                whitespaceBuf.append(c);
444                state=STATE_BEGOFLINE;
445             }
446             else if (Parser.isWhitespace(c)) {
447                whitespaceBuf.append(c);
448             }
449             else if (c=='@' || (c=='{' && peek=='@') || c==EOL) {
450                if (bufLength()>0) {
451                   AbstractTagImpl newTag = addTag(tags, "text", bufToString(), i<firstSentenceEnd, contextClass, contextMember, contextTag, false);
452                   if (null != newTag) {
453                      contextTag = newTag;
454                   }
455                   setBufLength(0);
456                }
457                if (c=='{') {
458                   ++i;
459                   state=STATE_INLINEPARAM;
460                }
461                else {
462                   state=STATE_PARAM;
463                }
464             }
465             else {
466                appendToBuf(whitespaceBuf);
467                whitespaceBuf.setLength(0);
468                appendToBuf(c);
469                state=STATE_TEXT;
470             }
471             break;
472 
473          case STATE_PARAMWRAP:
474             if (c=='\n') {
475                appendToBuf(c);
476             }
477             else if (Parser.isWhitespace(c)) {
478                // ignore
479             }
480             else if (c=='*') {
481                // ignore, but go to STATE_TEXT
482                /*
483                if (i<endIndex && comment[i+1]!='*' && comment[i+1]!='@') {
484                   state=STATE_PARAMVALUE;
485                }
486                */
487             }
488             else if (c=='@' || c==EOL) {
489                paramValue=bufToString();
490                AbstractTagImpl newTag = addTag(tags, paramName, paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, false);
491                if (null != newTag) {
492                   contextTag = newTag;
493                }
494                setBufLength(0);
495                if (c=='{') {
496                   ++i;
497                   state=STATE_INLINEPARAM;
498                }
499                else {
500                   state=STATE_PARAM;
501                }
502             }
503             else {
504                state=STATE_PARAMVALUE;
505                appendToBuf(c);
506             }
507             break;
508 
509          case STATE_PARAM:
510             if (!(c==EOL || Parser.isWhitespace(c))) {
511                appendToBuf(c);
512             }
513             else if (c=='\n') {
514                paramName=bufToString();
515                setBufLength(0);
516                state=STATE_PARAMWRAP;
517             }
518             else {
519                paramName=bufToString();
520                setBufLength(0);
521                state=STATE_PARAMVALUE;
522             }
523             break;
524 
525          case STATE_INLINEPARAM:
526             if (c=='}') {
527                // tag without value
528                paramName=bufToString();
529                AbstractTagImpl newTag = addTag(tags, paramName, "", i<firstSentenceEnd, contextClass, contextMember, contextTag, true);
530                if (null != newTag) {
531                   contextTag = newTag;
532                }
533                state=prevState;
534                setBufLength(0);
535             }
536             else if (!(c==EOL || Parser.isWhitespace(c))) {
537                appendToBuf(c);
538             }
539             else if (c=='\n') {
540                paramName=bufToString();
541                setBufLength(0);
542                state=STATE_INLINEPARAMVALUE_BOL;
543             }
544             else {
545                paramName=bufToString();
546                setBufLength(0);
547                state=STATE_INLINEPARAMVALUE;
548             }
549             break;
550 
551          case STATE_PARAMVALUE:
552             if (c==EOL) {
553                paramValue=bufToString();
554                AbstractTagImpl newTag = addTag(tags, paramName, paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, false);
555                if (null != newTag) {
556                   contextTag = newTag;
557                }
558             }
559             else if (c=='\n') {
560                appendToBuf(c);
561                state=STATE_PARAMWRAP;
562             }
563             else {
564                appendToBuf(c);
565             }
566             break;
567 
568          case STATE_INLINEPARAMVALUE:
569             if (c=='\n') {
570                appendToBuf(c);
571                state=STATE_INLINEPARAMVALUE_BOL;
572             }
573             else if (c==EOL || c=='}') {
574                paramValue=bufToString();
575                AbstractTagImpl newTag = addTag(tags, paramName, paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, true);
576                if (null != newTag) {
577                   contextTag = newTag;
578                }
579                state=prevState;
580                setBufLength(0);
581             }
582             else {
583                appendToBuf(c);
584             }
585             break;
586 
587          case STATE_INLINEPARAMVALUE_BOL:
588             if (Parser.isWhitespace(c)) {
589                // ignore
590             }
591             else if (c=='*') {
592                // ignore, but go to STATE_TEXT
593                if (i<endIndex && peek!='*') {
594                   state=STATE_IPV_WHITESPACE;
595                }
596             }
597             else if (c==EOL) {
598                if (bufLength()>0) {
599                   AbstractTagImpl newTag = addTag(tags, "text", bufToString(), i<firstSentenceEnd, contextClass, contextMember, contextTag, false);
600                   if (null != newTag) {
601                      contextTag = newTag;
602                   }
603                }
604             }
605             else {
606                state=STATE_INLINEPARAMVALUE;
607                appendToBuf(whitespaceBuf);
608                whitespaceBuf.setLength(0);
609                appendToBuf(c);
610             }
611             break;
612 
613          case STATE_IPV_WHITESPACE:
614             if (c=='\n') {
615                whitespaceBuf.append(c);
616                state=STATE_INLINEPARAMVALUE_BOL;
617             }
618             else if (Parser.isWhitespace(c)) {
619                whitespaceBuf.append(c);
620             }
621             else if (c==EOL) {
622                if (bufLength()>0) {
623                   AbstractTagImpl newTag = addTag(tags, "text", bufToString(), i<firstSentenceEnd, contextClass, contextMember, contextTag, false);
624                   if (null != newTag) {
625                      contextTag = newTag;
626                   }
627                   setBufLength(0);
628                }
629             }
630             else {
631                appendToBuf(whitespaceBuf);
632                whitespaceBuf.setLength(0);
633                appendToBuf(c);
634                state=STATE_INLINEPARAMVALUE;
635             }
636             break;
637 
638          case STATE_TEXT:
639             if (i==firstSentenceEnd) {
640                AbstractTagImpl newTag = addTag(tags, "text", bufToString(), true, contextClass, contextMember, contextTag, false);
641                if (null != newTag) {
642                   contextTag = newTag;
643                }
644                setBufLength(0);
645             }
646 
647             if (c==EOL) {
648                paramValue=bufToString();
649                AbstractTagImpl newTag = addTag(tags, "text", paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, false);
650                if (null != newTag) {
651                   contextTag = newTag;
652                }
653             }
654             else if (c=='\n') {
655                appendToBuf(c);
656                state=STATE_BEGOFLINE;
657             }
658             else if (c=='{' && peek=='@') {
659                paramValue=bufToString();
660                AbstractTagImpl newTag = addTag(tags, "text", paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, false);
661                if (null != newTag) {
662                   contextTag = newTag;
663                }
664                ++i;
665                setBufLength(0);
666                state=STATE_INLINEPARAM;
667             }
668             else {
669                appendToBuf(c);
670             }
671             break;
672 
673          default:
674             throw new Error("illegal state "+state);
675          }
676       }
677 
678 
679       if (null == contextMember && null != boilerplateComment && Main.getInstance().isCopyLicenseText()) {
680          addTag(tags, "@boilerplate", boilerplateComment, false, contextClass, null, null, false);
681       }
682 
683       Map rc=new HashMap();
684 
685       for (Iterator it=tags.keySet().iterator(); it.hasNext(); ) {
686          String key=(String)it.next();
687          Tag[] templateArr;
688          List list=(List)tags.get(key);
689 
690          if ("see".equals(key))
691             templateArr=new SeeTag[list.size()];
692          else if ("param".equals(key))
693             templateArr=new ParamTag[list.size()];
694          else if ("serialField".equals(key))
695             templateArr=new SerialFieldTag[list.size()];
696          else if ("throws".equals(key) || "exception".equals(key))
697             templateArr=new ThrowsTag[list.size()];
698          else {
699             templateArr=new Tag[list.size()];
700          }
701 
702          rc.put(key, list.toArray(templateArr));
703       }
704 
705       return rc;
706    }
707 
getContextClass()708    private ClassDocImpl getContextClass() {
709       if (isClass() || isInterface()) {
710          return (ClassDocImpl)this;
711       }
712       else if (isField() || isMethod() || isConstructor()) {
713          return (ClassDocImpl)((MemberDocImpl)this).containingClass();
714       }
715       else {
716          return null;
717       }
718    }
719 
getContextMember()720    private MemberDocImpl getContextMember() {
721       if (isField() || isMethod() || isConstructor()) {
722          return (MemberDocImpl)this;
723       }
724       else {
725          return null;
726       }
727    }
728 
addTag(Map tags, String name, String value, boolean isFirstSentence, ClassDocImpl contextClass, MemberDocImpl contextMember, AbstractTagImpl contextTag, boolean isInline)729    protected static AbstractTagImpl addTag(Map tags, String name,
730                                            String value, boolean isFirstSentence,
731                                            ClassDocImpl contextClass,
732                                            MemberDocImpl contextMember,
733                                            AbstractTagImpl contextTag,
734                                            boolean isInline) {
735 
736       AbstractTagImpl tag = null;
737 
738       boolean haveValue = (0 != value.trim().length());
739 
740       String emptyWarning = "Empty @" + name + " tag.";
741 
742       if (name.equals("param")) {
743          if (haveValue) {
744             tag=new ParamTagImpl(value, contextClass, contextMember);
745          }
746          else {
747             //printWarning(emptyWarning);
748          }
749       }
750       else if (name.equals("see")) {
751          if (haveValue) {
752             tag=new SeeTagImpl(value, contextClass);
753          }
754          else {
755             //printWarning(emptyWarning);
756          }
757       }
758       else if (name.equals("link") || name.equals("linkplain")) {
759          if (haveValue) {
760             tag=new LinkTagImpl("@" + name, value, contextClass);
761             isInline = true;
762          }
763          else {
764             //printWarning(emptyWarning);
765          }
766       }
767       else if (name.equals("value")) {
768          if (haveValue) {
769             tag=new ValueTagImpl(value, contextClass);
770             isInline = true;
771          }
772          else {
773             //printWarning(emptyWarning);
774          }
775       }
776       else if (name.equals("inheritDoc")) {
777          if (haveValue) {
778             //printWarning("@inheritDoc tags are not supposed to have any content.");
779          }
780          tag=new InheritDocTagImpl(contextClass, contextMember, contextTag);
781          isInline = true;
782       }
783       else if (name.equals("serialField")) {
784          if (haveValue) {
785             tag=new SerialFieldTagImpl(value, contextClass, contextMember);
786          }
787          else {
788             //printWarning(emptyWarning);
789          }
790       }
791       else if (name.equals("throws") || name.equals("exception")) {
792          if (haveValue) {
793             tag=new ThrowsTagImpl(value, contextClass, contextMember);
794          }
795          else {
796             //printWarning(emptyWarning);
797          }
798          name="throws";
799       }
800       else if (name.equals("text")) {
801          tag=new TextTagImpl(value);
802          isInline = true;
803       }
804       else {
805          tag=new TagImpl("@"+name, value.trim(), contextClass, contextMember);
806          // FIXME: consider taglets
807       }
808 
809       if (tag != null) {
810          if (isInline) {
811             ((List)tags.get("inline")).add(tag);
812             if (isFirstSentence) {
813                if (name.equals("text")) {
814                   String txt = ((TextTagImpl)tag).getText();
815                   Tag newTag;
816                   if (txt.startsWith("<p>")) {
817                      newTag = new TextTagImpl(txt.substring(3));
818                   }
819                   else if (txt.endsWith("</p>")) {
820                      newTag = new TextTagImpl(txt.substring(0, txt.length() - 4));
821                   }
822                   else {
823                      newTag = tag;
824                   }
825                   ((List)tags.get("first")).add(newTag);
826 
827                }
828                else {
829                   ((List)tags.get("first")).add(tag);
830                }
831             }
832          }
833          else {
834             ((List)tags.get("all")).add(tag);
835          }
836 
837          List l=((List)tags.get(name));
838          if (l==null) {
839             l=new LinkedList();
840             tags.put(name,l);
841          }
842          l.add(tag);
843 
844          return isInline ? tag : contextTag;
845       }
846       else {
847          return null;
848       }
849    }
850 
851    // Return all tags in this Doc item.
tags()852    public Tag[] tags() {
853       Tag[] rc=(Tag[])tagMap.get("all");
854       if (rc==null) rc=new Tag[0];
855       return rc;
856    }
857 
858    // Return tags of the specified kind in this Doc item.
tags(java.lang.String tagname)859    public Tag[] tags(java.lang.String tagname) {
860       Tag[] rc=(Tag[])tagMap.get(tagname);
861       if (rc==null) rc=new Tag[0];
862       return rc;
863    }
864 
865    protected String rawDocumentation;
866    protected long rawDocOffset=-1;
867 
868    protected Map tagMap = new HashMap();
869 
getTagMap()870    public Map getTagMap() { return tagMap; }
871 
resolveTags()872    protected void resolveTags() {
873 
874       Tag[] tags=tags();
875       for (int i=0; i<tags.length; ++i) {
876          ((AbstractTagImpl)tags[i]).resolve();
877       }
878 
879       Tag[] inlineTags=inlineTags();
880       for (int i=0; i<inlineTags.length; ++i) {
881          ((AbstractTagImpl)inlineTags[i]).resolve();
882       }
883    }
884 
885    private static Map classDocToFileMap = new HashMap();
886 
getFile(ClassDoc classDoc)887    private static File getFile(ClassDoc classDoc) {
888       File result = (File)classDocToFileMap.get(classDoc);
889       if (null == result) {
890          result = new File(((GjdocPackageDoc)classDoc.containingPackage()).packageDirectory(),
891                            classDoc.name() + ".java");
892          classDocToFileMap.put(classDoc, result);
893       }
894       return result;
895    }
896 
getPosition(ClassDoc classDoc)897    public static SourcePosition getPosition(ClassDoc classDoc)
898    {
899       return new SourcePositionImpl(getFile(classDoc), 0, 0);
900    }
901 
getPosition(ClassDoc classDoc, char[] source, int startIndex)902    public static SourcePosition getPosition(ClassDoc classDoc, char[] source, int startIndex)
903    {
904       int column = 0;
905       int line = 0;
906       for (int i=0; i<startIndex; ++i) {
907          if (10 == source[i]) {
908             ++ line;
909             column = 0;
910          }
911          else if (13 != source[i]) {
912             ++ column;
913          }
914       }
915       while (true) {
916          ClassDoc containingClassDoc = classDoc.containingClass();
917          if (null != containingClassDoc) {
918             classDoc = containingClassDoc;
919          }
920          else {
921             break;
922          }
923       }
924 
925       File file = getFile(classDoc);
926 
927       return new SourcePositionImpl(file, line + 1, column + 1);
928    }
929 
position()930    public SourcePosition position()
931    {
932       return this.position;
933    }
934 
DocImpl(SourcePosition position)935    public DocImpl(SourcePosition position)
936    {
937       this.position = position;
938    }
939 
setPosition(SourcePosition position)940    public void setPosition(SourcePosition position)
941    {
942       this.position = position;
943    }
944 
checkForInheritedDoc(ClassDoc classDoc, MemberDocImpl memberDoc, AbstractTagImpl tag)945    private static TagContainer checkForInheritedDoc(ClassDoc classDoc,
946                                                     MemberDocImpl memberDoc,
947                                                     AbstractTagImpl tag)
948    {
949       DocImpl result;
950 
951       if (!(classDoc instanceof ClassDocImpl)) {
952          result = null;
953       }
954       else if (null == memberDoc) {
955          result = (DocImpl)classDoc;
956       }
957       else if (memberDoc.isField()) {
958          result = (DocImpl)((ClassDocImpl)classDoc).getFieldDoc(memberDoc.name());
959       }
960       else if (memberDoc.isMethod()) {
961          result = (DocImpl)((ClassDocImpl)classDoc).getMethodDoc(memberDoc.name(),
962                                                                  ((MethodDoc)memberDoc).signature());
963       }
964       else if (memberDoc.isConstructor()) {
965          result = (DocImpl)((ClassDocImpl)classDoc).getConstructorDoc(((ConstructorDoc)memberDoc).signature());
966       }
967       else {
968          //assert(false);
969          throw new RuntimeException("memberDoc is supposed to be field, method or constructor");
970       }
971 
972       if (null != result
973           && null != memberDoc
974           && null != tag) {
975 
976          TagContainer tagDoc = null;
977 
978          Tag[] tags = result.tags();
979          for (int i=0; i<tags.length; ++i) {
980             if (tags[i].kind().equals(tag.kind())) {
981                if ("@param".equals(tag.kind())) {
982                   if (((ParamTagImpl)tags[i]).parameterName().equals(((ParamTagImpl)tag).parameterName())) {
983                      tagDoc = (TagContainer)tags[i];
984                      break;
985                   }
986                }
987                else if ("@throws".equals(tag.kind())) {
988                   if (((ThrowsTagImpl)tags[i]).exceptionName().equals(((ThrowsTagImpl)tag).exceptionName())) {
989                      tagDoc = (TagContainer)tags[i];
990                      break;
991                   }
992                }
993                else if ("@return".equals(tag.kind())) {
994                   tagDoc = (TagContainer)tags[i];
995                }
996             }
997          }
998 
999          return tagDoc;
1000       }
1001 
1002       if (null == result || result.isEmptyDoc()) {
1003          return null;
1004       }
1005       else {
1006          return result;
1007       }
1008    }
1009 
findInheritedDoc(ClassDoc classDoc, MemberDocImpl memberDoc, AbstractTagImpl tag)1010    public static TagContainer findInheritedDoc(ClassDoc classDoc,
1011                                                MemberDocImpl memberDoc,
1012                                                AbstractTagImpl tag)
1013    {
1014       TagContainer result;
1015 
1016       // (Taken from Javadoc Solaris Tool documentation 1.5,
1017       // section "Automatic Copying of Method Comments")
1018 
1019       // Algorithm for Inheriting Method Comments - If a method does
1020       // not have a doc comment, or has an {@inheritDoc} tag, the
1021       // Javadoc tool searches for an applicable comment using the
1022       // following algorithm, which is designed to find the most
1023       // specific applicable doc comment, giving preference to
1024       // interfaces over superclasses:
1025 
1026       // 1. Look in each directly implemented (or extended) interface
1027       // in the order they appear following the word implements (or
1028       // extends) in the method declaration. Use the first doc comment
1029       // found for this method.
1030 
1031       ClassDoc[] interfaces = classDoc.interfaces();
1032       if (null != interfaces) {
1033          for (int i=0; i<interfaces.length; ++i) {
1034             result = checkForInheritedDoc(interfaces[i], memberDoc, tag);
1035             if (null != result) {
1036                return result;
1037             }
1038          }
1039       }
1040 
1041       // 2. If step 1 failed to find a doc comment, recursively apply
1042       // this entire algorithm to each directly implemented (or
1043       // extended) interface, in the same order they were examined
1044       // in step 1.
1045 
1046       if (null != interfaces) {
1047          for (int i=0; i<interfaces.length; ++i) {
1048             result = findInheritedDoc(interfaces[i], memberDoc, tag);
1049             if (null != result) {
1050                return result;
1051             }
1052          }
1053       }
1054 
1055       ClassDoc superclassDoc = classDoc.superclass();
1056 
1057       // 3. If step 2 failed to find a doc comment and this is a class
1058       // other than Object (not an interface):
1059       if (!classDoc.isInterface()
1060           && null != superclassDoc
1061           && !"java.lang.Object".equals(classDoc.qualifiedTypeName())) {
1062 
1063          // 3a. If the superclass has a doc comment for this method, use it.
1064 
1065          result = checkForInheritedDoc(superclassDoc, memberDoc, tag);
1066          if (null != result) {
1067             return result;
1068          }
1069 
1070          // 3b. If step 3a failed to find a doc comment, recursively
1071          // apply this entire algorithm to the superclass.
1072 
1073          return findInheritedDoc(superclassDoc,
1074                                  memberDoc, tag);
1075       }
1076       else {
1077          return null;
1078       }
1079    }
1080 
isEmptyDoc()1081    public boolean isEmptyDoc()
1082    {
1083       return tagMap.isEmpty();
1084    }
1085 
setBoilerplateComment(String boilerplateComment)1086    void setBoilerplateComment(String boilerplateComment)
1087    {
1088       this.boilerplateComment = boilerplateComment;
1089    }
1090 }
1091