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    }