001    /*
002     $Id: SourceUnit.java 4098 2006-10-10 16:09:48Z blackdrag $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    
047    package org.codehaus.groovy.control;
048    
049    import groovy.lang.GroovyClassLoader;
050    
051    import java.io.File;
052    import java.io.FileWriter;
053    import java.io.IOException;
054    import java.io.Reader;
055    import java.net.URL;
056    import java.security.AccessController;
057    import java.security.PrivilegedAction;
058    
059    import org.codehaus.groovy.GroovyBugError;
060    import org.codehaus.groovy.ast.ModuleNode;
061    import org.codehaus.groovy.control.io.FileReaderSource;
062    import org.codehaus.groovy.control.io.ReaderSource;
063    import org.codehaus.groovy.control.io.StringReaderSource;
064    import org.codehaus.groovy.control.io.URLReaderSource;
065    import org.codehaus.groovy.control.messages.Message;
066    import org.codehaus.groovy.control.messages.SimpleMessage;
067    import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
068    import org.codehaus.groovy.syntax.*;
069    import org.codehaus.groovy.tools.Utilities;
070    
071    import antlr.CharScanner;
072    import antlr.MismatchedTokenException;
073    import antlr.NoViableAltException;
074    import antlr.NoViableAltForCharException;
075    
076    import com.thoughtworks.xstream.XStream;
077    
078    
079    /**
080     * Provides an anchor for a single source unit (usually a script file)
081     * as it passes through the compiler system.
082     *
083     * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
084     * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
085     * @version $Id: SourceUnit.java 4098 2006-10-10 16:09:48Z blackdrag $
086     */
087    
088    public class SourceUnit extends ProcessingUnit {
089    
090        /**
091         * The pluggable parser used to generate the AST - we allow pluggability currently as we need to have Classic and JSR support
092         */
093        private ParserPlugin parserPlugin;
094    
095        /**
096         * Where we can get Readers for our source unit
097         */
098        protected ReaderSource source;
099        /**
100         * A descriptive name of the source unit. This name shouldn't
101         * be used for controling the SourceUnit, it is only for error
102         * messages
103         */
104        protected String name;
105        /**
106         * A Concrete Syntax Tree of the source
107         */
108        protected Reduction cst;
109    
110        /**
111         * A facade over the CST
112         */
113        protected SourceSummary sourceSummary;
114        /**
115         * The root of the Abstract Syntax Tree for the source
116         */
117        protected ModuleNode ast;
118    
119    
120        /**
121         * Initializes the SourceUnit from existing machinery.
122         */
123        public SourceUnit(String name, ReaderSource source, CompilerConfiguration flags, GroovyClassLoader loader, ErrorCollector er) {
124            super(flags, loader, er);
125    
126            this.name = name;
127            this.source = source;
128        }
129    
130    
131        /**
132         * Initializes the SourceUnit from the specified file.
133         */
134        public SourceUnit(File source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
135            this(source.getPath(), new FileReaderSource(source, configuration), configuration, loader, er);
136        }
137    
138    
139        /**
140         * Initializes the SourceUnit from the specified URL.
141         */
142        public SourceUnit(URL source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
143            this(source.getPath(), new URLReaderSource(source, configuration), configuration, loader, er);
144        }
145    
146    
147        /**
148         * Initializes the SourceUnit for a string of source.
149         */
150        public SourceUnit(String name, String source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
151            this(name, new StringReaderSource(source, configuration), configuration, loader, er);
152        }
153    
154    
155        /**
156         * Returns the name for the SourceUnit. This name shouldn't
157         * be used for controling the SourceUnit, it is only for error
158         * messages
159         */
160        public String getName() {
161            return name;
162        }
163    
164    
165        /**
166         * Returns the Concrete Syntax Tree produced during parse()ing.
167         */
168        public Reduction getCST() {
169            return this.cst;
170        }
171    
172        /**
173         * Returns the Source Summary
174         */
175        public SourceSummary getSourceSummary() {
176            return this.sourceSummary;
177        }
178        /**
179         * Returns the Abstract Syntax Tree produced during parse()ing
180         * and expanded during later phases.
181         */
182        public ModuleNode getAST() {
183            return this.ast;
184        }
185    
186    
187        /**
188         * Convenience routine, primarily for use by the InteractiveShell,
189         * that returns true if parse() failed with an unexpected EOF.
190         */
191        public boolean failedWithUnexpectedEOF() {
192            // Implementation note - there are several ways for the Groovy compiler
193            // to report an unexpected EOF. Perhaps this implementation misses some.
194            // If you find another way, please add it.
195            if (getErrorCollector().hasErrors()) {
196                Message last = (Message) getErrorCollector().getLastError();
197                Throwable cause = null;
198                if (last instanceof SyntaxErrorMessage) {
199                    cause = ((SyntaxErrorMessage) last).getCause().getCause();
200                }
201                if (cause != null) {
202                    if (cause instanceof NoViableAltException) {
203                        return isEofToken(((NoViableAltException) cause).token);
204                    } else if (cause instanceof NoViableAltForCharException) {
205                            char badChar = ((NoViableAltForCharException) cause).foundChar;
206                            return badChar == CharScanner.EOF_CHAR;
207                    } else if (cause instanceof MismatchedTokenException) {
208                        return isEofToken(((MismatchedTokenException) cause).token);
209                    }
210                }
211            }
212            return false;    
213        }
214    
215        protected boolean isEofToken(antlr.Token token) {
216            return token.getType() == antlr.Token.EOF_TYPE;
217        }
218    
219    
220    
221        //---------------------------------------------------------------------------
222        // FACTORIES
223    
224    
225        /**
226         * A convenience routine to create a standalone SourceUnit on a String
227         * with defaults for almost everything that is configurable.
228         */
229        public static SourceUnit create(String name, String source) {
230            CompilerConfiguration configuration = new CompilerConfiguration();
231            configuration.setTolerance(1);
232    
233            return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration));
234        }
235    
236    
237        /**
238         * A convenience routine to create a standalone SourceUnit on a String
239         * with defaults for almost everything that is configurable.
240         */
241        public static SourceUnit create(String name, String source, int tolerance) {
242            CompilerConfiguration configuration = new CompilerConfiguration();
243            configuration.setTolerance(tolerance);
244    
245            return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration));
246        }
247    
248    
249    
250    
251    
252        //---------------------------------------------------------------------------
253        // PROCESSING
254    
255    
256        /**
257         * Parses the source to a CST.  You can retrieve it with getCST().
258         */
259        public void parse() throws CompilationFailedException {
260            if (this.phase > Phases.PARSING) {
261                throw new GroovyBugError("parsing is already complete");
262            }
263    
264            if (this.phase == Phases.INITIALIZATION) {
265                nextPhase();
266            }
267    
268    
269            //
270            // Create a reader on the source and run the parser.
271    
272            Reader reader = null;
273            try {
274                reader = source.getReader();
275    
276                // lets recreate the parser each time as it tends to keep around state
277                parserPlugin = getConfiguration().getPluginFactory().createParserPlugin();
278    
279                cst = parserPlugin.parseCST(this, reader);
280                sourceSummary = parserPlugin.getSummary();
281    
282                reader.close();
283                
284            }
285            catch (IOException e) {
286                getErrorCollector().addFatalError(new SimpleMessage(e.getMessage(),this));
287            }
288            finally {
289                if (reader != null) {
290                    try {
291                        reader.close();
292                    }
293                    catch (IOException e) {
294                    }
295                }
296            }
297        }
298    
299    
300        /**
301         * Generates an AST from the CST.  You can retrieve it with getAST().
302         */
303        public void convert() throws CompilationFailedException {
304            if (this.phase == Phases.PARSING && this.phaseComplete) {
305                gotoPhase(Phases.CONVERSION);
306            }
307    
308            if (this.phase != Phases.CONVERSION) {
309                throw new GroovyBugError("SourceUnit not ready for convert()");
310            }
311    
312            
313            //
314            // Build the AST
315            
316            try {
317                this.ast = parserPlugin.buildAST(this, this.classLoader, this.cst);
318    
319                this.ast.setDescription(this.name);
320            }
321            catch (SyntaxException e) {
322                getErrorCollector().addError(new SyntaxErrorMessage(e,this));
323            }
324    
325            String property = (String) AccessController.doPrivileged(new PrivilegedAction() {
326                    public Object run() {
327                            return System.getProperty("groovy.ast");
328                    }
329            });
330            
331            if ("xml".equals(property)) {
332                saveAsXML(name,ast);
333            }
334        }
335    
336        private void saveAsXML(String name, ModuleNode ast) {
337            XStream xstream = new XStream();
338            try {
339                xstream.toXML(ast,new FileWriter(name + ".xml"));
340                System.out.println("Written AST to " + name + ".xml");
341            } catch (Exception e) {
342                System.out.println("Couldn't write to " + name + ".xml");
343                e.printStackTrace();
344            }
345        }
346        //---------------------------------------------------------------------------    // SOURCE SAMPLING
347    
348        /**
349         * Returns a sampling of the source at the specified line and column,
350         * of null if it is unavailable.
351         */
352        public String getSample(int line, int column, Janitor janitor) {
353            String sample = null;
354            String text = source.getLine(line, janitor);
355    
356            if (text != null) {
357                if (column > 0) {
358                    String marker = Utilities.repeatString(" ", column - 1) + "^";
359    
360                    if (column > 40) {
361                        int start = column - 30 - 1;
362                        int end = (column + 10 > text.length() ? text.length() : column + 10 - 1);
363                        sample = "   " + text.substring(start, end) + Utilities.eol() + "   " + marker.substring(start, marker.length());
364                    }
365                    else {
366                        sample = "   " + text + Utilities.eol() + "   " + marker;
367                    }
368                }
369                else {
370                    sample = text;
371                }
372            }
373    
374            return sample;
375        }
376        
377        public void addException(Exception e) throws CompilationFailedException {
378            getErrorCollector().addException(e,this);
379        }
380        
381        public void addError(SyntaxException se) throws CompilationFailedException {
382            getErrorCollector().addError(se,this);
383        }
384    }
385    
386    
387    
388