1
2
3
4
5 package xjavadoc;
6
7 import java.io.BufferedReader;
8 import java.io.IOException;
9 import java.io.StringReader;
10 import java.util.*;
11 import xjavadoc.event.XDocListener;
12 import xjavadoc.event.XDocEvent;
13 import xjavadoc.event.XTagListener;
14 import xjavadoc.event.XTagEvent;
15
16 /***
17 * Represents documentation
18 *
19 * @author Aslak Hellesøy
20 * @created 20. mars 2003
21 */
22 public final class XDoc implements XTagListener
23 {
24 public static int instanceCount = 0;
25
26 /***
27 * Platform specific NEWLINE. Javadoc will use this as new line.
28 */
29 private final static String NEWLINE = System.getProperty( "line.separator" );
30
31 /***
32 * Default comment
33 */
34 private final static String EMPTY_COMMENT = "/**\n */";
35
36 /***
37 * Maps tag name to List. The Collection contains XTag instances whose name =
38 * name (the map key). The tags in the Lists are ordered by occurrance
39 */
40 private Map _tagMap;
41
42 /***
43 * Token (which is linked in the AST) that holds the string representation of
44 * the doc. Needed for printing out the class.
45 */
46 private Token _javadocToken;
47
48 private XProgramElement _owner;
49
50 /***
51 * Contains all the tags in the doc, in order of occurrence.
52 */
53 private List _tags;
54 /***
55 * description of program element
56 */
57 private String _commentText = "";
58 /***
59 * first sentence of comment text
60 */
61 private String _firstSentence;
62
63 private boolean _dirty = true;
64
65 private Set _docListeners = new HashSet();
66
67 private final XTagFactory _tagFactory;
68
69 /***
70 * Describe what the XDoc constructor does
71 *
72 * @param javadocToken Describe what the parameter does
73 * @param owner Describe what the parameter does
74 * @param tagFactory Describe what the parameter does
75 */
76 public XDoc( Token javadocToken, XProgramElement owner, XTagFactory tagFactory )
77 {
78 instanceCount++;
79 if( javadocToken == null )
80 {
81 _javadocToken = Token.newToken( NodeParserConstants.FORMAL_COMMENT );
82 }
83 else
84 {
85 _javadocToken = javadocToken;
86 }
87 _owner = owner;
88 _tagFactory = tagFactory;
89 if( _javadocToken.image == null )
90 {
91
92 _javadocToken.image = EMPTY_COMMENT;
93 }
94 }
95
96 /***
97 * Convert a tag name from the old colon-separated form to the new preferred dot-separated form.
98 *
99 * @param tagName The name of the tag
100 * @return Preferred form of the tag
101 */
102 public static String dotted( final String tagName )
103 {
104 return tagName.replace( ':', '.' );
105 }
106
107 private final static String tokenizeAndTrim( final String s )
108 {
109 StringBuffer sb = new StringBuffer();
110 StringTokenizer st = new StringTokenizer( s );
111
112 while( st.hasMoreTokens() )
113 {
114 sb.append( st.nextToken() ).append( " " );
115 }
116 return sb.toString().trim();
117 }
118
119 /***
120 * Gets the Owner attribute of the XDoc object
121 *
122 * @return The Owner value
123 */
124 public XProgramElement getOwner()
125 {
126 return _owner;
127 }
128
129 /***
130 * Returns all the tags in this doc with the specified tagName (not
131 * superclasses). If No tags are found, an empty Collection is returned.
132 *
133 * @param tagName the name of the tags to return (without the 'at')
134 * @return A Collection of XTag
135 */
136 public List getTags( String tagName )
137 {
138 return getTags( tagName, false );
139 }
140
141 /***
142 * Returns all the tags with the specified tagName. If No tags are found, an
143 * empty Collection is returned.
144 *
145 * @param tagName the name of the tags to return (without the 'at')
146 * @param superclasses if this is true, return tags from superclasses too.
147 * @return A Collection of XTag
148 */
149 public List getTags( String tagName, boolean superclasses )
150 {
151
152 tagName = dotted( tagName );
153 if( _dirty )
154 {
155 parse();
156 }
157
158 ensureTagMapInitialised();
159
160 List tags = ( List ) _tagMap.get( tagName );
161
162 if( !superclasses )
163 {
164 if( tags == null )
165 {
166 tags = AbstractProgramElement.EMPTY_LIST;
167 }
168 return Collections.unmodifiableList( tags );
169 }
170 else
171 {
172
173 LinkedList superTags = new LinkedList();
174
175
176 if( tags != null )
177 {
178 superTags.addAll( tags );
179 }
180
181
182 XDoc superDoc = getSuperDoc();
183
184 if( superDoc != null )
185 {
186 superTags.addAll( superDoc.getTags( tagName, true ) );
187 }
188
189
190
191
192 Iterator docs = getAllSuperDocs().iterator();
193 while ( docs.hasNext() )
194 {
195 XDoc interfaceDoc = (XDoc) docs.next();
196
197 List interfaceTags = interfaceDoc.getTags( tagName, false );
198 superTags.addAll( interfaceTags );
199 }
200
201 return Collections.unmodifiableList( superTags );
202 }
203 }
204
205 /***
206 * Returns all the tags in this doc (not superclasses). If No tags are found,
207 * an empty Collection is returned.
208 *
209 * @return A Collection of XTag
210 */
211 public List getTags()
212 {
213 return getTags( false );
214 }
215
216 /***
217 * Returns all the tags. If no tags are found, an
218 * empty List is returned.
219 *
220 * @param superclasses if this is true, return tags from superclasses too.
221 * @return A List of XTag
222 */
223 public List getTags( boolean superclasses )
224 {
225
226 if( _dirty )
227 {
228 parse();
229 }
230 if( !superclasses )
231 {
232 if( _tags == null )
233 {
234 return AbstractProgramElement.EMPTY_LIST;
235 }
236 else
237 {
238 return _tags;
239 }
240 }
241 else
242 {
243
244 LinkedList tags = new LinkedList();
245
246
247 if( _tags != null )
248 {
249 tags.addAll( _tags );
250 }
251
252
253 XDoc superDoc = getSuperDoc();
254
255 if( superDoc != null )
256 {
257 tags.addAll( superDoc.getTags( true ) );
258 }
259
260
261
262
263 Iterator docs = getAllSuperDocs().iterator();
264 while ( docs.hasNext() )
265 {
266 XDoc interfaceDoc = (XDoc) docs.next();
267
268 List interfaceTags = interfaceDoc.getTags( false );
269 tags.addAll( interfaceTags );
270 }
271
272 return Collections.unmodifiableList( tags );
273 }
274 }
275
276 /***
277 * Get the first tag of name tagName from this doc. Superclasses are not searched.
278 *
279 * @param tagName the name of the tag
280 * @return the tag
281 */
282 public XTag getTag( String tagName )
283 {
284 return getTag( tagName, false );
285 }
286
287 /***
288 * Get the first tag of name tagName.
289 *
290 * @param tagName the name of the tag to get (without the 'at')
291 * @param superclasses if this is true, return tags from superclasses too.
292 * @return the first XTag with name equal to tagName
293 */
294 public XTag getTag( String tagName, boolean superclasses )
295 {
296 tagName = dotted( tagName );
297
298 Collection tags = getTags( tagName, superclasses );
299
300 if( tags.size() == 0 )
301 {
302 return null;
303 }
304 else
305 {
306 return ( XTag ) tags.iterator().next();
307 }
308 }
309
310 /***
311 * Returns the tag attribute value. Does not look in superclasses. If nothing
312 * is found, null is returned.
313 *
314 * @param tagName The name of the tag to look for (without the 'at')
315 * @param attributeName The name of the attribute to look for within the tag.
316 * @return The value of the tag attribute.
317 */
318 public String getTagAttributeValue( String tagName, String attributeName )
319 {
320 return getTagAttributeValue( tagName, attributeName, false );
321 }
322
323 /***
324 * Returns the tag attribute value. If superclasses is true, the first
325 * occurrence is returned when walking up the class hierarchy. If nothing is
326 * found, null is returned.
327 *
328 * @param tagName The name of the tag to look for (without the 'at')
329 * @param attributeName The name of the attribute to look for within the tag.
330 * @param superclasses Set it to true to look in superclasses too.
331 * @return The value of the tag attribute.
332 */
333 public String getTagAttributeValue( String tagName, String attributeName, boolean superclasses )
334 {
335 tagName = dotted( tagName );
336
337
338 for( Iterator tags = getTags( tagName ).iterator(); tags.hasNext(); )
339 {
340 XTag tag = ( XTag ) tags.next();
341 String attributeValue = tag.getAttributeValue( attributeName );
342
343 if( attributeValue != null )
344 {
345
346 return attributeValue;
347 }
348 }
349
350
351
352 if( superclasses )
353 {
354 XDoc superDoc = getSuperDoc();
355
356 if( superDoc != null )
357 {
358
359 String superclassTagValue = superDoc.getTagAttributeValue( tagName, attributeName, true );
360 if (superclassTagValue!=null) return superclassTagValue;
361 }
362
363
364
365 Iterator docs = getAllSuperDocs().iterator();
366 while ( docs.hasNext() )
367 {
368 XDoc interfaceDoc = (XDoc) docs.next();
369
370
371 String tagValue = interfaceDoc.getTagAttributeValue( tagName, attributeName, true );
372 if (tagValue!=null) return tagValue;
373 }
374
375
376 return null;
377
378 }
379 else
380 {
381
382 return null;
383 }
384 }
385
386 /***
387 * return description of program element
388 *
389 * @return description of program element
390 */
391 public String getCommentText()
392 {
393 if( _dirty )
394 {
395 parse();
396 }
397 return _commentText;
398 }
399
400 /***
401 * Return the first sentence of the text of the comment for this doc item.
402 *
403 * @return First sentence
404 */
405 public String getFirstSentence()
406 {
407 if( _dirty )
408 {
409 parse();
410 }
411
412 if( _firstSentence == null )
413 {
414
415 if( _commentText.indexOf( '.' ) == -1 )
416 {
417 _firstSentence = _commentText;
418 return _firstSentence;
419 }
420
421
422
423 int fromIndex = 0;
424
425 while( fromIndex < _commentText.length() - 1 && _firstSentence == null )
426 {
427 int dotIndex = _commentText.indexOf( '.', fromIndex );
428
429 if( dotIndex != -1 && dotIndex < _commentText.length() - 1 )
430 {
431 if( " \t\r\n".indexOf( _commentText.charAt( dotIndex + 1 ) ) != -1 )
432 _firstSentence = _commentText.substring( 0, dotIndex + 1 );
433 else
434 fromIndex = dotIndex + 1;
435 }
436 else
437 _firstSentence = _commentText;
438 }
439
440
441
442 if( _firstSentence == null )
443 {
444 _firstSentence = _commentText;
445 }
446 }
447
448 return _firstSentence;
449 }
450
451 /***
452 * Set the text of the comment for this doc item.
453 *
454 * @param commentText The new comment text
455 */
456 public void setCommentText( String commentText )
457 {
458 if( _dirty )
459 {
460 parse();
461 }
462 _commentText = commentText;
463 _firstSentence = null;
464 fireDocChanged();
465 }
466
467 /***
468 * Returns true if the tag exists. Does not look in superclasses.
469 *
470 * @param tagName The name of the tag to look for (without the 'at')
471 * @return true if the tag exists
472 */
473 public boolean hasTag( String tagName )
474 {
475 return hasTag( tagName, false );
476 }
477
478 /***
479 * Returns true if the tag exists.
480 *
481 * @param tagName The name of the tag to look for (without the 'at')
482 * @param superclasses If true, look in superclasses too.
483 * @return true if the tag exists
484 */
485 public boolean hasTag( String tagName, boolean superclasses )
486 {
487 return getTags( tagName, superclasses ).size() != 0;
488 }
489
490 /***
491 * Utility method to set the value of a tag attribute. If the tag doesn't
492 * exist, it is created. If the attribute doesn't exist it is created. If the
493 * tag attribute exists, it is updated.
494 *
495 * @param tagName The new name of the tag to update (without the
496 * @param tagIndex The index of the tag to update, in case there
497 * are several tags with the same name.
498 * @param attributeName The attribute name
499 * @param attributeValue The new attribute value
500 * @return the updated tag
501 * @exception XJavaDocException
502 */
503 public XTag updateTagValue( String tagName, String attributeName, String attributeValue, int tagIndex ) throws XJavaDocException
504 {
505 XTag tag = null;
506 List tags = getTags( tagName );
507
508 if( tags.size() == 0 || tags.size() <= tagIndex )
509 {
510
511
512 String tagValue = attributeName + "=\"" + attributeValue + "\"";
513
514 tag = addTag_Impl( tagName, tagValue, -1 );
515 }
516 else
517 {
518
519 Iterator tagIterator = tags.iterator();
520
521 for( int i = 0; i < tagIndex; i++ )
522 {
523 tag = (XTag) tagIterator.next();
524 }
525 if( tag != null )
526 {
527 tag.setAttribute( attributeName, attributeValue );
528 }
529 }
530 return tag;
531 }
532
533 /***
534 * Add doc listener interested in changes.
535 *
536 * @param docListener doc listener to register
537 */
538 public void addDocListener( XDocListener docListener )
539 {
540 _docListeners.add( docListener );
541 }
542
543 /***
544 * remove doc listener
545 *
546 * @param docListener
547 */
548 public void removeDocListener( XDocListener docListener )
549 {
550 _docListeners.remove( docListener );
551 }
552
553 /***
554 * Returns a String representation of this doc.
555 *
556 * @return a String representation of this doc.
557 */
558 public String toString()
559 {
560 if( _dirty )
561 {
562 parse();
563 }
564
565 StringBuffer sb = new StringBuffer( "/**" ).append( NEWLINE );
566
567 if( !_commentText.trim().equals( "" ) )
568 {
569 appendWhiteSpaces( sb ).append( " * " ).append( _commentText ).append( NEWLINE );
570 appendWhiteSpaces( sb ).append( " * " ).append( NEWLINE );
571 }
572
573
574
575 for( Iterator tags = getTags().iterator(); tags.hasNext(); )
576 {
577 XTag tag = ( XTag ) tags.next();
578
579 appendWhiteSpaces( sb ).append( " * @" ).append( tag.getName() );
580
581 Collection attributeNames = tag.getAttributeNames();
582
583 if( attributeNames.size() == 0 )
584 {
585
586 sb.append( ' ' ).append( tag.getValue() ).append( NEWLINE );
587 }
588 else
589 {
590 sb.append( NEWLINE );
591 for( Iterator attrs = attributeNames.iterator(); attrs.hasNext(); )
592 {
593 String attibuteName = ( String ) attrs.next();
594 String attributeValue = tag.getAttributeValue( attibuteName );
595
596 appendWhiteSpaces( sb ).append( " * " ).append( attibuteName ).append( "=\"" ).append( attributeValue ).append( "\"" ).append( NEWLINE );
597 }
598 }
599 }
600 appendWhiteSpaces( sb ).append( " */" );
601
602 return sb.toString();
603 }
604
605 /***
606 * update token
607 */
608 public void updateToken()
609 {
610 _javadocToken.image = toString();
611 }
612
613 /***
614 * Removes tag. Note that XTag objects are compared by identity.
615 *
616 * @param tag tag to be removed
617 * @return true if it was removed
618 */
619 public boolean removeTag( XTag tag )
620 {
621 boolean removed = _tags.remove( tag );
622
623 if( removed )
624 {
625
626 ensureTagMapInitialised();
627
628 Collection tags = ( Collection ) _tagMap.get( dotted( tag.getName() ) );
629
630 tags.remove( tag );
631 fireDocChanged();
632 }
633 return removed;
634 }
635
636 /***
637 * Add a tag to the doc item.
638 *
639 * @param tagName The name of the tag to add
640 * @param text The value of the tag
641 * @return The created XTag
642 * @throws TagValidationException if validation is activated (in XTagFactory)
643 * and tagName is not among the registered tags.
644 */
645 public XTag addTag( String tagName, String text )
646 {
647 if( _dirty )
648 {
649 parse();
650 }
651
652 XTag rtag = addTag_Impl( tagName, text, -1 );
653
654
655 fireDocChanged();
656 return rtag;
657 }
658
659 /***
660 * receive change notification from xtag
661 *
662 * @param event
663 */
664 public void tagChanged( XTagEvent event )
665 {
666
667
668 if( event.getSource() instanceof XTag )
669 {
670
671
672
673
674
675
676
677
678 fireDocChanged();
679 }
680 }
681
682 /***
683 * Returns the doc in the superclass. If the super element is null, or not from
684 * source, null is returned.
685 *
686 * @return the superclass' doc
687 */
688 private XDoc getSuperDoc()
689 {
690 XProgramElement superElement = _owner.getSuperElement();
691
692 if( superElement != null )
693 {
694 return superElement.getDoc();
695 }
696 else
697 {
698 return null;
699 }
700 }
701
702 /***
703 * Returns the doc in all the superclasses. If the super element is null, or not from
704 * source, an empty list is returned.
705 *
706 * @return A List of XDoc
707 */
708 private List getAllSuperDocs()
709 {
710 List superElements = _owner.getSuperInterfaceElements();
711
712 if ( superElements!=null )
713 {
714 List result = new ArrayList();
715 Iterator elements = superElements.iterator();
716 while ( elements.hasNext() )
717 {
718 XDoc doc = ( (XProgramElement) elements.next() ).getDoc();
719 result.add(doc);
720 }
721 return result;
722 }
723 else
724 {
725 return Collections.EMPTY_LIST;
726 }
727 }
728
729 private final void ensureTagMapInitialised()
730 {
731 if( _tagMap == null )
732 {
733 _tagMap = new TreeMap();
734 }
735 }
736
737 /***
738 * Creates and adds a tag
739 *
740 * @param tagName The name of the tag (without the 'at')
741 * @param text The raw content of the tag
742 * @param lineNumber The feature to be added to the Tag_Impl
743 * attribute
744 * @return An instance of XTag, created by the
745 * current XTagFactory
746 * @exception TagValidationException
747 */
748 private XTag addTag_Impl( String tagName, String text, int lineNumber ) throws TagValidationException
749 {
750 tagName = dotted( tagName );
751
752 ensureTagMapInitialised();
753
754 Collection tags = ( Collection ) _tagMap.get( tagName );
755
756 if( tags == null )
757 {
758 tags = new LinkedList();
759 _tagMap.put( tagName, tags );
760 }
761
762 if( _tags == null )
763 {
764 _tags = new LinkedList();
765 }
766
767 XTag tag = _tagFactory.createTag( tagName, text, this, lineNumber );
768
769
770 tag.addTagListener( this );
771
772
773 tags.add( tag );
774
775
776 _tags.add( tag );
777
778 return tag;
779 }
780
781 /***
782 * fire docChange event
783 */
784 private void fireDocChanged()
785 {
786 for( Iterator i = _docListeners.iterator(); i.hasNext(); )
787 {
788 XDocListener docListener = ( XDocListener ) i.next();
789
790 docListener.docChanged( new XDocEvent( this ) );
791 }
792
793
794 if( _owner != null )
795 {
796 XClass clazz = _owner instanceof XClass ? ( XClass ) _owner : _owner.getContainingClass();
797
798 clazz.setDirty();
799 }
800 }
801
802 /***
803 * Add some white space to the string being built up in toString().
804 *
805 * @param sb StringBuffer that the text is being built in
806 * @return the StringBuffer
807 */
808 private StringBuffer appendWhiteSpaces( StringBuffer sb )
809 {
810 return sb.append( " " );
811
812
813
814
815
816
817
818
819
820
821
822 }
823
824 /***
825 * Parse token into comments, tags and tag attributes. We remove excess spaces.
826 *
827 * @exception TagValidationException
828 */
829 private void parse() throws TagValidationException
830 {
831 if( _dirty )
832 {
833
834
835 JavaDocReader javaDocReader = new JavaDocReader( new StringReader( _javadocToken.image ) );
836 BufferedReader in = new BufferedReader( javaDocReader );
837 StringBuffer docElement = new StringBuffer();
838 String tagName = null;
839 String line = null;
840
841 int tagStartLine = -1;
842
843 try
844 {
845 while( ( line = in.readLine() ) != null )
846 {
847 if( line.startsWith( "@" ) )
848 {
849
850 tagStartLine = _javadocToken.beginLine + javaDocReader.getLineOffset();
851
852
853 if( tagName == null )
854 {
855
856 _commentText = tokenizeAndTrim( docElement.toString() );
857 }
858 else
859 {
860
861 addTag_Impl( tagName, tokenizeAndTrim( docElement.toString() ), tagStartLine );
862 }
863 docElement = new StringBuffer();
864
865 StringTokenizer st = new StringTokenizer( line );
866
867 tagName = st.nextToken().substring( 1 );
868 docElement.append( line.substring( tagName.length() + 1 ).trim() ).append( ' ' );
869 }
870 else
871 {
872
873 if( docElement == null )
874 {
875
876 docElement = new StringBuffer();
877 }
878 docElement.append( line.trim() ).append( ' ' );
879 }
880 }
881 if( tagName == null )
882 {
883
884 _commentText = docElement.toString().trim();
885 }
886 else
887 {
888
889 addTag_Impl( tagName, tokenizeAndTrim( docElement.toString() ), tagStartLine );
890 }
891 }
892 catch( IOException e )
893 {
894 e.printStackTrace();
895 }
896 catch( StringIndexOutOfBoundsException e )
897 {
898 e.printStackTrace();
899 }
900 _dirty = false;
901 }
902 }
903
904 }