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 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  			// the passed token was not from source code, but was created because no javadoc existed.
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 			// Make a new Collection where we append all tags from this and super
166 			LinkedList superTags = new LinkedList();
167 
168 			// Add tags from this doc if any.
169 			if( tags != null )
170 			{
171 				superTags.addAll( tags );
172 			}
173 
174 			// Now add tags from super
175 			XDoc superDoc = getSuperDoc();
176 
177 			if( superDoc != null )
178 			{
179 				superTags.addAll( superDoc.getTags( tagName, true ) );
180 			}
181 
182 			// Now add tags from implemented interfaces
183 			// TODO: How are we going to ensure uniqueness ...
184 			// we could get to the same interface tag twice
185 			Iterator docs = getAllSuperDocs().iterator();
186 			while ( docs.hasNext() )
187 			{
188 				XDoc interfaceDoc = (XDoc) docs.next();
189 				// Perhaps the last argument to getTagAttributeValue() should be true
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 			// Make a new Collection where we append all tags from this and super
237 			LinkedList tags = new LinkedList();
238 
239 			// Add tags from this doc if any.
240 			if( _tags != null )
241 			{
242 				tags.addAll( _tags );
243 			}
244 
245 			// Now add tags from super
246 			XDoc superDoc = getSuperDoc();
247 
248 			if( superDoc != null )
249 			{
250 				tags.addAll( superDoc.getTags( true ) );
251 			}
252 
253 			// Now add tags from implemented interfaces
254 			// TODO: How are we going to ensure uniqueness ...
255 			// we could get to the same interface tag twice
256 			Iterator docs = getAllSuperDocs().iterator();
257 			while ( docs.hasNext() )
258 			{
259 				XDoc interfaceDoc = (XDoc) docs.next();
260 				// Perhaps the last argument to getTagAttributeValue() should be true
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 		// Get all the tags, loop over them and return the first occurrance of the attribute.
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 				// found one! Return that.
339 				return attributeValue;
340 			}
341 		}
342 
343 		// Couldn't find anything here. Ask superclasses
344 
345 		if( superclasses )
346 		{
347 			XDoc superDoc = getSuperDoc();
348 
349 			if( superDoc != null )
350 			{
351 				// prefer tags defined on a superclass to tags on interfaces
352 				String superclassTagValue = superDoc.getTagAttributeValue( tagName, attributeName, true );
353 				if (superclassTagValue!=null) return superclassTagValue;
354 			}
355 
356 
357 			// check interfaces!
358 			Iterator docs = getAllSuperDocs().iterator();
359 			while ( docs.hasNext() )
360 			{
361 				XDoc interfaceDoc = (XDoc) docs.next();
362 				// Note: this will do a "depth first" search, is that desirable?
363 				// Perhaps the last argument to getTagAttributeValue() should be false
364 				String tagValue = interfaceDoc.getTagAttributeValue( tagName, attributeName, true );
365 				if (tagValue!=null) return tagValue;
366 			}
367 
368 			// We've come to an end. Nothing found. Return null;
369 			return null;
370 
371 		}
372 		else
373 		{
374 			// Don't look in superclasses or implemented interfaces. Just return null;
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 			// Ok, we only have one sentence
408 			if( _commentText.indexOf( '.' ) == -1 )
409 			{
410 				_firstSentence = _commentText;
411 				return _firstSentence;
412 			}
413 
414 			// Let's look for the first sentence separator. It should be a dot followed
415 			// by a blank, tab or line terminator.
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 			// We did'n found a proper first sentence separator. So we only have
434 			// one sentence.
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 			//debug("@" + tagName + " at index " + tagIndex + " doesn't exist. creating new tag");
504 			// There was no such tags. Create a new one.
505 			String tagValue = attributeName + "=\"" + attributeValue + "\"";
506 
507 			tag = addTag_Impl( tagName, tagValue, -1 );
508 		}
509 		else
510 		{
511 			// Iterate to the tag at the right index
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 //		addSpaces(sb, _javadocToken).append(" * ").append(NEWLINE);
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 				// no parameters, or malformed
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 			// purge it from tag map too
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 		// fire doc change event
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 		// invalidate attribute value cache
660 		// for tag in question
661 		if( event.getSource() instanceof XTag )
662 		{
663 			//XTag tag = ( XTag ) event.getSource();
664 
665 			/*
666 			 * if( tagAttrValueCurrent != null )
667 			 * {
668 			 * tagAttrValueCurrent.remove( tag.getName() );
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 		// We want to be notified when this tag changes
757 		tag.addTagListener( this );
758 
759 		// Add to the Collection in the map
760 		tags.add( tag );
761 
762 		// Add to the global tag list
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 		// also set containing class tro dirty
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 		 * Token tk = _programElementToken;
800 		 * while (tk.previous != null && isTabOrSpace(tk.previous)) {
801 		 * tk = tk.previous;
802 		 * }
803 		 * while (tk.next != null && tk != _programElementToken) {
804 		 * sb.append(tk.image);
805 		 * tk = tk.next;
806 		 * }
807 		 * return sb;
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 			//debug("parse");
821 			// We must read line by line, since a @tags can only begin as the first token of a line.
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 						// remember the line number where the tag starts.
837 						tagStartLine = _javadocToken.beginLine + javaDocReader.getLineOffset();
838 
839 						// It's a new tag
840 						if( tagName == null )
841 						{
842 							// what we've been reading so far has been a general comment.
843 							_commentText = tokenizeAndTrim( docElement.toString() );
844 						}
845 						else
846 						{
847 							// Add the previous tag
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 						// It's the continuation of a tag or a comment, or start of comment;
860 						if( docElement == null )
861 						{
862 							// It was the start of the comment
863 							docElement = new StringBuffer();
864 						}
865 						docElement.append( line.trim() ).append( ' ' );
866 					}
867 				}
868 				if( tagName == null )
869 				{
870 					// what we've been reading so far has been a general comment.
871 					_commentText = docElement.toString().trim();
872 				}
873 				else
874 				{
875 					// Add the previous tag
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 }