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 }