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