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 }