1
2
3
4
5 package xjavadoc;
6
7 import xjavadoc.filesystem.AbstractFile;
8 import xjavadoc.filesystem.XJavadocFile;
9 import xjavadoc.filesystem.ReaderFile;
10
11 import java.util.*;
12 import java.io.*;
13
14 /***
15 * This class represents a class for which the source code is available
16 *XJavaDocFil
17 * @author Aslak Hellesøy
18 * @created 3. januar 2002
19 */
20 public final class SourceClass extends AbstractClass
21 {
22 public static int instanceCount = 0;
23
24 private final Map _qualifiedClasses = new HashMap();
25
26 private final boolean _isExtraClass;
27
28 private final List _tagsForValidation = new ArrayList();
29
30 /***
31 * The root node of the AST
32 */
33 private SimpleNode _compilationUnit;
34
35 private Reader _in = null;
36
37 /***
38 * Keep a ref to the file in case of warning reporting
39 */
40 private AbstractFile _sourceFile;
41
42
43
44 /***
45 * doe we nees saving?
46 */
47 private boolean _dirty;
48
49 /***
50 * Constructor to use for inner classes.
51 *
52 * @param containingClass The containing class;
53 */
54 public SourceClass( SourceClass containingClass, XTagFactory tagFactory )
55 {
56 super( containingClass, tagFactory );
57 setContainingPackage( containingClass.getContainingPackage().getName() );
58 _isExtraClass = false;
59 }
60
61 /***
62 * Constructor to use for "extra" classes, that is, secondary classes that
63 * figure in the same source.
64 *
65 * @param mainClass The containing class. Or rather the "main" class in the
66 * source.
67 * @param dummy
68 */
69 public SourceClass( SourceClass mainClass, int dummy, XTagFactory tagFactory )
70 {
71 super( mainClass.getXJavaDoc(), tagFactory );
72 setContainingPackage( mainClass.getContainingPackage().getName() );
73 _isExtraClass = true;
74 _sourceFile = mainClass.getFile();
75 }
76
77 /***
78 * Constructor to use for outer classes
79 *
80 * @param sourceFile The file containing the source
81 */
82 public SourceClass( XJavaDoc xJavaDoc, File sourceFile, XTagFactory tagFactory )
83 {
84 this( xJavaDoc, new XJavadocFile( sourceFile ), false, tagFactory, null );
85 }
86
87 /***
88 * Constructor to use for outer classes
89 *
90 * @param sourceFile The file containing the source
91 */
92 public SourceClass( XJavaDoc xJavaDoc, Reader sourceFile, XTagFactory tagFactory )
93 {
94 this( xJavaDoc, new ReaderFile( sourceFile ), false, tagFactory, null);
95 }
96
97 /***
98 * @param sourceFile
99 * @param useNodeParser
100 */
101 public SourceClass( XJavaDoc xJavaDoc, File sourceFile, boolean useNodeParser, XTagFactory tagFactory )
102 {
103 this( xJavaDoc, new XJavadocFile( sourceFile ), useNodeParser, tagFactory ,null );
104 }
105
106 /***
107 * Constructor to use for outer classes
108 *
109 * @param sourceFile The file containing the source
110 * @param useNodeParser
111 */
112 public SourceClass( XJavaDoc xJavaDoc, AbstractFile sourceFile, boolean useNodeParser, XTagFactory tagFactory ,String encoding)
113 {
114 super( xJavaDoc, tagFactory );
115 if( sourceFile == null )
116 {
117 throw new IllegalArgumentException( "sourceFile can't be null for outer classes!" );
118 }
119 _sourceFile = sourceFile;
120
121 try
122 {
123 _in = sourceFile.getReader(encoding);
124 parse( useNodeParser );
125 }
126 catch( IOException e )
127 {
128
129 if(encoding==null)
130 {
131 throw new IllegalStateException( "Couldn't find " + sourceFile );
132 }
133 else
134 {
135 throw new IllegalStateException( "Invalid Encoding '"+encoding+"' or couldn't find '" + sourceFile +"'");
136 }
137 }
138
139 instanceCount++;
140 _dirty = false;
141 _isExtraClass = false;
142 }
143
144 /***
145 * Describe what the method does
146 *
147 * @param qualifiedName Describe what the parameter does
148 * @return Describe the return value
149 */
150 public static String getFileName( String qualifiedName )
151 {
152 return qualifiedName.replace( '.', File.separatorChar ) + ".java";
153 }
154
155 public boolean isExtraClass()
156 {
157 return _isExtraClass;
158 }
159
160 /***
161 * Returns "1", "2", etc., depending on how many inner classes we have.
162 *
163 * @return String containing number of next anonymous inner class
164 */
165 public String getNextAnonymousClassName()
166 {
167 return String.valueOf( getInnerClasses().size() + 1 );
168 }
169
170 /***
171 * Gets the OuterClass attribute of the SourceClass object
172 *
173 * @return The OuterClass value
174 */
175 private boolean isOuterClass()
176 {
177 return _sourceFile != null;
178 }
179
180 /***
181 * Gets the Writeable attribute of the SourceClass object
182 *
183 * @return The Writeable value
184 */
185 public boolean isWriteable()
186 {
187 return _compilationUnit != null;
188 }
189
190 public SimpleNode getCompilationUnit()
191 {
192 return _compilationUnit;
193 }
194
195 /***
196 * Returns a reader for the source code.
197 *
198 * @return a reader for the source code.
199 */
200 public Reader getReader()
201 {
202 return _in;
203 }
204
205 public AbstractFile getFile()
206 {
207 return _sourceFile;
208 }
209
210 public boolean isPrimitive()
211 {
212 return false;
213 }
214
215 /***
216 * say this class is dirty and needs saving propagate to outer class ( if any )
217 */
218 public void setDirty()
219 {
220 if( isInner() )
221 {
222 getContainingClass().setDirty();
223 }
224 else
225 {
226 _dirty = true;
227 }
228 }
229 /***
230 * Called by JavaParser at the end of the parsing
231 *
232 * @param compilationUnit The new CompilationUnit value
233 */
234 public void setCompilationUnit( SimpleNode compilationUnit )
235 {
236 _compilationUnit = compilationUnit;
237 }
238
239 /***
240 * Called by XJavaDoc after the entire source is parsed, but only if validation
241 * is on.
242 *
243 * @throws TagValidationException
244 */
245 public void validateTags() throws TagValidationException
246 {
247
248 for( Iterator i = _tagsForValidation.iterator(); i.hasNext(); )
249 {
250 XTag tag = ( XTag ) i.next();
251
252 tag.validate();
253 }
254
255
256 for( Iterator i = getInnerClasses().iterator(); i.hasNext(); )
257 {
258 SourceClass inner = ( SourceClass ) i.next();
259
260 inner.validateTags();
261 }
262 }
263
264 public void addTagForValidation( DefaultXTag tag )
265 {
266 _tagsForValidation.add( tag );
267 }
268
269 public boolean saveNeeded()
270 {
271 return isWriteable() && _dirty;
272 }
273
274 /***
275 * Describe what the method does
276 *
277 * @return Describe the return value
278 */
279 public long lastModified()
280 {
281 if( isOuterClass() )
282 {
283 return _sourceFile.lastModified();
284 }
285 else
286 {
287 return getContainingClass().lastModified();
288 }
289 }
290
291 /***
292 * @param out
293 * @deprecated use the Writer method
294 */
295 public void print( OutputStream out )
296 {
297 print( new OutputStreamWriter( out ) );
298 }
299
300 /***
301 * Prints this class to a stream
302 *
303 * @param out Describe what the parameter does
304 */
305 public void print( Writer out )
306 {
307 updateDoc();
308 if( !isWriteable() )
309 {
310
311 throw new UnsupportedOperationException( "Can't save classes that are parsed with simpleparser" );
312 }
313 NodePrinter.print( _compilationUnit, out );
314 }
315
316 /***
317 * Saves the class at root dir rootDir. The actual java file is derived from
318 * tha package name. If no root dir is specified, save where it was loaded from
319 *
320 * @param rootDir the root directory.
321 * @return the relative fileName to which the file was saved.
322 * @throws IOException if the file couldn't be saved
323 */
324 public String save( File rootDir ) throws IOException
325 {
326 if( !isWriteable() )
327 {
328 throw new UnsupportedOperationException( "Can't save classes that aren't parsed in AST mode (do getXJavaDoc().setUseNodeParser(true) before parsing starts!)" );
329 }
330 if( getContainingClass() != null )
331 {
332
333 throw new UnsupportedOperationException( "Can't save inner classes" );
334 }
335 else if( rootDir != null )
336 {
337 String fileName = getFileName( getQualifiedName() );
338 File javaFile = new File( rootDir, fileName );
339
340 javaFile.getParentFile().mkdirs();
341 print( new FileWriter( javaFile ) );
342 return fileName;
343 }
344 else
345 {
346
347 Writer outputStream = _sourceFile.getWriter();
348
349 print( new PrintWriter( outputStream ) );
350 outputStream.flush();
351 outputStream.close();
352 return _sourceFile.toString();
353 }
354 }
355
356 /***
357 * Returns fully qualified name of a class. 1: check for "." 2: if "." it's
358 * already qualified 3: if no ".", must try with all imported packages or
359 * classes
360 *
361 * @param unqualifiedClassName Describe what the parameter does
362 * @return Describe the return value
363 */
364 public XClass qualify( final String unqualifiedClassName )
365 {
366 XClass result = null;
367
368 result = ( XClass ) _qualifiedClasses.get( unqualifiedClassName );
369 if( result == null )
370 {
371
372 if( getContainingClass() == null )
373 {
374
375
376 if( unqualifiedClassName.indexOf( '.' ) != -1 )
377 {
378 result = unqualifiedNameInImportedClassesInnerClasses( unqualifiedClassName );
379 if( result == null )
380 {
381
382 result = getXJavaDoc().getXClass( unqualifiedClassName );
383 }
384 }
385 else
386 {
387
388 Primitive primitive;
389
390 if( ( primitive = XJavaDoc.getPrimitive( unqualifiedClassName ) ) != null )
391 {
392 result = primitive;
393 }
394 else
395 {
396 String qualifiedName;
397
398 if( ( qualifiedName = unqualifiedNameInTheSameClassAsAnInnerClass( unqualifiedClassName ) ) != null )
399 {
400 result = getXJavaDoc().getXClass( qualifiedName );
401 }
402 else if( ( qualifiedName = unqualifiedNameInInnerClasses( unqualifiedClassName ) ) != null )
403 {
404 result = getXJavaDoc().getXClass( qualifiedName );
405 }
406 else if( ( qualifiedName = unqualifiedNameInJavaDotLang( unqualifiedClassName ) ) != null )
407 {
408 result = getXJavaDoc().getXClass( qualifiedName );
409 }
410 else if( ( qualifiedName = unqualifiedNameInImportedClasses( unqualifiedClassName ) ) != null )
411 {
412 result = getXJavaDoc().getXClass( qualifiedName );
413 }
414 else if( ( qualifiedName = unqualifiedNameInImportedPackages( unqualifiedClassName ) ) != null )
415 {
416 result = getXJavaDoc().getXClass( qualifiedName );
417 }
418 else if( ( qualifiedName = unqualifiedNameInTheSamePackage( unqualifiedClassName ) ) != null )
419 {
420 result = getXJavaDoc().getXClass( qualifiedName );
421 }
422 else if( ( qualifiedName = unqualifiedNameInInnerClassesOfSuperClass( unqualifiedClassName ) ) != null )
423 {
424 result = getXJavaDoc().getXClass( qualifiedName );
425 }
426 else if( ( qualifiedName = unqualifiedNameInInnerInterface( unqualifiedClassName ) ) != null )
427 {
428 result = getXJavaDoc().getXClass( qualifiedName );
429 }
430 else
431 {
432 String unknownClassName;
433
434 if( getContainingPackage().getName().equals( "" ) )
435 {
436 unknownClassName = unqualifiedClassName;
437 }
438 else
439 {
440 unknownClassName = getContainingPackage().getName() + "." + unqualifiedClassName;
441 }
442
443 UnknownClass unknownClass = new UnknownClass( getXJavaDoc(), unknownClassName );
444
445
446
447
448
449 if( !hasImportedPackages() )
450 {
451
452 getXJavaDoc().logMessage( this, unknownClass, unqualifiedClassName, XJavaDoc.NO_IMPORTED_PACKAGES );
453 }
454 else
455 {
456
457
458 getXJavaDoc().logMessage( this, unknownClass, unqualifiedClassName, XJavaDoc.ONE_OR_MORE_IMPORTED_PACKAGES );
459 }
460 result = unknownClass;
461 }
462 }
463 }
464 }
465 else
466 {
467 result = getContainingAbstractClass().qualify( unqualifiedClassName );
468 }
469 _qualifiedClasses.put( unqualifiedClassName, result );
470 }
471
472 return result;
473 }
474
475 public void reset()
476 {
477 super.reset();
478
479 _compilationUnit = null;
480 _in = null;
481 _sourceFile = null;
482 _qualifiedClasses.clear();
483 }
484
485 private final String unqualifiedNameInImportedClasses( final String unqualifiedClassName )
486 {
487 if( !hasImportedClasses() )
488 {
489 return null;
490 }
491
492 final String suffix = "." + unqualifiedClassName;
493 String candidate = null;
494
495 for( Iterator i = getImportedClasses().iterator(); i.hasNext(); )
496 {
497 XClass clazz = ( XClass ) i.next();
498 String qualifiedClassName = clazz.getQualifiedName();
499
500 if( qualifiedClassName.endsWith( suffix ) )
501 {
502
503 if( candidate != null && !candidate.equals( qualifiedClassName ) )
504 {
505
506 throw new IllegalStateException( "In class " + getQualifiedName() + ": Ambiguous class:" + unqualifiedClassName + ". Is it " + candidate + " or " + qualifiedClassName + "?" );
507 }
508 else
509 {
510 candidate = qualifiedClassName;
511 }
512 }
513 }
514 return candidate;
515 }
516
517 private final XClass unqualifiedNameInImportedClassesInnerClasses( final String unqualifiedClassName )
518 {
519 if( !hasImportedClasses() )
520 {
521 return null;
522 }
523
524 XClass candidate = null;
525
526 for( Iterator i = getImportedClasses().iterator(); i.hasNext(); )
527 {
528 XClass clazz = ( XClass ) i.next();
529
530
531 for( Iterator inners = clazz.getInnerClasses().iterator(); inners.hasNext(); )
532 {
533 XClass inner = ( XClass ) inners.next();
534 boolean isAccessible = inner.isPublic();
535
536 if( inner.getName().equals( unqualifiedClassName ) && isAccessible )
537 {
538 if( candidate != null )
539 {
540
541 throw new IllegalStateException( "In class " + getQualifiedName() + ": Ambiguous class:" + unqualifiedClassName + ". Is it " + candidate.getQualifiedName() + " or " + inner.getQualifiedName() + "?" );
542 }
543 else
544 {
545 candidate = inner;
546 }
547 }
548 }
549 }
550 return candidate;
551 }
552
553 /***
554 * Describe what the method does
555 *
556 * @param unqualifiedClassName Describe what the parameter does
557 * @return Describe the return value
558 */
559 private final String unqualifiedNameInInnerClasses( final String unqualifiedClassName )
560 {
561 if( !hasInnerClasses() )
562 {
563 return null;
564 }
565
566 final String innerClassName = getQualifiedName() + '.' + unqualifiedClassName;
567
568 String candidate = null;
569
570 for( Iterator i = getInnerClasses().iterator(); i.hasNext(); )
571 {
572 XClass innerClass = ( XClass ) i.next();
573 String qualifiedClassName = innerClass.getQualifiedName();
574
575 if( innerClassName.equals( qualifiedClassName ) )
576 {
577 candidate = qualifiedClassName;
578 break;
579 }
580 }
581 return candidate;
582 }
583
584 /***
585 * Resolves Inner interfaces that exist in current class.
586 *
587 * This catches inner classes as well because isInterface()
588 * does not indicate if it's an interface.
589 *
590 * @param unqualifiedClassName Name of the class to resolve
591 * @return The qualified name of the inner class.
592 */
593 private final String unqualifiedNameInInnerInterface( final String unqualifiedClassName )
594 {
595 String qualifiedClassName = getQualifiedName() + '$' + unqualifiedClassName;
596 if (getXJavaDoc().classExists(qualifiedClassName)) {
597
598
599
600
601 return getQualifiedName() + '.' + unqualifiedClassName;
602 }
603 return null;
604 }
605
606 /***
607 * Resolves Inner classes that exist in the super class hierarchy.
608 *
609 * @param unqualifiedClassName Name of the class to resolve
610 * @return The qualified name of the inner class.
611 */
612 private final String unqualifiedNameInInnerClassesOfSuperClass( final String unqualifiedClassName )
613 {
614 XClass clazz = getXJavaDoc().getXClass(getQualifiedName());
615 XClass superClazz = clazz.getSuperclass();
616 while (superClazz != null && ! superClazz.getQualifiedName().equals("java.lang.Object")) {
617 String innerClassName = superClazz.getQualifiedName() + '.' + unqualifiedClassName;
618 for( Iterator i = superClazz.getInnerClasses().iterator(); i.hasNext(); )
619 {
620 XClass innerClass = ( XClass ) i.next();
621 String qualifiedClassName = innerClass.getQualifiedName();
622 if( innerClassName.equals( qualifiedClassName ) )
623 {
624 return qualifiedClassName;
625 }
626 }
627 superClazz = superClazz.getSuperclass();
628 }
629 return null;
630 }
631
632 /***
633 * Describe what the method does
634 *
635 * @param unqualifiedClassName Describe what the parameter does
636 * @return Describe the return value
637 */
638 private final String unqualifiedNameInImportedPackages( final String unqualifiedClassName )
639 {
640 if( !hasImportedPackages() )
641 {
642 return null;
643 }
644
645 final String suffix = "." + unqualifiedClassName;
646 String candidate = null;
647
648 for( Iterator i = getImportedPackages().iterator(); i.hasNext(); )
649 {
650 String importedPackageName = ( ( XPackage ) i.next() ).getName();
651 String qualifiedClassName = importedPackageName + suffix;
652
653 if( getXJavaDoc().classExists( qualifiedClassName ) )
654 {
655 if( candidate != null && !candidate.equals( qualifiedClassName ) )
656 {
657
658 throw new IllegalStateException( "In class " + getQualifiedName() + ": Ambiguous class:" + unqualifiedClassName + ". Is it " + candidate + " or " + qualifiedClassName + "?" );
659 }
660 else
661 {
662 candidate = qualifiedClassName;
663 }
664 }
665 }
666 return candidate;
667 }
668
669 /***
670 * Returns the fully qualified class name if it's found in java.lang, otherwise
671 * null.
672 *
673 * @param unqualifiedClassName
674 * @return fully qualified class name, or null
675 */
676 private final String unqualifiedNameInJavaDotLang( final String unqualifiedClassName )
677 {
678 String qualifiedClassName = "java.lang." + unqualifiedClassName;
679
680 if( getXJavaDoc().classExists( qualifiedClassName ) )
681 {
682 return qualifiedClassName;
683 }
684 else
685 {
686 return null;
687 }
688 }
689
690 /***
691 * Describe what the method does
692 *
693 * @param unqualifiedClassName Describe what the parameter does
694 * @return Describe the return value
695 */
696 private final String unqualifiedNameInTheSamePackage( final String unqualifiedClassName )
697 {
698 String qualifiedClassName;
699
700 if( getContainingPackage().getName().equals( "" ) )
701 {
702 qualifiedClassName = unqualifiedClassName;
703 }
704 else
705 {
706 qualifiedClassName = getContainingPackage().getName() + '.' + unqualifiedClassName;
707 }
708
709 if( getXJavaDoc().classExists( qualifiedClassName ) )
710 {
711 return qualifiedClassName;
712 }
713 else
714 {
715 return null;
716 }
717
718 }
719
720 private final String unqualifiedNameInTheSameClassAsAnInnerClass( final String unqualifiedClassName )
721 {
722
723 String qualifiedClassName = getQualifiedName() + '.' + unqualifiedClassName;
724
725 if( getXJavaDoc().classExists( qualifiedClassName ) )
726 return qualifiedClassName;
727
728
729 if( getContainingPackage().getName().equals( "" ) )
730 {
731 qualifiedClassName = unqualifiedClassName;
732 }
733 else
734 {
735 qualifiedClassName = getContainingPackage().getName() + '.' + unqualifiedClassName;
736 }
737
738 if( getXJavaDoc().classExists( qualifiedClassName ) )
739 return qualifiedClassName;
740
741 return null;
742 }
743
744 /***
745 * Describe what the method does
746 *
747 * @param useNodeParser Describe what the parameter does
748 */
749 private void parse( boolean useNodeParser )
750 {
751 try
752 {
753 if( useNodeParser )
754 {
755
756
757
758 new NodeParser( getXJavaDoc(), getTagFactory() ).populate( this );
759 }
760 else
761 {
762
763 new SimpleParser( getXJavaDoc(), getTagFactory() ).populate( this );
764 }
765 }
766 catch( ParseException e )
767 {
768
769 String cls = _sourceFile != null ? _sourceFile.toString() : getQualifiedName();
770
771 System.err.println( "Error parsing " + cls + ':' + e.getMessage() );
772 }
773 catch( TokenMgrError e )
774 {
775 String cls = _sourceFile != null ? _sourceFile.toString() : getQualifiedName();
776
777 System.err.println( "Error parsing " + cls + ':' + e.getMessage() );
778 }
779 }
780 }