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 }