    001    /* Copyright 2000, 2001, Compaq Computer Corporation */
    002
    003    package javafe.filespace;
    004
    005
    006    import java.util.Enumeration;
    007    import java.io.IOException;
    008
    009    import javafe.genericfile.*;
    010
    011
    012    /**
    013     * A PkgTree is a filtered representation of a filespace {@link Tree}
    014     * (cf {@link PathComponent}) where some files and directories that
    015     * are clearly not part of the Java namespace have been filtered out;
    016     * the remaining nodes can be divided into two categories: (a)
    017     * (usually interior) nodes that correspond to potential Java
    018     * packages, and (b) exterior nodes that correspond to files that
    019     * reside in one of the potential Java packages and that have an
    020     * extension (e.g., .java).<p>
    021     *
    022     * A function, {@link #isPackage(Tree)}, is provided to distinguish
    023     * the two categories.  A convenience function, {@link
    024     * #packages(Tree)}, is provided to enumerate all the potential Java
    025     * packages in a PkgTree.<p>
    026     *
    027     * {@link #isPackage(Tree)} depends only on a node's label; this
    028     * ensures that a {@link UnionTree} of several PkgTree's never
    029     * combines package and non-package nodes into a single node.  (This
    030     * is why (b) excludes files without extensions.)  Accordingly,
    031     * PkgTree's accessors and enumerators can also be used on {@link
    032     * UnionTree}s of PkgTrees.<p>
    033     *
    034     * This module is meant to do a reasonable job of identifying potential
    035     * packages.  It is not 100% accurate, however, erring on the side of
    036     * admitting too many packages.  For example, it does not attempt to
    037     * disallow package names containing Java keywords.<p>
    038     */
    039
    040    public class PkgTree extends PreloadedTree {
    041
    042        /***************************************************
    043         *                                                 *
    044         * Creation:                                       *
    045         *                                                 *
    046         **************************************************/
    047
    048
    049        /** The non-null filespace Tree we are filtering */
    050        //@ invariant underlyingTree != null;
    051        protected Tree underlyingTree;
    052
    053
    054        /**
    055         * Filter a non-null filespace Tree, leaving potential Java
    056         * packages and files.
    057         */
    058        //@ requires underlyingTree != null;
    059        public PkgTree(Tree underlyingTree) {
    060            super(underlyingTree.data);
    061            this.underlyingTree = underlyingTree;
    062        }
    063
    064        /**
    065         * Create a non-root node.  underlyingTree must be a non-null
    066         * filespace Tree.
    067         */
    068        //@ requires underlyingTree != null;
    069        //@ requires parent != null && label != null;
    070        protected PkgTree(Tree parent, String label, Tree underlyingTree) {
    071            super(parent, label, underlyingTree.data);
    072            this.underlyingTree = underlyingTree;
    073        }
    074
    075
    076        /***************************************************
    077         *                                                 *
    078         * Deciding what nodes to filter out:              *
    079         *                                                 *
    080         **************************************************/
    081
    082        /*
    083         * Status codes returned by getStatus:
    084         */
    085
    086        /** ignore the node and its children */
    087        protected static final int IGNORE = 0;
    088
    089        /** include the node but not its children */
    090        protected static final int INCLUDE_NODE = 1;
    091
    092        /** include the node and its children */
    093        protected static final int INCLUDE_TREE = 2;
    094
    095
    096        /**
    097         * Decide what to do with a node of the underlying filespace, returning
    098         * one of the following codes: IGNORE, INCLUDE_NODE, or INCLUDE_TREE.
    099         */
    100        protected static int getStatus(/*@ non_null @*/ Tree node) {
    101            String label = node.getSimpleName();
    102            String extension = Extension.getExtension(label);
    103
    104            /*
    105             * Ignore all files beginning with "META-" (used by the .jar
    106             * format for meta-data):
    107             */
    108            if (label.startsWith("META-"))
    109                return IGNORE;
    110
    111            /*
    112             * For now, potential packages are directories without
    113             * extensions:
    114             */
    115            if (((GenericFile)node.data).isDirectory()      //@ nowarn Cast,Null;
    116                    && extension.equals(""))
    117                return INCLUDE_TREE;
    118
    119            /* Directories in jars do not appear as directories */
    120            if ((node.data instanceof ZipGenericFile)
    121                    && extension.equals(""))
    122                return INCLUDE_TREE;
    123
    124            /*
    125             * Non-package files include only those with extensions.
    126             *
    127             * Note that directories with extensions are treated here as
    128             * if they were ordinary files (i.e., ignore their children
    129             * and treat them as non-packages).  This is necessary to
    130             * minic javac's behavior and to allow the checker to complain
    131             * about such files.
    132             */
    133            if (extension.equals(""))
    134                return IGNORE;
    135            else
    136                return INCLUDE_NODE;
    137        }
    138
    139        /***************************************************
    140         *                                                 *
    141         * Loading the edges map:                          *
    142         *                                                 *
    143         **************************************************/
    144
    145        /** Load the edges map for use.  */
    146        protected void loadEdges() {
    147            if (getStatus(underlyingTree) != INCLUDE_TREE)
    148                return;     // We are ignoring this tree or its children
    149
    150            for (Enumeration E=underlyingTree.children(); E.hasMoreElements(); ) {
    151                Tree child = (Tree)E.nextElement();
    152                if (getStatus(child) != IGNORE) {
    153                    String label = child.getLabel();
    154                    //@ assume label != null;
    155                    edges.put(label, new PkgTree(this, label, child));
    156                }
    157            }
    158        }
    159
    160
    161        /***************************************************
    162         *                                                 *
    163         * Accessors:                                      *
    164         *                                                 *
    165         **************************************************/
    166
    167        /**
    168         * Is a node of a PkgTree (or a union of PkgTree's) a potential
    169         * Java package?
    170         */
    171        public static boolean isPackage(/*@ non_null @*/ Tree node) {
    172            return Extension.getExtension(node.getSimpleName()).equals("");
    173        }
    174
    175        /** The name to use for root packages */
    176        public static String rootPackageName = "<the unnamed package>";
    177
    178        /**
    179         * Return the human-readable name of a package.  Uses rootPackageName
    180         * as the name of root packages.<p>
    181         *
    182         * Note: the resulting name will only make sense if node is a
    183         * package.<p>
    184         */
    185        public static String getPackageName(/*@ non_null @*/ Tree node) {
    186            if (node.getParent() == null)
    187                return rootPackageName;
    188
    189            return node.getQualifiedName(".");
    190        }
    191
    192
    193        /***************************************************
    194         *                                                 *
    195         * Enumerators:                                    *
    196         *                                                 *
    197         **************************************************/
    198
    199        /**
    200         * Enumerate all the potential packages of a PkgTree (or a union of
    201         * PkgTree's) in depth-first pre-order using lexical ordering on
    202         * siblings (cf. TreeWalker).
    203         */
    204        //@ ensures !\result.returnsNull;
    205        //@ ensures \result.elementType == \type(Tree);
    206        public static /*@ non_null @*/ Enumeration packages(/*@ non_null @*/ Tree node) {
    207            Enumeration allNodes = new TreeWalker(node);
    208
    209            return new FilterEnum(allNodes, new PkgTree_PackagesOnly());
    210        }
    211
    212        /**
    213         * Enumerate all the components of package P with extension E in
    214         * sorted order (of labels).<p>
    215         *
    216         * For a PkgTree (or a union of PkgTrees), if E is "", then all
    217         * direct potential subpackages will be selected.  Otherwise, only
    218         * non-subpackages will be selected.<p>
    219         */
    220        //@ ensures !\result.returnsNull;
    221        //@ ensures \result.elementType == \type(Tree);
    222        public static /*@ non_null @*/ Enumeration components(/*@ non_null @*/ Tree P,
    223                                                              /*@ non_null @*/ String E) {
    224            Enumeration allComponents = TreeWalker.sortedChildren(P);
    225
    226            return new FilterEnum(allComponents,
    227                            new PkgTree_MatchesExtension(E));
    228        }
    229
    230
    231        /***************************************************
    232         *                                                 *
    233         * Debugging functions:                            *
    234         *                                                 *
    235         **************************************************/
    236
    237        /** Extend printDetails to include our isPackage status */
    238        public void printDetails(String prefix) {
    239            super.printDetails(prefix);
    240            if (isPackage(this))
    241                System.out.print(" [P]");
    242        }
    243
    244        /** A simple test driver */
    245        //@ requires (\forall int i; (0 <= i && i < args.length) ==> args[i] != null);
    246        public static void main(/*@ non_null @*/ String[] args) throws IOException {
    247            if (args.length != 1 && args.length != 3) {
    248                System.out.println("PkgTree: usage <path component>"
    249                    + " [<package name> <extension>]");
    250                return;
    251            }
    252
    253            /*
    254             * Convert the path component to a filespace then filter it via
    255             * PkgTree.
    256             */
    257            Tree T = PathComponent.open(args[0], false);
    258            T = new PkgTree(T);
    259
    260            // If a package name is provided, list all its components with
    261            // the given extension:
    262            if (args.length==3) {
    263                Tree P = T.getQualifiedChild(args[1], '.');
    264                if (P==null) {
    265                    System.out.println("No such package: " + args[1]);
    266                    return;
    267                };
    268
    269                System.out.println(getPackageName(P) + ":");
    270                Enumeration E = components(P, args[2]);
    271                while (E.hasMoreElements()) {
    272                    Tree next = (Tree)E.nextElement();
    273                    System.out.println(next.getSimpleName());
    274                }
    275                return;
    276            };
    277
    278            T.print("");
    279            System.out.println();
    280
    281            // Enumerate all the potential packages also to test packages():
    282            Enumeration E = packages(T);
    283            while (E.hasMoreElements()) {
    284                Tree next = (Tree)E.nextElement();
    285                System.out.println(getPackageName(next));
    286            }
    287        }
    288    }
    289
    290
    291    /**
    292     * A filter for accepting only packages:
    293     *
    294     * This filter is for the use of the PkgTree class only; if inner
    295     * classes were available, it would be expressed as an anonymous class.
    296     */
    297    class PkgTree_PackagesOnly implements Filter {
    298        //@ invariant acceptedType == \type(Tree);
    299
    300        public PkgTree_PackagesOnly() {
    301            //@ set acceptedType = \type(Tree);
    302        }
    303
    304        public boolean accept(Object node) {
    305            return PkgTree.isPackage((Tree)node);
    306        }
    307    }
    308
    309
    310    /**
    311     * A filter for accepting only node's with a particular extension:
    312     *
    313     * This filter is for the use of the PkgTree class only; if inner
    314     * classes were available, it would be expressed as an anonymous class.
    315     */
    316    class PkgTree_MatchesExtension implements Filter {
    317
    318        //@ invariant acceptedType == \type(Tree);
    319
    320        //@ invariant targetExtension != null;
    321        String targetExtension;
    322
    323        //@ requires targetExtension != null;
    324        PkgTree_MatchesExtension(String targetExtension) {
    325            this.targetExtension = targetExtension;
    326            //@ set acceptedType = \type(Tree);
    327        }
    328
    329        public boolean accept(Object node) {
    330            return Extension.hasExtension(
    331                ((Tree)node).getSimpleName(), targetExtension);
    332        }
    333
    334    }