1
2
3
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
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
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
281
282
283
284
285 if( doc != null )
286 {
287
288
289
290
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
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
382
383 if( i >= value.length() )
384 {
385 break;
386 }
387
388
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
400
401 if( i < value.length() && value.charAt( i ) == '=' )
402 {
403 i++;
404 }
405
406
407
408
409 i = skipWhitespace( value, i );
410
411
412
413 if( i < value.length() && value.charAt( i ) == '"' )
414 {
415 i++;
416 }
417 else
418 {
419
420 _isParsed = true;
421 return;
422 }
423
424
425
426 while( i < value.length() )
427 {
428 if( value.charAt( i ) == '"' )
429 {
430
431
432 if( value.charAt( i - 1 ) != '//' )
433 {
434
435
436 if( i + 1 >= value.length() )
437 {
438 break;
439 }
440 else
441 {
442
443
444 if( Character.isWhitespace( value.charAt( i + 1 ) ) )
445 {
446 break;
447 }
448 else
449 {
450
451
452 _isParsed = true;
453 return;
454 }
455 }
456 }
457 else
458 {
459
460 attributeValue.delete( attributeValue.length() - 1, attributeValue.length() );
461 }
462 }
463
464 attributeValue.append( value.charAt( i ) );
465
466 i++;
467 }
468
469
470
471 if( i < value.length() && value.charAt( i ) == '"' )
472 {
473 i++;
474 }
475 else
476 {
477
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 }