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
57 import org.codehaus.groovy.GroovyBugError;
58
59 import org.codehaus.groovy.ast.ASTNode;
60
61 import org.codehaus.groovy.ast.ClassNode;
62
63 import org.codehaus.groovy.ast.CompileUnit;
64 import org.codehaus.groovy.ast.ModuleNode;
65 import org.codehaus.groovy.classgen.*;
66 import org.codehaus.groovy.control.io.InputStreamReaderSource;
67 import org.codehaus.groovy.control.io.ReaderSource;
68 import org.codehaus.groovy.control.messages.ExceptionMessage;
69 import org.codehaus.groovy.control.messages.Message;
70 import org.codehaus.groovy.syntax.SyntaxException;
71 import org.codehaus.groovy.tools.GroovyClass;
72 import org.objectweb.asm.ClassVisitor;
73 import org.objectweb.asm.ClassWriter;
74
75
76 import groovy.lang.GroovyRuntimeException;
77
78
79 import java.io.*;
80 import java.net.MalformedURLException;
81 import java.net.URL;
82 import java.security.CodeSource;
83 import java.util.*;
84
85
86
87
88
89 /***
90 * Collects all compilation data as it is generated by the compiler system.
91 * Allows additional source units to be added and compilation run again (to
92 * affect only the deltas).
93 *
94 * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
95 * @version $Id: CompilationUnit.java,v 1.23 2005/06/13 16:21:54 blackdrag Exp $
96 */
97
98
99 public class CompilationUnit extends ProcessingUnit {
100
101
102
103
104
105
106 protected HashMap sources;
107 protected ArrayList names;
108
109
110 protected CompileUnit ast;
111 protected ArrayList classes;
112
113
114 protected Verifier verifier;
115
116
117 protected ClassCompletionVerifier completionVerifier;
118
119
120 protected boolean debug;
121 protected boolean configured;
122
123
124 protected ClassgenCallback classgenCallback;
125 protected ProgressCallback progressCallback;
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
187
188
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
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
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
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
441
442
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
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
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
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
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
617
618 generator.visitClass(classNode);
619 completionVerifier.visitClass(classNode);
620
621
622 if (!debug) {
623 byte[] bytes = ((ClassWriter) visitor).toByteArray();
624
625 }
626
627
628
629
630
631
632 if (CompilationUnit.this.classgenCallback != null) {
633 classgenCallback.call(visitor, classNode);
634 }
635
636
637
638
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
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
690
691
692 File directory = path.getParentFile();
693 if (directory != null && !directory.exists()) {
694 directory.mkdirs();
695 }
696
697
698
699
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
731
732
733 if (CompilationUnit.this.progressCallback != null) {
734 CompilationUnit.this.progressCallback.call(this, this.phase);
735 }
736 }
737
738
739
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
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
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
853 } catch (Exception e) {
854 failures = true;
855
856
857
858
859
860
861
862
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
885
886
887 /***
888 * Writes error messages to the specified PrintWriter.
889 */
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905 }