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