View Javadoc

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