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 }