View Javadoc

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