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 * Prints this class to a stream
293 *
294 * @param out Describe what the parameter does
295 */
296 public void print( Writer out )
297 {
298 updateDoc();
299 if( !isWriteable() )
300 {
301
302 throw new UnsupportedOperationException( "Can't save classes that are parsed with simpleparser" );
303 }
304 NodePrinter.print( _compilationUnit, out );
305 }
306
307 /***
308 * Saves the class at root dir rootDir. The actual java file is derived from
309 * tha package name. If no root dir is specified, save where it was loaded from
310 *
311 * @param rootDir the root directory.
312 * @return the relative fileName to which the file was saved.
313 * @throws IOException if the file couldn't be saved
314 */
315 public String save( File rootDir ) throws IOException
316 {
317 if( !isWriteable() )
318 {
319 throw new UnsupportedOperationException( "Can't save classes that aren't parsed in AST mode (do getXJavaDoc().setUseNodeParser(true) before parsing starts!)" );
320 }
321 if( getContainingClass() != null )
322 {
323
324 throw new UnsupportedOperationException( "Can't save inner classes" );
325 }
326 else if( rootDir != null )
327 {
328 String fileName = getFileName( getQualifiedName() );
329 File javaFile = new File( rootDir, fileName );
330
331 javaFile.getParentFile().mkdirs();
332 FileWriter fwtr = new FileWriter( javaFile );
333 print( fwtr );
334 fwtr.flush();
335 fwtr.close();
336 return fileName;
337 }
338 else
339 {
340
341 Writer outputStream = _sourceFile.getWriter();
342
343 print( new PrintWriter( outputStream ) );
344 outputStream.flush();
345 outputStream.close();
346 return _sourceFile.toString();
347 }
348 }
349
350 /***
351 * Returns fully qualified name of a class. 1: check for "." 2: if "." it's
352 * already qualified 3: if no ".", must try with all imported packages or
353 * classes
354 *
355 * @param unqualifiedClassName Describe what the parameter does
356 * @return Describe the return value
357 */
358 public XClass qualify( final String unqualifiedClassName )
359 {
360 XClass result = null;
361
362 result = ( XClass ) _qualifiedClasses.get( unqualifiedClassName );
363 if( result == null )
364 {
365
366 if( getContainingClass() == null )
367 {
368
369
370 if( unqualifiedClassName.indexOf( '.' ) != -1 )
371 {
372 result = unqualifiedNameInImportedClassesInnerClasses( unqualifiedClassName );
373 if( result == null )
374 {
375
376 result = getXJavaDoc().getXClass( unqualifiedClassName );
377 }
378 }
379 else
380 {
381
382 Primitive primitive;
383
384 if( ( primitive = XJavaDoc.getPrimitive( unqualifiedClassName ) ) != null )
385 {
386 result = primitive;
387 }
388 else
389 {
390 String qualifiedName;
391
392 if( ( qualifiedName = unqualifiedNameInTheSameClassAsAnInnerClass( unqualifiedClassName ) ) != null )
393 {
394 result = getXJavaDoc().getXClass( qualifiedName );
395 }
396 else if( ( qualifiedName = unqualifiedNameInInnerClasses( unqualifiedClassName ) ) != null )
397 {
398 result = getXJavaDoc().getXClass( qualifiedName );
399 }
400 else if( ( qualifiedName = unqualifiedNameInJavaDotLang( unqualifiedClassName ) ) != null )
401 {
402 result = getXJavaDoc().getXClass( qualifiedName );
403 }
404 else if( ( qualifiedName = unqualifiedNameInImportedClasses( unqualifiedClassName ) ) != null )
405 {
406 result = getXJavaDoc().getXClass( qualifiedName );
407 }
408 else if( ( qualifiedName = unqualifiedNameInImportedPackages( unqualifiedClassName ) ) != null )
409 {
410 result = getXJavaDoc().getXClass( qualifiedName );
411 }
412 else if( ( qualifiedName = unqualifiedNameInTheSamePackage( unqualifiedClassName ) ) != null )
413 {
414 result = getXJavaDoc().getXClass( qualifiedName );
415 }
416 else if( ( qualifiedName = unqualifiedNameInInnerClassesOfSuperClass( unqualifiedClassName ) ) != null )
417 {
418 result = getXJavaDoc().getXClass( qualifiedName );
419 }
420 else if( ( qualifiedName = unqualifiedNameInInnerInterface( unqualifiedClassName ) ) != null )
421 {
422 result = getXJavaDoc().getXClass( qualifiedName );
423 }
424 else
425 {
426 String unknownClassName;
427
428 if( getContainingPackage().getName().equals( "" ) )
429 {
430 unknownClassName = unqualifiedClassName;
431 }
432 else
433 {
434 unknownClassName = getContainingPackage().getName() + "." + unqualifiedClassName;
435 }
436
437 UnknownClass unknownClass = new UnknownClass( getXJavaDoc(), unknownClassName );
438
439
440
441
442
443 if( !hasImportedPackages() )
444 {
445
446 getXJavaDoc().logMessage( this, unknownClass, unqualifiedClassName, XJavaDoc.NO_IMPORTED_PACKAGES );
447 }
448 else
449 {
450
451
452 getXJavaDoc().logMessage( this, unknownClass, unqualifiedClassName, XJavaDoc.ONE_OR_MORE_IMPORTED_PACKAGES );
453 }
454 result = unknownClass;
455 }
456 }
457 }
458 }
459 else
460 {
461 result = getContainingAbstractClass().qualify( unqualifiedClassName );
462 }
463 _qualifiedClasses.put( unqualifiedClassName, result );
464 }
465
466 return result;
467 }
468
469 public void reset()
470 {
471 super.reset();
472
473 _compilationUnit = null;
474 _in = null;
475 _sourceFile = null;
476 _qualifiedClasses.clear();
477 }
478
479 private final String unqualifiedNameInImportedClasses( final String unqualifiedClassName )
480 {
481 if( !hasImportedClasses() )
482 {
483 return null;
484 }
485
486 final String suffix = "." + unqualifiedClassName;
487 String candidate = null;
488
489 for( Iterator i = getImportedClasses().iterator(); i.hasNext(); )
490 {
491 XClass clazz = ( XClass ) i.next();
492 String qualifiedClassName = clazz.getQualifiedName();
493
494 if( qualifiedClassName.endsWith( suffix ) )
495 {
496
497 if( candidate != null && !candidate.equals( qualifiedClassName ) )
498 {
499
500 throw new IllegalStateException( "In class " + getQualifiedName() + ": Ambiguous class:" + unqualifiedClassName + ". Is it " + candidate + " or " + qualifiedClassName + "?" );
501 }
502 else
503 {
504 candidate = qualifiedClassName;
505 }
506 }
507 }
508 return candidate;
509 }
510
511 private final XClass unqualifiedNameInImportedClassesInnerClasses( final String unqualifiedClassName )
512 {
513 if( !hasImportedClasses() )
514 {
515 return null;
516 }
517
518 XClass candidate = null;
519
520 for( Iterator i = getImportedClasses().iterator(); i.hasNext(); )
521 {
522 XClass clazz = ( XClass ) i.next();
523
524
525 for( Iterator inners = clazz.getInnerClasses().iterator(); inners.hasNext(); )
526 {
527 XClass inner = ( XClass ) inners.next();
528 boolean isAccessible = inner.isPublic();
529
530 if( inner.getName().equals( unqualifiedClassName ) && isAccessible )
531 {
532 if( candidate != null )
533 {
534
535 throw new IllegalStateException( "In class " + getQualifiedName() + ": Ambiguous class:" + unqualifiedClassName + ". Is it " + candidate.getQualifiedName() + " or " + inner.getQualifiedName() + "?" );
536 }
537 else
538 {
539 candidate = inner;
540 }
541 }
542 }
543 }
544 return candidate;
545 }
546
547 /***
548 * Describe what the method does
549 *
550 * @param unqualifiedClassName Describe what the parameter does
551 * @return Describe the return value
552 */
553 private final String unqualifiedNameInInnerClasses( final String unqualifiedClassName )
554 {
555 if( !hasInnerClasses() )
556 {
557 return null;
558 }
559
560 final String innerClassName = getQualifiedName() + '.' + unqualifiedClassName;
561
562 String candidate = null;
563
564 for( Iterator i = getInnerClasses().iterator(); i.hasNext(); )
565 {
566 XClass innerClass = ( XClass ) i.next();
567 String qualifiedClassName = innerClass.getQualifiedName();
568
569 if( innerClassName.equals( qualifiedClassName ) )
570 {
571 candidate = qualifiedClassName;
572 break;
573 }
574 }
575 return candidate;
576 }
577
578 /***
579 * Resolves Inner interfaces that exist in current class.
580 *
581 * This catches inner classes as well because isInterface()
582 * does not indicate if it's an interface.
583 *
584 * @param unqualifiedClassName Name of the class to resolve
585 * @return The qualified name of the inner class.
586 */
587 private final String unqualifiedNameInInnerInterface( final String unqualifiedClassName )
588 {
589 String qualifiedClassName = getQualifiedName() + '$' + unqualifiedClassName;
590 if (getXJavaDoc().classExists(qualifiedClassName)) {
591
592
593
594
595 return getQualifiedName() + '.' + unqualifiedClassName;
596 }
597 return null;
598 }
599
600 /***
601 * Resolves Inner classes that exist in the super class hierarchy.
602 *
603 * @param unqualifiedClassName Name of the class to resolve
604 * @return The qualified name of the inner class.
605 */
606 private final String unqualifiedNameInInnerClassesOfSuperClass( final String unqualifiedClassName )
607 {
608 XClass clazz = getXJavaDoc().getXClass(getQualifiedName());
609 XClass superClazz = clazz.getSuperclass();
610 while (superClazz != null && ! superClazz.getQualifiedName().equals("java.lang.Object")) {
611 String innerClassName = superClazz.getQualifiedName() + '.' + unqualifiedClassName;
612 for( Iterator i = superClazz.getInnerClasses().iterator(); i.hasNext(); )
613 {
614 XClass innerClass = ( XClass ) i.next();
615 String qualifiedClassName = innerClass.getQualifiedName();
616 if( innerClassName.equals( qualifiedClassName ) )
617 {
618 return qualifiedClassName;
619 }
620 }
621 superClazz = superClazz.getSuperclass();
622 }
623 return null;
624 }
625
626 /***
627 * Describe what the method does
628 *
629 * @param unqualifiedClassName Describe what the parameter does
630 * @return Describe the return value
631 */
632 private final String unqualifiedNameInImportedPackages( final String unqualifiedClassName )
633 {
634 if( !hasImportedPackages() )
635 {
636 return null;
637 }
638
639 final String suffix = "." + unqualifiedClassName;
640 String candidate = null;
641
642 for( Iterator i = getImportedPackages().iterator(); i.hasNext(); )
643 {
644 String importedPackageName = ( ( XPackage ) i.next() ).getName();
645 String qualifiedClassName = importedPackageName + suffix;
646
647 if( getXJavaDoc().classExists( qualifiedClassName ) )
648 {
649 if( candidate != null && !candidate.equals( qualifiedClassName ) )
650 {
651
652 throw new IllegalStateException( "In class " + getQualifiedName() + ": Ambiguous class:" + unqualifiedClassName + ". Is it " + candidate + " or " + qualifiedClassName + "?" );
653 }
654 else
655 {
656 candidate = qualifiedClassName;
657 }
658 }
659 }
660 return candidate;
661 }
662
663 /***
664 * Returns the fully qualified class name if it's found in java.lang, otherwise
665 * null.
666 *
667 * @param unqualifiedClassName
668 * @return fully qualified class name, or null
669 */
670 private final String unqualifiedNameInJavaDotLang( final String unqualifiedClassName )
671 {
672 String qualifiedClassName = "java.lang." + unqualifiedClassName;
673
674 if( getXJavaDoc().classExists( qualifiedClassName ) )
675 {
676 return qualifiedClassName;
677 }
678 else
679 {
680 return null;
681 }
682 }
683
684 /***
685 * Describe what the method does
686 *
687 * @param unqualifiedClassName Describe what the parameter does
688 * @return Describe the return value
689 */
690 private final String unqualifiedNameInTheSamePackage( final String unqualifiedClassName )
691 {
692 String qualifiedClassName;
693
694 if( getContainingPackage().getName().equals( "" ) )
695 {
696 qualifiedClassName = unqualifiedClassName;
697 }
698 else
699 {
700 qualifiedClassName = getContainingPackage().getName() + '.' + unqualifiedClassName;
701 }
702
703 if( getXJavaDoc().classExists( qualifiedClassName ) )
704 {
705 return qualifiedClassName;
706 }
707 else
708 {
709 return null;
710 }
711
712 }
713
714 private final String unqualifiedNameInTheSameClassAsAnInnerClass( final String unqualifiedClassName )
715 {
716
717 String qualifiedClassName = getQualifiedName() + '.' + unqualifiedClassName;
718
719 if( getXJavaDoc().classExists( qualifiedClassName ) )
720 return qualifiedClassName;
721
722
723 if( getContainingPackage().getName().equals( "" ) )
724 {
725 qualifiedClassName = unqualifiedClassName;
726 }
727 else
728 {
729 qualifiedClassName = getContainingPackage().getName() + '.' + unqualifiedClassName;
730 }
731
732 if( getXJavaDoc().classExists( qualifiedClassName ) )
733 return qualifiedClassName;
734
735 return null;
736 }
737
738 /***
739 * Describe what the method does
740 *
741 * @param useNodeParser Describe what the parameter does
742 */
743 private void parse( boolean useNodeParser )
744 {
745 try
746 {
747 if( useNodeParser )
748 {
749
750
751
752 new NodeParser( getXJavaDoc(), getTagFactory() ).populate( this );
753 }
754 else
755 {
756
757 new SimpleParser( getXJavaDoc(), getTagFactory() ).populate( this );
758 }
759 }
760 catch( ParseException e )
761 {
762
763 String cls = _sourceFile != null ? _sourceFile.toString() : getQualifiedName();
764
765 System.err.println( "Error parsing " + cls + ':' + e.getMessage() );
766 }
767 catch( TokenMgrError e )
768 {
769 String cls = _sourceFile != null ? _sourceFile.toString() : getQualifiedName();
770
771 System.err.println( "Error parsing " + cls + ':' + e.getMessage() );
772 }
773 }
774 }