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    }