001    /**
002     *
003     * Copyright 2004 James Strachan
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     * http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     *
017     **/
018    package org.codehaus.groovy.syntax;
019    
020    import org.codehaus.groovy.ast.ModuleNode;
021    import org.codehaus.groovy.control.CompilationFailedException;
022    import org.codehaus.groovy.control.SourceUnit;
023    import org.codehaus.groovy.syntax.Types;
024    
025    import java.util.ArrayList;
026    import java.util.HashMap;
027    import java.util.List;
028    import java.util.Map;
029    
030    /**
031     * A common base class of AST helper methods which can be shared across the classic and new parsers
032     *
033     * @author James Strachan
034     * @author Bob McWhirter
035     * @author Sam Pullara
036     * @author Chris Poirier
037     * @version $Revision: 1.6 $
038     */
039    public class ASTHelper {
040    
041        private static final String[] EMPTY_STRING_ARRAY = new String[0];
042        private static final String[] DEFAULT_IMPORTS = {"java.lang.", "java.io.", "java.net.", "java.util.", "groovy.lang.", "groovy.util."};
043    
044        /** The SourceUnit controlling us */
045        private SourceUnit controller;
046    
047        /** Our ClassLoader, which provides information on external types */
048        private ClassLoader classLoader;
049    
050        /** Our imports, simple name => fully qualified name */
051        private Map imports;
052        protected ModuleNode output;
053    
054        /** The package name in which the module sits */
055        private String packageName;   //
056    
057        // TODO should this really be static???
058        protected static HashMap resolutions = new HashMap();  // cleared on build(), to be safe
059    
060        private static String NOT_RESOLVED = new String();
061    
062        /** temporarily store the class names that the current modulenode contains */
063        private List newClasses = new ArrayList();
064    
065        public ASTHelper(SourceUnit controller, ClassLoader classLoader) {
066            this();
067            this.controller = controller;
068            this.classLoader = classLoader;
069        }
070    
071        public ASTHelper() {
072            imports = new HashMap();
073        }
074    
075        public String getPackageName() {
076            return packageName;
077        }
078    
079        public void setPackageName(String packageName) {
080            this.packageName = packageName;
081    
082            output.setPackageName(packageName);
083        }
084    
085    
086        /**
087         * Returns our class loader (as supplied on construction).
088         */
089        public ClassLoader getClassLoader() {
090            return classLoader;
091        }
092    
093        public void setClassLoader(ClassLoader classLoader) {
094            this.classLoader = classLoader;
095        }
096    
097        public SourceUnit getController() {
098            return controller;
099        }
100    
101        public void setController(SourceUnit controller) {
102            this.controller = controller;
103        }
104    
105        /**
106         * Returns a fully qualified name for any given potential type
107         * name.  Returns null if no qualified name could be determined.
108         */
109    
110        protected String resolveName(String name, boolean safe) {
111            //
112            // Use our cache of resolutions, if possible
113    
114            String resolution = (String) resolutions.get(name);
115            if (NOT_RESOLVED.equals(resolution)) {
116                return (safe ? name : null);
117            }
118            else if (resolution != null) {
119                return (String) resolution;
120            }
121    
122            try {
123                getClassLoader().loadClass(name);
124                resolutions.put(name,name);
125                return name;
126            } catch (ClassNotFoundException cnfe){
127                if (cnfe.getCause() instanceof CompilationFailedException) {
128                    resolutions.put(name,name);
129                    return name;
130                }
131            } catch (NoClassDefFoundError ncdfe) {
132                //fall through
133            }
134    
135            do {
136                //
137                // If the type name contains a ".", it's probably fully
138                // qualified, and we don't take it to verification here.
139    
140                if (name.indexOf(".") >= 0) {
141                    resolution = name;
142                    break;                                            // <<< FLOW CONTROL <<<<<<<<<
143                }
144    
145    
146                //
147                // Otherwise, we'll need the scalar type for checking, and
148                // the postfix for reassembly.
149    
150                String scalar = name, postfix = "";
151                while (scalar.endsWith("[]")) {
152                    scalar = scalar.substring(0, scalar.length() - 2);
153                    postfix += "[]";
154                }
155    
156    
157                //
158                // Primitive types are all valid...
159    
160                if (Types.ofType(Types.lookupKeyword(scalar), Types.PRIMITIVE_TYPE)) {
161                    resolution = name;
162                    break;                                            // <<< FLOW CONTROL <<<<<<<<<
163                }
164    
165    
166                //
167                // Next, check our imports and return the qualified name,
168                // if available.
169    
170                if (this.imports.containsKey(scalar)) {
171                    resolution = ((String) this.imports.get(scalar)) + postfix;
172                    break;                                            // <<< FLOW CONTROL <<<<<<<<<
173                }
174    
175    
176                //
177                // Next, see if our class loader can resolve it in the current package.
178    
179                if (packageName != null && packageName.length() > 0) {
180                    try {
181                        getClassLoader().loadClass(dot(packageName, scalar));
182                        resolution = dot(packageName, name);
183    
184                        break;                                        // <<< FLOW CONTROL <<<<<<<<<
185                    } catch (ClassNotFoundException cnfe){
186                        if (cnfe.getCause() instanceof CompilationFailedException) {
187                            break;
188                        }
189                    } catch (NoClassDefFoundError ncdfe) {
190                        //fall through
191                    } 
192                }
193    
194                // search the package imports path
195                List packageImports = output.getImportPackages();
196                for (int i = 0; i < packageImports.size(); i++) {
197                    String pack = (String) packageImports.get(i);
198                    String clsName = pack + name;
199                    try {
200                        getClassLoader().loadClass(clsName);
201                        resolution = clsName;
202                        break;
203                    } catch (ClassNotFoundException cnfe){
204                        if (cnfe.getCause() instanceof CompilationFailedException) {
205                            break;
206                        }
207                    } catch (NoClassDefFoundError ncdfe) {
208                        //fall through
209                    }
210                }
211                if (resolution != null) {
212                    break;
213                }
214    
215                //
216                // Last chance, check the default imports.
217    
218                for (int i = 0; i < DEFAULT_IMPORTS.length; i++) {
219                    try {
220                        String qualified = DEFAULT_IMPORTS[i] + scalar;
221                        getClassLoader().loadClass(qualified);
222    
223                        resolution = qualified + postfix;
224                        break;                                        // <<< FLOW CONTROL <<<<<<<<<
225                    } catch (ClassNotFoundException cnfe){
226                        if (cnfe.getCause() instanceof CompilationFailedException) {
227                            break;
228                        }
229                    } catch (NoClassDefFoundError ncdfee) {
230                        // fall through
231                    }
232                }
233    
234            }
235            while (false);
236    
237    
238            //
239            // Cache the solution and return it
240    
241            if (resolution == null) {
242                resolutions.put(name, NOT_RESOLVED);
243                return (safe ? name : null);
244            }
245            else {
246                resolutions.put(name, resolution);
247                return resolution;
248            }
249        }
250    
251        /**
252         * Returns two names joined by a dot.  If the base name is
253         * empty, returns the name unchanged.
254         */
255    
256        protected String dot(String base, String name) {
257            if (base != null && base.length() > 0) {
258                return base + "." + name;
259            }
260    
261            return name;
262        }
263    
264        protected void makeModule() {
265            this.newClasses.clear();
266            this.output = new ModuleNode(controller);
267            resolutions.clear();
268        }
269    
270        /**
271         * Returns true if the specified name is a known type name.
272         */
273    
274        protected boolean isDatatype(String name) {
275            return resolveName(name, false) != null;
276        }
277    
278        /**
279         * A synonym for <code>dot( base, "" )</code>.
280         */
281    
282        protected String dot(String base) {
283            return dot(base, "");
284        }
285    
286        protected String resolveNewClassOrName(String name, boolean safe) {
287            if (this.newClasses.contains(name)) {
288                return dot(packageName, name);
289            }
290            else {
291                return resolveName(name, safe);
292            }
293        }
294    
295        protected void addNewClassName(String name) {
296            this.newClasses.add(name);
297        }
298    
299        protected void importClass(String importPackage, String name, String as) {
300            //
301            // There appears to be a bug in the previous code for
302            // single imports, in that the old code passed unqualified
303            // class names to module.addImport().  This hasn't been a
304            // problem apparently because those names are resolved here.
305            // Passing module.addImport() a fully qualified name does
306            // currently causes problems with classgen, possibly because
307            // of name collisions.  So, for now, we use the old method...
308    
309            if (as==null) as=name;
310            output.addImport( as, name );  // unqualified
311    
312            name = dot( importPackage, name );
313    
314            // module.addImport( as, name );  // qualified
315            imports.put( as, name );
316        }
317    
318        protected void importPackageWithStar(String importPackage) {
319            String[] classes = output.addImportPackage( dot(importPackage) );
320            for( int i = 0; i < classes.length; i++ )
321            {
322                imports.put( classes[i], dot(importPackage, classes[i]) );
323            }
324        }
325    }