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 }