1 /*
2 * Copyright 2003-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18 package org.codehaus.groovy.control;
19
20 import groovy.lang.GroovyClassLoader;
21 import groovy.lang.GroovyRuntimeException;
22 import org.codehaus.groovy.GroovyBugError;
23 import org.codehaus.groovy.ast.ASTNode;
24 import org.codehaus.groovy.ast.ClassNode;
25 import org.codehaus.groovy.ast.CompileUnit;
26 import org.codehaus.groovy.ast.ModuleNode;
27 import org.codehaus.groovy.classgen;
28 import org.codehaus.groovy.control.io.InputStreamReaderSource;
29 import org.codehaus.groovy.control.io.ReaderSource;
30 import org.codehaus.groovy.control.messages.ExceptionMessage;
31 import org.codehaus.groovy.control.messages.Message;
32 import org.codehaus.groovy.control.messages.SimpleMessage;
33 import org.codehaus.groovy.syntax.SyntaxException;
34 import org.codehaus.groovy.tools.GroovyClass;
35 import org.codehaus.groovy.transform.ASTTransformationVisitor;
36 import org.objectweb.asm.ClassVisitor;
37 import org.objectweb.asm.ClassWriter;
38
39 import java.io.File;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.net.URL;
44 import java.security.CodeSource;
45 import java.util;
46
47 /**
48 * Collects all compilation data as it is generated by the compiler system.
49 * Allows additional source units to be added and compilation run again (to
50 * affect only the deltas).
51 *
52 * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
53 * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
54 * @version $Id: CompilationUnit.java 15935 2009-04-08 15:23:17Z blackdrag $
55 */
56
57 public class CompilationUnit extends ProcessingUnit {
58
59 //---------------------------------------------------------------------------
60 // CONSTRUCTION AND SUCH
61
62 private GroovyClassLoader transformLoader; // Classloader for global and local transforms
63
64 protected Map sources; // The SourceUnits from which this unit is built
65 protected Map summariesBySourceName; // Summary of each SourceUnit
66 protected Map summariesByPublicClassName; // Summary of each SourceUnit
67 protected Map classSourcesByPublicClassName; // Summary of each Class
68 protected List names; // Names for each SourceUnit in sources.
69 protected LinkedList queuedSources;
70
71 protected CompileUnit ast; // The overall AST for this CompilationUnit.
72 protected List<GroovyClass> generatedClasses; // The classes generated during classgen.
73
74 protected Verifier verifier; // For use by verify().
75
76 protected boolean debug; // Controls behavior of classgen() and other routines.
77 protected boolean configured; // Set true after the first configure() operation
78
79 protected ClassgenCallback classgenCallback; // A callback for use during classgen()
80 protected ProgressCallback progressCallback; // A callback for use during compile()
81 protected ResolveVisitor resolveVisitor;
82 protected StaticImportVisitor staticImportVisitor;
83 protected OptimizerVisitor optimizer;
84
85 LinkedList[] phaseOperations;
86
87
88 /**
89 * Initializes the CompilationUnit with defaults.
90 */
91 public CompilationUnit() {
92 this(null, null, null);
93 }
94
95
96 /**
97 * Initializes the CompilationUnit with defaults except for class loader.
98 */
99 public CompilationUnit(GroovyClassLoader loader) {
100 this(null, null, loader);
101 }
102
103
104 /**
105 * Initializes the CompilationUnit with no security considerations.
106 */
107 public CompilationUnit(CompilerConfiguration configuration) {
108 this(configuration, null, null);
109 }
110
111 /**
112 * Initializes the CompilationUnit with a CodeSource for controlling
113 * security stuff and a class loader for loading classes.
114 */
115 public CompilationUnit(CompilerConfiguration configuration, CodeSource security, GroovyClassLoader loader) {
116 this(configuration, security, loader, null);
117 }
118
119 /**
120 * Initializes the CompilationUnit with a CodeSource for controlling
121 * security stuff, a class loader for loading classes, and a class
122 * loader for loading AST transformations.
123 * <b>Note</b> The transform loader must be
124 * able to load compiler classes. That means CompilationUnit.class.classLoader
125 * must be at last a parent to transformLoader. The other loader has no such constraint.
126 *
127 * @param transformLoader - the loader for transforms
128 * @param loader - loader used to resolve classes against during compilation
129 * @param security - security setting for the compilation
130 * @param configuration - compilation configuration
131 *
132 */
133 public CompilationUnit(CompilerConfiguration configuration, CodeSource security,
134 GroovyClassLoader loader, GroovyClassLoader transformLoader)
135 {
136 super(configuration, loader, null);
137 this.transformLoader = transformLoader;
138 this.names = new ArrayList();
139 this.queuedSources = new LinkedList();
140 this.sources = new HashMap();
141 this.summariesBySourceName = new HashMap();
142 this.summariesByPublicClassName = new HashMap();
143 this.classSourcesByPublicClassName = new HashMap();
144
145 this.ast = new CompileUnit(this.classLoader, security, this.configuration);
146 this.generatedClasses = new ArrayList();
147
148
149 this.verifier = new Verifier();
150 this.resolveVisitor = new ResolveVisitor(this);
151 this.staticImportVisitor = new StaticImportVisitor(this);
152 this.optimizer = new OptimizerVisitor(this);
153
154 phaseOperations = new LinkedList[Phases.ALL + 1];
155 for (int i = 0; i < phaseOperations.length; i++) {
156 phaseOperations[i] = new LinkedList();
157 }
158 addPhaseOperation(new SourceUnitOperation() {
159 public void call(SourceUnit source) throws CompilationFailedException {
160 source.parse();
161 }
162 }, Phases.PARSING);
163 addPhaseOperation(convert, Phases.CONVERSION);
164 addPhaseOperation(new PrimaryClassNodeOperation() {
165 public void call(SourceUnit source, GeneratorContext context,
166 ClassNode classNode) throws CompilationFailedException {
167 EnumVisitor ev = new EnumVisitor(CompilationUnit.this, source);
168 ev.visitClass(classNode);
169 }
170 }, Phases.CONVERSION);
171 addPhaseOperation(resolve, Phases.SEMANTIC_ANALYSIS);
172 addPhaseOperation(staticImport, Phases.SEMANTIC_ANALYSIS);
173 addPhaseOperation(compileCompleteCheck, Phases.CANONICALIZATION);
174 addPhaseOperation(classgen, Phases.CLASS_GENERATION);
175 addPhaseOperation(output);
176
177 ASTTransformationVisitor.addPhaseOperations(this);
178
179 this.classgenCallback = null;
180 }
181
182 /**
183 * Returns the class loader for loading AST transformations.
184 * @return - the transform class loader
185 */
186 public GroovyClassLoader getTransformLoader() {
187 return transformLoader == null ? getClassLoader() : transformLoader;
188 }
189
190
191 public void addPhaseOperation(SourceUnitOperation op, int phase) {
192 if (phase < 0 || phase > Phases.ALL) throw new IllegalArgumentException("phase " + phase + " is unknown");
193 phaseOperations[phase].add(op);
194 }
195
196 public void addPhaseOperation(PrimaryClassNodeOperation op, int phase) {
197 if (phase < 0 || phase > Phases.ALL) throw new IllegalArgumentException("phase " + phase + " is unknown");
198 phaseOperations[phase].add(op);
199 }
200
201 public void addPhaseOperation(GroovyClassOperation op) {
202 phaseOperations[Phases.OUTPUT].addFirst(op);
203 }
204
205
206 /**
207 * Configures its debugging mode and classloader classpath from a given compiler configuration.
208 * This cannot be done more than once due to limitations in {@link java.net.URLClassLoader URLClassLoader}.
209 */
210 public void configure(CompilerConfiguration configuration) {
211 super.configure(configuration);
212 this.debug = configuration.getDebug();
213
214 if (!this.configured && this.classLoader instanceof GroovyClassLoader) {
215 appendCompilerConfigurationClasspathToClassLoader(configuration, (GroovyClassLoader) this.classLoader);
216 }
217
218 this.configured = true;
219 }
220
221 private void appendCompilerConfigurationClasspathToClassLoader(CompilerConfiguration configuration, GroovyClassLoader classLoader) {
222 /*for (Iterator iterator = configuration.getClasspath().iterator(); iterator.hasNext(); ) {
223 classLoader.addClasspath((String) iterator.next());
224 }*/
225 }
226
227 /**
228 * Returns the CompileUnit that roots our AST.
229 */
230 public CompileUnit getAST() {
231 return this.ast;
232 }
233
234 /**
235 * Get the source summaries
236 */
237 public Map getSummariesBySourceName() {
238 return summariesBySourceName;
239 }
240
241 public Map getSummariesByPublicClassName() {
242 return summariesByPublicClassName;
243 }
244
245 public Map getClassSourcesByPublicClassName() {
246 return classSourcesByPublicClassName;
247 }
248
249 public boolean isPublicClass(String className) {
250 return summariesByPublicClassName.containsKey(className);
251 }
252
253
254 /**
255 * Get the GroovyClasses generated by compile().
256 */
257 public List getClasses() {
258 return generatedClasses;
259 }
260
261
262 /**
263 * Convenience routine to get the first ClassNode, for
264 * when you are sure there is only one.
265 */
266 public ClassNode getFirstClassNode() {
267 return (ClassNode) ((ModuleNode) this.ast.getModules().get(0)).getClasses().get(0);
268 }
269
270
271 /**
272 * Convenience routine to get the named ClassNode.
273 */
274 public ClassNode getClassNode(final String name) {
275 final ClassNode[] result = new ClassNode[]{null};
276 PrimaryClassNodeOperation handler = new PrimaryClassNodeOperation() {
277 public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
278 if (classNode.getName().equals(name)) {
279 result[0] = classNode;
280 }
281 }
282 };
283
284 try {
285 applyToPrimaryClassNodes(handler);
286 } catch (CompilationFailedException e) {
287 if (debug) e.printStackTrace();
288 }
289 return result[0];
290 }
291
292 //---------------------------------------------------------------------------
293 // SOURCE CREATION
294
295
296 /**
297 * Adds a set of file paths to the unit.
298 */
299 public void addSources(String[] paths) {
300 for (int i = 0; i < paths.length; i++) {
301 File file = new File(paths[i]);
302 addSource(file);
303 }
304 }
305
306
307 /**
308 * Adds a set of source files to the unit.
309 */
310 public void addSources(File[] files) {
311 for (int i = 0; i < files.length; i++) {
312 addSource(files[i]);
313 }
314 }
315
316
317 /**
318 * Adds a source file to the unit.
319 */
320 public SourceUnit addSource(File file) {
321 return addSource(new SourceUnit(file, configuration, classLoader, getErrorCollector()));
322 }
323
324 /**
325 * Adds a source file to the unit.
326 */
327 public SourceUnit addSource(URL url) {
328 return addSource(new SourceUnit(url, configuration, classLoader, getErrorCollector()));
329 }
330
331
332 /**
333 * Adds a InputStream source to the unit.
334 */
335 public SourceUnit addSource(String name, InputStream stream) {
336 ReaderSource source = new InputStreamReaderSource(stream, configuration);
337 return addSource(new SourceUnit(name, source, configuration, classLoader, getErrorCollector()));
338 }
339
340
341 /**
342 * Adds a SourceUnit to the unit.
343 */
344 public SourceUnit addSource(SourceUnit source) {
345 String name = source.getName();
346 source.setClassLoader(this.classLoader);
347 for (Iterator iter = queuedSources.iterator(); iter.hasNext();) {
348 SourceUnit su = (SourceUnit) iter.next();
349 if (name.equals(su.getName())) return su;
350 }
351 queuedSources.add(source);
352 return source;
353 }
354
355
356 /**
357 * Returns an iterator on the unit's SourceUnits.
358 */
359 public Iterator iterator() {
360 return new Iterator() {
361 Iterator nameIterator = names.iterator();
362
363
364 public boolean hasNext() {
365 return nameIterator.hasNext();
366 }
367
368
369 public Object next() {
370 String name = (String) nameIterator.next();
371 return sources.get(name);
372 }
373
374
375 public void remove() {
376 throw new UnsupportedOperationException();
377 }
378 };
379 }
380
381
382 /**
383 * Adds a ClassNode directly to the unit (ie. without source).
384 * WARNING: the source is needed for error reporting, using
385 * this method without setting a SourceUnit will cause
386 * NullPinterExceptions
387 */
388 public void addClassNode(ClassNode node) {
389 ModuleNode module = new ModuleNode(this.ast);
390 this.ast.addModule(module);
391 module.addClass(node);
392 }
393
394 //---------------------------------------------------------------------------
395 // EXTERNAL CALLBACKS
396
397
398 /**
399 * A callback interface you can use to "accompany" the classgen()
400 * code as it traverses the ClassNode tree. You will be called-back
401 * for each primary and inner class. Use setClassgenCallback() before
402 * running compile() to set your callback.
403 */
404 public abstract static class ClassgenCallback {
405 public abstract void call(ClassVisitor writer, ClassNode node) throws CompilationFailedException;
406 }
407
408 /**
409 * Sets a ClassgenCallback. You can have only one, and setting
410 * it to null removes any existing setting.
411 */
412 public void setClassgenCallback(ClassgenCallback visitor) {
413 this.classgenCallback = visitor;
414 }
415
416 /**
417 * A callback interface you can use to get a callback after every
418 * unit of the compile process. You will be called-back with a
419 * ProcessingUnit and a phase indicator. Use setProgressCallback()
420 * before running compile() to set your callback.
421 */
422 public abstract static class ProgressCallback {
423
424 public abstract void call(ProcessingUnit context, int phase) throws CompilationFailedException;
425 }
426
427 /**
428 * Sets a ProgressCallback. You can have only one, and setting
429 * it to null removes any existing setting.
430 */
431 public void setProgressCallback(ProgressCallback callback) {
432 this.progressCallback = callback;
433 }
434
435 //---------------------------------------------------------------------------
436 // ACTIONS
437
438
439 /**
440 * Synonym for compile(Phases.ALL).
441 */
442 public void compile() throws CompilationFailedException {
443 compile(Phases.ALL);
444 }
445
446 /**
447 * Compiles the compilation unit from sources.
448 */
449 public void compile(int throughPhase) throws CompilationFailedException {
450 //
451 // To support delta compilations, we always restart
452 // the compiler. The individual passes are responsible
453 // for not reprocessing old code.
454 gotoPhase(Phases.INITIALIZATION);
455 throughPhase = Math.min(throughPhase, Phases.ALL);
456
457 while (throughPhase >= phase && phase <= Phases.ALL) {
458
459 for (Iterator it = phaseOperations[phase].iterator(); it.hasNext();) {
460 Object operation = it.next();
461 if (operation instanceof PrimaryClassNodeOperation) {
462 applyToPrimaryClassNodes((PrimaryClassNodeOperation) operation);
463 } else if (operation instanceof SourceUnitOperation) {
464 applyToSourceUnits((SourceUnitOperation) operation);
465 } else {
466 applyToGeneratedGroovyClasses((GroovyClassOperation) operation);
467 }
468 }
469
470 if (progressCallback != null) progressCallback.call(this, phase);
471 completePhase();
472 applyToSourceUnits(mark);
473
474 if (dequeued()) continue;
475
476 gotoPhase(phase + 1);
477
478 if (phase == Phases.CLASS_GENERATION) {
479 sortClasses();
480 }
481 }
482
483 errorCollector.failIfErrors();
484 }
485
486 private void sortClasses() throws CompilationFailedException {
487 Iterator modules = this.ast.getModules().iterator();
488 while (modules.hasNext()) {
489 ModuleNode module = (ModuleNode) modules.next();
490
491 // before we actually do the sorting we should check
492 // for cyclic references
493 List classes = module.getClasses();
494 for (Iterator iter = classes.iterator(); iter.hasNext();) {
495 ClassNode start = (ClassNode) iter.next();
496 ClassNode cn = start;
497 Set parents = new HashSet();
498 do {
499 if (parents.contains(cn.getName())) {
500 getErrorCollector().addErrorAndContinue(
501 new SimpleMessage("cyclic inheritance involving " + cn.getName() + " in class " + start.getName(), this)
502 );
503 cn = null;
504 } else {
505 parents.add(cn.getName());
506 cn = cn.getSuperClass();
507 }
508 } while (cn != null);
509 }
510 errorCollector.failIfErrors();
511 module.sortClasses();
512
513 }
514 }
515
516
517 /**
518 * Dequeues any source units add through addSource and resets the compiler phase
519 * to initialization.
520 * <p/>
521 * Note: this does not mean a file is recompiled. If a SoucreUnit has already passed
522 * a phase it is skipped until a higher phase is reached.
523 *
524 * @return true if there was a queued source
525 * @throws CompilationFailedException
526 */
527 protected boolean dequeued() throws CompilationFailedException {
528 boolean dequeue = !queuedSources.isEmpty();
529 while (!queuedSources.isEmpty()) {
530 SourceUnit su = (SourceUnit) queuedSources.removeFirst();
531 String name = su.getName();
532 names.add(name);
533 sources.put(name, su);
534 }
535 if (dequeue) {
536 gotoPhase(Phases.INITIALIZATION);
537 }
538 return dequeue;
539 }
540
541 /**
542 * Resolves all types
543 */
544 private final SourceUnitOperation resolve = new SourceUnitOperation() {
545 public void call(SourceUnit source) throws CompilationFailedException {
546 List classes = source.ast.getClasses();
547 for (Iterator it = classes.iterator(); it.hasNext();) {
548 ClassNode node = (ClassNode) it.next();
549
550 VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
551 scopeVisitor.visitClass(node);
552
553 resolveVisitor.startResolving(node, source);
554
555 GenericsVisitor genericsVisitor = new GenericsVisitor(source);
556 genericsVisitor.visitClass(node);
557 }
558
559 }
560 };
561
562 private PrimaryClassNodeOperation staticImport = new PrimaryClassNodeOperation() {
563 public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
564 staticImportVisitor.visitClass(classNode, source);
565 optimizer.visitClass(classNode, source);
566 }
567 };
568
569 /**
570 * Runs convert() on a single SourceUnit.
571 */
572 private SourceUnitOperation convert = new SourceUnitOperation() {
573 public void call(SourceUnit source) throws CompilationFailedException {
574 source.convert();
575 CompilationUnit.this.ast.addModule(source.getAST());
576
577
578 if (CompilationUnit.this.progressCallback != null) {
579 CompilationUnit.this.progressCallback.call(source, CompilationUnit.this.phase);
580 }
581 }
582 };
583
584 private GroovyClassOperation output = new GroovyClassOperation() {
585 public void call(GroovyClass gclass) throws CompilationFailedException {
586 boolean failures = false;
587 String name = gclass.getName().replace('.', File.separatorChar) + ".class";
588 File path = new File(configuration.getTargetDirectory(), name);
589
590 //
591 // Ensure the path is ready for the file
592 //
593 File directory = path.getParentFile();
594 if (directory != null && !directory.exists()) {
595 directory.mkdirs();
596 }
597
598 //
599 // Create the file and write out the data
600 //
601 byte[] bytes = gclass.getBytes();
602
603 FileOutputStream stream = null;
604 try {
605 stream = new FileOutputStream(path);
606 stream.write(bytes, 0, bytes.length);
607 } catch (IOException e) {
608 getErrorCollector().addError(Message.create(e.getMessage(), CompilationUnit.this));
609 failures = true;
610 } finally {
611 if (stream != null) {
612 try {
613 stream.close();
614 } catch (Exception e) {
615 // Ignore
616 }
617 }
618 }
619 }
620 };
621
622 /* checks if all needed classes are compiled before generating the bytecode */
623 private SourceUnitOperation compileCompleteCheck = new SourceUnitOperation() {
624 public void call(SourceUnit source) throws CompilationFailedException {
625 List classes = source.ast.getClasses();
626 for (Iterator it = classes.iterator(); it.hasNext();) {
627 ClassNode node = (ClassNode) it.next();
628 CompileUnit cu = node.getCompileUnit();
629 for (Iterator iter = cu.iterateClassNodeToCompile(); iter.hasNext();) {
630 String name = (String) iter.next();
631 SourceUnit su = ast.getScriptSourceLocation(name);
632 List classesInSourceUnit = su.ast.getClasses();
633 StringBuffer message = new StringBuffer();
634 message
635 .append("Compilation incomplete: expected to find the class ")
636 .append(name)
637 .append(" in ")
638 .append(su.getName());
639 if (classesInSourceUnit.isEmpty()) {
640 message.append(", but the file seems not to contain any classes");
641 } else {
642 message.append(", but the file contains the classes: ");
643 boolean first = true;
644 for (Iterator suClassesIter = classesInSourceUnit
645 .iterator(); suClassesIter.hasNext();) {
646 ClassNode cn = (ClassNode) suClassesIter.next();
647 if (!first) {
648 message.append(", ");
649 } else {
650 first = false;
651 }
652 message.append(cn.getName());
653 }
654 }
655
656 getErrorCollector().addErrorAndContinue(
657 new SimpleMessage(message.toString(), CompilationUnit.this)
658 );
659 iter.remove();
660 }
661 }
662 }
663 };
664
665
666 /**
667 * Runs classgen() on a single ClassNode.
668 */
669 private PrimaryClassNodeOperation classgen = new PrimaryClassNodeOperation() {
670 public boolean needSortedInput() {
671 return true;
672 }
673
674 public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
675
676 //
677 // Run the Verifier on the outer class
678 //
679 try {
680 verifier.visitClass(classNode);
681 } catch (GroovyRuntimeException rpe) {
682 ASTNode node = rpe.getNode();
683 getErrorCollector().addError(
684 new SyntaxException(rpe.getMessage(), null, node.getLineNumber(), node.getColumnNumber()),
685 source
686 );
687 }
688
689 LabelVerifier lv = new LabelVerifier(source);
690 lv.visitClass(classNode);
691
692 ClassCompletionVerifier completionVerifier = new ClassCompletionVerifier(source);
693 completionVerifier.visitClass(classNode);
694
695 ExtendedVerifier xverifier = new ExtendedVerifier(source);
696 xverifier.visitClass(classNode);
697
698 // because the class may be generated even if a error was found
699 // and that class may have an invalid format we fail here if needed
700 getErrorCollector().failIfErrors();
701
702 //
703 // Prep the generator machinery
704 //
705 ClassVisitor visitor = createClassVisitor();
706
707 String sourceName = (source == null ? classNode.getModule().getDescription() : source.getName());
708 // only show the file name and its extension like javac does in its stacktraces rather than the full path
709 // also takes care of both \ and / depending on the host compiling environment
710 if (sourceName != null)
711 sourceName = sourceName.substring(Math.max(sourceName.lastIndexOf('\\'), sourceName.lastIndexOf('/')) + 1);
712 ClassGenerator generator = new AsmClassGenerator(context, visitor, classLoader, sourceName);
713
714 //
715 // Run the generation and create the class (if required)
716 //
717 generator.visitClass(classNode);
718
719
720 byte[] bytes = ((ClassWriter) visitor).toByteArray();
721 generatedClasses.add(new GroovyClass(classNode.getName(), bytes));
722
723 //
724 // Handle any callback that's been set
725 //
726 if (CompilationUnit.this.classgenCallback != null) {
727 classgenCallback.call(visitor, classNode);
728 }
729
730 //
731 // Recurse for inner classes
732 //
733 LinkedList innerClasses = generator.getInnerClasses();
734 while (!innerClasses.isEmpty()) {
735 classgen.call(source, context, (ClassNode) innerClasses.removeFirst());
736 }
737 }
738 };
739
740
741 protected ClassVisitor createClassVisitor() {
742 return new ClassWriter(true);
743 }
744
745 //---------------------------------------------------------------------------
746 // PHASE HANDLING
747
748
749 /**
750 * Updates the phase marker on all sources.
751 */
752 protected void mark() throws CompilationFailedException {
753 applyToSourceUnits(mark);
754 }
755
756
757 /**
758 * Marks a single SourceUnit with the current phase,
759 * if it isn't already there yet.
760 */
761 private SourceUnitOperation mark = new SourceUnitOperation() {
762 public void call(SourceUnit source) throws CompilationFailedException {
763 if (source.phase < phase) {
764 source.gotoPhase(phase);
765 }
766
767
768 if (source.phase == phase && phaseComplete && !source.phaseComplete) {
769 source.completePhase();
770 }
771 }
772 };
773
774 //---------------------------------------------------------------------------
775 // LOOP SIMPLIFICATION FOR SourceUnit OPERATIONS
776
777
778 /**
779 * An callback interface for use in the applyToSourceUnits loop driver.
780 */
781 public abstract static class SourceUnitOperation {
782 public abstract void call(SourceUnit source) throws CompilationFailedException;
783 }
784
785
786 /**
787 * A loop driver for applying operations to all SourceUnits.
788 * Automatically skips units that have already been processed
789 * through the current phase.
790 */
791 public void applyToSourceUnits(SourceUnitOperation body) throws CompilationFailedException {
792 Iterator keys = names.iterator();
793 while (keys.hasNext()) {
794 String name = (String) keys.next();
795 SourceUnit source = (SourceUnit) sources.get(name);
796 if ((source.phase < phase) || (source.phase == phase && !source.phaseComplete)) {
797 try {
798 body.call(source);
799 } catch (CompilationFailedException e) {
800 throw e;
801 } catch (Exception e) {
802 GroovyBugError gbe = new GroovyBugError(e);
803 changeBugText(gbe, source);
804 throw gbe;
805 } catch (GroovyBugError e) {
806 changeBugText(e, source);
807 throw e;
808 }
809 }
810 }
811
812
813 getErrorCollector().failIfErrors();
814 }
815
816 //---------------------------------------------------------------------------
817 // LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS
818
819
820 /**
821 * An callback interface for use in the applyToSourceUnits loop driver.
822 */
823 public abstract static class PrimaryClassNodeOperation {
824 public abstract void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException;
825
826 public boolean needSortedInput() {
827 return false;
828 }
829 }
830
831 public abstract static class GroovyClassOperation {
832 public abstract void call(GroovyClass gclass) throws CompilationFailedException;
833 }
834
835 private int getSuperClassCount(ClassNode element) {
836 int count = 0;
837 while (element != null) {
838 count++;
839 element = element.getSuperClass();
840 }
841 return count;
842 }
843
844 private int getSuperInterfaceCount(ClassNode element) {
845 int count = 1;
846 ClassNode[] interfaces = element.getInterfaces();
847 for (int i=0; i<interfaces.length; i++) {
848 count = Math.max(count, getSuperInterfaceCount(interfaces[i])+1);
849 }
850 return count;
851 }
852
853 private List getPrimaryClassNodes(boolean sort) {
854 List unsorted = new ArrayList();
855 Iterator modules = this.ast.getModules().iterator();
856 while (modules.hasNext()) {
857 ModuleNode module = (ModuleNode) modules.next();
858
859 Iterator classNodes = module.getClasses().iterator();
860 while (classNodes.hasNext()) {
861 ClassNode classNode = (ClassNode) classNodes.next();
862 unsorted.add(classNode);
863 }
864 }
865
866 if (sort == false) return unsorted;
867
868 int[] indexClass = new int[unsorted.size()];
869 int[] indexInterface = new int[unsorted.size()];
870 {
871 int i = 0;
872 for (Iterator iter = unsorted.iterator(); iter.hasNext(); i++) {
873 ClassNode node = (ClassNode) iter.next();
874 ClassNode element = node;
875 if (node.isInterface()) {
876 indexInterface[i] = getSuperInterfaceCount(element);
877 indexClass[i] = -1;
878 } else {
879 indexClass[i] = getSuperClassCount(element);
880 indexInterface[i] = -1;
881 }
882 }
883 }
884
885 List sorted = getSorted(indexInterface, unsorted);
886 sorted.addAll(getSorted(indexClass, unsorted));
887
888 return sorted;
889 }
890
891 private List getSorted(int[] index, List unsorted) {
892 List sorted = new ArrayList(unsorted.size());
893 int start = 0;
894 for (int i = 0; i < unsorted.size(); i++) {
895 int min = -1;
896 for (int j = 0; j < unsorted.size(); j++) {
897 if (index[j] == -1) continue;
898 if (min == -1) {
899 min = j;
900 } else if (index[j] < index[min]) {
901 min = j;
902 }
903 }
904 if (min == -1) break;
905 sorted.add(unsorted.get(min));
906 index[min] = -1;
907 }
908 return sorted;
909 }
910
911 /**
912 * A loop driver for applying operations to all primary ClassNodes in
913 * our AST. Automatically skips units that have already been processed
914 * through the current phase.
915 */
916 public void applyToPrimaryClassNodes(PrimaryClassNodeOperation body) throws CompilationFailedException {
917 Iterator classNodes = getPrimaryClassNodes(body.needSortedInput()).iterator();
918 while (classNodes.hasNext()) {
919 SourceUnit context = null;
920 try {
921 ClassNode classNode = (ClassNode) classNodes.next();
922 context = classNode.getModule().getContext();
923 if (context == null || context.phase <= phase) {
924 body.call(context, new GeneratorContext(this.ast), classNode);
925 }
926 } catch (CompilationFailedException e) {
927 // fall through, getErrorReporter().failIfErrors() will triger
928 } catch (NullPointerException npe) {
929 throw npe;
930 } catch (GroovyBugError e) {
931 changeBugText(e, context);
932 throw e;
933 } catch (Exception e) {
934 // check the exception for a nested compilation exception
935 ErrorCollector nestedCollector = null;
936 for (Throwable next = e.getCause(); next != e && next != null; next = next.getCause()) {
937 if (!(next instanceof MultipleCompilationErrorsException)) continue;
938 MultipleCompilationErrorsException mcee = (MultipleCompilationErrorsException) next;
939 nestedCollector = mcee.collector;
940 break;
941 }
942
943 if (nestedCollector != null) {
944 getErrorCollector().addCollectorContents(nestedCollector);
945 } else {
946 getErrorCollector().addError(new ExceptionMessage(e, configuration.getDebug(), this));
947 }
948 }
949 }
950
951 getErrorCollector().failIfErrors();
952 }
953
954 public void applyToGeneratedGroovyClasses(GroovyClassOperation body) throws CompilationFailedException {
955 if (this.phase != Phases.OUTPUT && !(this.phase == Phases.CLASS_GENERATION && this.phaseComplete)) {
956 throw new GroovyBugError("CompilationUnit not ready for output(). Current phase=" + getPhaseDescription());
957 }
958
959 boolean failures = false;
960
961 Iterator iterator = this.generatedClasses.iterator();
962 while (iterator.hasNext()) {
963 //
964 // Get the class and calculate its filesystem name
965 //
966 GroovyClass gclass = (GroovyClass) iterator.next();
967 try {
968 body.call(gclass);
969 } catch (CompilationFailedException e) {
970 // fall thorugh, getErrorREporter().failIfErrors() will triger
971 } catch (NullPointerException npe) {
972 throw npe;
973 } catch (GroovyBugError e) {
974 changeBugText(e, null);
975 throw e;
976 } catch (Exception e) {
977 GroovyBugError gbe = new GroovyBugError(e);
978 throw gbe;
979 }
980 }
981
982 getErrorCollector().failIfErrors();
983 }
984
985 private void changeBugText(GroovyBugError e, SourceUnit context) {
986 e.setBugText("exception in phase '" + getPhaseDescription() + "' in source unit '" + ((context != null) ? context.getName() : "?") + "' " + e.getBugText());
987 }
988 }