001 /* Copyright 2000, 2001, Compaq Computer Corporation */ 002 003 package javafe.filespace; 004 005 006 import java.io.IOException; 007 008 009 /** 010 * This module encapsulates how to resolve an ambiguous multi-part 011 * identifier (i.e., X.Y.Z) into a package + (possibly a) reference 012 * type + a type field/member multi-part identifier, using the output 013 * of ClassPath.<p> 014 * 015 * I.e., it would split java.util.zip.Foo.Subclass.x.y into the package 016 * java.util.zip, the (inner) type Foo$Subclass, and the field/member 017 * identifier x.y.<p> 018 */ 019 020 public class Resolve { 021 022 /*************************************************** 023 * * 024 * Primitive functions for looking up identifiers: * 025 * * 026 **************************************************/ 027 028 /** 029 * Does a package contain a reference type with a given simple 030 * name?<p> 031 * 032 * Currently true if a source or a binary for that type exists 033 * (directly) in the given package.<p> 034 */ 035 public static boolean typeExists(/*@ non_null @*/ Tree P, 036 /*@ non_null @*/ String typeName) { 037 if (P.getChild(typeName+".java") != null) 038 return true; 039 if (P.getChild(typeName+".class") != null) 040 return true; 041 042 return false; 043 } 044 045 // The class Resolve_Result would be here as the static class Result if 046 // inner classes were being used.<p> 047 // 048 // See its comments.<p> 049 050 051 // The class Resolve_AmbiguousName would be here as the static class 052 // AmbiguousName if inner classes were being used.<p> 053 // 054 // See its comments.<p> 055 056 057 /** 058 * Lookup a multi-part identifier in a Java filespace in the same 059 * way that the Java compiler does so.<p> 060 * 061 * Precondition: the identifier parts should not contain the '.' or 062 * '$' characters, and each must be non-null and non-empty.<p> 063 * 064 * The leftmost part of the identifier is assumed to refer to an 065 * existing package; the longest such name is used. The returned 066 * package will always be non-null because package names may be 067 * empty ("" refers to the top package).<p> 068 * 069 * The remaining part of the identifier (may be empty) is then 070 * assumed to be the concatenation of a (inner) reference-type name 071 * and a remainder part. Again, as with the package name, the type 072 * name is assumed to be the longest such name that refers to an 073 * existing type, with the proviso that a type is considered to 074 * exist only if all prefix types (i.e., X and X$Y for X$Y$Z) 075 * also exist. If no non-empty prefix names an existing type, 076 * then the type name is taken to be empty and the returned 077 * typeName will be null. Otherwise, the returned typeName 078 * contains the (non-null) name of the type, with its parts 079 * separated by '$'s.<p> 080 * 081 * The remaining part of the identifier after the package name and 082 * type name have been removed is returned as the remainder part.<p> 083 * 084 * 085 * EXCEPTION: if, while identifying the package name, a package is 086 * encountered that has the same name as a reference type, then the 087 * exception Resolve_AmbiguousName will be thrown with 088 * ambiguousPackage set to the package with the ambiguous name. 089 * Such package/type naming conflicts are illegal according to the 090 * Java documentation.<p> 091 */ 092 //@ requires (\forall int i; (0 <= i && i < identifier.length) ==> identifier[i] != null); 093 //@ ensures \result.myPackage != null; 094 public static /*@ non_null @*/ Resolve_Result lookup(/*@ non_null @*/ Tree filespace, 095 /*@ non_null @*/ String[] identifier) 096 throws Resolve_AmbiguousName { 097 // Resulting package starts with the top package: 098 Tree P = filespace; 099 100 int i = 0; // Index into identifier as we scan it L->R 101 102 /* 103 * While the next identifier part is the simple name of a 104 * subpackage of the current package, but *not* the simple name 105 * of a direct type of the current package, then move down the 106 * package tree. If it is both, throw Resolve_AmbiguousName. 107 */ 108 while (i<identifier.length && !typeExists(P, identifier[i])) { 109 Tree tp = P.getChild(identifier[i]); 110 if (tp == null) 111 break; 112 P = tp; 113 i++; 114 } 115 if (i<identifier.length && typeExists(P, identifier[i])) { 116 Tree ambiguousPackage = P.getChild(identifier[i]); 117 if (ambiguousPackage != null) { 118 throw new Resolve_AmbiguousName("ambiguous name: " 119 + PkgTree.getPackageName(ambiguousPackage) 120 + " is both a class or interface type and" 121 + " a package", 122 ambiguousPackage); 123 } 124 } 125 126 /* 127 * Now, find the longest type name that exists (directly) in 128 * the current package. If X$Y does not exist, then we assume 129 * that X$Y$Z does not exist. 130 */ 131 String typeName = ""; 132 while (i<identifier.length && 133 typeExists(P, combineNames(typeName,identifier[i],"$"))) 134 typeName = combineNames(typeName, identifier[i++], "$"); 135 if (typeName.equals("")) 136 typeName = null; 137 138 // Finally, return the results: 139 Resolve_Result answer = new Resolve_Result(); 140 answer.myPackage = P; 141 answer.myTypeName = typeName; 142 answer.remainder = new String[identifier.length-i]; 143 for (int j=0; j+i<identifier.length; j++) 144 answer.remainder[j] = identifier[j+i]; 145 return answer; 146 } 147 148 149 /*************************************************** 150 * * 151 * Handling names: * 152 * * 153 **************************************************/ 154 155 /** 156 * Combine two names using a separator if both are non-empty. <p> 157 * 158 */ 159 //@ requires first != null && second != null; 160 //@ ensures \result != null; 161 public static String combineNames(String first, String second, 162 String separator) { 163 if (first.equals("") || second.equals("")) 164 return first+second; 165 else 166 return first+separator+second; 167 } 168 169 /** 170 * Convert a multi-part identifier into a path. Returns null if 171 * the identifier is badly formed (i.e., contains empty 172 * components). id must be non-null. <p> 173 * 174 * Only uses '.' as a separator. If you wish to allow '$' as well, 175 * use tr first to map all the '$'s in the name into '.'s.<p> 176 */ 177 //@ requires id != null; 178 /*@ ensures \result != null ==> 179 (\forall int i; (0<=i && i<\result.length) ==> \result[i] != null); */ 180 public static String[] parseIdentifier(String id) { 181 String[] path = StringUtil.parseList(id, '.'); 182 183 for (int i=0; i<path.length; i++) 184 if (path[i].equals("")) 185 return null; 186 187 return path; 188 } 189 190 /** 191 * Do a lookup using the result of parseIdentifier extended to 192 * allow '$' as an additional separator.<p> 193 * 194 * Complains to System.err then returns null if the name is badly 195 * formed. identifier and filespace must be non-null.<p> 196 */ 197 //@ requires filespace != null && identifier != null; 198 //@ ensures \result != null ==> \result.myPackage != null; 199 public static Resolve_Result lookupName(Tree filespace, String identifier) 200 throws Resolve_AmbiguousName { 201 // Allow '$' as an additional separator: 202 identifier = tr(identifier, '$', '.'); 203 204 String[] idPath = Resolve.parseIdentifier(identifier); 205 if (idPath==null) { 206 System.err.println(identifier + ": badly formed name"); 207 return null; 208 } 209 210 return lookup(filespace, idPath); 211 } 212 213 214 /*************************************************** 215 * * 216 * Maintaining a notion of a current namespace: * 217 * * 218 **************************************************/ 219 220 /** 221 * The current Java namespace; must be a non-null filespace.<p> 222 * 223 * Starts out empty.<p> 224 */ 225 //@ invariant namespace != null; 226 public static Tree namespace = PathComponent.empty(); 227 228 229 /** 230 * Attempt to set the current namespace to a new non-null class path.<p> 231 * 232 * Complains about any errors to System.err. The current namespace 233 * remains unchanged in the case of an error.<p> 234 * 235 * Iff complain is set, we complain if non-existent 236 * or ill-formed path components are present in the classpath.<p> 237 */ 238 //@ requires classpath != null; 239 public static void set(String classpath, boolean complain) { 240 try { 241 namespace = ClassPath.open(classpath, complain); 242 } catch (IOException E) { 243 System.err.println("I/O error: " + E.getMessage()); 244 } 245 } 246 247 /** 248 * Attempt to set the current namespace to current classpath (cf. 249 * ClassPath).<p> 250 * 251 * Complains about any errors to System.err. The current namespace 252 * remains unchanged in the case of an error.<p> 253 * 254 * Iff complain is set, we complain if non-existent 255 * or ill-formed path components are present in the classpath.<p> 256 */ 257 public static void init(boolean complain) { 258 set(ClassPath.current(), complain); 259 } 260 261 /** 262 * Convenience function: do a lookupName using the current namespace 263 */ 264 //@ requires identifier != null; 265 //@ ensures \result != null ==> \result.myPackage != null; 266 public static Resolve_Result lookupName(String identifier) 267 throws Resolve_AmbiguousName { 268 return lookupName(namespace, identifier); 269 } 270 271 272 /*************************************************** 273 * * 274 * Error handling: * 275 * * 276 **************************************************/ 277 278 /** 279 * Check the result of a lookup to ensure that it refers to an 280 * (inner) reference type or a package. I.e., that there are no 281 * remainder parts.<p> 282 * 283 * If the check fails, complains appropriately to System.err and then 284 * returns null. If answer is already null, returns null 285 * immediately.<p> 286 * 287 * Otherwise, returns its argument unchanged; the argument will 288 * always have a remainder of length 0 in this case.<p> 289 */ 290 /*@ requires answer != null ==> answer.myPackage != null; */ 291 //@ ensures \result != null ==> \result.myPackage != null; 292 public static Resolve_Result ensureUnit(Resolve_Result answer) { 293 // Return if check succeeds or answer already null: 294 if (answer==null || answer.remainder.length==0) 295 return answer; 296 297 String packageName = answer.myPackage.getQualifiedName("."); 298 String unresolved = answer.remainder[0]; 299 300 if (answer.myTypeName==null) { 301 // Didn't find any (potentially enclosing) type at all: 302 System.err.println(Resolve.combineNames(packageName, 303 unresolved, ".") 304 + ": no such package, class, or interface"); 305 } else { 306 // Found a potentially enclosing type, but not one 307 // of the inner ones we need: 308 System.err.println( 309 Resolve.combineNames(packageName, 310 answer.myTypeName+"$"+unresolved, ".") 311 + ": no such class or interface"); 312 } 313 314 return null; 315 } 316 317 /** 318 * Check the result of a lookup to ensure that it refers to an 319 * (inner) reference type.<p> 320 * 321 * If the check fails, complains appropriately to System.err and then 322 * returns null. If answer is already null, returns null 323 * immediately.<p> 324 * 325 * Otherwise, returns its argument unchanged; the argument will 326 * have a non-null myTypeName and a remainder with length 0 in 327 * this case.<p> 328 */ 329 //@ requires answer != null ==> answer.myPackage != null; 330 //@ ensures \result != null ==> \result.myTypeName != null; 331 //@ ensures \result != null ==> \result.myPackage != null; 332 public static Resolve_Result ensureType(Resolve_Result answer) { 333 // Handle the cases where we didn't find a type or a package: 334 answer = ensureUnit(answer); 335 if (answer==null) 336 return null; 337 338 // Handle the case where typeName names a package: 339 if (answer.myTypeName==null) { 340 System.err.println(PkgTree.getPackageName(answer.myPackage) 341 + ": names a package, not a class or interface"); 342 return null; 343 } 344 345 return answer; 346 } 347 348 349 /*************************************************** 350 * * 351 * Utility functions: * 352 * * 353 **************************************************/ 354 355 /** 356 * Convert 1 character to another everywhere it appears in a given 357 * string. 358 * 359 */ 360 //@ requires input != null; 361 //@ ensures \result != null; 362 public static String tr(String input, char from, char to) { 363 StringBuffer chars = new StringBuffer(input); 364 365 for (int i=0; i<input.length(); i++) 366 if (chars.charAt(i)==from) 367 chars.setCharAt(i,to); 368 369 return chars.toString(); 370 } 371 372 373 /*************************************************** 374 * * 375 * Debugging functions: * 376 * * 377 **************************************************/ 378 379 /** A simple test driver */ 380 //@ requires args != null; 381 /*@ requires (\forall int i; (0<=i && i<args.length) 382 ==> args[i] != null); */ 383 public static void main(String[] args) throws IOException { 384 /* 385 * Parse command arguments: 386 */ 387 if (args.length != 1) { 388 System.out.println("Resolve: usage <identifier>"); 389 return; 390 } 391 392 init(false); 393 Resolve_Result answer; 394 try { 395 answer = lookupName(args[0]); 396 if (answer==null) 397 return; 398 } catch (Resolve_AmbiguousName name) { 399 System.err.println(args[0] + ": " + name.getMessage()); 400 return; 401 } 402 403 404 System.out.println("Package name: " 405 + PkgTree.getPackageName(answer.myPackage)); 406 if (answer.myTypeName==null) 407 System.out.println("No reference-type name"); 408 else 409 System.out.println("(inner) reference-type name: " 410 + answer.myTypeName); 411 System.out.print("Remaining identifier parts: "); 412 for (int i=0; i<answer.remainder.length; i++) 413 System.out.print("." + answer.remainder[i]); 414 System.out.println(); 415 416 System.out.println(); 417 System.out.println("Checking that it's a package or a reference type:"); 418 ensureUnit(answer); 419 System.out.println("Checking that it's a reference type:"); 420 ensureType(answer); 421 } 422 }