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 }