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 	 * (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 	 *      .
199 	 * @param propertyRefs  List to add property names to. Must not be <code>null</code>
200 	 */
201 	public static void parsePropertyString( String value, List fragments, List propertyRefs )
202 	{
203 		int prev = 0;
204 		int pos;
205 
206 		while( ( pos = value.indexOf( '$', prev ) ) >= 0 )
207 		{
208 
209 			if( pos > 0 )
210 			{
211 				String fragment = value.substring( prev, pos );
212 
213 				fragments.add( fragment );
214 			}
215 
216 			if( pos == ( value.length() - 1 ) )
217 			{
218 				fragments.add( "$" );
219 				prev = pos + 1;
220 			}
221 			else if( value.charAt( pos + 1 ) != '{' )
222 			{
223 				fragments.add( value.substring( pos, pos + 1 ) );
224 				prev = pos + 1;
225 			}
226 			else
227 			{
228 				int endName = value.indexOf( '}', pos );
229 
230 				if( endName < 0 )
231 				{
232 					// In Ant this is a BuildException condition as its an
233 					// incomplete property reference. Here we'll leave it
234 					// in the output string
235 					String fragment = value.substring( pos );
236 
237 					fragments.add( fragment );
238 					continue;
239 				}
240 
241 				String propertyName = value.substring( pos + 2, endName );
242 
243 				fragments.add( null );
244 				propertyRefs.add( propertyName );
245 				prev = endName + 1;
246 			}
247 		}
248 
249 		if( prev < value.length() )
250 		{
251 			String fragment = value.substring( prev );
252 
253 			fragments.add( fragment );
254 		}
255 	}
256 
257 	/***
258 	 * Gets the Primitive attribute of the XJavaDoc class
259 	 *
260 	 * @param name  Describe what the parameter does
261 	 * @return      The Primitive value
262 	 */
263 	static Primitive getPrimitive( String name )
264 	{
265 		return ( Primitive ) _primitiveClasses.get( name );
266 	}
267 
268 	/***
269 	 * Gets the file the pe is contained in. Note: calling this method with a
270 	 * XProgramElement not from source (but from a binary or unknown class) will
271 	 * result in a ClassCastException, so don't do that. This method is only used
272 	 * for diagnostics in error reporting.
273 	 *
274 	 * @param pe  the program element we want the source for.
275 	 * @return    the file the program element is contained in.
276 	 */
277 	static AbstractFile getSourceFileFor( XProgramElement pe )
278 	{
279 		SourceClass containingClass = null;
280 
281 		if( !( pe instanceof SourceClass ) )
282 		{
283 			// pe is a field, method or constructor. get the surrounding class
284 			containingClass = ( SourceClass ) pe.getContainingClass();
285 		}
286 		else
287 		{
288 			containingClass = ( SourceClass ) pe;
289 		}
290 		// in case the class is an inner class, loop until we have the outermost.
291 		while( containingClass.getContainingClass() != null )
292 		{
293 			containingClass = ( SourceClass ) containingClass.getContainingClass();
294 		}
295 		return containingClass.getFile();
296 	}
297 
298 	/***
299 	 * Describe the method
300 	 *
301 	 * @param name  Describe the method parameter
302 	 * @param type  The feature to be added to the Primitive attribute
303 	 */
304 	private final void addPrimitive( String name, String type )
305 	{
306 		_primitiveClasses.put( name, new Primitive( this, name, type ) );
307 	}
308 
309 	public Collection getSourceClasses( Predicate predicate )
310 	{
311 		return CollectionUtils.select( getSourceClasses(), predicate );
312 	}
313 
314 	/***
315 	 * @param useNodeParser
316 	 * @param inner
317 	 * @return   A Collection of XClass
318 	 * @deprecated           Call setUseNodeParser() prior to parsing and call the
319 	 *      no-arg getSourceClasses() or getSourceClasses(Predicate) with a
320 	 *      Predicate that doesn't accept inner classes.
321 	 */
322 	public Collection getSourceClasses( boolean useNodeParser, boolean inner )
323 	{
324 		if( inner )
325 		{
326 			return getSourceClasses();
327 		}
328 		else
329 		{
330 			return getSourceClasses( new NoInnerClassesPredicate() );
331 		}
332 	}
333 
334 	/***
335 	 * @param useNodeParser
336 	 * @return   A Collection of XClass
337 	 * @deprecated           Call setUseNodeParser() prior to parsing and call the
338 	 *      no-arg getSourceClasses()
339 	 */
340 	public Collection getSourceClasses( boolean useNodeParser )
341 	{
342 		return getSourceClasses();
343 	}
344 
345 	/***
346 	 * Returns all classes in the registered source sets, including inner classes
347 	 *
348 	 * @return   A Collection of XClass
349 	 */
350 	public Collection getSourceClasses()
351 	{
352 		if( _sourceSetSourceClassesWithInnerClasses.isEmpty() )
353 		{
354 			// Add the regular classes
355 			_sourceSetSourceClassesWithInnerClasses.addAll( getOuterSourceClasses() );
356 
357 			// Add inner classes
358 			for( Iterator outers = getOuterSourceClasses().iterator(); outers.hasNext();  )
359 			{
360 				addInnerClassRecursive( (XClass) outers.next(), _sourceSetSourceClassesWithInnerClasses );
361 			}
362 		}
363 		return Collections.unmodifiableCollection( _sourceSetSourceClassesWithInnerClasses );
364 	}
365 
366 	/***
367 	 * Returns the packages of the specified classes during parsing.
368 	 *
369 	 * @return   Describe the return value
370 	 */
371 	public Collection getSourcePackages()
372 	{
373 		Set packages = new TreeSet();
374 		Collection classes = getSourceClasses();
375 
376 		for( Iterator i = classes.iterator(); i.hasNext();  )
377 		{
378 			packages.add( ((XClass)i.next()).getContainingPackage() );
379 		}
380 
381 		returng> Collections.unmodifiableCollection( packages );
382 	}
383 
384 	public Map getPropertyMap()
385 	{
386 		return Collections.unmodifiableMap( _properties );
387 	}
388 
389 	/***
390 	 * Get the XClass corresponding to the qualifiedName. This can be a class from
391 	 * source, a precompiled class or a primitive. UnknownClass is never returned
392 	 * from this method, unless it has been previously instantiated. <b>IMPORTANT:
393 	 * </b> If the Java source can be located, an instance of SourceClass will be
394 	 * returned. -Even if that file was not among the files in the fileset or
395 	 * sourceset. <b>IMPORTANT: </b> If qualifiedName represents an inner class, an
396 	 * UnknownClass will be returned unless the enclousing "outer" class has been
397 	 * resolved first.
398 	 *
399 	 * @param qualifiedName  Fully qualified class name
400 	 * @return               The XClass value
401 	 */
402 	public XClass getXClass( String qualifiedName )
403 	{
404 		if( qualifiedName.equals( "" ) )
405 		{
406 			throw new IllegalStateException( "Classname can't be empty String" );
407 		}
408 
409 		XClass result = null;
410 		Primitive primitive;
411 		SourceClass sourceClass;
412 		BinaryClass binaryClass;
413 		UnknownClass unknownClass;
414 
415 		// first, check all caches
416 		if( ( primitive = getPrimitive( qualifiedName ) ) != null )
417 		{
418 			result = primitive;
419 		}
420 		else if( ( sourceClass = ( SourceClass ) _allSourceClasses.get( qualifiedName ) ) != null )
421 		{
422 			result = sourceClass;
423 		}
424 		else if( ( binaryClass = ( BinaryClass ) _binaryClasses.get( qualifiedName ) ) != null )
425 		{
426 			result = binaryClass;
427 		}
428 		else if( ( unknownClass = ( UnknownClass ) _unknownClasses.get( qualifiedName ) ) != null )
429 		{
430 			result = unknownClass;
431 		}
432 		else
433 		{
434 			// Let's try to read the class from source
435 			if( sourceExists( qualifiedName ) )
436 			{
437 				// The source exists. Let's parse it.
438 				sourceClass = scanAndPut( qualifiedName );
439 				result = sourceClass;
440 			}
441 			else
442 			{
443 				// Couldn't find the class among the sources.
444 				// Try a BinaryClass
445 				Class clazz = getClass( qualifiedName );
446 
447 				if( clazz != null )
448 				{
449 					binaryClass = new BinaryClass( this, clazz );
450 					_binaryClasses.put( qualifiedName, binaryClass );
451 					result = binaryClass;
452 				}
453 				else
454 				{
455 					// Binary didn't exist either. Return an UnknownClass
456 					result = new UnknownClass( this, qualifiedName );
457 					_unknownClasses.put( qualifiedName, result );
458 				}
459 			}
460 		}
461 		return result;
462 	}
463 
464 	/***
465 	 * Returns the package. The package must be one of the packages of the sources.
466 	 * Other packages, such as java.lang are not available.
467 	 *
468 	 * @param packageName
469 	 * @return             an XPackage, or null if the packageName is not among the
470 	 *      sources.
471 	 */
472 	publicXPackage getSourcePackage( String packageName )/package-summary.html">> XPackage getSourcePackage( String packageName )
473 	{
474 		// This is not optimal, but this method is primarily used for testing.
475 		for( Iterator i = getSourcePackages().iterator(); i.hasNext();  )
476 		{
477 			XPackage p = ( XPackage ) i.next();
478 
479 			ifg>( p.getName().equals( packageName ) )
480 			{
481 				return p;
482 			}
483 		}
484 		return null;
485 	}
486 
487 	/***
488 	 * This method can be called prior to parsing so that all classes are parsed
489 	 * with AST (to make it possible to write the source back to disk)
490 	 *
491 	 * @param useNodeParser
492 	 */
493 	public void setUseNodeParser( boolean useNodeParser )
494 	{
495 		_useNodeParser = useNodeParser;
496 	}
497 
498 	public void setPropertyMap( Map properties )
499 	{
500 		_properties.putAll( properties );
501 	}
502 
503 	/***
504 	 * Resets the caches. Same as calling <code>reset(true)</code>.
505 	 *
506 	 * @deprecated   use reset(boolean).
507 	 */
508 	public void reset()
509 	{
510 		reset( true );
511 	}
512 
513 	/***
514 	 * Resets the caches.
515 	 *
516 	 * @param resetTimeStamp  true if timestamps should be reset too.
517 	 */
518 	public void reset( boolean resetTimeStamp )
519 	{
520 		for( Iterator iterator =  _packages.values().iterator(); iterator.hasNext();  )
521 		{
522 			XPackage xPackage = (XPackage) iterator.next();
523 
524 			for( Iterator i = xPackage.getClasses().iterator(); i.hasNext();  )
525 			{
526 				AbstractClass clazz = ( AbstractClass ) i.next();
527 
528 				clazz.reset();
529 			}
530 		}
531 		_binaryClasses.clear();
532 		_unknownClasses.clear();
533 		_packages.clear();
534 		_sourceSets.clear();
535 		_sourceSetSourceClasses.clear();
536 		_sourceSetClassNames.clear();
537 		_allSourceClasses.clear();
538 		_sourceSetSourceClassesWithInnerClasses.clear();
539 
540 		_logMessages.clear();
541 		_properties.clear();
542 		_abstractFileClasses.clear();
543 
544 		//_primitiveClasses = null;
545 
546 		//AbstractProgramElement.NULL_XDOC = null;
547 
548 		// if we start new life, we can as well get new birth certificate,
549 		// so classes saved in previous life can be loaded again without
550 		// hating them :)
551 		if( resetTimeStamp )
552 		{
553 			_birthday = System.currentTimeMillis();
554 		}
555 	}
556 
557 	/***
558 	 * Prints the log messages encountered during parsing
559 	 *
560 	 * @param out
561 	 * @param level
562 	 */
563 	public void printLogMessages( PrintStream out, int level )
564 	{
565 		boolean printedHeader = false;
566 
567 		for( Iterator i = _logMessages.iterator(); i.hasNext();  )
568 		{
569 			LogMessage m = ( LogMessage ) i.next();
570 
571 			if( m._level == level )
572 			{
573 				if( !printedHeader )
574 				{
575 					if( level == ONE_OR_MORE_IMPORTED_PACKAGES )
576 					{
577 						// Could be an inner class too!!!
578 						out.println( "WARNING: Some classes refer to other classes that were not found among the sources or on the classpath." );
579 						out.println( "         (Perhaps the referred class doesn't exist? Hasn't been generated yet?)" );
580 						out.println( "         The referring classes do not import any fully qualified classes matching these classes." );
581 						out.println( "         Since at least one package is imported, it is impossible for xjavadoc to figure out" );
582 						out.println( "         what package the referred classes belong to. The classes are:" );
583 					}
584 					else
585 					{
586 						out.println( "INFO:    Some classes refer to other classes that were not found among the sources or on the classpath." );
587 						out.println( "         (Perhaps the referred class doesn't exist? Hasn't been generated yet?)" );
588 						out.println( "         The referring classes do not import any fully qualified classes matching these classes." );
589 						out.println( "         However, since no packages are imported, xjavadoc has assumed that the referred classes" );
590 						out.println( "         belong to the same package as the referring class. The classes are:" );
591 					}
592 					printedHeader = true;
593 				}
594 				out.println( m._sourceClass.getFile().getPath() + " --> " + m._unqualifiedClassName + " qualified to " + m._unknownClass.getQualifiedName() );
595 			}
596 		}
597 	}
598 
599 	/***
600 	 * Adds a new set of java sources to be parsed.
601 	 *
602 	 * @param sourceSet  a set of java sources.
603 	 */
604 	public void addSourceSet( SourceSet sourceSet )
605 	{
606 		_sourceSets.add( sourceSet );
607 		for( int j = 0; j < sourceSet.getSize(); j++ )
608 		{
609 			String qualifiedName = sourceSet.getQualifiedName( j );
610 
611 			if( _sourceSetClassNames.contains( qualifiedName ) )
612 			{
613 				String msg = "The class \"" + qualifiedName + "\" occurs in more than one fileset. That's illegal.";
614 
615 				System.err.println( msg );
616 			}
617 			_sourceSetClassNames.add( qualifiedName );
618 		}
619 	}
620 
621 	public void addAbstractFile( String qualifiedName, AbstractFile file )
622 	{
623 		_abstractFileClasses.put( qualifiedName, file );
624 
625 		if( _sourceSetClassNames.contains( qualifiedName ) )
626 		{
627 			String msg = "The class \"" + qualifiedName + "\" occurs in more than one fileset. That's illegal.";
628 
629 			System.err.println( msg );
630 		}
631 		_sourceSetClassNames.add( qualifiedName );
632 	}
633 
634 	/***
635 	 * Describe what the method does
636 	 *
637 	 * @param className                qualified name of class
638 	 * @param tagName                  tag name
639 	 * @param parameterName            parameter name
640 	 * @param parameterValue           new parameter value
641 	 * @param tagIndex                 index of tag (??)
642 	 * @param methodNameWithSignature  method name followed by signature. no
643 	 *      spaces. Ex:<br>
644 	 *      <code>doIt(java.lang.String,int)</code>
645 	 * @return                         the class corresponding to the className
646 	 * @exception XJavaDocException    If the tag for some reason couldn't be
647 	 *      updated
648 	 */
649 	public XClass updateMethodTag(
650 		String className,
651 		String methodNameWithSignature,
652 		String tagName,
653 		String parameterName,
654 		String parameterValue,
655 		int tagIndex
656 		 ) throws XJavaDocException
657 	{
658 		XClass clazz = getXClass( className );
659 		XMethod method = clazz.getMethod( methodNameWithSignature );
660 
661 		XDoc doc = method.getDoc();
662 
663 		doc.updateTagValue( tagName, parameterName, parameterValue, tagIndex );
664 
665 		return clazz;
666 	}
667 
668 	/***
669 	 * Describe what the method does
670 	 *
671 	 * @param className              Describe what the parameter does
672 	 * @param tagName                Describe what the parameter does
673 	 * @param parameterName          Describe what the parameter does
674 	 * @param parameterValue         Describe what the parameter does
675 	 * @param tagIndex               Describe what the parameter does
676 	 * @return                       Describe the return value
677 	 * @exception XJavaDocException  Describe the exception
678 	 */
679 	public XClass updateClassTag(
680 		String className,
681 		String tagName,
682 		String parameterName,
683 		String parameterValue,
684 		int tagIndex
685 		 ) throws XJavaDocException
686 	{
687 		XClass clazz = getXClass( className );
688 		XDoc doc = clazz.getDoc();
689 
690 		doc.updateTagValue( tagName, parameterName, parameterValue, tagIndex );
691 		return clazz;
692 	}
693 
694 	public String dereferenceProperties( String value )
695 	{
696 
697 		return replaceProperties( value, _properties );
698 	}
699 
700 	/***
701 	 * @param qualifiedClassName
702 	 * @return                    true if the class exists, either as source or
703 	 *      binary
704 	 */
705 	final boolean classExists( final String qualifiedClassName )
706 	{
707 		// See if we have the source
708 		if( sourceExists( qualifiedClassName ) )
709 		{
710 			return true;
711 		}
712 		// See if we kand find the class (binary)
713 		else if( getClass( qualifiedClassName ) != null )
714 		{
715 			return true;
716 		}
717 		else
718 		{
719 			return false;
720 		}
721 	}
722 
723 	void logMessage( SourceClass clazz, UnknownClass unknownClass, String unqualifiedClassName, int level )
724 	{
725 		_logMessages.add( new LogMessage( clazz, unknownClass, unqualifiedClassName, level ) );
726 	}
727 
728 	/***
729 	 * Describe the method
730 	 *
731 	 * @param packageName  Describe the method parameter
732 	 * @return             Describe the return value
733 	 */
734 	XPackage addPackageMaybe( String packageName )
735 	{
736 		XPackage result = ( XPackage ) _packages.get( packageName );
737 
738 		if( result == null )
739 		{
740 			// The package doesn't exist yet. Add it then
741 			resXPackage( packageName )/package-summary.html">ult = new XPackage( packageName );
742 			_packages.put( packageName, result );
743 		}
744 		return result;
745 	}
746 
747 	/***
748 	 * Adds a source class to the cache. This method is also called from JavaParser
749 	 * when parsing inner classes.
750 	 *
751 	 * @param sourceClass  Describe the method parameter
752 	 */
753 	void addSourceClass( SourceClass sourceClass )
754 	{
755 		_allSourceClasses.put( sourceClass.getQualifiedName(), sourceClass );
756 
757 		// Also add it to _sourceSetSourceClasses if it was among the source sets
758 		// or if it is an "extra" class (this is due to XJD-8)
759 		if( _sourceSetClassNames.contains( sourceClass.getQualifiedName() ) || sourceClass.isExtraClass() )
760 		{
761 			_sourceSetSourceClasses.put( sourceClass.getQualifiedName(), sourceClass );
762 		}
763 	}
764 
765 	/***
766 	 * Returns the Class with the given name, or null if unknown.
767 	 *
768 	 * @param qualifiedName  Describe what the parameter does
769 	 * @return               The Class value
770 	 */
771 	private final Class getClass( String qualifiedName )
772 	{
773 		try
774 		{
775 			return Class.forName( qualifiedName, false, getClass().getClassLoader() );
776 		}
777 		catch( Throwable e )
778 		{
779 			// e can be LinkageError, ClassNotFoundException or ExceptionInInitializerError
780 			// We don't care what we get. If the forName fails, we don't have a class to return
781 			return null;
782 		}
783 	}
784 
785 	/***
786 	 * Returns all classes in the registered source sets
787 	 *
788 	 * @return   A Collection of XClass
789 	 */
790 	private Collection getOuterSourceClasses()
791 	{
792 		if( _sourceSetSourceClasses.isEmpty() )
793 		{
794 			for( Iterator i = _sourceSetClassNames.iterator(); i.hasNext();  )
795 			{
796 				String qualifiedName = ( String ) i.next();
797 
798 				/*
799 				 * This will result in the class being added to
800 				 * _sourceSetSourceClasses AND _allSourceClasses
801 				 */
802 				getXClass( qualifiedName );
803 			}
804 
805 			for( Iterator iterator = _abstractFileClasses.keySet().iterator(); iterator.hasNext();  )
806 			{
807 				String fqcn = ( String ) iterator.next();
808 
809 				getXClass( fqcn );
810 			}
811 		}
812 
813 		//a new collection should be created, becuase we might be looping over classes and generatePerClass for each
814 		//one, now if a new class is discovered and registered (maybe we are analyzing a new class, maybe a template
815 		//is now interested in superclass of a class, maybe ...), we'll get a ConcurrentModificationException. To prevent
816 		//it we need to looping over a new collection (initiated from XJavaDoc mostly).
817 		Collection new_collection = new ArrayList( _sourceSetSourceClasses.values() );
818 
819 		return Collections.unmodifiableCollection( new_collection );
820 	}
821 
822 	/***
823 	 * Gets the SourceFile attribute of the XJavaDoc object
824 	 *
825 	 * @param qualifiedName  Describe what the parameter does
826 	 * @return               The SourceFile value
827 	 */
828 	private AbstractFile getSourceFile( String qualifiedName )
829 	{
830 		// loop over all SourceSets. If a source is found more than once -> bang!
831 		AbstractFile found = null;
832 
833 		for( Iterator i = _sourceSets.iterator(); i.hasNext();  )
834 		{
835 			SourceSet sourceSet = ( SourceSet ) i.next();
836 			AbstractFile javaFile = sourceSet.getSourceFile( qualifiedName );
837 
838 			if( javaFile != null )
839 			{
840 				// isn't this an impossible situation?  Have a look at addSourceSet - we check
841 				// there to ensure that no classes are added twice.....
842 //				if( found != null && !found.getAbsolutePath().equals( javaFile.getAbsolutePath() ) )
843 //				{
844 //					throw new IllegalStateException( "Ambiguous sources for " + qualifiedName + " : " + found.getAbsolutePath() + " or " + javaFile.getAbsolutePath() );
845 //				}
846 				found = javaFile;
847 			}
848 		}
849 		return found;
850 	}
851 
852 	/***
853 	 * Recursively adds inner classes to a collection
854 	 *
855 	 * @param outer  The feature to be added to the InnerClassRecursive attribute
856 	 * @param c      The feature to be added to the InnerClassRecursive attribute
857 	 */
858 	private void addInnerClassRecursive( XClass outer, Collection c )
859 	{
860 		for( Iterator inners = outer.getInnerClasses().iterator(); inners.hasNext();  )
861 		{
862 			XClass inner = (XClass) inners.next();
863 
864 			c.add( inner );
865 			addInnerClassRecursive( inner, c );
866 		}
867 	}
868 
869 	/***
870 	 * Checks is the source exists
871 	 *
872 	 * @param qualifiedName  the class to check for
873 	 * @return               true if source exists.
874 	 */
875 	private boolean sourceExists( String qualifiedName )
876 	{
877 		/*
878 		 * When used with XDoclet, some classes might be resolved by xjavadoc
879 		 * after they are generated by XDoclet.
880 		 * (An example is e.g. a primary key
881 		 * class that doesn't exist before XDoclet is run, and which might be
882 		 * referenced by some of the methods in the @tagged classes).
883 		 * We will pretend that any classes that didn't exist before xjavadoc started
884 		 * scanning sources (generated classes) don't exist. This is to avoid modifying
885 		 * collections that are being iterated over in parallel, as this will throw
886 		 * ConcurrentModificationException. (Aslak)
887 		 */
888 		AbstractFile sourceFile = getSourceFile( qualifiedName );
889 
890 		if( sourceFile != null )
891 		{
892 			if( sourceFile.lastModified() > _birthday )
893 			{
894 				// The source appeared after xjavadoc was reset. Pretend it doesn't exist.
895 				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 ) + ")" );
896 				return false;
897 			}
898 		}
899 
900 		boolean sourceFileExists = sourceFile != null;
901 
902 		if( !sourceFileExists )
903 		{
904 			sourceFileExists = _abstractFileClasses.containsKey( qualifiedName );
905 		}
906 
907 		return sourceFileExists;
908 	}
909 
910 	/***
911 	 * Scan's a class and puts it in the cache.
912 	 *
913 	 * @param qualifiedName  Describe what the parameter does
914 	 * @return               Describe the return value
915 	 */
916 	private SourceClass scanAndPut( String qualifiedName )
917 	{
918 		AbstractFile sourceFile = getSourceFile( qualifiedName );
919 
920 		sourceFile = sourceFile != null ? sourceFile : ( AbstractFile ) _abstractFileClasses.get( qualifiedName );
921 
922 		if( sourceFile == null )
923 		{
924 			throw new IllegalStateException( "No source found for " + qualifiedName );
925 		}
926 
927 		SourceClass sourceClass = new SourceClass( this, sourceFile, _useNodeParser, _tagFactory ,_encoding);
928 
929 		// now that the entire file is parsed, validate the tags.
930 		if( _tagFactory.isValidating() )
931 		{
932 			sourceClass.validateTags();
933 		}
934 
935 //		addSourceClass( sourceClass );
936 
937 		return sourceClass;
938 	}
939 
940     public XTagFactory getTagFactory() {
941         return _tagFactory;
942     }
943 
944     /***
945      * Registers tags.
946      *
947      * @param classpath where tags are found.
948      */
949     public void registerTags( String classpath ) {
950         new TagIntrospector().registerTags( classpath, getTagFactory() );
951     }
952 
953 	public final static class NoInnerClassesPredicate implements Predicate
954 	{
955 		public boolean evaluate( Object o )
956 		{
957 			XClass clazz = ( XClass ) o;
958 
959 			return !clazz.isInner();
960 		}
961 	}
962 
963 	class LogMessage
964 	{
965 		public final SourceClass _sourceClass;
966 		public final UnknownClass _unknownClass;
967 		public final String _unqualifiedClassName;
968 		public final int  _level;
969 		LogMessage( SourceClass sourceClass, UnknownClass unknownClass, String unqualifiedClassName, int level )
970 		{
971 			_sourceClass = sourceClass;
972 			_unknownClass = unknownClass;
973 			_unqualifiedClassName = unqualifiedClassName;
974 			_level = level;
975 		}
976 	}
977 
978 	/***
979 	 * Getter for source file charset.
980 	 * @return encoding
981 	 */
982 	public String getEncoding() {
983 		return _encoding;
984 	}
985 
986 	/***
987 	 * Setter for source file charset.
988 	 * @param string encoding
989 	 */
990 	public void setEncoding(String encoding) {
991 		_encoding = encoding;
992 	}
993 
994 	/***
995 	 * Getter for generated file charset.
996 	 * @return encoding
997 	 */
998 	public String getDocEncoding() {
999 		return _docEncoding;
1000 	}
1001 
1002 	/***
1003 	 * Setter for generated file charset.
1004 	 * @param string encoding 
1005 	 */
1006 	public void setDocEncoding(String docencoding) {
1007 		_docEncoding = docencoding;
1008 	}
1009 
1010 }