001 /* 002 $Id: CompilationUnit.java,v 1.23 2005/06/13 16:21:54 blackdrag Exp $ 003 004 005 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 006 007 008 Redistribution and use of this software and associated documentation 009 ("Software"), with or without modification, are permitted provided 010 that the following conditions are met: 011 012 1. Redistributions of source code must retain copyright 013 statements and notices. Redistributions must also contain a 014 copy of this document. 015 016 017 2. Redistributions in binary form must reproduce the 018 above copyright notice, this list of conditions and the 019 following disclaimer in the documentation and/or other 020 materials provided with the distribution. 021 022 023 3. The name "groovy" must not be used to endorse or promote 024 products derived from this Software without prior written 025 permission of The Codehaus. For written permission, 026 please contact info@codehaus.org. 027 028 029 4. Products derived from this Software may not be called "groovy" 030 nor may "groovy" appear in their names without prior written 031 permission of The Codehaus. "groovy" is a registered 032 trademark of The Codehaus. 033 034 035 5. Due credit should be given to The Codehaus - 036 http://groovy.codehaus.org/ 037 038 039 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS 040 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 041 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 042 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 043 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 044 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 045 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 046 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 047 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 048 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 049 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 050 OF THE POSSIBILITY OF SUCH DAMAGE. 051 */ 052 053 054 package org.codehaus.groovy.control; 055 056 057 import org.codehaus.groovy.GroovyBugError; 058 059 import org.codehaus.groovy.ast.ASTNode; 060 061 import org.codehaus.groovy.ast.ClassNode; 062 063 import org.codehaus.groovy.ast.CompileUnit; 064 import org.codehaus.groovy.ast.ModuleNode; 065 import org.codehaus.groovy.classgen.*; 066 import org.codehaus.groovy.control.io.InputStreamReaderSource; 067 import org.codehaus.groovy.control.io.ReaderSource; 068 import org.codehaus.groovy.control.messages.ExceptionMessage; 069 import org.codehaus.groovy.control.messages.Message; 070 import org.codehaus.groovy.syntax.SyntaxException; 071 import org.codehaus.groovy.tools.GroovyClass; 072 import org.objectweb.asm.ClassVisitor; 073 import org.objectweb.asm.ClassWriter; 074 075 076 import groovy.lang.GroovyRuntimeException; 077 078 079 import java.io.*; 080 import java.net.MalformedURLException; 081 import java.net.URL; 082 import java.security.CodeSource; 083 import java.util.*; 084 085 086 087 088 089 /** 090 * Collects all compilation data as it is generated by the compiler system. 091 * Allows additional source units to be added and compilation run again (to 092 * affect only the deltas). 093 * 094 * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a> 095 * @version $Id: CompilationUnit.java,v 1.23 2005/06/13 16:21:54 blackdrag Exp $ 096 */ 097 098 099 public class CompilationUnit extends ProcessingUnit { 100 101 102 //--------------------------------------------------------------------------- 103 // CONSTRUCTION AND SUCH 104 105 106 protected HashMap sources; // The SourceUnits from which this unit is built 107 protected ArrayList names; // Names for each SourceUnit in sources. 108 109 110 protected CompileUnit ast; // The overall AST for this CompilationUnit. 111 protected ArrayList classes; // The classes generated during classgen. 112 113 114 protected Verifier verifier; // For use by verify(). 115 116 117 protected ClassCompletionVerifier completionVerifier; // for use by checkClassCompletion 118 119 120 protected boolean debug; // Controls behaviour of classgen() and other routines. 121 protected boolean configured; // Set true after the first configure() operation 122 123 124 protected ClassgenCallback classgenCallback; // A callback for use during classgen() 125 protected ProgressCallback progressCallback; // A callback for use during compile() 126 127 128 129 /** 130 * Initializes the CompilationUnit with defaults. 131 */ 132 public CompilationUnit() { 133 this(null, null, null); 134 } 135 136 137 138 /** 139 * Initializes the CompilationUnit with defaults except for class loader. 140 */ 141 public CompilationUnit(ClassLoader loader) { 142 this(null, null, loader); 143 } 144 145 146 147 /** 148 * Initializes the CompilationUnit with no security considerations. 149 */ 150 public CompilationUnit(CompilerConfiguration configuration) { 151 this(configuration, null, null); 152 } 153 154 /** 155 * Initializes the CompilationUnit with a CodeSource for controlling 156 * security stuff and a class loader for loading classes. 157 */ 158 public CompilationUnit(CompilerConfiguration configuration, CodeSource security, ClassLoader loader) { 159 super(configuration, loader, null); 160 161 this.names = new ArrayList(); 162 this.sources = new HashMap(); 163 164 165 this.ast = new CompileUnit(this.classLoader, security, this.configuration); 166 this.classes = new ArrayList(); 167 168 169 this.verifier = new Verifier(); 170 this.completionVerifier = new ClassCompletionVerifier(); 171 172 173 this.classgenCallback = null; 174 } 175 176 177 /** 178 * Reconfigures the CompilationUnit. 179 */ 180 public void configure(CompilerConfiguration configuration) { 181 super.configure(configuration); 182 this.debug = configuration.getDebug(); 183 184 185 // 186 // Configure our class loader's classpath, if it is of 187 // a configurable type. We can't reconfigure it, 188 // unfortunately, due to limitations in URLClassLoader. 189 if (!this.configured && this.classLoader instanceof CompilerClassLoader) { 190 CompilerClassLoader loader = (CompilerClassLoader) this.classLoader; 191 192 193 Iterator iterator = configuration.getClasspath().iterator(); 194 while (iterator.hasNext()) { 195 try { 196 this.configured = true; 197 loader.addPath((String) iterator.next()); 198 } catch (MalformedURLException e) { 199 throw new ConfigurationException(e); 200 } 201 } 202 } 203 } 204 205 206 /** 207 * Returns the CompileUnit that roots our AST. 208 */ 209 public CompileUnit getAST() { 210 return this.ast; 211 } 212 213 214 /** 215 * Get the GroovyClasses generated by compile(). 216 */ 217 218 219 public List getClasses() { 220 return classes; 221 } 222 223 224 /** 225 * Convenience routine to get the first ClassNode, for 226 * when you are sure there is only one. 227 */ 228 public ClassNode getFirstClassNode() { 229 return (ClassNode) ((ModuleNode) this.ast.getModules().get(0)).getClasses().get(0); 230 } 231 232 233 /** 234 * Convenience routine to get the named ClassNode. 235 */ 236 public ClassNode getClassNode(final String name) { 237 final ClassNode[] result = new ClassNode[]{null}; 238 LoopBodyForPrimaryClassNodeOperations handler = new LoopBodyForPrimaryClassNodeOperations() { 239 240 241 public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) { 242 243 244 if (classNode.getName().equals(name)) { 245 246 247 result[0] = classNode; 248 249 250 } 251 252 253 } 254 255 256 }; 257 258 259 try { 260 applyToPrimaryClassNodes(handler); 261 } catch (CompilationFailedException e) { 262 if (debug) e.printStackTrace(); 263 } 264 return result[0]; 265 } 266 267 268 269 270 271 //--------------------------------------------------------------------------- 272 // SOURCE CREATION 273 274 275 /** 276 * Adds a set of file paths to the unit. 277 */ 278 public void addSources(String[] paths) { 279 for (int i = 0; i < paths.length; i++) { 280 File file = new File(paths[i]); 281 addSource(file); 282 } 283 } 284 285 286 /** 287 * Adds a set of source files to the unit. 288 */ 289 public void addSources(File[] files) { 290 for (int i = 0; i < files.length; i++) { 291 addSource(files[i]); 292 } 293 } 294 295 296 /** 297 * Adds a source file to the unit. 298 */ 299 public SourceUnit addSource(File file) { 300 return addSource(new SourceUnit(file, configuration, classLoader, getErrorCollector())); 301 } 302 303 304 /** 305 * Adds a source file to the unit. 306 */ 307 public SourceUnit addSource(URL url) { 308 return addSource(new SourceUnit(url, configuration, classLoader,getErrorCollector())); 309 } 310 311 312 /** 313 * Adds a InputStream source to the unit. 314 */ 315 public SourceUnit addSource(String name, InputStream stream) { 316 ReaderSource source = new InputStreamReaderSource(stream, configuration); 317 return addSource(new SourceUnit(name, source, configuration, classLoader, getErrorCollector())); 318 } 319 320 321 /** 322 * Adds a SourceUnit to the unit. 323 */ 324 public SourceUnit addSource(SourceUnit source) { 325 String name = source.getName(); 326 327 328 source.setClassLoader(this.classLoader); 329 330 331 names.add(name); 332 sources.put(name, source); 333 334 335 return source; 336 } 337 338 339 /** 340 * Returns an iterator on the unit's SourceUnits. 341 */ 342 public Iterator iterator() { 343 return new Iterator() { 344 Iterator nameIterator = names.iterator(); 345 346 347 public boolean hasNext() { 348 return nameIterator.hasNext(); 349 } 350 351 352 public Object next() { 353 String name = (String) nameIterator.next(); 354 return sources.get(name); 355 } 356 357 358 public void remove() { 359 throw new UnsupportedOperationException(); 360 } 361 }; 362 } 363 364 365 /** 366 * Adds a ClassNode directly to the unit (ie. without source). 367 * Used primarily for testing support. 368 */ 369 public void addClassNode(ClassNode node) { 370 ModuleNode module = new ModuleNode(this.ast); 371 this.ast.addModule(module); 372 module.addClass(node); 373 } 374 375 376 377 378 379 //--------------------------------------------------------------------------- 380 // EXTERNAL CALLBACKS 381 382 383 /** 384 * A callback interface you can use to "accompany" the classgen() 385 * code as it traverses the ClassNode tree. You will be called-back 386 * for each primary and inner class. Use setClassgenCallback() before 387 * running compile() to set your callback. 388 */ 389 public static abstract class ClassgenCallback { 390 public abstract void call(ClassVisitor writer, ClassNode node) throws CompilationFailedException; 391 } 392 393 394 /** 395 * Sets a ClassgenCallback. You can have only one, and setting 396 * it to null removes any existing setting. 397 */ 398 public void setClassgenCallback(ClassgenCallback visitor) { 399 this.classgenCallback = visitor; 400 } 401 402 403 /** 404 * A callback interface you can use to get a callback after every 405 * unit of the compile process. You will be called-back with a 406 * ProcessingUnit and a phase indicator. Use setProgressCallback() 407 * before running compile() to set your callback. 408 */ 409 public static abstract class ProgressCallback { 410 411 public abstract void call(ProcessingUnit context, int phase) throws CompilationFailedException; 412 } 413 414 /** 415 * Sets a ProgressCallback. You can have only one, and setting 416 * it to null removes any existing setting. 417 */ 418 public void setProgressCallback(ProgressCallback callback) { 419 this.progressCallback = callback; 420 } 421 422 423 //--------------------------------------------------------------------------- 424 // ACTIONS 425 426 427 /** 428 * Synonym for compile(Phases.ALL). 429 */ 430 public void compile() throws CompilationFailedException { 431 compile(Phases.ALL); 432 } 433 434 435 /** 436 * Compiles the compilation unit from sources. 437 */ 438 public void compile(int throughPhase) throws CompilationFailedException { 439 // 440 // To support delta compilations, we always restart 441 // the compiler. The individual passes are responsible 442 // for not reprocessing old code. 443 gotoPhase(Phases.INITIALIZATION); 444 445 446 do { 447 if (throughPhase < Phases.PARSING) { 448 break; 449 } 450 gotoPhase(Phases.PARSING); 451 parse(); 452 453 454 if (throughPhase < Phases.CONVERSION) { 455 break; 456 } 457 458 459 gotoPhase(Phases.CONVERSION); 460 convert(); 461 462 463 if (throughPhase < Phases.CLASS_GENERATION) { 464 break; 465 } 466 467 468 gotoPhase(Phases.CLASS_GENERATION); 469 classgen(); 470 471 472 if (throughPhase < Phases.OUTPUT) { 473 break; 474 } 475 476 477 gotoPhase(Phases.OUTPUT); 478 output(); 479 480 481 if (throughPhase < Phases.FINALIZATION) { 482 break; 483 } 484 485 486 gotoPhase(Phases.FINALIZATION); 487 } while (false); 488 } 489 490 491 492 /** 493 * Parses all sources. 494 */ 495 public void parse() throws CompilationFailedException { 496 if (this.phase != Phases.PARSING) { 497 throw new GroovyBugError("CompilationUnit not read for parse()"); 498 } 499 500 501 applyToSourceUnits(parse); 502 completePhase(); 503 applyToSourceUnits(mark); 504 } 505 506 507 /** 508 * Runs parse() on a single SourceUnit. 509 */ 510 private LoopBodyForSourceUnitOperations parse = new LoopBodyForSourceUnitOperations() { 511 public void call(SourceUnit source) throws CompilationFailedException { 512 source.parse(); 513 514 515 if (CompilationUnit.this.progressCallback != null) { 516 CompilationUnit.this.progressCallback.call(source, CompilationUnit.this.phase); 517 } 518 } 519 }; 520 521 522 /** 523 * Builds ASTs for all parsed sources. 524 */ 525 public void convert() throws CompilationFailedException { 526 if (this.phase != Phases.CONVERSION) { 527 throw new GroovyBugError("CompilationUnit not ready for convert()"); 528 } 529 530 531 applyToSourceUnits(convert); 532 533 534 completePhase(); 535 applyToSourceUnits(mark); 536 } 537 538 539 /** 540 * Runs convert() on a single SourceUnit. 541 */ 542 private LoopBodyForSourceUnitOperations convert = new LoopBodyForSourceUnitOperations() { 543 public void call(SourceUnit source) throws CompilationFailedException { 544 source.convert(); 545 CompilationUnit.this.ast.addModule(source.getAST()); 546 547 548 if (CompilationUnit.this.progressCallback != null) { 549 CompilationUnit.this.progressCallback.call(source, CompilationUnit.this.phase); 550 } 551 } 552 }; 553 554 555 /** 556 * Expands and canonicalizes the ASTs generated during 557 * parsing and conversion, then generates classes. 558 */ 559 public void classgen() throws CompilationFailedException { 560 if (this.phase != Phases.CLASS_GENERATION) { 561 throw new GroovyBugError("CompilationUnit not ready for classgen()"); 562 } 563 564 applyToPrimaryClassNodes(classgen); 565 566 completePhase(); 567 applyToSourceUnits(mark); 568 569 // 570 // Callback progress, if necessary 571 572 573 if (this.progressCallback != null) { 574 this.progressCallback.call(this, CompilationUnit.this.phase); 575 } 576 } 577 578 /** 579 * Runs classgen() on a single ClassNode. 580 */ 581 private LoopBodyForPrimaryClassNodeOperations classgen = new LoopBodyForPrimaryClassNodeOperations() { 582 public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException { 583 // 584 // Run the Verifier on the outer class 585 // 586 try { 587 verifier.visitClass(classNode); 588 } catch (GroovyRuntimeException rpe) { 589 ASTNode node = rpe.getNode(); 590 getErrorCollector().addError( 591 new SyntaxException(rpe.getMessage(),null,node.getLineNumber(),node.getColumnNumber()), 592 source 593 ); 594 } 595 596 // 597 // do scoping 598 // 599 if (source!=null && (!classNode.isSynthetic()) && (!"false".equals(System.getProperty("groovy.jsr.check")))) { 600 JSRVariableScopeCodeVisitor scopeVisitor = new JSRVariableScopeCodeVisitor(null ,source); 601 scopeVisitor.visitClass(classNode); 602 source.getErrorCollector().failIfErrors(); 603 } 604 605 // 606 // Prep the generator machinery 607 // 608 ClassVisitor visitor = createClassVisitor(); 609 610 611 String sourceName = (source == null ? classNode.getModule().getDescription() : source.getName()); 612 ClassGenerator generator = new AsmClassGenerator(context, visitor, classLoader, sourceName); 613 614 615 // 616 // Run the generation and create the class (if required) 617 // 618 generator.visitClass(classNode); 619 completionVerifier.visitClass(classNode); 620 621 622 if (!debug) { 623 byte[] bytes = ((ClassWriter) visitor).toByteArray(); 624 /* this. */classes.add(new GroovyClass(classNode.getName(), bytes)); 625 } 626 627 628 // 629 // Handle any callback that's been set 630 631 632 if (CompilationUnit.this.classgenCallback != null) { 633 classgenCallback.call(visitor, classNode); 634 } 635 636 637 // 638 // Recurse for inner classes 639 640 LinkedList innerClasses = generator.getInnerClasses(); 641 while (!innerClasses.isEmpty()) { 642 classgen.call(source, context, (ClassNode) innerClasses.removeFirst()); 643 } 644 } 645 }; 646 647 648 protected ClassVisitor createClassVisitor() { 649 /** avoid runtime dependency on asm util 650 ClassVisitor visitor; 651 if( debug ) 652 { 653 visitor = new DumpClassVisitor(output); 654 } 655 else 656 { 657 visitor = new ClassWriter(true); 658 } 659 return visitor; 660 */ 661 return new ClassWriter(true); 662 } 663 664 665 /** 666 * Outputs the generated class files to permanent storage. 667 */ 668 public void output() throws CompilationFailedException { 669 if (this.phase != Phases.OUTPUT && !(this.phase == Phases.CLASS_GENERATION && this.phaseComplete)) { 670 throw new GroovyBugError("CompilationUnit not ready for output()"); 671 } 672 673 674 boolean failures = false; 675 676 677 Iterator iterator = this.classes.iterator(); 678 while (iterator.hasNext()) { 679 // 680 // Get the class and calculate its filesystem name 681 682 683 GroovyClass gclass = (GroovyClass) iterator.next(); 684 String name = gclass.getName().replace('.', File.separatorChar) + ".class"; 685 File path = new File(configuration.getTargetDirectory(), name); 686 687 688 // 689 // Ensure the path is ready for the file 690 691 692 File directory = path.getParentFile(); 693 if (directory != null && !directory.exists()) { 694 directory.mkdirs(); 695 } 696 697 698 // 699 // Create the file and write out the data 700 701 byte[] bytes = gclass.getBytes(); 702 703 704 FileOutputStream stream = null; 705 try { 706 stream = new FileOutputStream(path); 707 stream.write(bytes, 0, bytes.length); 708 } catch (IOException e) { 709 getErrorCollector().addError(Message.create(e.getMessage(),this)); 710 failures = true; 711 } finally { 712 if (stream != null) { 713 try { 714 stream.close(); 715 } catch (Exception e) { 716 } 717 } 718 } 719 } 720 721 722 getErrorCollector().failIfErrors(); 723 724 725 completePhase(); 726 applyToSourceUnits(mark); 727 728 729 // 730 // Callback progress, if necessary 731 732 733 if (CompilationUnit.this.progressCallback != null) { 734 CompilationUnit.this.progressCallback.call(this, this.phase); 735 } 736 } 737 738 //--------------------------------------------------------------------------- 739 // PHASE HANDLING 740 741 742 /** 743 * Updates the phase marker on all sources. 744 */ 745 protected void mark() throws CompilationFailedException { 746 applyToSourceUnits(mark); 747 } 748 749 750 /** 751 * Marks a single SourceUnit with the current phase, 752 * if it isn't already there yet. 753 */ 754 private LoopBodyForSourceUnitOperations mark = new LoopBodyForSourceUnitOperations() { 755 public void call(SourceUnit source) throws CompilationFailedException { 756 if (source.phase < phase) { 757 source.gotoPhase(phase); 758 } 759 760 761 if (source.phase == phase && phaseComplete && !source.phaseComplete) { 762 source.completePhase(); 763 } 764 } 765 }; 766 767 768 769 770 771 //--------------------------------------------------------------------------- 772 // LOOP SIMPLIFICATION FOR SourceUnit OPERATIONS 773 774 775 /** 776 * An callback interface for use in the applyToSourceUnits loop driver. 777 */ 778 public abstract class LoopBodyForSourceUnitOperations { 779 public abstract void call(SourceUnit source) throws CompilationFailedException; 780 } 781 782 783 784 785 /** 786 * A loop driver for applying operations to all SourceUnits. 787 * Automatically skips units that have already been processed 788 * through the current phase. 789 */ 790 public void applyToSourceUnits(LoopBodyForSourceUnitOperations body) throws CompilationFailedException { 791 boolean failures = false; 792 793 794 Iterator keys = names.iterator(); 795 while (keys.hasNext()) { 796 String name = (String) keys.next(); 797 SourceUnit source = (SourceUnit) sources.get(name); 798 if (source.phase <= phase) { 799 try { 800 body.call(source); 801 } catch (CompilationFailedException e) { 802 throw e; 803 } catch (Exception e) { 804 throw new GroovyBugError(e); 805 } 806 } 807 } 808 809 810 getErrorCollector().failIfErrors(); 811 } 812 813 814 //--------------------------------------------------------------------------- 815 // LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS 816 817 818 819 /** 820 * An callback interface for use in the applyToSourceUnits loop driver. 821 */ 822 public abstract class LoopBodyForPrimaryClassNodeOperations { 823 public abstract void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException; 824 } 825 826 827 828 /** 829 * A loop driver for applying operations to all primary ClassNodes in 830 * our AST. Automatically skips units that have already been processed 831 * through the current phase. 832 */ 833 public void applyToPrimaryClassNodes(LoopBodyForPrimaryClassNodeOperations body) throws CompilationFailedException { 834 boolean failures = false; 835 836 837 Iterator modules = this.ast.getModules().iterator(); 838 while (modules.hasNext()) { 839 ModuleNode module = (ModuleNode) modules.next(); 840 841 842 try { 843 Iterator classNodes = module.getClasses().iterator(); 844 while (classNodes.hasNext()) { 845 ClassNode classNode = (ClassNode) classNodes.next(); 846 SourceUnit context = module.getContext(); 847 if (context == null || context.phase <= phase) { 848 body.call(module.getContext(), new GeneratorContext(this.ast), classNode); 849 } 850 } 851 } catch (CompilationFailedException e) { 852 // fall thorugh, getErrorREporter().failIfErrors() will triger 853 } catch (Exception e) { 854 failures = true; 855 // String msg = e.getMessage(); 856 // if (e instanceof RuntimeParserException) { 857 // RuntimeParserException rpe = (RuntimeParserException) e; 858 // ASTNode node = rpe.getNode(); 859 // msg += ". The probable error location: [" + node.getLineNumber() + ":" + node.getColumnNumber() + "]"; 860 // } 861 862 // check the exception for a nested compilation exception 863 ErrorCollector nestedCollector = null; 864 for (Throwable next = e.getCause(); next!=e && next!=null; next=next.getCause()) { 865 if (!(next instanceof MultipleCompilationErrorsException)) continue; 866 MultipleCompilationErrorsException mcee = (MultipleCompilationErrorsException) next; 867 nestedCollector = mcee.collector; 868 break; 869 } 870 871 if (nestedCollector!=null) { 872 getErrorCollector().addCollectorContents(nestedCollector); 873 } else { 874 getErrorCollector().addError(new ExceptionMessage(e,configuration.getDebug(),this)); 875 } 876 } 877 } 878 879 getErrorCollector().failIfErrors(); 880 } 881 882 883 //--------------------------------------------------------------------------- 884 // OUTPUT 885 886 887 /** 888 * Writes error messages to the specified PrintWriter. 889 */ 890 /*public void write(PrintWriter writer, Janitor janitor) { 891 super.write(writer, janitor); 892 893 Iterator keys = names.iterator(); 894 while (keys.hasNext()) { 895 String name = (String) keys.next(); 896 SourceUnit source = (SourceUnit) sources.get(name); 897 898 if (source.hasErrors()) { 899 source.write(writer, janitor); 900 } 901 } 902 }*/ 903 904 905 }