View Javadoc

1   /*
2    * Copyright (c) 2001-2003 The XDoclet team
3    * All rights reserved.
4    */
5   package xjavadoc;
6   
7   import xjavadoc.filesystem.AbstractFile;
8   import xjavadoc.tags.TagIntrospector;
9   
10  import java.io.PrintStream;
11  import java.util.*;
12  
13  import org.apache.commons.collections.Predicate;
14  import org.apache.commons.collections.CollectionUtils;
15  
16  /***
17   * This class represents the entry-point for xjavadoc classes. Come here to get
18   * classes and packages.
19   *
20   * @author    Aslak Hellesøy
21   * @created   3. januar 2002
22   */
23  public final class XJavaDoc
24  {
25  	/***
26  	 * Indicates whether this XJavaDoc was built with or without unicode support
27  	 */
28  	public final static String IS_UNICODE = "@IS_UNICODE@";
29  	/***
30  	 * messgage level for reporting unqualified classes when there are no imported
31  	 * packages
32  	 */
33  	public final static int NO_IMPORTED_PACKAGES = 0;
34  	/***
35  	 * messgage level for reporting unqualified classes when there are one or more
36  	 * imported packages
37  	 */
38  	public final static int ONE_OR_MORE_IMPORTED_PACKAGES = 1;
39  
40  	private final static List PRIMITIVES = Collections.unmodifiableList( Arrays.asList( new String[]
41  		{"void", "java.lang.Void.TYPE",
42  		"byte", "java.lang.Byte.TYPE",
43  		"short", "java.lang.Short.TYPE",
44  		"int", "java.lang.Integer.TYPE",
45  		"long", "java.lang.Long.TYPE",
46  		"char", "java.lang.Character.TYPE",
47  		"float", "java.lang.Float.TYPE",
48  		"double", "java.lang.Double.TYPE",
49  		"boolean", "java.lang.Boolean.TYPE"
50  		} ) );
51  
52  	private static HashMap _primitiveClasses = new HashMap();
53  	private final Map  _binaryClasses = new HashMap();
54  	private final Map  _unknownClasses = new HashMap();
55  	private> final Map  _packages = new HashMap();
56  	private final Set  _sourceSets = new HashSet();
57  
58  	/***
59  	 * This map contains all the classes that were passed in the source sets,
60  	 * excluding all inner classes.
61  	 */
62  	private final Map  _sourceSetSourceClasses = new HashMap();
63  
64  	/***
65  	 * This map contains the same classes as _sourceSetSourceClasses, but it is
66  	 * also populated with additional classes that may be accessed that were not in
67  	 * the source sets. This can be superclasses, classes referenced in methods,
68  	 * import statements etc.
69  	 */
70  	private final Map  _allSourceClasses = new HashMap();
71  
72  	private final Set  _sourceSetClassNames = new TreeSet();
73  
74  	private final Map  _properties = new HashMap();
75  
76  	private final Map  _abstractFileClasses = new HashMap();
77  
78      private final XTagFactory _tagFactory = new XTagFactory();
79  
80  	/***
81  	 * This map contains all the classes that were passed in the source sets,
82  	 * including all inner classes.
83  	 */
84  	private Collection _sourceSetSourceClassesWithInnerClasses = new ArrayList();
85  
86  	/***
87  	 * Remember when we're born. We hate sources that are born after us and we
88  	 * pretend they don't exist, because if we don't we'll have very unpredictable
89  	 * behaviour. Well, since we have editor plugin and this is singleton object,
90  	 * we have to relax our policy on this. Or we will have to restart editor every
91  	 * time we like to tag the same class again...
92  	 */
93  	private long       _birthday;
94  
95  	/***
96  	 * info, error and warning messages related to parsing and class qualification
97  	 */
98  	private List       _logMessages = new LinkedList();
99  
100 	/***
101 	 * sticky parameter for useNodeParser. _useNodeParser = true -> slower parsing,
102 	 * but modifiable javaodcs.
103 	 */
104 	private boolean    _useNodeParser = false;
105 
106 	/*** charset for source file */
107 	private String _encoding = null;
108 	
109 	/*** charset for generated file */
110 	private String _docEncoding = null;
111 	
112 	public XJavaDoc()
113 	{
114 		_birthday = System.currentTimeMillis();
115         for( int i = 0; i < PRIMITIVES.size(); i += 2 )
116         {
117             addPrimitive( ( String ) PRIMITIVES.get( i ), ( String ) PRIMITIVES.get( i + 1 ) );
118         }
119 	}
120 
121 	/***
122 	 * Dump to sytem out the status of XJavadoc.
123 	 */
124 	public static void printMemoryStatus()
125 	{
126 		System.out.println( "ParameterImpl instances:   " + ParameterImpl.instanceCount );
127 		System.out.println( "MethodImpl instances:      " + MethodImpl.instanceCount );
128 		System.out.println( "ConstructorImpl instances: " + ConstructorImpl.instanceCount );
129 		System.out.println( "SimpleNode instances:      " + SimpleNode.instanceCount );
130 		System.out.println( "SourceClass instances:     " + SourceClass.instanceCount );
131 		System.out.println( "XDoc instances:            " + XDoc.instanceCount );
132 		System.out.println( "DefaultXTag instances:     " + DefaultXTag.instanceCount );
133 		System.out.println( "BinaryClass instances:     " + BinaryClass.instanceCount );
134 		System.out.println( "UnknownClass instances:    " + UnknownClass.instanceCount );
135 
136 		System.out.println( "Total memory:    " + ( Runtime.getRuntime().totalMemory() / ( 1024 * 1024 ) ) );
137 		System.out.println( "Free memory:    " + Runtime.getRuntime().freeMemory() / ( 1024 * 1024 ) );
138 	}
139 
140 	/***
141 	 * Replaces <code>${xxx}</code> style constructions in the given value with the
142 	 * string value of the corresponding data types. NOTE: This method was taken
143 	 * directly from Ant's source code (org.apache.tools.ant.ProjectHelper) and
144 	 * modified slightly to use a Map instead of a HashMap.
145 	 *
146 	 * @param value  The string to be scanned for property references. May be
147 	 *      <code>null</code> , in which case this method returns immediately with
148 	 *      no effect.
149 	 * @param keys   Mapping (String to String) of property names to their values.
150 	 *      Must not be <code>null</code>.
151 	 * @return       the original string with the properties replaced, or <code>null</code>
152 	 *      if the original string is <code>null</code>.
153 	 */
154 	public static String replaceProperties( String value, Map keys )
155 	{
156 		if( value == null )
157 		{
158 			return null;
159 		}
160 
161 		ArrayList fragments = new ArrayList();
162 		ArrayList propertyRefs = new ArrayList();
163 
164 		parsePropertyString( value, fragments, propertyRefs );
165 
166 		StringBuffer sbuf = new StringBuffer();
167 		Iterator i = fragments.iterator();
168 		Iterator j = propertyRefs.iterator();
169 
170 		while( i.hasNext() )
171 		{
172 			String fragment = ( String ) i.next();
173 
174 			if( fragment == null )
175 			{
176 				String propertyName = ( String ) j.next();
177 
178 				fragment = ( keys.containsKey( propertyName ) ) ? ( String ) keys.get( propertyName )
179 					 : "${" + propertyName + '}';
180 			}
181 			sbuf.append( fragment );
182 		}
183 
184 		return sbuf.toString();
185 	}
186 
187 	/***
188 	 * Parses a string containing <code>${xxx}</code> style property references
189 	 * into two lists. The first list is a collection of text fragments, while the
190 	 * other is a set of string property names. <code>null</code> entries in the
191 	 * first list indicate a property reference from the second list. NOTE: This
192 	 * method was taken directly from Ant's source code
193 	 * ({@link org.apache.tools.ant.ProjectHelper}) with the BuildException throwing
194 	 * removed.
195 	 *
196 	 * @param value         Text to parse. Must not be <code>null</code>.
197 	 * @param fragments     List to add text fragments to. Must not be <code>null</code>.
198 	 * @param propertyRefs  List to add property names to. Must not be <code>null</code>.
199 	 */
200 	public static void parsePropertyString( String value, List fragments, List propertyRefs )
201 	{
202 		int prev = 0;
203 		int pos;
204 
205 		while( ( pos = value.indexOf( '$', prev ) ) >= 0 )
206 		{
207 
208 			if( pos > 0 )
209 			{
210 				String fragment = value.substring( prev, pos );
211 
212 				fragments.add( fragment );
213 			}
214 
215 			if( pos == ( value.length() - 1 ) )
216 			{
217 				fragments.add( "$" );
218 				prev = pos + 1;
219 			}
220 			else if( value.charAt( pos + 1 ) != '{' )
221 			{
222 				fragments.add( value.substring( pos, pos + 1 ) );
223 				prev = pos + 1;
224 			}
225 			else
226 			{
227 				int endName = value.indexOf( '}', pos );
228 
229 				if( endName < 0 )
230 				{
231 					// In Ant this is a BuildException condition as its an
232 					// incomplete property reference. Here we'll leave it
233 					// in the output string
234 					String fragment = value.substring( pos );
235 
236 					fragments.add( fragment );
237 					continue;
238 				}
239 
240 				String propertyName = value.substring( pos + 2, endName );
241 
242 				fragments.add( null );
243 				propertyRefs.add( propertyName );
244 				prev = endName + 1;
245 			}
246 		}
247 
248 		if( prev < value.length() )
249 		{
250 			String fragment = value.substring( prev );
251 
252 			fragments.add( fragment );
253 		}
254 	}
255 
256 	/***
257 	 * Gets the Primitive attribute of the XJavaDoc class
258 	 *
259 	 * @param name  Describe what the parameter does
260 	 * @return      The Primitive value
261 	 */
262 	static Primitive getPrimitive( String name )
263 	{
264 		return ( Primitive ) _primitiveClasses.get( name );
265 	}
266 
267 	/***
268 	 * Gets the file the pe is contained in. Note: calling this method with a
269 	 * XProgramElement not from source (but from a binary or unknown class) will
270 	 * result in a ClassCastException, so don't do that. This method is only used
271 	 * for diagnostics in error reporting.
272 	 *
273 	 * @param pe  the program element we want the source for.
274 	 * @return    the file the program element is contained in.
275 	 */
276 	static AbstractFile getSourceFileFor( XProgramElement pe )
277 	{
278 		SourceClass containingClass = null;
279 
280 		if( !( pe instanceof SourceClass ) )
281 		{
282 			// pe is a field, method or constructor. get the surrounding class
283 			containingClass = ( SourceClass ) pe.getContainingClass();
284 		}
285 		else
286 		{
287 			containingClass = ( SourceClass ) pe;
288 		}
289 		// in case the class is an inner class, loop until we have the outermost.
290 		while( containingClass.getContainingClass() != null )
291 		{
292 			containingClass = ( SourceClass ) containingClass.getContainingClass();
293 		}
294 		return containingClass.getFile();
295 	}
296 
297 	/***
298 	 * Describe the method
299 	 *
300 	 * @param name  Describe the method parameter
301 	 * @param type  The feature to be added to the Primitive attribute
302 	 */
303 	private final void addPrimitive( String name, String type )
304 	{
305 		_primitiveClasses.put( name, new Primitive( this, name, type ) );
306 	}
307 
308 	public Collection getSourceClasses( Predicate predicate )
309 	{
310 		return CollectionUtils.select( getSourceClasses(), predicate );
311 	}
312 
313 	/***
314 	 * Returns all classes in the registered source sets, including inner classes
315 	 *
316 	 * @return   A Collection of XClass
317 	 */
318 	public Collection getSourceClasses()
319 	{
320 		if( _sourceSetSourceClassesWithInnerClasses.isEmpty() )
321 		{
322 			// Add the regular classes
323 			_sourceSetSourceClassesWithInnerClasses.addAll( getOuterSourceClasses() );
324 
325 			// Add inner classes
326 			for( Iterator outers = getOuterSourceClasses().iterator(); outers.hasNext();  )
327 			{
328 				addInnerClassRecursive( (XClass) outers.next(), _sourceSetSourceClassesWithInnerClasses );
329 			}
330 		}
331 		return Collections.unmodifiableCollection( _sourceSetSourceClassesWithInnerClasses );
332 	}
333 
334 	/***
335 	 * Returns the packages of the specified classes during parsing.
336 	 *
337 	 * @return   Describe the return value
338 	 */
339 	public Collection getSourcePackages()
340 	{
341 		Set packages = new TreeSet();
342 		Collection classes = getSourceClasses();
343 
344 		for( Iterator i = classes.iterator(); i.hasNext();  )
345 		{
346 			packages.add( ((XClass)i.next()).getContainingPackage() );
347 		}
348 
349 		returng> Collections.unmodifiableCollection( packages );
350 	}
351 
352 	public Map getPropertyMap()
353 	{
354 		return Collections.unmodifiableMap( _properties );
355 	}
356 
357 	/***
358 	 * Get the XClass corresponding to the qualifiedName. This can be a class from
359 	 * source, a precompiled class or a primitive. UnknownClass is never returned
360 	 * from this method, unless it has been previously instantiated. <b>IMPORTANT:
361 	 * </b> If the Java source can be located, an instance of SourceClass will be
362 	 * returned. -Even if that file was not among the files in the fileset or
363 	 * sourceset. <b>IMPORTANT: </b> If qualifiedName represents an inner class, an
364 	 * UnknownClass will be returned unless the enclousing "outer" class has been
365 	 * resolved first.
366 	 *
367 	 * @param qualifiedName  Fully qualified class name
368 	 * @return               The XClass value
369 	 */
370 	public XClass getXClass( String qualifiedName )
371 	{
372 		if( qualifiedName.equals( "" ) )
373 		{
374 			throw new IllegalStateException( "Classname can't be empty String" );
375 		}
376 
377 		XClass result = null;
378 		Primitive primitive;
379 		SourceClass sourceClass;
380 		BinaryClass binaryClass;
381 		UnknownClass unknownClass;
382 
383 		// first, check all caches
384 		if( ( primitive = getPrimitive( qualifiedName ) ) != null )
385 		{
386 			result = primitive;
387 		}
388 		else if( ( sourceClass = ( SourceClass ) _allSourceClasses.get( qualifiedName ) ) != null )
389 		{
390 			result = sourceClass;
391 		}
392 		else if( ( binaryClass = ( BinaryClass ) _binaryClasses.get( qualifiedName ) ) != null )
393 		{
394 			result = binaryClass;
395 		}
396 		else if( ( unknownClass = ( UnknownClass ) _unknownClasses.get( qualifiedName ) ) != null )
397 		{
398 			result = unknownClass;
399 		}
400 		else
401 		{
402 			// Let's try to read the class from source
403 			if( sourceExists( qualifiedName ) )
404 			{
405 				// The source exists. Let's parse it.
406 				sourceClass = scanAndPut( qualifiedName );
407 				result = sourceClass;
408 			}
409 			else
410 			{
411 				// Couldn't find the class among the sources.
412 				// Try a BinaryClass
413 				Class clazz = getClass( qualifiedName );
414 
415 				if( clazz != null )
416 				{
417 					binaryClass = new BinaryClass( this, clazz );
418 					_binaryClasses.put( qualifiedName, binaryClass );
419 					result = binaryClass;
420 				}
421 				else
422 				{
423 					// Binary didn't exist either. Return an UnknownClass
424 					result = new UnknownClass( this, qualifiedName );
425 					_unknownClasses.put( qualifiedName, result );
426 				}
427 			}
428 		}
429 		return result;
430 	}
431 
432 	/***
433 	 * Returns the package. The package must be one of the packages of the sources.
434 	 * Other packages, such as java.lang are not available.
435 	 *
436 	 * @param packageName
437 	 * @return             an XPackage, or null if the packageName is not among the
438 	 *      sources.
439 	 */
440 	publicXPackage getSourcePackage( String packageName )/package-summary.html">> XPackage getSourcePackage( String packageName )
441 	{
442 		// This is not optimal, but this method is primarily used for testing.
443 		for( Iterator i = getSourcePackages().iterator(); i.hasNext();  )
444 		{
445 			XPackage p = ( XPackage ) i.next();
446 
447 			ifg>( p.getName().equals( packageName ) )
448 			{
449 				return p;
450 			}
451 		}
452 		return null;
453 	}
454 
455 	/***
456 	 * This method can be called prior to parsing so that all classes are parsed
457 	 * with AST (to make it possible to write the source back to disk)
458 	 *
459 	 * @param useNodeParser
460 	 */
461 	public void setUseNodeParser( boolean useNodeParser )
462 	{
463 		_useNodeParser = useNodeParser;
464 	}
465 
466 	public void setPropertyMap( Map properties )
467 	{
468 		_properties.putAll( properties );
469 	}
470 
471 	/***
472 	 * Resets the caches.
473 	 *
474 	 * @param resetTimeStamp  true if timestamps should be reset too.
475 	 */
476 	public void reset( boolean resetTimeStamp )
477 	{
478 		for( Iterator iterator =  _packages.values().iterator(); iterator.hasNext();  )
479 		{
480 			XPackage xPackage = (XPackage) iterator.next();
481 
482 			for( Iterator i = xPackage.getClasses().iterator(); i.hasNext();  )
483 			{
484 				AbstractClass clazz = ( AbstractClass ) i.next();
485 
486 				clazz.reset();
487 			}
488 		}
489 		_binaryClasses.clear();
490 		_unknownClasses.clear();
491 		_packages.clear();
492 		_sourceSets.clear();
493 		_sourceSetSourceClasses.clear();
494 		_sourceSetClassNames.clear();
495 		_allSourceClasses.clear();
496 		_sourceSetSourceClassesWithInnerClasses.clear();
497 
498 		_logMessages.clear();
499 		_properties.clear();
500 		_abstractFileClasses.clear();
501 
502 		//_primitiveClasses = null;
503 
504 		//AbstractProgramElement.NULL_XDOC = null;
505 
506 		// if we start new life, we can as well get new birth certificate,
507 		// so classes saved in previous life can be loaded again without
508 		// hating them :)
509 		if( resetTimeStamp )
510 		{
511 			_birthday = System.currentTimeMillis();
512 		}
513 	}
514 
515 	/***
516 	 * Prints the log messages encountered during parsing
517 	 *
518 	 * @param out
519 	 * @param level
520 	 */
521 	public void printLogMessages( PrintStream out, int level )
522 	{
523 		boolean printedHeader = false;
524 
525 		for( Iterator i = _logMessages.iterator(); i.hasNext();  )
526 		{
527 			LogMessage m = ( LogMessage ) i.next();
528 
529 			if( m._level == level )
530 			{
531 				if( !printedHeader )
532 				{
533 					if( level == ONE_OR_MORE_IMPORTED_PACKAGES )
534 					{
535 						// Could be an inner class too!!!
536 						out.println( "WARNING: Some classes refer to other classes that were not found among the sources or on the classpath." );
537 						out.println( "         (Perhaps the referred class doesn't exist? Hasn't been generated yet?)" );
538 						out.println( "         The referring classes do not import any fully qualified classes matching these classes." );
539 						out.println( "         Since at least one package is imported, it is impossible for xjavadoc to figure out" );
540 						out.println( "         what package the referred classes belong to. The classes are:" );
541 					}
542 					else
543 					{
544 						out.println( "INFO:    Some classes refer to other classes that were not found among the sources or on the classpath." );
545 						out.println( "         (Perhaps the referred class doesn't exist? Hasn't been generated yet?)" );
546 						out.println( "         The referring classes do not import any fully qualified classes matching these classes." );
547 						out.println( "         However, since no packages are imported, xjavadoc has assumed that the referred classes" );
548 						out.println( "         belong to the same package as the referring class. The classes are:" );
549 					}
550 					printedHeader = true;
551 				}
552 				out.println( m._sourceClass.getFile().getPath() + " --> " + m._unqualifiedClassName + " qualified to " + m._unknownClass.getQualifiedName() );
553 			}
554 		}
555 	}
556 
557 	/***
558 	 * Adds a new set of java sources to be parsed.
559 	 *
560 	 * @param sourceSet  a set of java sources.
561 	 */
562 	public void addSourceSet( SourceSet sourceSet )
563 	{
564 		_sourceSets.add( sourceSet );
565 		for( int j = 0; j < sourceSet.getSize(); j++ )
566 		{
567 			String qualifiedName = sourceSet.getQualifiedName( j );
568 
569 			if( _sourceSetClassNames.contains( qualifiedName ) )
570 			{
571 				String msg = "The class \"" + qualifiedName + "\" occurs in more than one fileset. That's illegal.";
572 
573 				System.err.println( msg );
574 			}
575 			_sourceSetClassNames.add( qualifiedName );
576 		}
577 	}
578 
579 	public void addAbstractFile( String qualifiedName, AbstractFile file )
580 	{
581 		_abstractFileClasses.put( qualifiedName, file );
582 
583 		if( _sourceSetClassNames.contains( qualifiedName ) )
584 		{
585 			String msg = "The class \"" + qualifiedName + "\" occurs in more than one fileset. That's illegal.";
586 
587 			System.err.println( msg );
588 		}
589 		_sourceSetClassNames.add( qualifiedName );
590 	}
591 
592 	/***
593 	 * Describe what the method does
594 	 *
595 	 * @param className                qualified name of class
596 	 * @param tagName                  tag name
597 	 * @param parameterName            parameter name
598 	 * @param parameterValue           new parameter value
599 	 * @param tagIndex                 index of tag (??)
600 	 * @param methodNameWithSignature  method name followed by signature. no
601 	 *      spaces. Ex:<br>
602 	 *      <code>doIt(java.lang.String,int)</code>
603 	 * @return                         the class corresponding to the className
604 	 * @exception XJavaDocException    If the tag for some reason couldn't be
605 	 *      updated
606 	 */
607 	public XClass updateMethodTag(
608 		String className,
609 		String methodNameWithSignature,
610 		String tagName,
611 		String parameterName,
612 		String parameterValue,
613 		int tagIndex
614 		 ) throws XJavaDocException
615 	{
616 		XClass clazz = getXClass( className );
617 		XMethod method = clazz.getMethod( methodNameWithSignature );
618 
619 		XDoc doc = method.getDoc();
620 
621 		doc.updateTagValue( tagName, parameterName, parameterValue, tagIndex );
622 
623 		return clazz;
624 	}
625 
626 	/***
627 	 * Describe what the method does
628 	 *
629 	 * @param className              Describe what the parameter does
630 	 * @param tagName                Describe what the parameter does
631 	 * @param parameterName          Describe what the parameter does
632 	 * @param parameterValue         Describe what the parameter does
633 	 * @param tagIndex               Describe what the parameter does
634 	 * @return                       Describe the return value
635 	 * @exception XJavaDocException  Describe the exception
636 	 */
637 	public XClass updateClassTag(
638 		String className,
639 		String tagName,
640 		String parameterName,
641 		String parameterValue,
642 		int tagIndex
643 		 ) throws XJavaDocException
644 	{
645 		XClass clazz = getXClass( className );
646 		XDoc doc = clazz.getDoc();
647 
648 		doc.updateTagValue( tagName, parameterName, parameterValue, tagIndex );
649 		return clazz;
650 	}
651 
652 	public String dereferenceProperties( String value )
653 	{
654 
655 		return replaceProperties( value, _properties );
656 	}
657 
658 	/***
659 	 * @param qualifiedClassName
660 	 * @return                    true if the class exists, either as source or
661 	 *      binary
662 	 */
663 	final boolean classExists( final String qualifiedClassName )
664 	{
665 		// See if we have the source
666 		if( sourceExists( qualifiedClassName ) )
667 		{
668 			return true;
669 		}
670 		// See if we kand find the class (binary)
671 		else if( getClass( qualifiedClassName ) != null )
672 		{
673 			return true;
674 		}
675 		else
676 		{
677 			return false;
678 		}
679 	}
680 
681 	void logMessage( SourceClass clazz, UnknownClass unknownClass, String unqualifiedClassName, int level )
682 	{
683 		_logMessages.add( new LogMessage( clazz, unknownClass, unqualifiedClassName, level ) );
684 	}
685 
686 	/***
687 	 * Describe the method
688 	 *
689 	 * @param packageName  Describe the method parameter
690 	 * @return             Describe the return value
691 	 */
692 	XPackage addPackageMaybe( String packageName )
693 	{
694 		XPackage result = ( XPackage ) _packages.get( packageName );
695 
696 		if( result == null )
697 		{
698 			// The package doesn't exist yet. Add it then
699 			resXPackage( packageName )/package-summary.html">ult = new XPackage( packageName );
700 			_packages.put( packageName, result );
701 		}
702 		return result;
703 	}
704 
705 	/***
706 	 * Adds a source class to the cache. This method is also called from JavaParser
707 	 * when parsing inner classes.
708 	 *
709 	 * @param sourceClass  Describe the method parameter
710 	 */
711 	void addSourceClass( SourceClass sourceClass )
712 	{
713 		_allSourceClasses.put( sourceClass.getQualifiedName(), sourceClass );
714 
715 		// Also add it to _sourceSetSourceClasses if it was among the source sets
716 		// or if it is an "extra" class (this is due to XJD-8)
717 		if( _sourceSetClassNames.contains( sourceClass.getQualifiedName() ) || sourceClass.isExtraClass() )
718 		{
719 			_sourceSetSourceClasses.put( sourceClass.getQualifiedName(), sourceClass );
720 		}
721 	}
722 
723 	/***
724 	 * Returns the Class with the given name, or null if unknown.
725 	 *
726 	 * @param qualifiedName  Describe what the parameter does
727 	 * @return               The Class value
728 	 */
729 	private final Class getClass( String qualifiedName )
730 	{
731 		try
732 		{
733 			return Class.forName( qualifiedName, false, getClass().getClassLoader() );
734 		}
735 		catch( Throwable e )
736 		{
737 			// e can be LinkageError, ClassNotFoundException or ExceptionInInitializerError
738 			// We don't care what we get. If the forName fails, we don't have a class to return
739 			return null;
740 		}
741 	}
742 
743 	/***
744 	 * Returns all classes in the registered source sets
745 	 *
746 	 * @return   A Collection of XClass
747 	 */
748 	private Collection getOuterSourceClasses()
749 	{
750 		if( _sourceSetSourceClasses.isEmpty() )
751 		{
752 			for( Iterator i = _sourceSetClassNames.iterator(); i.hasNext();  )
753 			{
754 				String qualifiedName = ( String ) i.next();
755 
756 				/*
757 				 * This will result in the class being added to
758 				 * _sourceSetSourceClasses AND _allSourceClasses
759 				 */
760 				getXClass( qualifiedName );
761 			}
762 
763 			for( Iterator iterator = _abstractFileClasses.keySet().iterator(); iterator.hasNext();  )
764 			{
765 				String fqcn = ( String ) iterator.next();
766 
767 				getXClass( fqcn );
768 			}
769 		}
770 
771 		//a new collection should be created, becuase we might be looping over classes and generatePerClass for each
772 		//one, now if a new class is discovered and registered (maybe we are analyzing a new class, maybe a template
773 		//is now interested in superclass of a class, maybe ...), we'll get a ConcurrentModificationException. To prevent
774 		//it we need to looping over a new collection (initiated from XJavaDoc mostly).
775 		Collection new_collection = new ArrayList( _sourceSetSourceClasses.values() );
776 
777 		return Collections.unmodifiableCollection( new_collection );
778 	}
779 
780 	/***
781 	 * Gets the SourceFile attribute of the XJavaDoc object
782 	 *
783 	 * @param qualifiedName  Describe what the parameter does
784 	 * @return               The SourceFile value
785 	 */
786 	private AbstractFile getSourceFile( String qualifiedName )
787 	{
788 		// loop over all SourceSets. If a source is found more than once -> bang!
789 		AbstractFile found = null;
790 
791 		for( Iterator i = _sourceSets.iterator(); i.hasNext();  )
792 		{
793 			SourceSet sourceSet = ( SourceSet ) i.next();
794 			AbstractFile javaFile = sourceSet.getSourceFile( qualifiedName );
795 
796 			if( javaFile != null )
797 			{
798 				// isn't this an impossible situation?  Have a look at addSourceSet - we check
799 				// there to ensure that no classes are added twice.....
800 //				if( found != null && !found.getAbsolutePath().equals( javaFile.getAbsolutePath() ) )
801 //				{
802 //					throw new IllegalStateException( "Ambiguous sources for " + qualifiedName + " : " + found.getAbsolutePath() + " or " + javaFile.getAbsolutePath() );
803 //				}
804 				found = javaFile;
805 			}
806 		}
807 		return found;
808 	}
809 
810 	/***
811 	 * Recursively adds inner classes to a collection
812 	 *
813 	 * @param outer  The feature to be added to the InnerClassRecursive attribute
814 	 * @param c      The feature to be added to the InnerClassRecursive attribute
815 	 */
816 	private void addInnerClassRecursive( XClass outer, Collection c )
817 	{
818 		for( Iterator inners = outer.getInnerClasses().iterator(); inners.hasNext();  )
819 		{
820 			XClass inner = (XClass) inners.next();
821 
822 			c.add( inner );
823 			addInnerClassRecursive( inner, c );
824 		}
825 	}
826 
827 	/***
828 	 * Checks is the source exists
829 	 *
830 	 * @param qualifiedName  the class to check for
831 	 * @return               true if source exists.
832 	 */
833 	private boolean sourceExists( String qualifiedName )
834 	{
835 		/*
836 		 * When used with XDoclet, some classes might be resolved by xjavadoc
837 		 * after they are generated by XDoclet.
838 		 * (An example is e.g. a primary key
839 		 * class that doesn't exist before XDoclet is run, and which might be
840 		 * referenced by some of the methods in the @tagged classes).
841 		 * We will pretend that any classes that didn't exist before xjavadoc started
842 		 * scanning sources (generated classes) don't exist. This is to avoid modifying
843 		 * collections that are being iterated over in parallel, as this will throw
844 		 * ConcurrentModificationException. (Aslak)
845 		 */
846 		AbstractFile sourceFile = getSourceFile( qualifiedName );
847 
848 		if( sourceFile != null )
849 		{
850 			if( sourceFile.lastModified() > _birthday )
851 			{
852 				// The source appeared after xjavadoc was reset. Pretend it doesn't exist.
853 				System.out.println( "XJavaDoc Ignoring class " + qualifiedName + " in " + sourceFile.getPath() + ". It was generated (" + new Date( sourceFile.lastModified() ) + ") after XJavaDoc's timestamp was reset (" + new Date( _birthday ) + ")" );
854 				return false;
855 			}
856 		}
857 
858 		boolean sourceFileExists = sourceFile != null;
859 
860 		if( !sourceFileExists )
861 		{
862 			sourceFileExists = _abstractFileClasses.containsKey( qualifiedName );
863 		}
864 
865 		return sourceFileExists;
866 	}
867 
868 	/***
869 	 * Scan's a class and puts it in the cache.
870 	 *
871 	 * @param qualifiedName  Describe what the parameter does
872 	 * @return               Describe the return value
873 	 */
874 	private SourceClass scanAndPut( String qualifiedName )
875 	{
876 		AbstractFile sourceFile = getSourceFile( qualifiedName );
877 
878 		sourceFile = sourceFile != null ? sourceFile : ( AbstractFile ) _abstractFileClasses.get( qualifiedName );
879 
880 		if( sourceFile == null )
881 		{
882 			throw new IllegalStateException( "No source found for " + qualifiedName );
883 		}
884 
885 		SourceClass sourceClass = new SourceClass( this, sourceFile, _useNodeParser, _tagFactory ,_encoding);
886 
887 		// now that the entire file is parsed, validate the tags.
888 		if( _tagFactory.isValidating() )
889 		{
890 			sourceClass.validateTags();
891 		}
892 
893 //		addSourceClass( sourceClass );
894 
895 		return sourceClass;
896 	}
897 
898     public XTagFactory getTagFactory() {
899         return _tagFactory;
900     }
901 
902     /***
903      * Registers tags.
904      *
905      * @param classpath where tags are found.
906      */
907     public void registerTags( String classpath ) {
908         new TagIntrospector().registerTags( classpath, getTagFactory() );
909     }
910 
911 	public final static class NoInnerClassesPredicate implements Predicate
912 	{
913 		public boolean evaluate( Object o )
914 		{
915 			XClass clazz = ( XClass ) o;
916 
917 			return !clazz.isInner();
918 		}
919 	}
920 
921 	class LogMessage
922 	{
923 		public final SourceClass _sourceClass;
924 		public final UnknownClass _unknownClass;
925 		public final String _unqualifiedClassName;
926 		public final int  _level;
927 		LogMessage( SourceClass sourceClass, UnknownClass unknownClass, String unqualifiedClassName, int level )
928 		{
929 			_sourceClass = sourceClass;
930 			_unknownClass = unknownClass;
931 			_unqualifiedClassName = unqualifiedClassName;
932 			_level = level;
933 		}
934 	}
935 
936 	/***
937 	 * Getter for source file charset.
938 	 * @return encoding
939 	 */
940 	public String getEncoding() {
941 		return _encoding;
942 	}
943 
944 	/***
945 	 * Setter for source file charset.
946 	 * @param string encoding
947 	 */
948 	public void setEncoding(String encoding) {
949 		_encoding = encoding;
950 	}
951 
952 	/***
953 	 * Getter for generated file charset.
954 	 * @return encoding
955 	 */
956 	public String getDocEncoding() {
957 		return _docEncoding;
958 	}
959 
960 	/***
961 	 * Setter for generated file charset.
962 	 * @param string encoding 
963 	 */
964 	public void setDocEncoding(String docencoding) {
965 		_docEncoding = docencoding;
966 	}
967 
968 }