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.7 $ 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 resolution = dot(packageName, name); 188 break; 189 } 190 } catch (NoClassDefFoundError ncdfe) { 191 //fall through 192 } 193 } 194 195 // search the package imports path 196 List packageImports = output.getImportPackages(); 197 for (int i = 0; i < packageImports.size(); i++) { 198 String pack = (String) packageImports.get(i); 199 String clsName = pack + name; 200 try { 201 getClassLoader().loadClass(clsName); 202 resolution = clsName; 203 break; 204 } catch (ClassNotFoundException cnfe){ 205 if (cnfe.getCause() instanceof CompilationFailedException) { 206 resolution = clsName; 207 break; 208 } 209 } catch (NoClassDefFoundError ncdfe) { 210 //fall through 211 } 212 } 213 if (resolution != null) { 214 break; 215 } 216 217 // 218 // Last chance, check the default imports. 219 220 for (int i = 0; i < DEFAULT_IMPORTS.length; i++) { 221 String qualified = DEFAULT_IMPORTS[i] + scalar; 222 try { 223 getClassLoader().loadClass(qualified); 224 225 resolution = qualified + postfix; 226 break; // <<< FLOW CONTROL <<<<<<<<< 227 } catch (ClassNotFoundException cnfe){ 228 if (cnfe.getCause() instanceof CompilationFailedException) { 229 resolution = qualified + postfix; 230 break; 231 } 232 } catch (NoClassDefFoundError ncdfee) { 233 // fall through 234 } 235 } 236 237 } 238 while (false); 239 240 241 // 242 // Cache the solution and return it 243 244 if (resolution == null) { 245 resolutions.put(name, NOT_RESOLVED); 246 return (safe ? name : null); 247 } 248 else { 249 resolutions.put(name, resolution); 250 return resolution; 251 } 252 } 253 254 /** 255 * Returns two names joined by a dot. If the base name is 256 * empty, returns the name unchanged. 257 */ 258 259 protected String dot(String base, String name) { 260 if (base != null && base.length() > 0) { 261 return base + "." + name; 262 } 263 264 return name; 265 } 266 267 protected void makeModule() { 268 this.newClasses.clear(); 269 this.output = new ModuleNode(controller); 270 resolutions.clear(); 271 } 272 273 /** 274 * Returns true if the specified name is a known type name. 275 */ 276 277 protected boolean isDatatype(String name) { 278 return resolveName(name, false) != null; 279 } 280 281 /** 282 * A synonym for <code>dot( base, "" )</code>. 283 */ 284 285 protected String dot(String base) { 286 return dot(base, ""); 287 } 288 289 protected String resolveNewClassOrName(String name, boolean safe) { 290 if (this.newClasses.contains(name)) { 291 return dot(packageName, name); 292 } 293 else { 294 return resolveName(name, safe); 295 } 296 } 297 298 protected void addNewClassName(String name) { 299 this.newClasses.add(name); 300 } 301 302 protected void importClass(String importPackage, String name, String as) { 303 // 304 // There appears to be a bug in the previous code for 305 // single imports, in that the old code passed unqualified 306 // class names to module.addImport(). This hasn't been a 307 // problem apparently because those names are resolved here. 308 // Passing module.addImport() a fully qualified name does 309 // currently causes problems with classgen, possibly because 310 // of name collisions. So, for now, we use the old method... 311 312 if (as==null) as=name; 313 output.addImport( as, name ); // unqualified 314 315 name = dot( importPackage, name ); 316 317 // module.addImport( as, name ); // qualified 318 imports.put( as, name ); 319 } 320 321 protected void importPackageWithStar(String importPackage) { 322 String[] classes = output.addImportPackage( dot(importPackage) ); 323 for( int i = 0; i < classes.length; i++ ) 324 { 325 imports.put( classes[i], dot(importPackage, classes[i]) ); 326 } 327 } 328 }