View Javadoc

    1   /*
    2    * Copyright (c) 2001-2003 The XDoclet team
    3    * All rights reserved.
    4    */
    5   package xjavadoc;
    6
    7   import java.util.*;
    8
    9   import xjavadoc.event.XTagListener;
    10  import xjavadoc.event.XTagEvent;
    11  import xjavadoc.filesystem.AbstractFile;
    12
    13  /***
    14   * @author    Aslak Hellesøy
    15   * @created   11. januar 2002
    16   */
    17  public class DefaultXTag implements XTag
    18  {
    19  	public static int  instanceCount = 0;
    20
    21  	/***
    22  	 * tag name
    23  	 */
    24  	private String     _name;
    25
    26  	/***
    27  	 * string representation of tag
    28  	 */
    29  	private String     _value;
    30
    31  	/***
    32  	 * attribute map
    33  	 */
    34  	private Map        _attributes;
    35
    36  	/***
    37  	 * Ordered List of attribute names
    38  	 */
    39  	private List       _attributeNames = null;
    40
    41  	/***
    42  	 * tag parse status
    43  	 */
    44  	private boolean    _isParsed = false;
    45
    46  	private int        _hash = Integer.MIN_VALUE;
    47
    48  	/***
    49  	 * indicate dirty state
    50  	 */
    51  	private boolean    _isDirty = false;
    52
    53  	private XDoc       _doc;
    54
    55  	/***
    56  	 * tag listeners interested in changes. This would be parent xdoc
    57  	 */
    58  	private Set        _tagListeners;
    59
    60  	private int        _lineNumber;
    61
    62      private XJavaDoc _xJavaDoc;
    63
    64  	public DefaultXTag()
    65  	{
    66  		instanceCount++;
    67  	}
    68
    69  	/***
    70  	 * Skips whitespaces, starting from index i till the first non-whitespace
    71  	 * character or end of s and returns the new index.
    72  	 *
    73  	 * @param s  Describe what the parameter does
    74  	 * @param i  Describe what the parameter does
    75  	 * @return   Describe the return value
    76  	 */
    77  	private static int skipWhitespace( String s, int i )
    78  	{
    79  		while( i < s.length() && Character.isWhitespace( s.charAt( i ) ) )
    80  		{
    81  			i++;
    82  		}
    83  		return i;
    84  	}
    85
    86  	public final XDoc getDoc()
    87  	{
    88  		return _doc;
    89  	}
    90
    91  	/***
    92  	 * Returns the first tag parameter with the given name, or null if none exist;
    93  	 *
    94  	 * @param attributeName  Describe what the parameter does
    95  	 * @return               The Parameter value
    96  	 */
    97  	public final String getAttributeValue( String attributeName )
    98  	{
    99  		if( !_isParsed )
    100 		{
    101 			parse();
    102 		}
    103
    104 		return _attributes == null ? null : ( String ) _attributes.get( attributeName );
    105 	}
    106
    107 	/***
    108 	 * Returns all tag parameters with the given name, or an empty List if none
    109 	 * exist;
    110 	 *
    111 	 * @return   The Parameters value
    112 	 */
    113 	public final Collection getAttributeNames()
    114 	{
    115 		if( !_isParsed )
    116 		{
    117 			parse();
    118 		}
    119 		return _attributeNames == null ? AbstractProgramElement.EMPTY_LIST : _attributeNames;
    120 	}
    121
    122 	/***
    123 	 * Returns the full name of the tag, excluding the @
    124 	 *
    125 	 * @return   tag name
    126 	 */
    127 	public final String getName()
    128 	{
    129 		if( !_isParsed )
    130 		{
    131 			parse();
    132 		}
    133 		return _name;
    134 	}
    135
    136 	/***
    137 	 * Returns the full value of the tag.
    138 	 *
    139 	 * @return   full value of the tag
    140 	 */
    141 	public final String getValue()
    142 	{
    143 		return _value;
    144 	}
    145
    146 	public final int getLineNumber()
    147 	{
    148 		return _lineNumber;
    149 	}
    150
    151 	public final String getInfo()
    152 	{
    153 		XProgramElement pe = getDoc().getOwner();
    154 		AbstractFile file = XJavaDoc.getSourceFileFor( pe );
    155
    156 		return "@" + getName() + " at " + file.getPath() + ":" + getLineNumber();
    157 	}
    158
    159 	/***
    160 	 * Adds a parameter
    161 	 *
    162 	 * @param attributeName   The new Attribute value
    163 	 * @param attributeValue  The new Attribute value
    164 	 */
    165 	public final void setAttribute( String attributeName, String attributeValue )
    166 	{
    167 		if( !_isParsed )
    168 		{
    169 			parse();
    170 		}
    171 		setAttribute_Impl( attributeName, attributeValue );
    172 		fireTagChanged();
    173 		_isDirty = true;
    174 		_value = null;
    175 	}
    176
    177 	/***
    178 	 * add doc listener interested in chages
    179 	 *
    180 	 * @param tagListener  The feature to be added to the TagListener attribute
    181 	 */
    182 	public final void addTagListener( XTagListener tagListener )
    183 	{
    184 		ensureTagListenersInitialised();
    185
    186 		_tagListeners.add( tagListener );
    187 	}
    188
    189 	/***
    190 	 * remove doc listener
    191 	 *
    192 	 * @param tagListener
    193 	 */
    194 	public final void removeTagListener( XTagListener tagListener )
    195 	{
    196 		ensureTagListenersInitialised();
    197
    198 		_tagListeners.remove( tagListener );
    199 	}
    200
    201 	/***
    202 	 * Removes an attribute
    203 	 *
    204 	 * @param attributeName  atribute to remove
    205 	 * @return               the removed attribute value or null if it didn't exist
    206 	 */
    207 	public final String removeAttribute( String attributeName )
    208 	{
    209 		if( !_isParsed )
    210 		{
    211 			parse();
    212 		}
    213 		_isDirty = true;
    214         resetValue();
    215 		fireTagChanged();
    216
    217 		String removed = ( String ) _attributes.remove( attributeName );
    218
    219 		if( removed != null )
    220 		{
    221 			_attributeNames.remove( attributeName );
    222 			_value = null;
    223 		}
    224 		return removed;
    225 	}
    226
    227 	public final boolean equals( Object o )
    228 	{
    229 		// we compare by equality
    230 		return this == o;
    231 	}
    232
    233 	public final int hashCode()
    234 	{
    235 		if( _hash == Integer.MIN_VALUE )
    236 		{
    237 			_hash += _name.hashCode();
    238 		}
    239 		return _hash;
    240 	}
    241
    242 	/***
    243 	 * Validates the tag
    244 	 *
    245 	 * @exception TagValidationException
    246 	 */
    247 	public void validate() throws TagValidationException
    248 	{
    249 		// Default is OK.
    250 	}
    251
    252     /***
    253      * Utility method that should be called from {@link #validate()} in
    254      * case ov a validation failure. Throws a new TagValidationException
    255      * with
    256      * @param message the message to include
    257      * @throws TagValidationException always thrown.
    258      */
    259     protected final void fail(String message) throws TagValidationException {
    260         throw new TagValidationException( message, this );
    261     }
    262
    263 	/***
    264 	 * Sets the name and value. Called immediately after initialisation by
    265 	 * XTagFactory. Don't call this method from anywhere else.
    266 	 *
    267 	 * @param name
    268 	 * @param value
    269 	 * @param doc
    270 	 * @param lineNumber
    271 	 */
    272 	final void init( String name, String value, XDoc doc, int lineNumber )
    273 	{
    274 		_name = name;
    275 		_doc = doc;
    276 		_lineNumber = lineNumber;
    277 		_isDirty = false;
    278         _value = value;
    279
    280 		// we register ourself as one of the global tags in the corresponding
    281 		// SourceClass. This is to make it easier for the SourceClass to
    282 		// loop over all tags and ask them to validate themselves when the parsing
    283 		// is done.
    284
    285 		if( doc != null )
    286 		{
    287 			// In fact, doc should never be null. -But some of the JUnit tests
    288 			// fail to set up mocks properly, so they pass in null for doc.
    289 			// This is only to avoid NPE from tht JUnit tests. It's a dirty
    290 			// hack and the tests should be fixed.
    291 			XProgramElement owner = doc.getOwner();
    292             _xJavaDoc = owner.getXJavaDoc();
    293             _value = _xJavaDoc.dereferenceProperties( value );
    294
    295 			if( owner != null )
    296 			{
    297 				SourceClass sourceClass;
    298
    299 				if( owner.getContainingClass() != null )
    300 				{
    301 					sourceClass = ( SourceClass ) owner.getContainingClass();
    302 				}
    303 				else
    304 				{
    305 					sourceClass = ( SourceClass ) owner;
    306 				}
    307 				sourceClass.addTagForValidation( this );
    308 			}
    309 		}
    310 	}
    311
    312 	private final void setAttribute_Impl( String attributeName, String attributeValue )
    313 	{
    314 		if( attributeName == null )
    315 		{
    316 			throw new IllegalArgumentException( "attributeName can't be null!" );
    317 		}
    318 		if( _attributes == null )
    319 		{
    320 			_attributes = new HashMap();
    321 			_attributeNames = new LinkedList();
    322 		}
    323 		if( !_attributes.containsKey( attributeName ) )
    324 		{
    325 			// New attribute. Just append it.
    326 			_attributeNames.add( attributeName );
    327 		}
    328
    329         if( _xJavaDoc != null ) {
    330             attributeValue = _xJavaDoc.dereferenceProperties( attributeValue );
    331         }
    332 		_attributes.put( attributeName, attributeValue );
    333
    334         resetValue();
    335 	}
    336
    337 	private final void ensureTagListenersInitialised()
    338 	{
    339 		if( _tagListeners == null )
    340 		{
    341 			_tagListeners = new HashSet();
    342 		}
    343 	}
    344
    345 	/***
    346 	 * fire tagChanged event
    347 	 */
    348 	private void fireTagChanged()
    349 	{
    350 		if( _tagListeners == null )
    351 			return;
    352
    353 		for( Iterator i = _tagListeners.iterator(); i.hasNext();  )
    354 		{
    355 			XTagListener tagListener = ( XTagListener ) i.next();
    356
    357 			tagListener.tagChanged( new XTagEvent( this ) );
    358 		}
    359 	}
    360
    361 	/***
    362 	 * Given the raw javadoc tag content as the <i>value</i> parameter parses it
    363 	 * and sets the parameter. If anything is malformed (not (foo="xxx")+), then
    364 	 * nothing is set.
    365 	 */
    366 	private final void parse()
    367 	{
    368 		if( !_isParsed )
    369 		{
    370 			String attributeName = null;
    371 			StringBuffer attributeValue = new StringBuffer();
    372 			int i = 0;
    373 			int end = 0;
    374
    375 			String value = getValue();
    376
    377 			while( i < value.length() )
    378 			{
    379 				i = skipWhitespace( value, i );
    380
    381 				//explicitly to handle the tailing white spaces
    382
    383 				if( i >= value.length() )
    384 				{
    385 					break;
    386 				}
    387
    388 				//read attribute name
    389
    390 				end = i;
    391 				while( end < value.length() && value.charAt( end ) != '=' && ( !Character.isWhitespace( value.charAt( end ) ) ) )
    392 				{
    393 					end++;
    394 				}
    395
    396 				attributeName = value.substring( i, end );
    397 				i = skipWhitespace( value, end );
    398
    399 				//skip = sign
    400
    401 				if( i < value.length() && value.charAt( i ) == '=' )
    402 				{
    403 					i++;
    404 				}
    405
    406 				/*
    407 				 * removed single valued
    408 				 */
    409 				i = skipWhitespace( value, i );
    410
    411 				//skip " sign
    412
    413 				if( i < value.length() && value.charAt( i ) == '"' )
    414 				{
    415 					i++;
    416 				}
    417 				else
    418 				{
    419 					//if (_log.isDebugEnabled()) _log.debug("Error in @tag: \" sign expected but something different found, @tags=" + value);
    420 					_isParsed = true;
    421 					return;
    422 				}
    423
    424 				//read attribute value
    425
    426 				while( i < value.length() )
    427 				{
    428 					if( value.charAt( i ) == '"' )
    429 					{
    430 						//if not escaped \" char
    431
    432 						if( value.charAt( i - 1 ) != '//' )
    433 						{
    434 							//if last " (last parameter) in whole value string
    435
    436 							if( i + 1 >= value.length() )
    437 							{
    438 								break;
    439 							}
    440 							else
    441 							{
    442 								//if tailing " with whitespace after it
    443
    444 								if( Character.isWhitespace( value.charAt( i + 1 ) ) )
    445 								{
    446 									break;
    447 								}
    448 								else
    449 								{
    450 									//probably user does not know escaping is needed!
    451 									//if (_log.isDebugEnabled()) _log.debug("Error in @tag: to put \" in a parameter value you need to escape \" character with //\", @tags=" + value);
    452 									_isParsed = true;
    453 									return;
    454 								}
    455 							}
    456 						}
    457 						else
    458 						{
    459 							//remove previous \
    460 							attributeValue.delete( attributeValue.length() - 1, attributeValue.length() );
    461 						}
    462 					}
    463
    464 					attributeValue.append( value.charAt( i ) );
    465
    466 					i++;
    467 				}
    468
    469 				//skip " sign
    470
    471 				if( i < value.length() && value.charAt( i ) == '"' )
    472 				{
    473 					i++;
    474 				}
    475 				else
    476 				{
    477 					//_log.warn("Error in @tag: tailing \" sign expected but not found, @tags=" + value);
    478 					_isParsed = true;
    479 					return;
    480 				}
    481 				setAttribute_Impl( attributeName, attributeValue.toString() );
    482 				attributeName = null;
    483 				attributeValue.delete( 0, attributeValue.length() );
    484 			}
    485 			_isParsed = true;
    486 		}
    487 	}
    488
    489     private final void resetValue() {
    490         StringBuffer sb = new StringBuffer();
    491
    492         if( _attributeNames != null )
    493         {
    494             for( Iterator attributeNames = _attributeNames.iterator(); attributeNames.hasNext();  )
    495             {
    496                 String attributeName = ( String ) attributeNames.next();
    497                 String attributeValue = ( String ) _attributes.get( attributeName );
    498
    499                 sb.append( attributeName );
    500                 sb.append( "=\"" );
    501                 sb.append( attributeValue.trim() );
    502                 sb.append( "\" " );
    503             }
    504         }
    505         _value = sb.toString().trim();
    506     }
    507 }