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 }