1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package org.apache.river.tool;
19
20 import org.apache.river.tool.classdepend.ClassDepend;
21 import org.apache.river.tool.classdepend.ClassDependParameters;
22 import org.apache.river.tool.classdepend.ClassDependParameters.CDPBuilder;
23 import org.apache.river.tool.classdepend.ClassDependencyRelationship;
24 import java.io.File;
25 import java.io.IOException;
26 import java.text.MessageFormat;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.Iterator;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.MissingResourceException;
36 import java.util.ResourceBundle;
37 import java.util.Set;
38 import java.util.SortedSet;
39 import java.util.TreeSet;
40
41 /**
42 * Tool used to analyze a set of classes and determine on what other classes
43 * they directly or indirectly depend. Typically this tool is used to
44 * compute the necessary and sufficient set of classes to include in a JAR
45 * file, for use in the class path of a client or service, or for use in the
46 * codebase of a client or service. The tool starts with a set of "root"
47 * classes and recursively computes a dependency graph, finding all of the
48 * classes referenced directly by the root classes, finding all of the
49 * classes referenced in turn by those classes, and so on, until no new
50 * classes are found or until classes that are not of interest are
51 * found. The normal output of the tool is a list of all of the classes in
52 * the dependency graph. The output from this command can be used as input
53 * to the <code>jar</code> tool, to create a JAR file containing precisely
54 * those classes.
55 * <p>
56 * The following items are discussed below:
57 * <ul>
58 * <li><a href="#running">Running the Tool</a>
59 * <li><a href="#processing">Processing Options</a>
60 * <li><a href="#output">Output Options and Arguments</a>
61 * <li><a href="#examples">Examples</a>
62 * </ul>
63 *
64 * <a name="running"></a>
65 * <h3>Running the Tool</h3>
66 *
67 * The command line for running the tool has the form:
68 * <blockquote><pre>
69 * java -jar <var><b>install_dir</b></var>/lib/classdep.jar \
70 * -cp <var><b>input_classpath</b></var> <var><b>processing_options</b></var> <var><b>output_options</b></var>
71 * </pre></blockquote>
72 * <p>
73 * where <var><b>install_dir</b></var> is the directory where the JGDMS release
74 * is installed.
75 * Note that the options for this tool can be specified in any order, and
76 * can be intermixed.
77 *
78 * <p>
79 * The <code>-cp</code> class path value,
80 * <var><b>input_classpath</b></var>,
81 * is an argument to the <code>ClassDep</code> tool itself and should
82 * include all of the classes that might need to be included in the
83 * dependency analysis. Typically this will include all of your application
84 * classes, classes from the JGDMS release, and any other classes on which
85 * your classes might depend. It is safe to include more classes than are
86 * actually necessary (since the purpose of this tool is, after all, to
87 * determine which subset of these classes is actually necessary), but it is
88 * not necessary to include any classes that are part of the Java 2 SDK.
89 * The class path should be in the form of a list of directories or JAR
90 * files, delimited by a colon (":") on UNIX platforms and a semi-colon
91 * (";") on Microsoft Windows platforms. The order of locations in the path
92 * does not matter. If you use JAR files, any <code>Class-Path</code>
93 * manifest entries in those JAR files are ignored, so you must include the
94 * values of those manifest entries explicitly in the path, or errors may
95 * result. For example, if you include <code>jini-ext.jar</code> then you
96 * should explicitly include <code>jini-core.jar</code> as well, because
97 * <code>jini-core.jar</code> is in the <code>Class-Path</code> manifest
98 * entry of <code>jini-ext.jar</code>.
99 *
100 * <a name="processing"></a>
101 * <h3>Processing Options</h3>
102 *
103 * The root classes of the dependency graph can be specified by any
104 * combination of individual classes and directories of classes. Each of
105 * these options can be used any number of times, and are illustrated in the
106 * <a href="#examples">Examples</a> section of this page.
107 * <p>
108 * In general, you only need to specify concrete classes as roots, not
109 * interface types. When analyzing classes for the class path of an
110 * application, you typically need to include the top-level class (the one
111 * with the <code>main</code> method). When analyzing classes for the
112 * codebase of a service, you typically need to include the top-level proxy
113 * classes used by the service, any trust verifier classes for those
114 * proxies, and any custom entry classes used by the service for lookup
115 * service attributes. Also when analyzing classes for the codebase of a
116 * service, if the service's proxy can return leases, registration objects,
117 * or other proxies, or if your service generates events, and if those
118 * objects are only referenced by interface type in the top-level proxy, not
119 * by concrete class, then you also need to include the concrete classes of
120 * those other objects. In all cases, you typically need to include any stub
121 * classes that you generated with the <code>rmic</code> tool.
122 * <p>
123 * <dl>
124 * <dt><b><var>class</var></b>
125 * <dd>This option specifies the fully qualified name of an individual class
126 * to include as a root of the dependency graph. This option can be
127 * specified zero or more times. Each class you specify with this option
128 * needs to be in a package that is defined to be "inside" the graph (as
129 * described further below).</dd>
130 *
131 * <dt><b><var>directory</var></b>
132 * <dd>This option specifies the root directory of a tree of compiled class
133 * files, all of which are to be included as roots of the dependency
134 * graph. This option can be specified zero or more times. The directory
135 * must be one of the directories specified in
136 * <var><b>input_classpath</b></var>,
137 * or a subdirectory of one, and must contain at least one filename
138 * separator character. Each class in the tree needs to be in a package that
139 * is defined to be "inside" the graph (as described further below).
140 * <p>
141 * <dt><b><var>directory</var></b>
142 * <dd>This option specifies the root directory of a tree of compiled class
143 * files that are considered as root for the dependency checking. This option
144 * can be specified zero or more times. The actual behavior depends on whether
145 * the <code>-newdirbehavior</code> options is specified. The directory must
146 * contain at least one filename separator character and be one of the
147 * directories specified in <var><b>input_classpath</b></var>, or when the old
148 * behavior is effective it can be a subdirectory of one of the directories on
149 * the <var><b>input_classpath</b></var>. Each class in the tree needs to be in
150 * a package that is defined to be "inside" the graph (as described further
151 * below).
152 * <p>
153 * When the <code>-newdirbehavior</code> options is set the <code>-inroot</code>
154 * and <code>-outroot</code> options can be used to include/exclude particular
155 * subtrees from the potential set of roots. When the old behavior is effective
156 * all classes are considered as root, but can be excluded through the
157 * <code>-prune</code> option.
158 * <p>
159 * <dl>
160 * <dt><b><code>-inroot</code> <var>package-prefix</var></b> (only valid with
161 * <code>-newdirbehavior</code>)
162 * <dd>Specifies a package namespace to include when selecting roots from the
163 * directory trees. Any classes found in this package or a subpackage of it are
164 * considered as root for the dependency checking, unless they are explicitly
165 * excluded using <code>-out</code>, <code>-skip</code> or <code>-outroot</code>
166 * options. If not specified all found classes are considered roots. This option
167 * can be specified zero or more times. Note that the argument to
168 * <code>-inroot</code> is a package namespace (delimited by "."), not a
169 * directory.</dd>
170 * </dl>
171 * <p>
172 * <dl>
173 * <dt><b><code>-outroot</code> <var>package-prefix</var></b> (only valid with
174 * <code>-newdirbehavior</code>)
175 * <dd>Specifies a package namespace to exclude when selecting roots from the
176 * directory trees. Within the directory trees as specified by the
177 * <code>rootdir</code> element, any classes that are in the given package or a
178 * subpackage of it are not treated as roots. This option can be specified zero
179 * or more times. Note that the argument to <code>-outroot</code> is a package
180 * namespace (delimited by "."), not a directory.</dd>
181 * </dl>
182 * <p>
183 * <dl>
184 * <dt><b><code>-prune</code> <var>package-prefix</var></b> (old behavior only)
185 * <dd>Specifies a package namespace to exclude when selecting roots from
186 * directory trees. Within the directory trees, any classes that are in the
187 * given package or a subpackage of it are not treated as roots. Note that
188 * this option has <i>no</i> effect on whether the classes in question end
189 * up "inside" or "outside" the dependency graph (as defined further below);
190 * it simply controls their use as roots. This option can be specified zero
191 * or more times. Note that the argument to <code>-prune</code> is a package
192 * namespace (delimited by "."), not a directory.</dd>
193 * </dl>
194 * <p>
195 * The <code>-skip</code> option (described further below) can be used to
196 * exclude specific classes from the set of roots.
197 * </dd>
198 * </dl>
199 * <p>
200 * Starting with the root classes, a dependency graph is constructed by
201 * examining the compiled class file for a class, finding all of the classes
202 * it references, and then in turn examining those classes. The extent of
203 * the graph is determined by which packages are defined to be "inside" the
204 * graph and which are defined to be "outside" the graph. If a referenced
205 * class is in a package that is defined to be outside the graph, that class
206 * is not included in the graph, and none of classes that it references are
207 * examined. All of the root classes must be in packages that are defined to
208 * be "inside" the graph.
209 * <p>
210 * The inside and outside packages are specified by using the following
211 * options. Each of these options may be specified zero or more times. Some
212 * variations are illustrated in the <a href="#examples">Examples</a> section
213 * of this page.
214 * <p>
215 * <dl>
216 * <dt><b><code>-in</code> <var>package-prefix</var></b>
217 * <dd>Specifies a namespace of "inside" packages. Any classes in this
218 * package or a subpackage of it are included in the dependency graph (and
219 * hence are to be included in your JAR file), unless they are explicitly
220 * excluded using <code>-out</code> or <code>-skip</code> options. This
221 * option can be specified zero or more times. If no <code>-in</code>
222 * options are specified, the default is that all packages are considered to
223 * be inside packages. Note that the argument to <code>-in</code> is a
224 * namespace, so none of its subpackages need to be specified as an argument
225 * to <code>-in</code>.
226 * <p>
227 * If you use this option, you will likely need to use it multiple
228 * times. For example, if your application classes are in the
229 * <code>com.corp.foo</code> namespace, and you also use some classes in the
230 * <code>org.apache.river</code> and <code>net.jini</code> namespaces, then you
231 * might specify:
232 * <pre>-in com.corp.foo -in org.apache.river -in net.jini</pre>
233 * </dd>
234 *
235 * <dt><b><code>-out</code> <var>package-prefix</var></b>
236 * <dd>Specifies a namespace of "outside" packages. Any classes in this
237 * package or a subpackage of it are excluded from the dependency graph (and
238 * hence are to be excluded from your JAR file). This option can be
239 * specified zero or more times. If you specify <code>-in</code> options,
240 * then each <code>-out</code> namespace should be a subspace of some
241 * <code>-in</code> namespace. Note that the argument to <code>-out</code>
242 * is a namespace, so none of its subpackages need to be specified as an
243 * argument to <code>-out</code>.
244 * <p>
245 * If you use this option, you will likely need to use it multiple
246 * times. For example, if you do not specify any <code>-in</code> options,
247 * then all packages are considered inside the graph, including packages
248 * defined in the Java 2 SDK that you typically want to exclude, so you
249 * might exclude them by specifying:
250 * <pre>-out java -out javax</pre>
251 * As another example, if you have specified <code>-in com.corp.foo</code>
252 * but you don't want to include any of the classes in the
253 * <code>com.corp.foo.test</code> or <code>com.corp.foo.qa</code> namespaces
254 * in the dependency graph, then you would specify:
255 * <pre>-out com.corp.foo.test -out com.corp.foo.qa</pre>
256 * </dd>
257 *
258 * <dt><b><code>-skip</code> <var>class</var></b>
259 * <dd>Specifies the fully qualified name of a specific class to exclude
260 * from the dependency graph. This option allows an individual class to be
261 * considered "outside" without requiring the entire package it is defined
262 * in to be considered outside. This option can be specified zero or more
263 * times.
264 * </dd>
265 *
266 * <dt><b><code>-outer</code></b>
267 * <dd>By default, if a static nested class is included in the dependency
268 * graph, all references from that static nested class to its immediate
269 * lexically enclosing class are ignored (except when the static nested class
270 * extends its outer class), to avoid inadvertent inclusion of the enclosing
271 * class. (The default is chosen this way because the compiled class file of a
272 * static nested class always contains a reference to the immediate lexically
273 * enclosing class.) This option causes all such references to be considered
274 * rather than ignored. Note that this option is needed very infrequently.</dd>
275 * </dl>
276 * <dl>
277 * <dt><b><code>-newdirbehavior</code></b>
278 * <dd>This option causes the utility to select classes, to serve as root for
279 * the dependency checking, from the directory argument based on the
280 * <code>-inroot</code> and <code>-outroot</code> options specified. When
281 * this option is set subdirectories of the specified directory are no longer
282 * considered as root for finding classes. When this option is not set, the
283 * default, the utility maintains the old behavior with respect to the semantics
284 * for the directories passed in and the <code>-prune</code> option must be
285 * used. You are advised to set this option as there are some edge cases for
286 * which the old behavior won't work as expected, especially when no
287 * <code>-in</code> options are set.</dd>
288 * </dl>
289 *
290 * <a name="output"></a>
291 * <h3>Output Options and Arguments</h3>
292 *
293 * The following options and arguments determine the content and format of
294 * the output produced by this tool. These options do not affect the
295 * dependency graph computation, only the information displayed in the
296 * output as a result of the computation. Most of these options may be
297 * specified multiple times. Some variations are illustrated in the
298 * <a href="#examples">Examples</a> section of this page.
299 * <dl>
300 * <dt><b><code>-edges</code></b>
301 * <dd>By default, the classes which are included in the dependency graph
302 * are displayed in the output. This option specifies that instead, the
303 * classes which are excluded from the dependency graph, but which are
304 * directly referenced by classes in the dependency graph, should be
305 * displayed in the output. These classes form the outside "edges" of the
306 * dependency graph.
307 * <p>
308 * For example, you might exclude classes from the Java 2 SDK from the
309 * dependency graph because you don't want to include them in your JAR file,
310 * but you might be interested in knowing which classes from the Java 2 SDK
311 * are referenced directly by the classes in your JAR file. The
312 * <code>-edges</code> option can be used to display this information.
313 * </dd>
314 *
315 * <dt><b><code>-show</code> <var>package-prefix</var></b>
316 * <dd>Displays the classes that are in the specified package or a
317 * subpackage of it. This option can be specified zero or more times. If no
318 * <code>-show</code> options are specified, the default is that all classes
319 * in the dependency graph are displayed (or all edge classes, if
320 * <code>-edges</code> is specified). Note that the argument to
321 * <code>-show</code> is a namespace, so none of its subpackages need to be
322 * specified as an argument to <code>-show</code>.
323 * <p>
324 * For example, to determine which classes from the Java 2 SDK your
325 * application depends on, you might not specify any <code>-in</code>
326 * options, but limit the output by specifying:
327 * <pre>-show java -show javax</pre></dd>
328 *
329 * <dt><b><code>-hide</code> <var>package-prefix</var></b>
330 * <dd>Specifies a namespace of packages which should not be displayed. Any
331 * classes in this package or a subpackage of it are excluded from the
332 * output. This option can be specified zero or more times. If you specify
333 * <code>-show</code> options, then each <code>-hide</code> namespace should
334 * be a subspace of some <code>-show</code> namespace. Note that the
335 * argument to <code>-hide</code> is a namespace, so none of its subpackages
336 * need to be specified as an argument to <code>-hide</code>.
337 * <p>
338 * For example, to determine which non-core classes from the
339 * <code>net.jini</code> namespace you use, you might specify:
340 * <pre>-show net.jini -hide net.jini.core</pre></dd>
341 *
342 * <dt><b><code>-files</code></b>
343 * <dd>By default, fully qualified class names are displayed, with package
344 * names delimited by ".". This option causes the output to be in filename
345 * format instead, with package names delimited by filename separators and
346 * with ".class" appended. For example, using this option on Microsoft
347 * Windows platforms would produce output in the form of
348 * <code>com\corp\foo\Bar.class</code> instead of
349 * <code>com.corp.foo.Bar</code>. This option should be used to generate
350 * output suitable as input to the <code>jar</code> tool.
351 * <p>
352 * For more information on the <code>jar</code> tool, see:
353 * <ul>
354 * <li><a href="http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/jar.html">
355 * http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/jar.html</a>
356 * <li><a href="http://java.sun.com/j2se/1.4/docs/tooldocs/windows/jar.html">
357 * http://java.sun.com/j2se/1.4/docs/tooldocs/windows/jar.html</a>
358 * <li><a href="http://java.sun.com/j2se/1.4/docs/guide/jar/jar.html">
359 * http://java.sun.com/j2se/1.4/docs/guide/jar/jar.html</a>
360 * </ul>
361 * </dd>
362 *
363 * <dt><b><code>-tell</code> <var>class</var></b>
364 * <dd>Specifies the fully qualified name of a class for which dependency
365 * information is desired. This option causes the tool to display
366 * information about every class in the dependency graph that references the
367 * specified class. This information is sent to the error stream of the
368 * tool, not to the normal output stream. This option can be specified zero
369 * or more times. If this option is used, all other output options are
370 * ignored, and the normal class output is not produced. This option is
371 * useful for debugging.
372 * </dd>
373 * </dl>
374 *
375 * <a name="examples"></a>
376 * <h3>Examples</h3>
377 *
378 * (The examples in this section assume you ran the JGDMS release installer
379 * with an "Install Set" selection that created the top-level
380 * <code>classes</code> directory. Alternatively, if you have compiled from
381 * the source code, substitute <code>source/classes</code> for
382 * <code>classes</code> in the examples.)
383 * <p>
384 * The following example computes the classes required for a codebase JAR
385 * file, starting with a smart proxy class and a stub class as roots, and
386 * displays them in filename format. (A more complete example would include
387 * additional roots for leases, registrations, events, and lookup service
388 * attributes, and would exclude platform classes such as those in
389 * <code>jsk-platform.jar</code>.)
390 * <p>
391 * <blockquote><pre>
392 * java -jar <var><b>install_dir</b></var>/lib/classdep.jar \
393 * -cp <var><b>install_dir</b></var>/classes \
394 * org.apache.river.reggie.RegistrarProxy org.apache.river.reggie.RegistrarImpl_Stub \
395 * -in org.apache.river -in net.jini \
396 * -files
397 * </pre></blockquote>
398 * <p>
399 * The following example computes the classes required for a classpath JAR
400 * file, starting with all of the classes in a directory as roots, and
401 * displays them in class name format. (A more complete example would exclude
402 * platform classes such as those in <code>jsk-platform.jar</code>.)
403 * <p>
404 * <blockquote><pre>
405 * java -jar <var><b>install_dir</b></var>/lib/classdep.jar \
406 * -cp <var><b>install_dir</b></var>/classes \
407 * <var><b>install_dir</b></var>/classes/org/apache/river/reggie \
408 * -in org.apache.river -in net.jini
409 * </pre></blockquote>
410 * <p>
411 * The following example computes the <code>org.apache.river</code> classes used
412 * by a service implementation, and displays the <code>net.jini</code>
413 * classes that are immediately referenced by those classes.
414 * <p>
415 * <blockquote><pre>
416 * java -jar <var><b>install_dir</b></var>/lib/classdep.jar \
417 * -cp <var><b>install_dir</b></var>/classes \
418 * org.apache.river.reggie.RegistrarImpl \
419 * -in org.apache.river \
420 * -edges \
421 * -show net.jini
422 * </pre></blockquote>
423 * <p>
424 * The following example computes all of the classes used by a service
425 * implementation that are not part of the Java 2 SDK, and displays the
426 * classes that directly reference the class <code>java.awt.Image</code>.
427 * <p>
428 * <blockquote><pre>
429 * java -jar <var><b>install_dir</b></var>/lib/classdep.jar \
430 * -cp <var><b>install_dir</b></var>/classes \
431 * org.apache.river.reggie.RegistrarImpl \
432 * -out java -out javax \
433 * -tell java.awt.Image
434 * </pre></blockquote>
435 * <p>
436 * The following example computes all of the classes to include in
437 * <code>jini-ext.jar</code> and displays them in filename format. Note
438 * that both <code>-out</code> and <code>-prune</code> options are needed
439 * for the <code>net.jini.core</code> namespace; <code>-out</code> to
440 * exclude classes from the dependency graph, and <code>-prune</code> to
441 * prevent classes from being used as roots.
442 * <p>
443 * <blockquote><pre>
444 * java -jar <var><b>install_dir</b></var>/lib/classdep.jar \
445 * -cp <var><b>install_dir</b></var>/classes \
446 * -in net.jini -out net.jini.core -in org.apache.river \
447 * <var><b>install_dir</b></var>/classes/net/jini -prune net.jini.core \
448 * -files
449 * </pre></blockquote>
450 *
451 * @author Sun Microsystems, Inc.
452 */
453 public class ClassDep {
454 private ClassDepend cd;
455
456 /**
457 * If true class names are printed using
458 * the system's File.separator, else the
459 * fully qualified class name is printed.
460 */
461 private boolean files = false;
462 /**
463 * Set of paths to find class definitions in order to determine
464 * dependencies.
465 */
466 private String classpath = "";
467 /**
468 * Flag to determine whether there is interest
469 * in dependencies that go outside the set of
470 * interested classes. If false then outside,
471 * references are ignored, if true they are noted.
472 * i.e, if looking only under <code>net.jini.core.lease</code>
473 * a reference to a class in <code>net.jini</code> is found it
474 * will be noted if the flag is set to true, else
475 * it will be ignored. <p>
476 * <b>Note:</b> these edge case dependencies must be
477 * included in the classpath in order to find their
478 * definitions.
479 */
480 private boolean edges = false;
481 /**
482 * Static inner classes have a dependency on their outer
483 * parent class. Because the parent class may be really
484 * big and may pull other classes along with it we allow the
485 * choice to ignore the parent or not. If the flag is set to
486 * true we pull in the parent class. If it is false we don't
487 * look at the parent. The default is is to not include the
488 * parent. <p>
489 * <b>Note:</b> This is an optimization for those who plan
490 * on doing work with the output of this utility. It does
491 * not impact this utility, but the work done on its
492 * generated output may have an impact.
493 *
494 * This will have to be implemented in ClassDepend note the above
495 * description conflicts with the variable name.
496 */
497 private boolean ignoreOuter = true;
498 /**
499 * Package set that we have interest to work in.
500 */
501 private final ArrayList inside = new ArrayList();
502 /**
503 * Package set to not work with. This is useful if
504 * there is a subpackage that needs to be ignored.
505 */
506 private final ArrayList outside = new ArrayList();
507 /**
508 * Class set to look at for dependencies. These are
509 * fully qualified names, ie, net.jini.core.lease.Lease.
510 * This is a subset of the values in
511 * <code>inside</code>.
512 */
513 private final ArrayList classes = new ArrayList();
514 /**
515 * Set of directories to find dependencies in.
516 */
517 private final ArrayList roots = new ArrayList();
518 /**
519 * Set of packages to skip over in the processing of dependencies.
520 * This can be used in conjunction with <em>-out</em> option.
521 */
522 private final ArrayList prunes = new ArrayList();
523 /**
524 * Indicates whether the root directories specified for finding classes
525 * for dependency checking must be traversed in the 'old' way, or that the
526 * new behavior must be effective.
527 */
528 private boolean newRootDirBehavior;
529 /**
530 * Set of packages to include when classes are found through the specified
531 * root directories.
532 */
533 private final ArrayList insideRoots = new ArrayList();
534 /**
535 * Set of packages to exclude when classes are found through the specified
536 * root directories.
537 */
538 private final ArrayList outsideRoots = new ArrayList();
539 /**
540 * Set of classes to skip over in the processing of dependencies.
541 */
542 private final ArrayList skips = new ArrayList();
543 /**
544 * Given a specific fully qualified classes, what other classes
545 * in the roots list depend on it. This is more for debugging
546 * purposes rather then normal day to day usage.
547 */
548 private final ArrayList tells = new ArrayList();
549 /**
550 * Only display found dependencies that fall under the provided
551 * <code>roots</code> subset.
552 */
553 private final ArrayList shows = new ArrayList();
554 /**
555 * Suppress display of found dependencies that are under
556 * the provided package prefixes subset.
557 */
558 private final ArrayList hides = new ArrayList();
559 /**
560 * Container for found dependency classes.
561 */
562 private final SortedSet results = new TreeSet();
563
564 /**
565 * Indicates whether a failure has been encountered during deep dependency
566 * checking.
567 */
568 private boolean failed;
569 private Set<String> providers = new TreeSet<String>();
570
571 /**
572 * No argument constructor. The user must fill in the
573 * appropriate fields prior to asking for the processing
574 * of dependencies.
575 */
576 public ClassDep() {
577 }
578
579 /**
580 * Constructor that takes command line arguments and fills in the
581 * appropriate fields. See the description of this class
582 * for a list and description of the acceptable arguments.
583 * @param cmdLine
584 */
585 public ClassDep(String[] cmdLine){
586 setupOptions(cmdLine);
587 }
588
589 /**
590 * Take the given argument and add it to the provided container.
591 * We make sure that each inserted package-prefix is unique. For
592 * example if we had the following packages:
593 * <ul>
594 * <li>a.b
595 * <li>a.bx
596 * <li>a.b.c
597 * </ul>
598 * Looking for <code>a.b</code> should not match
599 * <code>a.bx</code> and <code>a.b</code>,
600 * just <code>a.b</code>.
601 *
602 * @param arg the package-prefix in string form
603 * @param elts container to add elements to
604 *
605 */
606 private static void add(String arg, ArrayList elts) {
607 if (!arg.endsWith(".")) {
608 arg = arg + '.';
609 }
610 if (".".equals(arg)) {
611 arg = null;
612 }
613 elts.add(arg);
614 }
615
616 /**
617 * Recursively traverse a given path, finding all the classes that
618 * make up the set to work with. We take into account skips,
619 * prunes, and out sets defined.
620 * <p>
621 * This implementation is here to maintain the old behavior with regard
622 * to how root directories were interpreted.
623 *
624 * @param path path to traverse down from
625 */
626 private void traverse(String path) {
627 String apath = path;
628 /*
629 * We append File.separator to make sure that the path
630 * is unique for the matching that we are going to do
631 * next.
632 */
633 if (!apath.startsWith(File.separator)) {
634 apath = File.separator + apath;
635 }
636 for (int i = 0; i < prunes.size(); i++) {
637 /*
638 * If we are on a root path that needs to be
639 * pruned leave this current recursive thread.
640 */
641 if (apath.endsWith((String)prunes.get(i))) {
642 return;
643 }
644 }
645
646 /*
647 * Get the current list of files at the current directory
648 * we are in. If there are no files then leave this current
649 * recursive thread.
650 */
651 String[] files_ = new File(path).list();
652 if (files_ == null) {
653 return;
654 }
655 outer:
656 /*
657 * Now, take the found list of files and iterate over them.
658 */
659 for (int i = 0; i < files_.length; i++) {
660 String file = files_[i];
661 /*
662 * Now see if we have a ".class" file.
663 * If we do not then we lets call ourselves again.
664 * The assumption here is that we have a directory. If it
665 * is a class file we would have already been throw out
666 * by the empty directory contents test above.
667 */
668 if (!file.endsWith(".class")) {
669 traverse(path + File.separatorChar + file);
670 } else {
671 /*
672 * We have a class file, so remove the ".class" from it
673 * using the pattern:
674 *
675 * directory_name + File.Separator + filename = ".class"
676 *
677 * At this point the contents of the skip container follow
678 * the pattern of:
679 *
680 * "File.Separator+DirectoryPath"
681 *
682 * with dots converted to File.Separators
683 */
684 file = apath + File.separatorChar +
685 file.substring(0, file.length() - 6);
686 /*
687 * See if there are any class files that need to be skipped.
688 */
689 for (int j = 0; j < skips.size(); j++) {
690 String skip = (String)skips.get(j);
691 int k = file.indexOf(skip);
692 if (k < 0) {
693 continue;
694 }//leave this current loop.
695 k += skip.length();
696 /*
697 * If we matched the entire class or if we have
698 * a class with an inner class, skip it and go
699 * on to the next outer loop.
700 */
701 if (file.length() == k || file.charAt(k) == '$') {
702 continue outer;
703 }
704 }
705 /*
706 * things to do:
707 * prune when outside.
708 * handle inside when its empty.
709 *
710 * Now see if we have classes within our working set "in".
711 * If so add them to our working list "classes".
712 */
713 for (int j = 0; j < inside.size(); j++) {
714 if (inside.get(j) == null) {
715 continue;
716 }
717 int k = file.indexOf(File.separatorChar +
718 ((String)inside.get(j)).replace(
719 '.', File.separatorChar));
720 if (k >= 0) {
721 /*
722 * Insert the class and make sure to replace
723 * File.separators into dots.
724 */
725 classes.add(file.substring(k + 1).replace(
726 File.separatorChar, '.'));
727 }
728 }
729 }
730 }
731 }
732
733 /**
734 * Recursively traverse a given path, finding all the classes that make up
735 * the set of classes to work with. We take into account inroot and outroot
736 * sets, skips, and the in and out sets defined.
737 *
738 * @param path path to traverse down from
739 * @param rootPath path to the directory that serves as the root for the
740 * class files found, any path component below the root is part of
741 * the full qualified name of the class
742 */
743 private void traverse(String path, String rootPath) {
744 // get the current list of files at the current directory we are in. If
745 // there are no files then leave this current recursive thread.
746 String[] files_ = new File(path).list();
747 if (files_ == null) {
748 return;
749 }
750
751 // determine the package name in File.Separators notation
752 String packageName = path.substring(rootPath.length(), path.length())
753 + File.separatorChar;
754 outer:
755 //take the found list of files and iterate over them
756 for (int i = 0; i < files_.length; i++) {
757 String file = files_[i];
758 // see if we have a ".class" file. If not then we call ourselves
759 // again, assuming it is a directory, if not the call will return.
760 if (!file.endsWith(".class")) {
761 traverse(path + File.separatorChar + file, rootPath);
762 } else {
763 // when we have in roots defined verify whether we are inside
764 if (!insideRoots.isEmpty()) {
765 boolean matched = false;
766 for (int j = 0; j < insideRoots.size(); j++) {
767 if (packageName.startsWith(
768 (String) insideRoots.get(j))) {
769 matched = true;
770 break;
771 }
772 }
773 if (!matched) {
774 continue;
775 }
776 }
777
778 // when we have out roots and we are at this level outside we
779 // can break the recursion
780 if (!outsideRoots.isEmpty()) {
781 for (int j = 0; j < outsideRoots.size(); j++) {
782 if (packageName.startsWith(
783 (String) outsideRoots.get(j))) {
784 return;
785 }
786 }
787 }
788
789 // determine the fully qualified class name, but with dots
790 // converted to File.Separators and starting with a
791 // File.Separators as well
792 String className = packageName
793 + file.substring(0, file.length() - 6);
794 // see if there are any class files that need to be skipped, the
795 // skip classes are in the above notation as well
796 for (int j = 0; j < skips.size(); j++) {
797 String skip = (String) skips.get(j);
798 if (!className.startsWith(skip)) {
799 continue;
800 }
801 // if we matched the entire class or if we have a class with
802 // an inner class, skip it and go on to the next outer loop
803 if (className.length() == skip.length()
804 || className.charAt(skip.length()) == '$') {
805 continue outer;
806 }
807 }
808
809 // we found a class that satisfy all the criteria, convert it
810 // to the proper notation
811 classes.add(className.substring(1).replace(File.separatorChar,
812 '.'));
813 }
814 }
815 }
816
817 private static class Compare implements Comparator {
818 public int compare(Object o1, Object o2) {
819 if (o1 == null) {
820 return o2 == null ? 0 : 1;
821 }
822 else {
823 return o2 == null ? -1 : ((Comparable) o2).compareTo(o1);
824 }
825 }
826 }
827
828 /**
829 * Method that takes the user provided switches that
830 * logically define the domain in which to look for
831 * dependencies.
832 * <p>
833 * Whether a failure has occurred during computing the dependent classes
834 * can be found out with a call to {@link #hasFailed()}.
835 *
836 * @return array containing the dependent classes found in the format as
837 * specified by the options passed in
838 */
839 public String[] compute() {
840 failed = false;
841
842 if (!newRootDirBehavior) {
843 if (!insideRoots.isEmpty()) {
844 failed = true;
845 print("classdep.invalidoption", "-newdirbehavior", "-inroot");
846 }
847 if (!outsideRoots.isEmpty()) {
848 failed = true;
849 print("classdep.invalidoption", "-newdirbehavior", "-outroot");
850 }
851 if (failed) {
852 return new String[0];
853 }
854 }
855
856 /* sort ins and outs, longest first */
857 Comparator c = new Compare();
858 Collections.sort(inside, c);
859 Collections.sort(outside, c);
860 Collections.sort(shows, c);
861 Collections.sort(hides, c);
862
863 /*
864 * Traverse the roots i.e the set of handed directories.
865 */
866 for (int i = 0; i < roots.size(); i++) {
867 /*
868 * Get the classes that we want do to dependency checking on.
869 */
870 if (newRootDirBehavior) {
871 traverse((String)roots.get(i), (String)roots.get(i));
872 }
873 else {
874 traverse((String)roots.get(i));
875 }
876 }
877
878 // Now use ClassDepend to perform dependency computation
879 if (classpath.length() == 0) { classpath = null; }
880 try {
881 cd = ClassDepend.newInstance(classpath, null, true);
882 } catch (IOException e) {
883 e.printStackTrace();
884 }
885 Map classDependencyRelationMap = null;
886 try{
887 classDependencyRelationMap = cd.getDependencyRelationshipMap(classes, true); // get the reference to Collection<Class>
888 } catch (ClassNotFoundException e){
889 e.printStackTrace();
890 } catch (IOException e) {
891 e.printStackTrace();
892 }
893
894 if (!providers.isEmpty()){
895 List classDependencyRelations = new LinkedList();
896 Iterator it = providers.iterator();
897 while (it.hasNext()){
898 classDependencyRelations.add(classDependencyRelationMap.get(it.next()));
899 }
900 it = classDependencyRelations.iterator();
901 while (it.hasNext()){
902 ClassDependencyRelationship cdrs = (ClassDependencyRelationship) it.next();
903 if (cdrs == null) continue;
904 Set dependants = cdrs.getDependants();
905 Iterator i = dependants.iterator();
906 while (i.hasNext()){
907 results.add(i.next().toString());
908 }
909 }
910 }
911 if (tells.isEmpty()){
912 // Here's where we should build the parameter list and call the filter
913 //Ready the Parameter Builder for ClassDepend
914 // .ignoreOuterParentClass(ignoreOuter) isn't implemented in ClassDepend yet.
915 CDPBuilder cdpb = new CDPBuilder();
916 ClassDependParameters cdp = cdpb.addOutsidePackagesOrClasses(addClassesRecursively(outside))
917 .addOutsidePackagesOrClasses(skips)
918 .addInsidePackages(addClassesRecursively(inside))
919 .addShowPackages(addClassesRecursively(shows))
920 .addHidePackages(addClassesRecursively(hides))
921 .ignoreOuterParentClass(ignoreOuter)
922 .excludePlatformClasses(false)
923 .edges(edges)
924 .build();
925 Set result = cd.filterClassDependencyRelationShipMap
926 (classDependencyRelationMap, cdp);
927 Iterator itr = result.iterator();
928 while (itr.hasNext()) {
929 results.add(itr.next().toString());
930 }
931 }else{
932
933 Iterator iter = tells.iterator();
934 while (iter.hasNext()){
935 String name = (String) iter.next();
936 if ( classDependencyRelationMap.containsKey(name)){
937 ClassDependencyRelationship provider = (ClassDependencyRelationship) classDependencyRelationMap.get(name);
938 Set dependants = provider.getDependants();
939 if (!dependants.isEmpty()) {
940 Iterator it = dependants.iterator();
941 while (it.hasNext()) {
942 ClassDependencyRelationship dependant = (ClassDependencyRelationship) it.next();
943 if (tells.size() > 1) {
944 print("classdep.cause", provider, dependant);
945 }
946 else {
947 print("classdep.cause1", dependant);
948 }
949 }
950 }
951 }
952 }
953 }
954 // cannot change the return type or we break the API backward compatibility.
955 return (String[])results.toArray(new String[results.size()]);
956 }
957
958 /**
959 * Add all classes in Packages and subpackages recursively by appending **
960 * as per the syntax requirements of the ClassDepend API.
961 */
962 private List addClassesRecursively(List list){
963 for ( int i = 0, l = list.size() ; i < l ; i++){
964 list.set(i , list.get(i) + "**");
965 }
966 return list;
967 }
968
969 /**
970 * Print out the usage for this utility.
971 */
972 public static void usage() {
973 print("classdep.usage", null);
974 }
975
976 /**
977 * Set the classpath to use for finding our class definitions.
978 * @param classpath
979 */
980 public void setClassPath(String classpath) {
981 this.classpath = classpath;
982 }
983
984 /**
985 * Determines how to print out the fully qualified
986 * class names. If <code>true</code> it will use
987 * <code>File.separator</code>, else <code>.</code>'s
988 * will be used.
989 * If not set the default is <code>false</code>.
990 * @param files
991 */
992 public void setFiles(boolean files) {
993 this.files = files;
994 }
995
996 /**
997 * Add an entry into the set of package prefixes that
998 * are to remain hidden from processing.
999 * @param packagePrefix
1000 */
1001 public void addHides(String packagePrefix) {
1002 add(packagePrefix, hides);
1003 }
1004
1005 /**
1006 * Add an entry into the working set of package prefixes
1007 * that will make up the working domain space.
1008 * @param packagePrefix
1009 */
1010 public void addInside(String packagePrefix) {
1011 add(packagePrefix, inside);
1012 }
1013
1014 /**
1015 * Determines whether to include package references
1016 * that lie outside the declared set of interest.
1017 * <p>
1018 * If true edges will be processed as well, else
1019 * they will be ignored. If not set the default
1020 * will be <code>false</code>.
1021 * <p>
1022 * <b>Note:</b> These edge classes must included
1023 * in the classpath for this utility.
1024 * @param edges
1025 */
1026 public void setEdges(boolean edges) {
1027 this.edges = edges;
1028 }
1029
1030 /**
1031 * Add an entry into the set of package prefixes
1032 * that will bypassed during dependency checking.
1033 * These entries should be subsets of the contents
1034 * on the inside set.
1035 * @param packagePrefix
1036 */
1037 public void addOutside(String packagePrefix) {
1038 add(packagePrefix, outside);
1039 }
1040
1041 /**
1042 * Add an entry into the set of package prefixes
1043 * that will be skipped as part of the dependency
1044 * generation.
1045 * <p>
1046 * This method has no impact if the new behavior is effective for the
1047 * interpretation of the root directories for finding class files to
1048 * include for dependency checking.
1049 * @param packagePrefix
1050 */
1051 public void addPrune(String packagePrefix) {
1052 String arg = packagePrefix;
1053 if (arg.endsWith(".")) {
1054 arg = arg.substring(0, arg.length() - 1);
1055 }
1056 /*
1057 * Convert dots into File.separator for later usage.
1058 */
1059 arg = File.separator + arg.replace('.', File.separatorChar);
1060 prunes.add(arg);
1061 }
1062
1063 /**
1064 * Controls whether the behavior for finding class files in the specified
1065 * directories, if any, must be based on the old behavior (the default) or
1066 * the new behavior that solves some of the problems with the old behavior.
1067 * @param newBehavior
1068 */
1069 public void setRootDirBehavior(boolean newBehavior) {
1070 newRootDirBehavior = newBehavior;
1071 }
1072
1073 /**
1074 * Adds an entry into the set of package prefixes for which classes found
1075 * through the specified root directories will be considered for dependency
1076 * generation.
1077 * <p>
1078 * Adding entries without a call to {@link #setRootDirBehavior(boolean)}
1079 * with <code>true</code> as the argument will cause {@link #compute()} to
1080 * fail.
1081 * @param packagePrefix
1082 */
1083 public void addInsideRoot(String packagePrefix) {
1084 String arg = packagePrefix;
1085 if (arg.endsWith(".")) {
1086 arg = arg.substring(0, arg.length() - 1);
1087 }
1088 /*
1089 * Convert dots into File.separator for later usage.
1090 */
1091 if (arg.trim().length() == 0) {
1092 arg = File.separator;
1093 }
1094 else {
1095 arg = File.separator + arg.replace('.', File.separatorChar) + File.separator;
1096 }
1097 insideRoots.add(arg);
1098 }
1099
1100 /**
1101 * Adds an entry into the set of package prefixes for which classes found
1102 * through the specified root directories, and that are part of the inside
1103 * root namespace, will be skipped as part of the dependency generation.
1104 * <p>
1105 * Adding entries without a call to {@link #setRootDirBehavior(boolean)}
1106 * with <code>true</code> as the argument will cause {@link #compute()} to
1107 * fail.
1108 * @param packagePrefix
1109 */
1110 public void addOutsideRoot(String packagePrefix) {
1111 String arg = packagePrefix;
1112 if (arg.endsWith(".")) {
1113 arg = arg.substring(0, arg.length() - 1);
1114 }
1115 /*
1116 * Convert dots into File.separator for later usage.
1117 */
1118 if (arg.trim().length() == 0) {
1119 arg = File.separator;
1120 }
1121 else {
1122 arg = File.separator + arg.replace('.', File.separatorChar) + File.separator;
1123 }
1124 outsideRoots.add(arg);
1125 }
1126
1127 /**
1128 * Add an entry into the set of package prefixes
1129 * that we want to display.
1130 * This applies only to the final output, so this
1131 * set should be a subset of the inside set with
1132 * edges, if that was indicated.
1133 * @param packagePrefix
1134 */
1135 public void addShow(String packagePrefix) {
1136 add(packagePrefix, shows);
1137 }
1138
1139 /**
1140 * Add an entry into the set of classes that
1141 * should be skipped during dependency generation.
1142 * @param packagePrefix
1143 */
1144 public void addSkip(String packagePrefix){
1145 String arg = packagePrefix;
1146 if (arg.endsWith(".")) {
1147 arg = arg.substring(0, arg.length() - 1);
1148 }
1149 /* No Longer required as ClassDepend will be passed the skips array
1150 else {
1151 seen.add(Identifier.lookup(arg));
1152 }
1153 */
1154
1155 /*
1156 * Convert dots into File.separator for later usage.
1157 */
1158 arg = File.separator + arg.replace('.', File.separatorChar);
1159 skips.add(arg);
1160 }
1161
1162 /**
1163 * Add an entry in to the set of classes whose dependents
1164 * that lie with the inside set are listed. This in
1165 * the converse of the rest of the utility and is meant
1166 * more for debugging purposes.
1167 * @param className
1168 */
1169 public void addTells(String className) {
1170 tells.add(className);
1171 }
1172
1173 /**
1174 * Add an entry into the set of directories to
1175 * look under for the classes that fall within
1176 * the working domain space as defined by the
1177 * intersection of the following sets:
1178 * inside,outside,prune,show, and hide.
1179 * @param rootName
1180 */
1181 public void addRoots(String rootName) {
1182 if (rootName.endsWith(File.separator)) {
1183 //remove trailing File.separator
1184 rootName = rootName.substring(0, rootName.length() - 1);
1185 }
1186 //these are directories.
1187 roots.add(rootName);
1188 }
1189
1190 /**
1191 * Add an entry into the set of classes that
1192 * dependencies are going to be computed on.
1193 * @param className
1194 */
1195 public void addClasses(String className) {
1196 classes.add(className);
1197 }
1198
1199 /**
1200 * If true classnames will be separated using
1201 * File.separator, else it will use dots.
1202 * @return true or false
1203 */
1204 public boolean getFiles() {
1205 return files;
1206 }
1207
1208 /**
1209 * Accessor method for the found dependencies.
1210 * @return String[] dependencies
1211 */
1212 public String[] getResults() {
1213 String[] vals = (String[])results.toArray(new String[results.size()]);
1214 Arrays.sort(vals);
1215 return vals;
1216 }
1217
1218 /**
1219 * Indicates whether computing the dependent classes as result of the last
1220 * call to {@link #compute()} resulted in one or more failures.
1221 *
1222 * @return <code>true</code> in case a failure has happened, such as a
1223 * class not being found, <code>false</code> otherwise
1224 */
1225 public boolean hasFailed() {
1226 return failed;
1227 }
1228
1229 /**
1230 * Convenience method for initializing an instance with specific
1231 * command line arguments. See the description of this class
1232 * for a list and description of the acceptable arguments.
1233 * @param args
1234 */
1235 public void setupOptions(String[] args) {
1236 for (int i = 0; i < args.length ; i++ ) {
1237 String arg = args[i];
1238 if (arg.equals("-newdirbehavior")) {
1239 newRootDirBehavior = true;
1240 }
1241 else if (arg.equals("-cp")) {
1242 i++;
1243 setClassPath(args[i]);
1244 } else if (arg.equals("-files")) {
1245 setFiles(true);
1246 } else if (arg.equals("-hide")) {
1247 i++;
1248 addHides(args[i]);
1249 } else if (arg.equals("-in")) {
1250 i++;
1251 addInside(args[i]);
1252 } else if (arg.equals("-edges")) {
1253 setEdges(true);
1254 } else if (arg.equals("-out")) {
1255 i++;
1256 addOutside(args[i]);
1257 } else if (arg.equals("-outer")) {
1258 ignoreOuter = false;
1259 } else if (arg.equals("-prune")) {
1260 i++;
1261 addPrune(args[i]);
1262 } else if (arg.equals("-inroot")) {
1263 i++;
1264 addInsideRoot(args[i]);
1265 } else if (arg.equals("-outroot")) {
1266 i++;
1267 addOutsideRoot(args[i]);
1268 } else if (arg.equals("-show")) {
1269 i++;
1270 addShow(args[i]);
1271 } else if (arg.equals("-skip")) {
1272 i++;
1273 addSkip(args[i]);
1274 } else if (arg.equals("-tell")) {
1275 i++;
1276 addTells(args[i]);
1277 } else if (arg.indexOf(File.separator) >= 0) {
1278 addRoots(arg);
1279 } else if ("-prov".equals(args[i])){
1280 i++;
1281 providers.add(args[i]);
1282 } else if (arg.startsWith("-")) {
1283 usage();
1284 } else {
1285 addClasses(arg);
1286 }
1287 }
1288 }
1289
1290 private static ResourceBundle resources;
1291 private static boolean resinit = false;
1292
1293 /**
1294 * Get the strings from our resource localization bundle.
1295 */
1296 private static synchronized String getString(String key) {
1297 if (!resinit) {
1298 resinit = true;
1299 try {
1300 resources = ResourceBundle.getBundle
1301 ("org.apache.river.tool.resources.classdep");
1302 } catch (MissingResourceException e) {
1303 e.printStackTrace();
1304 }
1305 }
1306 if (resources != null) {
1307 try {
1308 return resources.getString(key);
1309 } catch (MissingResourceException e) {
1310 }
1311 }
1312 return null;
1313 }
1314
1315 /**
1316 * Print out string according to resourceBundle format.
1317 */
1318 private static void print(String key, Object val) {
1319 String fmt = getString(key);
1320 if (fmt == null) {
1321 fmt = "no text found: \"" + key + "\" {0}";
1322 }
1323 System.err.println(MessageFormat.format(fmt, new Object[]{val}));
1324 }
1325
1326 /**
1327 * Print out string according to resourceBundle format.
1328 */
1329 private static void print(String key, Object val1, Object val2) {
1330 String fmt = getString(key);
1331 if (fmt == null) {
1332 fmt = "no text found: \"" + key + "\" {0} {1}";
1333 }
1334 System.err.println(MessageFormat.format(fmt,
1335 new Object[]{val1, val2}));
1336 }
1337
1338 /**
1339 * Command line interface for generating the list of classes that
1340 * a set of classes depends upon. See the description of this class
1341 * for a list and description of the acceptable arguments.
1342 */
1343 public static void main(String[] args) {
1344 try {
1345 if (args.length == 0) {
1346 usage();
1347 return;
1348 }
1349 ClassDep dep = new ClassDep();
1350 //boolean files = false;
1351 dep.setupOptions(args);
1352 String[] vals = dep.compute();
1353 int l = vals.length;
1354 for (int i = 0; i < l; i++) {
1355 if (dep.getFiles()) {
1356 System.out.println(vals[i].replace('.', File.separatorChar) + ".class");
1357 } else {
1358 System.out.println(vals[i]);
1359 }
1360 }
1361 } finally {
1362 System.out.flush();
1363 System.out.close();
1364 }
1365 }
1366 }