View Javadoc

1   /*
2    * Copyright (c) 2001-2003 The XDoclet team
3    * All rights reserved.
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  			// the passed token was not from source code, but was created because no javadoc existed.
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 			// Make a new Collection where we append all tags from this and super
173 			LinkedList superTags = new LinkedList();
174 
175 			// Add tags from this doc if any.
176 			if( tags != null )
177 			{
178 				superTags.addAll( tags );
179 			}
180 
181 			// Now add tags from super
182 			XDoc superDoc = getSuperDoc();
183 
184 			if( superDoc != null )
185 			{
186 				superTags.addAll( superDoc.getTags( tagName, true ) );
187 			}
188 
189 			// Now add tags from implemented interfaces
190 			// TODO: How are we going to ensure uniqueness ...
191 			// we could get to the same interface tag twice
192 			Iterator docs = getAllSuperDocs().iterator();
193 			while ( docs.hasNext() )
194 			{
195 				XDoc interfaceDoc = (XDoc) docs.next();
196 				// Perhaps the last argument to getTagAttributeValue() should be true
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 			// Make a new Collection where we append all tags from this and super
244 			LinkedList tags = new LinkedList();
245 
246 			// Add tags from this doc if any.
247 			if( _tags != null )
248 			{
249 				tags.addAll( _tags );
250 			}
251 
252 			// Now add tags from super
253 			XDoc superDoc = getSuperDoc();
254 
255 			if( superDoc != null )
256 			{
257 				tags.addAll( superDoc.getTags( true ) );
258 			}
259 
260 			// Now add tags from implemented interfaces
261 			// TODO: How are we going to ensure uniqueness ...
262 			// we could get to the same interface tag twice
263 			Iterator docs = getAllSuperDocs().iterator();
264 			while ( docs.hasNext() )
265 			{
266 				XDoc interfaceDoc = (XDoc) docs.next();
267 				// Perhaps the last argument to getTagAttributeValue() should be true
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 		// Get all the tags, loop over them and return the first occurrence of the attribute.
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 				// found one! Return that.
346 				return attributeValue;
347 			}
348 		}
349 
350 		// Couldn't find anything here. Ask superclasses
351 
352 		if( superclasses )
353 		{
354 			XDoc superDoc = getSuperDoc();
355 
356 			if( superDoc != null )
357 			{
358 				// prefer tags defined on a superclass to tags on interfaces
359 				String superclassTagValue = superDoc.getTagAttributeValue( tagName, attributeName, true );
360 				if (superclassTagValue!=null) return superclassTagValue;
361 			}
362 
363 
364 			// check interfaces!
365 			Iterator docs = getAllSuperDocs().iterator();
366 			while ( docs.hasNext() )
367 			{
368 				XDoc interfaceDoc = (XDoc) docs.next();
369 				// Note: this will do a "depth first" search, is that desirable?
370 				// Perhaps the last argument to getTagAttributeValue() should be false
371 				String tagValue = interfaceDoc.getTagAttributeValue( tagName, attributeName, true );
372 				if (tagValue!=null) return tagValue;
373 			}
374 
375 			// We've come to an end. Nothing found. Return null;
376 			return null;
377 
378 		}
379 		else
380 		{
381 			// Don't look in superclasses or implemented interfaces. Just return null;
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 			// Ok, we only have one sentence
415 			if( _commentText.indexOf( '.' ) == -1 )
416 			{
417 				_firstSentence = _commentText;
418 				return _firstSentence;
419 			}
420 
421 			// Let's look for the first sentence separator. It should be a dot followed
422 			// by a blank, tab or line terminator.
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 			// We didn't find a proper first sentence separator. So we only have
441 			// one sentence.
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 			//debug("@" + tagName + " at index " + tagIndex + " doesn't exist. creating new tag");
511 			// There was no such tags. Create a new one.
512 			String tagValue = attributeName + "=\"" + attributeValue + "\"";
513 
514 			tag = addTag_Impl( tagName, tagValue, -1 );
515 		}
516 		else
517 		{
518 			// Iterate to the tag at the right index
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 //		addSpaces(sb, _javadocToken).append(" * ").append(NEWLINE);
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 				// no parameters, or malformed
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 			// purge it from tag map too
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 		// fire doc change event
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 		// invalidate attribute value cache
667 		// for tag in question
668 		if( event.getSource() instanceof XTag )
669 		{
670 			//XTag tag = ( XTag ) event.getSource();
671 
672 			/*
673 			 * if( tagAttrValueCurrent != null )
674 			 * {
675 			 * tagAttrValueCurrent.remove( tag.getName() );
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 		// We want to be notified when this tag changes
770 		tag.addTagListener( this );
771 
772 		// Add to the Collection in the map
773 		tags.add( tag );
774 
775 		// Add to the global tag list
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 		// also set containing class to dirty
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 		 * Token tk = _programElementToken;
813 		 * while (tk.previous != null && isTabOrSpace(tk.previous)) {
814 		 * tk = tk.previous;
815 		 * }
816 		 * while (tk.next != null && tk != _programElementToken) {
817 		 * sb.append(tk.image);
818 		 * tk = tk.next;
819 		 * }
820 		 * return sb;
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 			//debug("parse");
834 			// We must read line by line, since a @tags can only begin as the first token of a line.
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 						// remember the line number where the tag starts.
850 						tagStartLine = _javadocToken.beginLine + javaDocReader.getLineOffset();
851 
852 						// It's a new tag
853 						if( tagName == null )
854 						{
855 							// what we've been reading so far has been a general comment.
856 							_commentText = tokenizeAndTrim( docElement.toString() );
857 						}
858 						else
859 						{
860 							// Add the previous tag
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 						// It's the continuation of a tag or a comment, or start of comment;
873 						if( docElement == null )
874 						{
875 							// It was the start of the comment
876 							docElement = new StringBuffer();
877 						}
878 						docElement.append( line.trim() ).append( ' ' );
879 					}
880 				}
881 				if( tagName == null )
882 				{
883 					// what we've been reading so far has been a general comment.
884 					_commentText = docElement.toString().trim();
885 				}
886 				else
887 				{
888 					// Add the previous tag
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 }