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