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 }