    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    }