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 java.io.BufferedReader; 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.io.FileOutputStream; 24 import java.io.InputStreamReader; 25 import java.io.IOException; 26 import java.io.PrintWriter; 27 28 import java.lang.Comparable; 29 import java.lang.reflect.Constructor; 30 import java.lang.reflect.Field; 31 import java.lang.reflect.Method; 32 import java.lang.reflect.Modifier; 33 34 import java.net.URL; 35 import java.net.URLClassLoader; 36 37 import java.text.MessageFormat; 38 39 import java.util.ArrayList; 40 import java.util.Collection; 41 import java.util.Enumeration; 42 import java.util.HashSet; 43 import java.util.Iterator; 44 import java.util.MissingResourceException; 45 import java.util.ResourceBundle; 46 import java.util.StringTokenizer; 47 import java.util.TreeSet; 48 49 import java.util.jar.JarEntry; 50 import java.util.jar.JarFile; 51 import java.util.jar.JarInputStream; 52 import java.util.jar.JarOutputStream; 53 import java.util.jar.Manifest; 54 55 import java.util.regex.Matcher; 56 import java.util.regex.Pattern; 57 58 /** 59 * Tool used to generate the preferred class information for downloadable JAR 60 * files in the form of a META-INF/PREFERRED.LIST required for use by the <code> 61 * net.jini.loader.pref.PreferredClassLoader</code>. The list is generated by 62 * examining the dependencies of classes contained within a target JAR file and 63 * zero or more additional supporting JAR files. Through various command-line 64 * options, a set of "root" classes are identified as belonging to a public API. 65 * These root classes provide the starting point for recursively computing a 66 * dependency graph, finding all of the classes referenced in the public API of 67 * the root classes, finding all of the classes referenced in turn by the public 68 * API of those classes, and so on, until no new classes are found. The results 69 * of the dependency analysis are combined with the preferred list information 70 * in the additional supporting JAR files to compute a preferred list having the 71 * smallest number of entries that describes the preferred state of the classes 72 * and resources contained in all of the JAR files. The output of the tool is a 73 * new version of the target JAR file containing the generated preferred list, 74 * and/or a copy of the list printed to <code>System.out</code>. 75 * <p> 76 * This tool implements the first guideline described in <code> 77 * net.jini.loader.pref</code>. In many cases it is sufficient to specify 78 * the roots via the <code>-proxy</code> option. The <code>-api</code> and 79 * <code>-impl</code> options are used to generate lists for JAR files 80 * in which the roots are not completely defined by the 81 * proxy classes, or for non-service JAR files. Since there is no definitive 82 * set of rules for determining whether a class should be preferred, 83 * the developer should verify the correctness of the generated list. 84 * <p> 85 * The following items are discussed below: 86 * <ul> 87 * <li><a href="#running">Running the Tool</a> 88 * <li><a href="#processing">Processing Options</a> 89 * <li><a href="#examples">Examples</a> 90 * </ul> 91 * 92 * <a name="running"></a> 93 * <h3>Running the Tool</h3> 94 * 95 * To run the tool on UNIX platforms: 96 * <blockquote><pre> 97 * java -jar <var><b>install_dir</b></var>/lib/preferredlistgen.jar <var><b>processing_options</b></var> 98 * </pre></blockquote> 99 * To run the tool on Microsoft Windows platforms: 100 * <blockquote><pre> 101 * java -jar <var><b>install_dir</b></var>\lib\preferredlistgen.jar <var><b>processing_options</b></var> 102 * </pre></blockquote> 103 * <p> 104 * Note that the options for this tool can be specified in any order, and 105 * can be intermixed. 106 * 107 * <a name="processing"></a> 108 * <h3>Processing Options</h3> 109 * <p> 110 * <dl> 111 * <dt><b><code>-cp</code> <var>input_classpath</var></b> 112 * <dd>Identifies the classpath for all of the classes that might need to be 113 * included in the dependency analysis. Typically this will include all of your 114 * application classes, classes from the JGDMS release, and any other classes on 115 * which your classes might depend. It is safe to include more classes than are 116 * actually necessary because the tool limits the scope of the preferred list to 117 * those classes actually included in the JAR files being analyzed. It is not 118 * necessary to include the JAR files being analyzed in the classpath as they 119 * will be appended automatically. It is also unnecessary to include any classes 120 * that are part of the Java(TM) 2 SDK. The class path should be in the form of a 121 * list of directories or JAR files, delimited by a colon (":") on UNIX 122 * platforms and a semi-colon (";") on Microsoft Windows platforms. The order of 123 * locations in the path does not matter. 124 * </dd> 125 * </dl> 126 * <dl> 127 * <dt><b><code>-jar</code> <var>file</var></b> 128 * <dd>Identifies a JAR file containing the classes to analyze. If the JAR 129 * manifest includes a <code>Class-Path</code> attribute, then these JAR files 130 * will also be processed recursively. The default behavior is to replace the 131 * original JAR file with a new file containing the generated preferred list. If 132 * the original target JAR file contained a preferred list, that list is ignored 133 * and is replaced by the newly generated list. This option may be specified 134 * zero or more times. If multiple <code>-jar</code> options are specified, the 135 * first file specified is considered the target JAR file. 136 * </dd> 137 * 138 * <dt><b><code>-proxy</code> <var>classname</var></b> 139 * <dd>Identifies the class name of a proxy in the target JAR file. All of the 140 * public interfaces implemented by the proxy, and all of the public super 141 * interfaces of any non-public interfaces implemented by the proxy, are 142 * included in the set of roots for performing dependency analysis. This option 143 * may be specified zero or more times. 144 * </dd> 145 * 146 * <dt><b><code>-api</code> <var>name-expression</var></b> 147 * <dd> 148 * This option identifies a class or a JAR entry, package, or namespace that is 149 * to be considered public and therefore <b>not</b> preferred. If 150 * <var>name-expression</var> ends with ".class", it represents a class whose 151 * name is <var>name-expression</var> without the ".class" suffix and with each 152 * '/' character replaced with a '.'. Otherwise, if <var>name-expression</var> 153 * ends with "/" or "/*", it represents a directory wildcard matching all 154 * entries in the named directory. Otherwise, if <var>name-expression</var> ends 155 * with "/-", it represents a namespace wildcard that matches all entries in the 156 * named directory and all of its subdirectories. Otherwise 157 * <var>name-expression</var> represents a non-class resource in the JAR 158 * file. Alternatively, <var>name-expression</var> may be expressed directly as 159 * a class name. A nested (including inner) class must be expressed as a binary 160 * class name; if <code>Bar</code> is a nested class of <code>Foo,</code> then 161 * <code>Bar</code> would be expressed as <code>Foo$Bar</code>. The most 162 * specific <var>name-expression</var> is used to match an entry. By default, 163 * any public class in the JAR file that matches <var>name-expression</var> will 164 * be included in the set of roots for dependency analysis. If 165 * <var>name-expression</var> is a class name, then that class will be included 166 * in the set of roots irregardless of its access modifier. If the 167 * <code>-nonpublic</code> option is also present, then matching non-public 168 * classes will also be included in the set of roots. The <code>-api</code> 169 * option may be specified zero or more times. 170 * <p> 171 * As an example, presuming the class <code>org.apache.river.example.Foo</code> 172 * was included in the target JAR file, then the following would all cause 173 * that class to be included in the public API: 174 * <blockquote><pre> 175 * -api org/apache/river/example/Foo.class 176 * -api org.apache.river.example.Foo 177 * -api org/apache/river/example/* 178 * -api org/apache/river/example/- 179 * </pre></blockquote> 180 * and the last example would also apply to, for instance, 181 * <code>org.apache.river.example.gui.FooPanel</code>. 182 * </dd> 183 * 184 * <dt><b><code>-impl</code> <var>name-expression</var></b> 185 * <dd>This option identifies a class or a JAR entry, package, or namespace that 186 * is to be considered private and therefore preferred. 187 * <var>name-expression</var> is interpreted as described for the 188 * <code>-api</code> option. If <var>name-expression</var> is a class name or a 189 * class JAR entry name, that class will be considered preferred and will not be 190 * selected by or included in the dependency analysis even if it was included in 191 * the set of roots as a result of processing the <code>-proxy</code> and 192 * <code>-api</code> options. This option may be specified zero or more times. 193 * </dd> 194 * 195 * <dt><b><code>-nonpublic</code></b> 196 * <dd>This option forces any non-public classes matched by the 197 * <code>-api</code> <var>name-expression</var>s to be included in the set of 198 * roots for dependency analysis. 199 * </dd> 200 * 201 * <dt><b><code>-nomerge</code></b> 202 * <dd>Causes the classes in JAR files which do not contain preferred 203 * lists to be considered not preferred. If this option is not specified, all classes in 204 * JAR files which do not contain preferred lists are merged with the classes supplied by 205 * the target JAR file for purposes of dependency 206 * analysis; the additional classes are not included in the generated target JAR file. 207 * The <code>-impl</code> and <code>-api</code> options may be used to initialize 208 * the preferred state of the merged classes. 209 * </dd> 210 * 211 * <dt><b><code>-default</code> <var>false|true</var></b> 212 * <dd>Specifies the default preferred value to use when generating the 213 * preferred list and forces the generation of an explicit default preferred 214 * entry in the preferred list. If this option is not provided, the default 215 * that produces a list with the fewest entries is used; an explicit entry for 216 * the default <code>false</code> case will not be generated (except when no 217 * single entry is found, in which case a default preferred value of 218 * <code>false</code> is written). In the event of optimization ties, a default 219 * value of <var>false</var> is used. 220 * </dd> 221 * 222 * <dt><b><code>-noreplace</code></b> 223 * <dd> 224 * Inhibits the replacement of the original JAR file with a new updated JAR 225 * file. If this option is specified, the preferred list is printed on 226 * <code>System.out</code>. 227 * </dd> 228 * 229 * <dt><b><code>-print</code></b> 230 * <dd> 231 * Causes the preferred list to be printed to <code>System.out</code>, even if 232 * the list is also placed in an updated JAR file. 233 * </dd> 234 * 235 * <dt><b><code>-tell</code> <var>classname</var></b> 236 * <dd>Specifies the fully qualified name of a class for which dependency 237 * information is desired. This option causes the tool to display information 238 * about every class in the dependency graph that references the specified 239 * class. If no class references the specified class, it will be identified as a 240 * root class. This information is sent to the error stream of the tool, not to 241 * the normal output stream. This option can be specified zero or more 242 * times. If this option is used, all other output options are ignored, and the 243 * normal class output is not produced. This option is useful for debugging. 244 * </dd> 245 * </dl> 246 * 247 * Using values from the <code>-api</code> and <code>-impl</code> options, a 248 * graph is constructed that defines initial preferred values to be inherited by 249 * the target JAR entries as they are loaded into the graph. If there were no 250 * such options specified, all entries from the target JAR file loaded into the 251 * graph initially will be marked as preferred. The classes and resources 252 * identified by the first <code>-jar</code> option (the target JAR file) are 253 * then loaded into this graph and are assigned their initial preferred 254 * values. The remaining JAR files that include preferred lists are loaded into 255 * the graph and the entries assigned preferred values based on the preferred 256 * list contained in the JAR file being loaded. If a non-target JAR file does 257 * not contain a preferred list, the default behavior is to merge the classes 258 * and resources in the file with those of the target JAR file (for purposes of 259 * dependency analysis only), making them subject to the <code>-api</code> and 260 * <code>-impl</code> options. The <code>-nomerge</code> option can be used to 261 * override the default behavior, causing all such classes to be assigned a 262 * value of not preferred. The set of root classes is constructed by finding 263 * all of the classes from the target JAR file that are marked as not preferred 264 * in the graph, and by adding all of the public interfaces, or any public 265 * superinterfaces of non-public interfaces implemented by the proxy classes 266 * specified via the <code>-proxy</code> option. Starting with the root classes, 267 * dependent classes are identified by examining the compiled class file for the 268 * class, finding all of the public and protected fields, methods, constructors, 269 * interfaces, and super classes it references, and then in turn examining those 270 * classes. Any dependent classes found that also exist in the graph will be 271 * marked not preferred, unless that class was explicitly named by a 272 * <code>-impl</code> option. Any root class or dependent class named by a 273 * <code>-impl</code> option retains its original preferred value and no further 274 * dependency analysis is performed for the class. The range of the dependency 275 * analysis is restricted to the set of classes included in the graph. 276 * <p> 277 * The tool then processes the graph to find the smallest number of preferred 278 * list entries that describes the preferred state of all classes and resources 279 * in the graph. The resulting preferred list may be printed or included in a 280 * JAR file that replaces the original (first) JAR file. 281 * <p> 282 * <a name="examples"></a> 283 * <h3>Examples</h3> 284 * 285 * The following example generates the preferred list for the codebase JAR file 286 * for reggie, replacing the original reggie-dl.jar with a new file containing 287 * the preferred list. The reggie implementation includes four proxy classes, 288 * however <code>org.apache.river.reggie.RegistrarProxy</code> and 289 * <code>org.apache.river.reggie.AdminProxy</code> are not identified on the command 290 * line because they are parent classes of 291 * <code>org.apache.river.reggie.ConstrainableRegistrarProxy</code> and 292 * <code>org.apache.river.reggie.ConstrainableAdminProxy</code>. 293 * <p> 294 * <blockquote><pre> 295 * java -jar <var><b>install_dir</b></var>/lib/preferredlistgen.jar \ 296 * -cp <var><b>install_dir</b></var>/lib/jsk-platform.jar \ 297 * -jar <var><b>install_dir</b></var>/lib-dl/reggie-dl.jar \ 298 * -jar <var><b>install_dir</b></var>/lib-dl/jsk-dl.jar \ 299 * -proxy org.apache.river.reggie.ConstrainableRegistrarProxy \ 300 * -proxy org.apache.river.reggie.ConstrainableAdminProxy 301 * </pre></blockquote> 302 * <p> 303 * 304 * @author Sun Microsystems, Inc. 305 */ 306 307 public class PreferredListGen { 308 309 /** remember classes processed to avoid redundant work or loops */ 310 private final Collection seen; 311 312 /* 313 * NOTE: the Boolean class is used extensively to represent the three 314 * possible states of a preferred value of true/false/undefined 315 * (for instance, if there is no default preferred value). 316 */ 317 318 /** Boolean equivalent of true */ 319 private final Boolean TRUE; 320 321 /** Boolean equivalent of false */ 322 private final Boolean FALSE; 323 324 /** the names of proxies supplied by the -proxy option */ 325 private final Collection proxies; 326 327 /** the set of classes to report based on the -tell options */ 328 private final Collection tells; 329 330 /** the first JAR in the set of files loaded via the -jar option */ 331 private File targetJar; 332 333 /** replace the first JAR with a copy containing the preferred list */ 334 private boolean replaceJar; 335 336 /** print the preferred list, only meaningful with the -jar option */ 337 private boolean printResults; 338 339 /** ordered list of JAR files names specified by the -jar options */ 340 private final Collection jarList; 341 342 /** the classpath specified by the -cp option */ 343 private String classpath; 344 345 /** the ordered graph containing the JAR class info */ 346 private final Graph listGraph; 347 348 /** if true, use defaultToForce for default, otherwise optimize */ 349 private boolean forceDefault; 350 351 /** the default preference value to force */ 352 private boolean defaultToForce; 353 354 /** the class loader for resolving class names */ 355 private ClassLoader loader; 356 357 /** I18N resource bundle */ 358 private static ResourceBundle resources; 359 360 /** flag to indicate that initialization of resources has been attempted */ 361 private static boolean resinit = false; 362 363 /** union of the entries in all JAR files for -api/-impl existence check */ 364 private final HashSet jarEntries; 365 366 /** loaded JAR names, to avoid infinite loops due to circular definitions */ 367 private final HashSet jarFiles; 368 369 /** if true, non-public roots are allowed */ 370 private boolean keepNonPublicRoots; 371 372 /** if true, load JARs without preferred lists directly into listGraph */ 373 private boolean doMerge; 374 375 /** 376 * Get the strings from our resource localization bundle. 377 */ 378 private synchronized static String getString(String key, Object v1, Object v2, Object v3) { 379 String fmt = "no text found: \"" + key + "\""; 380 if (!resinit) { 381 try { 382 resinit = true; 383 resources = ResourceBundle.getBundle 384 ("org.apache.river.tool.resources.preflistgen"); 385 } catch (MissingResourceException e) { 386 e.printStackTrace(); 387 } 388 } 389 if (resources != null) { 390 try { 391 fmt = resources.getString(key); 392 } catch (MissingResourceException e) { 393 } 394 } 395 return MessageFormat.format(fmt, new Object[]{v1, v2, v3}); 396 } 397 398 /** 399 * Return the string according to resourceBundle format. 400 */ 401 private static String getString(String key) { 402 return getString(key, null, null, null); 403 } 404 405 /** 406 * Return the string according to resourceBundle format. 407 */ 408 private static String getString(String key, Object v1) { 409 return getString(key, v1, null, null); 410 } 411 412 /** 413 * Return the string according to resourceBundle format. 414 */ 415 private static String getString(String key, Object v1, Object v2) { 416 return getString(key, v1, v2, null); 417 } 418 419 /** 420 * Print out string according to resourceBundle format. 421 */ 422 private static void print(String key, Object v1) { 423 System.err.println(getString(key, v1)); 424 } 425 426 /** 427 * Print out string according to resourceBundle format. 428 */ 429 private static void print(String key, Object v1, Object v2) { 430 System.err.println(getString(key, v1, v2)); 431 } 432 433 /** 434 * Print out string according to resourceBundle format. 435 */ 436 private static void print(String key, Object v1, Object v2, Object v3) { 437 System.err.println(getString(key, v1, v2, v3)); 438 } 439 440 /** 441 * Create a preferred list generator and process the command line arguments. 442 * 443 * @param args the command line arguments 444 */ 445 private PreferredListGen(String[] args) { 446 this.FALSE = Boolean.FALSE; 447 this.TRUE = Boolean.TRUE; 448 this.doMerge = true; 449 this.keepNonPublicRoots = false; 450 this.jarFiles = new HashSet(); 451 this.jarEntries = new HashSet(); 452 this.loader = getClass().getClassLoader(); 453 this.defaultToForce = false; 454 this.forceDefault = false; 455 this.forceDefault = false; 456 this.listGraph = new Graph(); 457 this.jarList = new ArrayList(); 458 this.printResults = false; 459 this.replaceJar = true; 460 this.tells = new HashSet(); 461 this.proxies = new HashSet(); 462 this.seen = new HashSet(); 463 seen.add("int"); // mark primitives as already seen or stack traces fly 464 seen.add("long"); 465 seen.add("float"); 466 seen.add("double"); 467 seen.add("short"); 468 seen.add("void"); 469 seen.add("char"); 470 seen.add("byte"); 471 seen.add("boolean"); 472 if (args.length == 0) { 473 throw new IllegalArgumentException(getString("preflistgen.noargs")); 474 } 475 for (int i = 0; i < args.length ; i++ ) { 476 String arg = args[i]; 477 if (arg.equals("-print")) { 478 setPrint(true); 479 } else if (arg.equals("-noreplace")) { 480 setReplaceJar(false); 481 setPrint(true); 482 } else if (arg.equals("-jar")) { 483 addJar(args[++i]); 484 } else if (arg.equals("-tell")) { 485 addTell(args[++i]); 486 } else if (arg.equals("-impl")) { 487 addImpl(args[++i]); 488 } else if (arg.equals("-api")) { 489 addApi(args[++i]); 490 } else if (arg.equals("-nonpublic")) { 491 setKeepNonPublicRoots(true); 492 } else if (arg.equals("-nomerge")) { 493 setMerge(false); 494 } else if (arg.equals("-default")) { 495 String def = args[++i]; 496 if (def.equalsIgnoreCase("true") 497 || def.equalsIgnoreCase("false")) 498 { 499 setDefault(def.equalsIgnoreCase("true")); 500 } else { 501 String msg = getString("preflistgen.baddefault", def); 502 throw new IllegalArgumentException(msg); 503 } 504 } else if (arg.equals("-cp")) { 505 setClasspath(args[++i]); 506 } else if (arg.equals("-proxy")) { 507 addProxy(args[++i]); 508 } else { 509 String msg = getString("preflistgen.badoption", arg); 510 throw new IllegalArgumentException(msg); 511 } 512 } 513 } 514 515 /** 516 * Constructor for programmatic access. The public <code>set</code> and 517 * <code>add</code> methods must be called to supply the argument 518 * values. Then <code>compute</code> and <code>generatePreferredList</code> 519 * must be called to perform the dependency analysis and to generate the 520 * preferred list. 521 */ 522 public PreferredListGen() { 523 this.doMerge = true; 524 this.keepNonPublicRoots = false; 525 this.jarFiles = new HashSet(); 526 this.jarEntries = new HashSet(); 527 this.loader = (ClassLoader) getClass().getClassLoader(); 528 this.defaultToForce = false; 529 this.forceDefault = false; 530 this.forceDefault = false; 531 this.listGraph = new Graph(); 532 this.jarList = new ArrayList(); 533 this.printResults = false; 534 this.replaceJar = true; 535 this.tells = new HashSet(); 536 this.proxies = new HashSet(); 537 this.FALSE = Boolean.FALSE; 538 this.TRUE = Boolean.TRUE; 539 this.seen = new HashSet(); 540 seen.add("int"); // mark primitives as already seen or stack traces fly 541 seen.add("long"); 542 seen.add("float"); 543 seen.add("double"); 544 seen.add("short"); 545 seen.add("void"); 546 seen.add("char"); 547 seen.add("byte"); 548 seen.add("boolean"); 549 } 550 551 /** 552 * Set the flag controlling whether a preferred list is to be printed. 553 * This flag is ignored if the <code>PrintWriter</code> supplied in 554 * the call to <code>generatePreferredList</code> is non-<code>null</code>. 555 * The default value is <code>false</code>. 556 * 557 * @param printResults if <code>true</code>, print the preferred list 558 */ 559 public final synchronized void setPrint(boolean printResults) { 560 this.printResults = printResults; 561 } 562 563 /** 564 * Set the flag controlling whether non-public classes should be retained 565 * in the set of roots used for performing dependency analysis. By default, 566 * non-public classes are discarded. 567 * 568 * @param keepNonPublicRoots if <code>true</code>, non-public root classes 569 * are retained 570 */ 571 public final synchronized void setKeepNonPublicRoots(boolean keepNonPublicRoots) { 572 this.keepNonPublicRoots = keepNonPublicRoots; 573 } 574 575 /** 576 * Select the behavior for processing non-target JAR files which do not 577 * contain preferred lists. If <code>doMerge</code> is <code>true</code>, the 578 * classes contained in these JAR files are merged with the target JAR 579 * file for purposes of dependency analysis. The <code>-impl</code> and 580 * <code>-api</code> options may be used to initialize the preferred state 581 * of the merged classes. If <code>doMerge</code> is <code>false</code>, 582 * the classes in non-target JAR files which do not contain preferred lists are 583 * initialized with a preferred state of 'not preferred'. The default behavior 584 * corresponds to calling <code>setMerge(true)</code>. 585 * 586 * @param doMerge if <code>true</code>, perform the merge 587 */ 588 public final synchronized void setMerge(boolean doMerge) { 589 this.doMerge = doMerge; 590 } 591 592 /** 593 * Set the flag controlling whether a preferred list is to be placed 594 * in the target JAR file. The default value is <code>true</code>. 595 * 596 * @param replaceJar if <code>true</code>, update the target JAR file 597 */ 598 public final synchronized void setReplaceJar(boolean replaceJar) { 599 this.replaceJar = replaceJar; 600 } 601 602 /** 603 * Add <code>jarName</code> to the list of JAR files to process. 604 * The first call identifies the target JAR file. This method must 605 * be called at least once. 606 * 607 * @param jarName the name of the JAR file to add to the set. 608 */ 609 public final void addJar(String jarName) { 610 jarList.add(jarName); 611 } 612 613 /** 614 * Add <code>tellName</code> to the tell list. If a class is identified 615 * as not preferred through the dependency analysis, and if that class 616 * name is in the tell list, then the source dependency causing the class to 617 * be included is printed. This is for debugging purposes. 618 * 619 * @param tellName the name of the JAR file to add to the tell set. 620 */ 621 public final void addTell(String tellName) { 622 String tellClass = fileToClass(tellName); 623 tells.add(tellClass); 624 } 625 626 /** 627 * Initialize the dependency graph with a private API entry. 628 * <code>implName</code> identifies a class or a JAR entry, package, or 629 * namespace that is to be considered private and therefore preferred. If 630 * <code>implName</code> ends with ".class", it represents a class whose 631 * name is <code>implName</code> without the ".class" suffix and with each 632 * '/' character replaced with a '.'. Otherwise, if <code>implName</code> 633 * ends with "/" or "/*", it represents a directory wildcard matching all 634 * entries in the named directory. Otherwise, if <code>implName</code> ends 635 * with "/-", it represents a namespace wildcard that matches all entries in 636 * the named directory and all of its subdirectories. Otherwise 637 * <code>implName</code> represents a non-class resource in the JAR 638 * file. Alternatively, <code>implName</code> may be expressed directly as a 639 * class name. The most specific <code>implName</code> is used to match an 640 * entry found in the JAR files being analyzed. If <code>implName</code> is 641 * either of the class name forms, then that class is forced to be preferred 642 * and is not included in the public API even it is found by the dependency 643 * analysis. 644 * 645 * @param implName the identifier for the private API entry 646 * 647 * @throws IllegalArgumentException if <code>implName</code> does not match 648 * any of the criteria above. 649 */ 650 public final void addImpl(String implName) { 651 listGraph.initialize(implName, true, null); // preferred 652 } 653 654 /** 655 * Initialize the dependency graph with a public API entry. 656 * <code>apiName</code> identifies a class or a JAR entry, package, or 657 * namespace that is to be considered public and therefore <b>not</b> 658 * preferred. If <code>apiName</code> ends with ".class", it represents a 659 * class whose name is <code>apiName</code> without the ".class" suffix and 660 * with each '/' character replaced with a '.'. Otherwise, if 661 * <code>apiName</code> ends with "/" or "/*", it represents a directory 662 * wildcard matching all entries in the named directory. Otherwise, if 663 * <code>apiName</code> ends with "/-", it represents a namespace wildcard 664 * that matches all entries in the named directory and all of its 665 * subdirectories. Otherwise <code>apiName</code> represents a non-class 666 * resource in the JAR file. Alternatively, <code>apiName</code> may be 667 * expressed directly as a class name. The most specific 668 * <code>apiName</code> is used to match an entry found in the JAR files 669 * being analyzed. Any class in the JAR file that matches 670 * <code>apiName</code> will be included in the set of roots for dependency 671 * analysis. This method may be called zero or more times. 672 * 673 * @param apiName the identifier for the public API entry 674 * 675 * @throws IllegalArgumentException if <code>apiName</code> does not match 676 * any of the criteria above. 677 */ 678 public final void addApi(String apiName) { 679 listGraph.initialize(apiName, false, null); // not preferred 680 } 681 682 /** 683 * Set the default value to use for the preferred list. If this method 684 * is not called, the default will be chosen which results in a preferred 685 * list with the smallest number of entries. In the event of optimization 686 * ties, a default value of <code>false</code> is used. 687 * 688 * @param def the default value to use for the list 689 */ 690 public final synchronized void setDefault(boolean def) { 691 forceDefault = true; 692 defaultToForce = def; 693 } 694 695 /** 696 * Set the classpath of the classes to include in the analysis. It is 697 * not necessary to include the JAR files supplied via calls to the 698 * <code>addJar</code> method. 699 * 700 * @param path the classpath for the classes to include in the analysis 701 */ 702 public final synchronized void setClasspath(String path) { 703 this.classpath = path; 704 } 705 706 /** 707 * Add <code>proxy</code> to the set of proxies used to identify 708 * roots. This method may be called zero or more times. 709 * 710 * @param proxy the name of the proxy class 711 */ 712 public final void addProxy(String proxy) { 713 proxies.add(proxy); 714 } 715 716 717 /** 718 * Load all of the JAR files named on the command line or referenced in 719 * Class-Path manifest entries of those JAR files. 720 * 721 * @throws IOException if an error occurs reading the contents 722 * of any of the JAR files. 723 */ 724 private void loadJars() throws IOException { 725 Iterator it = jarList.iterator(); 726 while (it.hasNext()) { 727 String jarName = (String) it.next(); 728 File jarFile = new File(jarName); 729 loadJar(jarFile); 730 } 731 } 732 733 /** 734 * Load the given JAR <code>File</code>, adding every class or resource in 735 * the file a graph. The first time this method is called, 736 * <code>listGraph</code> is loaded directly and all non-preferred classes 737 * found in the graph are marked as roots. On subsequent calls, a new 738 * temporary graph is created and initialized based on the preferred list in 739 * the JAR. After entries are loaded into the graph, it is merged into 740 * <code>listGraph</code>. The manifest is also read, and any JARs specified 741 * by the manifest <code>Class-Path</code> attribute are loaded 742 * recursively. If the given JAR file does not exist or is a directory, an 743 * error message is printed. 744 * 745 * @param jar the <code>File</code> representing the JAR file 746 * @throws IOException if an error occured reading the contents 747 * of the JAR or any of the JARs in the Class-Path. 748 * @throws IllegalArgumentException if the JAR file does not exist 749 * or is a directory 750 */ 751 private synchronized void loadJar(File jar) throws IOException { 752 if (jarFiles.contains(jar)) { 753 return; // short-circuit circular definitions 754 } 755 jarFiles.add(jar); 756 if (!jar.exists()) { 757 String msg = getString("preflistgen.nojarfound", jar); 758 throw new IllegalArgumentException(msg); 759 } 760 if (jar.isDirectory()) { 761 String msg = getString("preflistgen.jarisdir", jar); 762 throw new IllegalArgumentException(msg); 763 } 764 if (targetJar == null) { 765 targetJar = jar; 766 populateGraph(listGraph, jar); 767 } else if (doMerge && noPreferredList(jar)) { 768 populateGraph(listGraph, jar); 769 } else { 770 Graph g = createGraph(jar); // read pref list to init graph 771 populateGraph(g, jar); 772 listGraph.merge(g); 773 } 774 } 775 776 /** 777 * Return <code>true</code> if the given JAR file does not contain a preferred list. 778 * 779 * @param jar the file to examine 780 * @return true if no preferred list is present 781 * @throws IOException if an error occured reading the contents of the JAR 782 */ 783 private boolean noPreferredList(File jar) throws IOException { 784 JarFile jarFile = new JarFile(jar); 785 return jarFile.getJarEntry("META-INF/PREFERRED.LIST") == null; 786 } 787 788 /** 789 * Load the given JAR <code>File</code>, adding every class or resource in 790 * the file to the given graph. The manifest is also read, and any JARs 791 * specified by the manifest <code>Class-Path</code> attribute are loaded 792 * recursively. 793 * 794 * @param g the graph to populate 795 * @param jar the <code>File</code> representing the JAR file 796 */ 797 private void populateGraph(Graph g, File jar) { 798 try { 799 JarFile jarFile = new JarFile(jar); 800 Enumeration en = jarFile.entries(); 801 while (en.hasMoreElements()) { 802 JarEntry entry = (JarEntry) en.nextElement(); 803 String id = entry.getName(); 804 if (id.startsWith("META-INF") || id.endsWith("/")) { 805 continue; // ignore directories and anything in meta-inf 806 } 807 jarEntries.add(id); 808 if (id.endsWith(".class")) { 809 g.add(fileToClass(id), Graph.CLASS, jar); 810 } else { 811 g.add(fileToClass(id), Graph.RESOURCE, jar); 812 } 813 } 814 Manifest manifest = jarFile.getManifest(); 815 if (manifest == null) { 816 return; // processed a zip file 817 } 818 String classPath = 819 manifest.getMainAttributes().getValue("Class-Path"); 820 if (classPath != null) { 821 StringTokenizer tok = new StringTokenizer(classPath); 822 while (tok.hasMoreTokens()) { 823 String fileName = tok.nextToken(); 824 File nextJar = new File(jar.getParentFile(), fileName); 825 loadJar(nextJar); 826 } 827 } 828 } catch (IOException e) { 829 e.printStackTrace(); 830 } 831 } 832 833 /** 834 * Create a <code>Graph</code> initialized from the preferred list of 835 * the given JAR file. 836 * 837 * @param jarFile the JAR file from which an initialized graph is 838 * to be derived 839 * @return an initialized <code>Graph</code> 840 * @throws IOException if an error occurs reading <code>jarFile</code> 841 */ 842 private Graph createGraph(File jarFile) throws IOException { 843 844 final Pattern headerPattern = 845 Pattern.compile("^PreferredResources-Version:\\s*(.*?)$"); 846 final Pattern versionPattern = 847 Pattern.compile("^1\\.\\d+$"); 848 final Pattern namePattern = 849 Pattern.compile("^Name:\\s*(.*)$"); 850 final Pattern preferredPattern = 851 Pattern.compile("^Preferred:\\s*(.*)$"); 852 853 /** 854 * Parses the given JAR file's preferred list, if any. 855 */ 856 Graph graph = new Graph(); 857 JarFile jar = new JarFile(jarFile); 858 JarEntry ent = jar.getJarEntry("META-INF/PREFERRED.LIST"); 859 if (ent == null) { 860 graph.setDefaultPref(false); 861 return graph; 862 } 863 BufferedReader r = new BufferedReader( 864 new InputStreamReader(jar.getInputStream(ent), "UTF8")); 865 866 String s = r.readLine(); 867 if (s == null) { 868 throw new IOException("missing preferred list header"); 869 } 870 s = s.trim(); 871 Matcher m = headerPattern.matcher(s); 872 if (!m.matches()) { 873 throw new IOException("illegal preferred list header: " + s); 874 } 875 s = m.group(1); 876 if (!versionPattern.matcher(s).matches()) { 877 throw new IOException( 878 "unsupported preferred list version: " + s); 879 } 880 881 s = nextNonBlankLine(r); 882 if (s == null) { 883 throw new IOException("empty preferred list"); 884 } 885 if ((m = preferredPattern.matcher(s)).matches()) { 886 graph.setDefaultPref(Boolean.valueOf(m.group(1)).booleanValue()); 887 s = nextNonBlankLine(r); 888 } else { 889 graph.setDefaultPref(false); 890 } 891 892 while (s != null) { 893 if (!(m = namePattern.matcher(s)).matches()) { 894 throw new IOException( 895 "expected preferred entry name: " + s); 896 } 897 String name = m.group(1); 898 899 s = nextNonBlankLine(r); 900 if (s == null) { 901 throw new IOException("EOF before preferred entry"); 902 } 903 if (!(m = preferredPattern.matcher(s)).matches()) { 904 throw new IOException("expected preferred entry: " + s); 905 } 906 boolean pref = Boolean.valueOf(m.group(1)).booleanValue(); 907 graph.initialize(name, pref, jarFile); 908 s = nextNonBlankLine(r); 909 } 910 return graph; 911 } 912 913 /** 914 * Returns next non-blank, non-comment line, or null if end of file has 915 * been reached. 916 * 917 * @param reader the input stream 918 * @return a <code>String</code> containing the next line, 919 * or <code>null</code> 920 * @throws IOException if an error occurs reading the stream 921 */ 922 private static String nextNonBlankLine(BufferedReader reader) 923 throws IOException 924 { 925 String s; 926 while ((s = reader.readLine()) != null) { 927 s = s.trim(); 928 if (s.length() > 0 && s.charAt(0) != '#') { 929 return s; 930 } 931 } 932 return null; 933 } 934 935 /** 936 * Convert the given class reference to a class name. If the argument is 937 * already a class name, the value is returned unchanged. If the argument is 938 * a file name, the equivalent class name is returned. Path names are in JAR 939 * format: path separators must be '/' characters and the first character 940 * must not be a path separator. 941 * 942 * @param classID the class identifier, which may be a class file 943 * reference or a class name 944 * @return the associated class name 945 */ 946 private String fileToClass(String classID) { 947 classID = classID.replace('/', '.'); 948 if (classID.endsWith(".class")) { 949 classID = classID.substring(0, classID.length() - 6); 950 } 951 return classID; 952 } 953 954 /** 955 * Inspect the given class for dependencies. If the class is an 956 * array, it's component type is inspected. Inspection consists 957 * of processing the classes name. 958 * 959 * @param from the dependent of the given class, or null if this 960 * is the top-level class 961 * @param c the class to inspect 962 */ 963 private void process(String from, Class c) { 964 while (c.isArray()) 965 c = c.getComponentType(); 966 process(from, c.getName()); 967 } 968 969 /** 970 * Inspect the given array of classes for dependencies. 971 * 972 * @param from the dependent of the given array, or null if this 973 * is the top-level class 974 * @param arr the array of classes to inspect 975 */ 976 private void process(String from, Class[] arr) { 977 for (int i = arr.length; --i >= 0; ) 978 process(from, arr[i]); 979 } 980 981 /** 982 * Determine whether the given modifier imply inclusion in the public api. 983 * Return <code>true</code> if the modifiers include the public or protected 984 * flags. 985 * 986 * @param modifiers the modifier value 987 * @return true if a public api member is implied 988 */ 989 private boolean include(int modifiers) { 990 return (modifiers & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0; 991 } 992 993 /** 994 * Inspect the given constructor for dependencies. Any parameter types 995 * and exception types declared for the constructor are inspected. 996 * 997 * @param from the dependent of the constructor, or null if this 998 * is the top-level class 999 * @param c the constructor to inspect 1000 */ 1001 private void process(String from, Constructor c) { 1002 if (!include(c.getModifiers())) 1003 return; 1004 process(from, c.getParameterTypes()); 1005 process(from, c.getExceptionTypes()); 1006 } 1007 1008 /** 1009 * Inspect the given method for dependencies. Any parameter types, 1010 * exception types, and the return type declared for the method 1011 * are inspected. 1012 * 1013 * @param from the dependent of the method, or null if this 1014 * is the top-level class 1015 * @param m the method to inspect 1016 */ 1017 private void process(String from, Method m) { 1018 if (!include(m.getModifiers())) 1019 return; 1020 process(from, m.getReturnType()); 1021 process(from, m.getParameterTypes()); 1022 process(from, m.getExceptionTypes()); 1023 } 1024 1025 /** 1026 * Inspect the given field for dependencies. The field type is inspected. 1027 * 1028 * @param from the dependent of the field, or null if this 1029 * is the top-level class 1030 * @param f the field to inspect 1031 */ 1032 private void process(String from, Field f) { 1033 if (!include(f.getModifiers())) 1034 return; 1035 process(from, f.getType()); 1036 } 1037 1038 /** 1039 * Mark the given class in the <code>Graph</code> as 'not preferred' 1040 * unless overridden by the command line <code>-impl</code> argument. 1041 * If so marked, then the public and private methods, fields, 1042 * constructors, interfaces, and superclasses declared by the class are 1043 * also inspected recursively. Only classes classes contained within 1044 * <code>listGraph</code> are processed. 1045 * 1046 * @param from the class referencing the given class, or null if this 1047 * is the top-level class 1048 * @param clazz the class to add to the public api 1049 */ 1050 private void process(String from, String clazz) { 1051 if (!seen.contains(clazz)) { 1052 seen.add(clazz); 1053 if ( ! listGraph.contains(clazz)) { 1054 return; 1055 } 1056 for (Iterator it = tells.iterator(); it.hasNext();) { 1057 if (clazz.equals((String)it.next())) { 1058 if (from != null) { 1059 System.err.println(clazz + " caused by " + from); 1060 } else { 1061 System.err.println(clazz + " is a root class"); 1062 } 1063 } 1064 } 1065 if (!listGraph.setPreferred(clazz, false, false)) { 1066 return; 1067 } 1068 try { 1069 inspectClass(clazz); 1070 } catch (NoClassDefFoundError e){ 1071 System.err.println(e.toString()); 1072 } 1073 } 1074 } 1075 1076 /** 1077 * Process all public and protected fields, methods, constructors, 1078 * interfaces, and superclasses declared by the given class. 1079 * 1080 * @param className the name of the class to process 1081 */ 1082 private void inspectClass(String className) { 1083 Class c; 1084 try { 1085 c = Class.forName(className, false, loader); 1086 } catch (Exception e) { 1087 e.printStackTrace(); 1088 return; 1089 } 1090 Class sup = c.getSuperclass(); 1091 if (sup != null) 1092 process(className, sup); 1093 Class[] ifaces = c.getInterfaces(); 1094 for (int i = ifaces.length; --i >= 0; ) 1095 process(className, ifaces[i]); 1096 Constructor[] cons = c.getDeclaredConstructors(); 1097 for (int i = cons.length; --i >= 0; ) { 1098 process(className, cons[i]); 1099 } 1100 Method[] methods = c.getDeclaredMethods(); 1101 for (int i = methods.length; --i >= 0; ) { 1102 process(className, methods[i]); 1103 } 1104 Field[] fields = c.getDeclaredFields(); 1105 for (int i = fields.length; --i >= 0; ) { 1106 process(className, fields[i]); 1107 } 1108 } 1109 1110 /** 1111 * Load JAR files, initialize the dependency graph, and perform the 1112 * dependency analysis. 1113 * 1114 * @throws IOException if an error occurs constructing the class loader 1115 * or reading any of the JAR files. 1116 * @throws IllegalArgumentException in the following cases: 1117 * <ul> 1118 * <li>if <code>addJar</code> was never 1119 * called or <code>addJar</code> was called with a file which 1120 * does not exist or is a directory 1121 * <li>if any proxies supplied via the <code>addProxy</code> 1122 * method could not be found 1123 * <li>if any component in the classpath does not exist 1124 * </ul> 1125 */ 1126 public synchronized void compute() throws IOException { 1127 if (jarList.isEmpty()) { 1128 throw new IllegalArgumentException(getString("preflistgen.nojars")); 1129 } 1130 ArrayList list = new ArrayList(); 1131 if (classpath != null) { 1132 StringTokenizer st = new StringTokenizer(classpath, 1133 File.pathSeparator); 1134 while (st.hasMoreTokens()) { 1135 String fileName = st.nextToken(); 1136 File cpFile = new File(fileName); 1137 if (!cpFile.exists()) { 1138 String msg = getString("preflistgen.badcp", fileName); 1139 throw new IllegalArgumentException(msg); 1140 } 1141 list.add(cpFile.getCanonicalFile().toURI().toURL()); 1142 } 1143 } 1144 for (Iterator it = jarList.iterator(); it.hasNext(); ) { 1145 String fileName = (String) it.next(); 1146 list.add(new File(fileName).getCanonicalFile().toURI().toURL()); 1147 } 1148 if (list.size() > 0) { 1149 URL[] urls = (URL[]) list.toArray(new URL[list.size()]); 1150 ClassLoader cl = ClassLoader.getSystemClassLoader(); 1151 if (cl != null) { 1152 cl = cl.getParent(); // the extension classloader 1153 } 1154 loader = new URLClassLoader(urls, cl); 1155 } 1156 loadJars(); 1157 Collection roots = getRoots(); 1158 for (Iterator it = roots.iterator(); it.hasNext(); ) { 1159 String clazz = (String) it.next(); 1160 Class c = null; 1161 try { 1162 c = Class.forName(clazz, false, loader); 1163 } catch (ClassNotFoundException e) { 1164 throw new IllegalStateException("could not load root class " + clazz); 1165 } 1166 if ((c.getModifiers() & Modifier.PUBLIC) == 0) { 1167 if (keepNonPublicRoots) { 1168 print("preflistgen.rootNotPublic", c); 1169 } else { 1170 if (!listGraph.isFrozen(clazz)) { 1171 //remove non public root and force preferred 1172 it.remove(); 1173 listGraph.setPreferred(clazz, true, true); 1174 continue; 1175 } 1176 } 1177 } 1178 process(null, clazz); 1179 } 1180 } 1181 1182 /** 1183 * Generate the <code>Collection</code> of roots to use for the dependency 1184 * analysis. All classes from the first JAR with a preferred state of false 1185 * (which will only be true due to the -api option, or because the same 1186 * class in another JAR is marked not-preferred) are added to the set; 1187 * Also, all interfaces of classes in the set of <code>-proxy</code> 1188 * arguments are added to the set of roots. 1189 * 1190 * @return the <code>Collection</code> of roots 1191 * @throws IllegalArgumentException if any of the proxies supplied via 1192 * the <code>addProxy</code> method could not be found. 1193 */ 1194 private Collection getRoots() { 1195 HashSet roots = new HashSet(); 1196 listGraph.addRoots(roots); 1197 Iterator proxyIt = proxies.iterator(); 1198 while (proxyIt.hasNext()) { 1199 String proxy = (String) proxyIt.next(); 1200 try { 1201 Class proxyClass = Class.forName(proxy, false, loader); 1202 addProxyRoot(proxyClass, roots); 1203 } catch (ClassNotFoundException e) { 1204 String msg = getString("preflistgen.badproxyclass", proxy); 1205 throw new IllegalArgumentException(msg, e); 1206 } 1207 } 1208 return roots; 1209 } 1210 1211 /** 1212 * Add to the given set of roots the public interfaces implemented by 1213 * the <code>proxyClass</code> and all of its superclasses. 1214 * If any non-public interfaces are implemented, then any parent 1215 * interfaces which are public are added. 1216 * 1217 * @param proxyClass the proxy to inspect 1218 * @param roots the set of root class names 1219 */ 1220 private void addProxyRoot(Class proxyClass, Collection roots) { 1221 Class[] interfaces = proxyClass.getInterfaces(); 1222 for (int j = 0; j < interfaces.length; j++) { 1223 addIfPublic(interfaces[j], roots); 1224 } 1225 Class superClass = proxyClass.getSuperclass(); 1226 if (superClass != null) { 1227 addProxyRoot(superClass, roots); 1228 } 1229 } 1230 1231 /** 1232 * Add to the given set of roots the interface named <code>intFace</code> if 1233 * that interface is public. Otherwise, add any superinterfaces which are 1234 * public. 1235 * 1236 * @param intFace the interface to add 1237 * @param roots the set of roots 1238 */ 1239 private void addIfPublic(Class intFace, Collection roots) { 1240 if ((intFace.getModifiers() & Modifier.PUBLIC) != 0) { 1241 roots.add(intFace.getName()); 1242 return; 1243 } 1244 Class[] parents = intFace.getInterfaces(); 1245 int l = parents.length; 1246 for (int i = 0; i < l; i++) { 1247 addIfPublic(parents[i], roots); 1248 } 1249 } 1250 1251 /** 1252 * Print the usage message. 1253 */ 1254 private static void usage() { 1255 print("preflistgen.usage", null); 1256 } 1257 1258 /** 1259 * Generate the preferred list from the dependency graph. If a default value 1260 * was specified, the optimal list using that default value is 1261 * generated. Otherwise, the optimal list among the two possibilities 1262 * (<code>false/true</code> in order of precedence for 'optimization ties') 1263 * is generated. An explicit default entry is generated only for the 1264 * default <code>true</code> case. 1265 * <p> 1266 * The preferred list is sorted such that more specific 1267 * definitions precede less specific definitions; ties are broken with 1268 * an alphabetic secondary sort. 1269 * <p> 1270 * The preferred list will be placed in the target JAR file unless 1271 * <code>setReplaceJar(false)</code> was called. The preferred list will be 1272 * written to <code>writer</code> if it is non-<code>null</code>. If 1273 * <code>writer</code> is <code>null</code> and <code>setPrint(true)</code> 1274 * was called, the preferred list will be written to 1275 * <code>System.out</code>. 1276 * 1277 * @param writer the <code>PrintWriter</code> to write the preferred list 1278 * to. 1279 * 1280 * @throws IOException if an error occurs updating the target JAR file. 1281 */ 1282 public synchronized void generatePreferredList(PrintWriter writer) throws IOException { 1283 if (writer == null && printResults) { 1284 writer = new PrintWriter(System.out); 1285 } 1286 String newLine = System.getProperty("line.separator"); 1287 if (!tells.isEmpty()) { 1288 return; 1289 } 1290 StringBuilder sb = new StringBuilder(); 1291 sb.append("PreferredResources-Version: 1.0"); 1292 sb.append(newLine); 1293 Collection entries = new TreeSet(); 1294 boolean pref; 1295 listGraph.markStaleInnerClasses(); // throw away inners matching outers 1296 listGraph.deleteStaleNodes(); // discard all stale nodes 1297 if (forceDefault) { 1298 pref = defaultToForce; 1299 listGraph.setDefaultPref(pref); 1300 listGraph.addEntries(entries); // create sorted list of entries 1301 } else { 1302 // create list using optimal default 1303 HashSet bestEntries = new HashSet(); 1304 // first create entries for default=false 1305 pref = false; 1306 listGraph.setDefaultPref(pref); 1307 listGraph.addEntries(bestEntries); 1308 // create entries for default=true and remember the best result 1309 HashSet test = new HashSet(); 1310 listGraph.setDefaultPref(true); 1311 listGraph.addEntries(test); 1312 if (test.size() < bestEntries.size()) { 1313 bestEntries = test; 1314 pref = true; 1315 } 1316 entries = new TreeSet(bestEntries); // do the sort 1317 } 1318 if (pref || forceDefault || entries.isEmpty()) { 1319 sb.append(newLine); 1320 sb.append("Preferred: "); 1321 sb.append(pref); 1322 sb.append(newLine); 1323 } 1324 // write the individual entries 1325 Iterator it = entries.iterator(); 1326 while (it.hasNext()) { 1327 PrefData entry = (PrefData) it.next(); 1328 sb.append(newLine); 1329 sb.append("Name: "); 1330 sb.append(entry.name); 1331 sb.append(newLine); 1332 sb.append("Preferred: "); 1333 sb.append(entry.preferred); 1334 sb.append(newLine); 1335 } 1336 // output the resulting list or JAR file or both 1337 String preferredList = sb.toString(); 1338 if (writer != null) { 1339 writer.print(preferredList); 1340 writer.flush(); 1341 } 1342 if (replaceJar) { 1343 buildJarFile(preferredList); 1344 } 1345 } 1346 1347 /** 1348 * Replace the first JAR file in the set of JARs with a new copy containing 1349 * the given preferred list. If the JAR contained a preferred list, it is 1350 * replaced. 1351 * 1352 * @param preferredList the new preferred list 1353 * @throws IOException if an error occurs updating the target JAR file. 1354 */ 1355 private void buildJarFile(String preferredList) throws IOException { 1356 // prepare to create a temp JAR file from the original 1357 File newJar = File.createTempFile("pref", "jar"); 1358 newJar.deleteOnExit(); 1359 JarInputStream ji = new JarInputStream(new FileInputStream(targetJar)); 1360 JarOutputStream jo = 1361 new JarOutputStream(new FileOutputStream(newJar)); 1362 JarEntry entry; 1363 byte[] ba; 1364 1365 // write the META-INF directory entry, probably not really necessary 1366 entry = new JarEntry("META-INF/"); 1367 jo.putNextEntry(entry); 1368 jo.closeEntry(); 1369 1370 // copy the original manifest entry to the new file 1371 entry = new JarEntry("META-INF/MANIFEST.MF"); 1372 jo.putNextEntry(entry); 1373 ji.getManifest().write(jo); 1374 jo.closeEntry(); 1375 1376 // write the new preferred list entry to the new file 1377 entry = new JarEntry("META-INF/PREFERRED.LIST"); 1378 jo.putNextEntry(entry); 1379 ba = preferredList.getBytes(); 1380 jo.write(ba, 0, ba.length); 1381 1382 // copy the remaining entries, discarding the old preferred list 1383 while ((entry = ji.getNextJarEntry()) != null) { 1384 if (entry.getName().equals("META-INF/PREFERRED.LIST")) { 1385 ji.closeEntry(); 1386 continue; 1387 } 1388 jo.putNextEntry(entry); 1389 ba = new byte[1000]; 1390 int size; 1391 while ((size = ji.read(ba, 0, 1000)) >= 0) { 1392 jo.write(ba, 0, size); 1393 } 1394 jo.closeEntry(); 1395 } 1396 ji.close(); 1397 jo.close(); 1398 1399 // copy the new JAR file over the old one 1400 FileInputStream fi = new FileInputStream(newJar); 1401 FileOutputStream fo = new FileOutputStream(targetJar); 1402 ba = new byte[1000]; 1403 int size; 1404 while ((size = fi.read(ba)) >= 0) { 1405 fo.write(ba, 0, size); 1406 } 1407 fi.close(); 1408 fo.close(); 1409 } 1410 1411 /** 1412 * The command line interface to the tool. Parses the command line arguments, 1413 * computes the dependency graph, and generates the preferred list. 1414 * 1415 * @param args the command line arguments 1416 */ 1417 public static void main(String[] args) { 1418 try { 1419 PreferredListGen dep = new PreferredListGen(args); 1420 dep.compute(); 1421 dep.generatePreferredList(null); 1422 System.exit(0); 1423 } catch (IllegalArgumentException e) { 1424 print("preflistgen.badarg", e.getMessage()); 1425 usage(); 1426 } catch (IOException e) { 1427 print("preflistgen.ioproblem", e.getMessage()); 1428 e.printStackTrace(); 1429 } catch (Error e){ 1430 print("preflistgen.error", e.getMessage()); 1431 e.printStackTrace(); 1432 } 1433 System.exit(1); 1434 } 1435 1436 /** 1437 * A representation of a class path, package path wildcard, or namespace 1438 * path wildcard preferred list entry. This class implements 1439 * <code>Comparable</code> to provide a primary sort key for the 1440 * ordering of class/path/namespace, and a secondary sort key which 1441 * is alphabetic. 1442 */ 1443 private class PrefData implements Comparable { 1444 1445 /** the preferred list entry name string */ 1446 final String name; 1447 1448 /** the preferred value for this entry */ 1449 final boolean preferred; 1450 1451 /** the sourceJar, used when merging two graphs */ 1452 final File sourceJar; 1453 1454 PrefData(String name, boolean preferred) { 1455 this(name, preferred , null); 1456 } 1457 1458 PrefData(String name, boolean preferred, File sourceJar) { 1459 this.name = name; 1460 this.preferred = preferred; 1461 this.sourceJar = sourceJar; 1462 } 1463 1464 public int compareTo(Object o) { 1465 String oPrefixed = ((PrefData) o).prefixedString(); 1466 return prefixedString().compareTo(oPrefixed); 1467 } 1468 1469 private String prefixedString() { 1470 if (name.endsWith(".class")) { 1471 return "a" + name; 1472 } else if (name.endsWith("/*")) { 1473 return "b" + name; 1474 } else { 1475 return "c" + name; 1476 } 1477 } 1478 } 1479 1480 /** 1481 * A representation of the graph of classes contained in the set of 1482 * JARs being analyzed. This class implements the search algorithm to 1483 * find the optimal (smallest) set of preferred list entries which 1484 * describes the preferred state of entries in the graph. Nodes in 1485 * the graph can be one of seven types: 1486 * <ul> 1487 * <li><code>PKG</code> nodes represent package wildcards 1488 * <li><code>NAMESPACE</code> nodes represent namespace wildcards 1489 * <li><code>DEFAULT</code> nodes represent the default preference. Only 1490 * the root node of the graph can be of this type. 1491 * <li><code>INHERIT</code> nodes inherit their preference values from 1492 * their parent nodes. For the case where there is no default 1493 * preferred value, the root of the graph will be of this type. 1494 * <li> <code>CLASS</code> leaf node representing a class 1495 * <li> <code>RESOURCE</code> leaf node representing a non-class resource 1496 * <li> <code>STALE</code> an entry which has been marked for deletion 1497 * </ul> 1498 * All non-terminal nodes represent a component in the package name 1499 * of a class. All leaf nodes represent classes or resources. 1500 */ 1501 private class Graph { 1502 1503 /** type value when non-leaf node inherits preference from its parent */ 1504 static final int INHERIT = 0; 1505 1506 /** type value for package wildcard nodes */ 1507 static final int PKG = 1; 1508 1509 /** type value for namespace wildcard nodes */ 1510 static final int NAMESPACE = 2; 1511 1512 /** type value for the default preference (root) node */ 1513 static final int DEFAULT = 3; 1514 1515 /** type value for a class node */ 1516 static final int CLASS = 4; 1517 1518 /** type value for a resource (non-class JAR entry) node */ 1519 static final int RESOURCE = 5; 1520 1521 /** type value for a stale node (marked for deletion) */ 1522 static final int STALE = 6; 1523 1524 /** the name of this node (e.g. com or sun, not com.sun) */ 1525 String name; 1526 1527 /** the set of child nodes of this node */ 1528 HashSet nodes; 1529 1530 /** the preferred state, only meaningful to leaf (class) nodes */ 1531 boolean preferred; 1532 1533 /** the parent of this node, or null for the root node */ 1534 Graph parent; 1535 1536 /** the type of the node */ 1537 int type; 1538 1539 /** the preferred value implied for child nodes */ 1540 Boolean impliedPref = null; 1541 1542 /** the source JAR causing creation of this node, or null */ 1543 File sourceJar; 1544 1545 /** 1546 * Create the root node of the graph, setting implied pref to TRUE 1547 */ 1548 Graph() { 1549 this.type = INHERIT; 1550 this.sourceJar = null; 1551 this.parent = null; 1552 this.preferred = true; 1553 this.nodes = new HashSet(); 1554 name = ""; 1555 impliedPref = TRUE; 1556 type = DEFAULT; 1557 } 1558 1559 /** 1560 * Create a node of the graph having the given name and parent. 1561 * If the name contains multiple '.' separated tokens, the name 1562 * is taken from the first token, and another node is constructed 1563 * using the remainder of the name and this node as the parent. 1564 * The final (leaf) node created is given a preferred value inherited 1565 * from its ancestors. 1566 * 1567 * @param parent the parent of this node, or <code>null</code> if 1568 * this is the root node. 1569 * @param name the (possibly dot-separated) name for this node (and 1570 * child nodes). 1571 * @param type the node type, must be CLASS or RESOURCE 1572 * @param sourceJar source of this node definition, or null defined 1573 * through the -api or -impl options 1574 */ 1575 Graph(Graph parent, String name, int type, File sourceJar) { 1576 this.type = INHERIT; 1577 this.nodes = new HashSet(); 1578 if (type != CLASS && type != RESOURCE) { 1579 throw new IllegalStateException("type must be CLASS or " 1580 + "RESOURCE"); 1581 } 1582 this.parent = parent; 1583 this.sourceJar = sourceJar; 1584 int firstDot = name.indexOf("."); 1585 if (firstDot < 0) { 1586 this.name = name; 1587 this.type = type; 1588 preferred = parent.childPref(); 1589 if (type == CLASS) { 1590 Graph outer = getOuter(); 1591 /* if outer == null, then either this node does not represent 1592 * a nested class, or it represents a nested class who's outer 1593 * class hasn't been loaded yet. In either case, inheriting the 1594 * parents preference is the right thing to do because when the 1595 * outer class get loaded later it will also inherit the parents 1596 * preference value. 1597 */ 1598 if (outer != null) { 1599 preferred = outer.preferred; 1600 } 1601 } 1602 } else { 1603 this.name = name.substring(0, firstDot); 1604 nodes.add(new Graph(this, 1605 name.substring(firstDot + 1), 1606 type, 1607 sourceJar)); 1608 } 1609 } 1610 1611 /** 1612 * Create a node of the graph having the given name, parent, type and 1613 * preferred value. If the name contains multiple '.' separated tokens, 1614 * the name is taken from the first token, and another node is 1615 * constructed using the remainder of the name and this node as the 1616 * parent. The type and preferred value are only set on the last node 1617 * created in the chain. 1618 * 1619 * @param parent the parent of this node, or <code>null</code> if 1620 * this is the root node. 1621 * @param name the (possibly dot-separated) name for this node (and 1622 * child nodes). 1623 * @param type the node type, must not be INHERIT 1624 * @param preferred the preferred state of this node 1625 * @param sourceJar source of this node definition, or null defined 1626 * through the -api or -impl options 1627 * 1628 * @throws IllegalStateException if type is INHERIT 1629 */ 1630 Graph(Graph parent, 1631 String name, 1632 int type, 1633 boolean preferred, 1634 File sourceJar) 1635 { 1636 this.type = INHERIT; 1637 this.preferred = true; 1638 this.nodes = new HashSet(); 1639 if (type == INHERIT) { 1640 throw new IllegalStateException("Cannot create a preference " 1641 + "node of type INHERIT"); 1642 } 1643 this.parent = parent; 1644 this.sourceJar = sourceJar; 1645 int firstDot = name.indexOf("."); 1646 if (firstDot < 0) { 1647 this.name = name; 1648 this.type = type; 1649 if (type == CLASS || type == RESOURCE) { 1650 this.preferred = preferred; 1651 } else { 1652 impliedPref = Boolean.valueOf(preferred); 1653 } 1654 } else { 1655 this.name = name.substring(0, firstDot); 1656 String childName = name.substring(firstDot + 1); 1657 nodes.add(new Graph(this, 1658 childName, 1659 type, 1660 preferred, 1661 sourceJar)); 1662 } 1663 } 1664 1665 /** 1666 * Determines whether <code>arg</code> represents a path reference to a 1667 * class, a resource, a package wildcard, or a namespace wild card, and 1668 * add the value, converted to a '.' separated name, to the graph with 1669 * the given preferred value and appropriate type. If <code>arg</code> 1670 * contains at least one '.' character and no '/' characters, assume it 1671 * is a class name and add the value to the graph with the given 1672 * preferred value and a type of <code>Graph.CLASS</code>. 1673 * 1674 * @param arg the name of the component to add to the graph 1675 * @param preferred the preferred value of the component 1676 * @throws IllegalArgumentException if <code>arg</code> is not 1677 * a recognized form. 1678 */ 1679 void initialize(String arg, boolean preferred, File sourceJar) { 1680 if (name.length() > 0) { 1681 throw new IllegalStateException("must initialize " 1682 + "only the root"); 1683 } 1684 if (arg.endsWith("/-")) { 1685 String pkgName = fileToClass(arg.substring(0, arg.length() - 2)); 1686 addWithPreference(pkgName, NAMESPACE, preferred, sourceJar); 1687 } else if (arg.endsWith("/*")) { 1688 String pkgName = 1689 fileToClass(arg.substring(0, arg.length() - 2)); 1690 addWithPreference(pkgName, PKG, preferred, sourceJar); 1691 } else if (arg.endsWith("/")) { 1692 String pkgName = 1693 fileToClass(arg.substring(0, arg.length() - 1)); 1694 addWithPreference(pkgName, PKG, preferred, sourceJar); 1695 } else if (arg.endsWith(".class")) { // class file reference 1696 String leafName = fileToClass(arg); 1697 addWithPreference(leafName, CLASS, preferred, sourceJar); 1698 } else if (arg.indexOf('/') != -1) { // resource file reference 1699 String leafName = fileToClass(arg); 1700 addWithPreference(leafName, RESOURCE, preferred, sourceJar); 1701 } else if (arg.indexOf('.') != -1) { // class name reference 1702 addWithPreference(arg, Graph.CLASS, preferred, sourceJar); 1703 } else { 1704 String msg = getString("preflistgen.badapi", arg); 1705 throw new IllegalArgumentException(msg); 1706 } 1707 } 1708 1709 /** 1710 * Add roots from this subtree of the graph to the given 1711 * collection of roots. If this is a CLASS node and this node 1712 * has a preferred value of <code>false</code> and was defined 1713 * by a command line option or was supplied by the first JAR file, 1714 * add this node to the collection. 1715 * 1716 * @param roots the collection to add to 1717 */ 1718 synchronized void addRoots(Collection roots) { 1719 if (type == CLASS && sourceJar == null) { 1720 String entryName = getFullName() + ".class"; 1721 if (!jarEntries.contains(entryName)) { 1722 System.err.println("Class entry " + entryName 1723 + " identified by the -" 1724 + (preferred ? "impl" : "api") 1725 + " option does not exist in any JAR file. " 1726 + "That entry has been discarded"); 1727 return; 1728 } 1729 } 1730 if (type == CLASS 1731 && !preferred 1732 && (sourceJar == null || sourceJar == targetJar)) 1733 { 1734 roots.add(fileToClass(getFullName())); 1735 } else { 1736 for (Iterator it = nodes.iterator();it.hasNext(); ) { 1737 Graph g = (Graph) it.next(); 1738 g.addRoots(roots); 1739 } 1740 } 1741 } 1742 1743 /** 1744 * For non-leaf nodes, 1745 * Reset the type and implied preference value for this node 1746 * and all child nodes recursively. If this is a leaf node, 1747 * do nothing. 1748 */ 1749 synchronized void reset() { 1750 if (type != CLASS && type != RESOURCE) { 1751 type = INHERIT; 1752 impliedPref = null; 1753 Iterator it = nodes.iterator(); 1754 while (it.hasNext()) { 1755 Graph g = (Graph) it.next(); 1756 g.reset(); 1757 } 1758 } 1759 } 1760 1761 /** 1762 * Return the implied preference as determined by the nearest 1763 * parent namespace wildcard, or the default preference if 1764 * there are no parent namespace wildcards. This method should 1765 * only be called indirectly as a side-effect of calling 1766 * <code>childPref</code>. 1767 * 1768 * @return the implied preferred value 1769 */ 1770 synchronized boolean impliedChildPref() { 1771 if (type == NAMESPACE || type == DEFAULT) { 1772 if (!(impliedPref instanceof Boolean)) { 1773 throw new IllegalStateException("impliedPref is null"); 1774 } 1775 return impliedPref.booleanValue(); 1776 } 1777 if (parent == null) { 1778 throw new IllegalStateException("default undefined in graph"); 1779 } 1780 return parent.impliedChildPref(); 1781 } 1782 1783 /** 1784 * Return the implied preference of the parent node if the parent is a 1785 * package wildcard. Otherwise, return the implied preference as 1786 * determined by the nearest parent namespace wildcard, or the default 1787 * preference if there are no parent namespace wildcards. 1788 * 1789 * @return the implied preferred value 1790 */ 1791 synchronized boolean childPref() { 1792 if (type == NAMESPACE || type == DEFAULT || type == PKG) { 1793 if (impliedPref == null) { 1794 throw new IllegalStateException("impliedPref is null"); 1795 } 1796 return impliedPref.booleanValue(); 1797 } 1798 if (parent == null) { 1799 throw new IllegalStateException("default undefined in graph"); 1800 } 1801 return parent.impliedChildPref(); 1802 } 1803 1804 /** 1805 * Set the default preferred value for the graph. This method 1806 * may only be called on the root node of the graph. 1807 * 1808 * @param value the default preferred value, which may be 1809 * <code>null</code> to indicate that no default is defined 1810 */ 1811 synchronized void setDefaultPref(boolean value) { 1812 if (name.length() > 0) { 1813 throw new IllegalStateException("must set default on root"); 1814 } 1815 type = DEFAULT; 1816 impliedPref = Boolean.valueOf(value); 1817 } 1818 1819 // inherit javadoc 1820 public String toString() { 1821 return "Graph[" + name + ", Parent = " + parent + "]"; 1822 } 1823 1824 /** 1825 * Add a child node to this node having the given name. the name 1826 * may have multiple '.' separated components, in which case a 1827 * hierarchy of child nodes will be added. Only child nodes which 1828 * do not already exist are created. The preferred value for 1829 * the child will be set based on the value inherited from its 1830 * ancestors. Nodes added by this method must always be leaf nodes. 1831 * 1832 * @param name the name of the child node to add 1833 * @param type the node type, must be CLASS or RESOURCE 1834 * @param sourceJar the JAR file causing this class or resource to be added 1835 */ 1836 synchronized void add(String name, int type, File sourceJar) { 1837 if (type != CLASS && type != RESOURCE) { 1838 throw new IllegalStateException("type must be CLASS or " 1839 + "RESOURCE"); 1840 } 1841 if (name == null) { // added previously - noop 1842 return; 1843 } 1844 String nodeName; 1845 String childName = null; 1846 int firstDot = name.indexOf("."); 1847 if (firstDot < 0) { 1848 nodeName = name; 1849 } else { 1850 nodeName = name.substring(0, firstDot); 1851 childName = name.substring(firstDot + 1); 1852 } 1853 Iterator it = nodes.iterator(); 1854 // search for a match to an existing child and recursively add 1855 while (it.hasNext()) { 1856 Graph g = (Graph) it.next(); 1857 if (g.name.equals(nodeName)) { 1858 g.add(childName, type, sourceJar); // noop if null 1859 return; 1860 } 1861 } 1862 // if here, name doesn't exist in collection of child nodes 1863 nodes.add(new Graph(this, name, type, sourceJar)); 1864 } 1865 1866 /** 1867 * Return <code>true</code> if the graph contains a node having 1868 * the given name. 1869 * 1870 * @param name the name of the node to test for 1871 * @return true if the node is found 1872 */ 1873 synchronized boolean contains(String name) { 1874 String nodeName; 1875 String childName = null; 1876 int firstDot = name.indexOf("."); 1877 if (firstDot < 0) { 1878 nodeName = name; 1879 } else { 1880 nodeName = name.substring(0, firstDot); 1881 childName = name.substring(firstDot + 1); 1882 } 1883 Iterator it = nodes.iterator(); 1884 // search for a match to an existing child and recursively add 1885 while (it.hasNext()) { 1886 Graph g = (Graph) it.next(); 1887 if (g.name.equals(nodeName)) { 1888 if (childName == null) { 1889 return true; 1890 } else { 1891 return g.contains(childName); 1892 } 1893 } 1894 } 1895 // if here, name doesn't exist in collection of child nodes 1896 return false; 1897 } 1898 1899 /** 1900 * Return the frozen state of the class node having the 1901 * given name. A node is frozen if it was created through 1902 * the <code>-api</code> or <code>-impl</code> options. 1903 * 1904 * @param name the dot-separate name 1905 * @return true if the node is frozen 1906 */ 1907 synchronized boolean isFrozen(String name) { 1908 String nodeName; 1909 String childName = null; 1910 int firstDot = name.indexOf("."); 1911 if (firstDot < 0) { 1912 nodeName = name; 1913 } else { 1914 nodeName = name.substring(0, firstDot); 1915 childName = name.substring(firstDot + 1); 1916 } 1917 Iterator it = nodes.iterator(); 1918 while (it.hasNext()) { 1919 Graph g = (Graph) it.next(); 1920 if (g.name.equals(nodeName)) { 1921 if (childName == null) { 1922 if (g.type != CLASS) { 1923 throw new IllegalStateException("Not a class: " 1924 + g.getFullName()); 1925 } 1926 return g.sourceJar == null; // set by -api/-impl 1927 } else { 1928 return g.isFrozen(childName); 1929 } 1930 } 1931 } 1932 // if here, name doesn't exist in collection of child nodes 1933 return false; 1934 } 1935 1936 1937 1938 /** 1939 * Add a child node to this node having the given name. the name may 1940 * have multiple '.' separated components, in which case a hierarchy of 1941 * child nodes will be added. Only child nodes which do not already 1942 * exist are created. The final node created will be assigned the given 1943 * type and preferred value. If the node already exists, new type and 1944 * preferred values will be assigned unless the original source JAR is 1945 * null, indicating node was created via the -api or -impl options. The 1946 * source JAR is not changed from it's original value. If the node 1947 * already exists, the original source JAR is non-null and is also not 1948 * the first JAR, this is a leaf node, and preferred values differ, a 1949 * warning message is written to System.err to identify the conflicting 1950 * definitions; in this case, the state of the node is set to 1951 * not-preferred. The first JAR is ignored in this case because the 1952 * correct value for the preferred state of these classes is not known 1953 * until the dependency analysis is done. Therefore in this special case 1954 * the preferred state is reset to the new state. 1955 * 1956 * @param name the name of the child node to add 1957 */ 1958 synchronized void addWithPreference(String name, 1959 int type, 1960 boolean preferred, 1961 File sourceJar) 1962 { 1963 if (type == INHERIT) { 1964 throw new IllegalStateException("Cannot add preference node " 1965 + "of type INHERIT"); 1966 } 1967 // allow type to be redefined - should probably do an analysis 1968 if (name == null) { 1969 if (this.sourceJar != null) { 1970 this.type = type; 1971 if (type == CLASS || type == RESOURCE) { 1972 if(this.preferred != preferred 1973 && this.sourceJar != targetJar) 1974 { 1975 print("preflistgen.prefconflict", 1976 getFullName(), 1977 this.sourceJar, 1978 sourceJar); 1979 this.preferred = false; 1980 } else { 1981 this.preferred = preferred; 1982 } 1983 } else { 1984 impliedPref = Boolean.valueOf(preferred); 1985 } 1986 } 1987 return; 1988 } 1989 String nodeName; 1990 String childName = null; 1991 int firstDot = name.indexOf("."); 1992 if (firstDot < 0) { 1993 nodeName = name; 1994 } else { 1995 nodeName = name.substring(0, firstDot); 1996 childName = name.substring(firstDot + 1); 1997 } 1998 Iterator it = nodes.iterator(); 1999 // search for a match to an existing child and recursively add 2000 while (it.hasNext()) { 2001 Graph g = (Graph) it.next(); 2002 if (g.name.equals(nodeName)) { 2003 g.addWithPreference(childName, type, preferred, sourceJar); 2004 return; 2005 } 2006 } 2007 // if here, name doesn't exist in collection of child nodes 2008 nodes.add(new Graph(this, name, type, preferred, sourceJar)); 2009 } 2010 2011 /** 2012 * Set the preferred state of the leaf (class) child node having the 2013 * given '.' qualified name to the given value. If <code>name</code> 2014 * is <code>null</code>, set the preferred value for this node. 2015 * If the target name does not exist in the graph, this method 2016 * is a noop. 2017 * <p> 2018 * This method enforces the requirement that the ultimate target 2019 * node for the call must be a leaf node. 2020 * 2021 * @param name the name of the child node to set, or <code>null</code> 2022 * to represent this node 2023 * @param value the preferred value to set. 2024 * @param force if true, ignore frozen state 2025 * @return true if the preferred value matches <code>value</code> 2026 * or is changed to <code>value</code> or if the named 2027 * node does not exist in the graph. Returns 2028 * <code>false</code> if the values don't match and 2029 * the node is frozen and force is false. 2030 */ 2031 synchronized boolean setPreferred(String name, boolean value, boolean force) { 2032 if (name == null) { 2033 if (type != CLASS && type != RESOURCE) { 2034 throw new IllegalStateException("only leaf node preferred" 2035 + " state may be set"); 2036 } 2037 if (sourceJar != null || force) { // frozen if defined by -api or -impl 2038 preferred = value; 2039 } 2040 return preferred == value; 2041 } 2042 String childName = name; 2043 String targetName = null; 2044 int firstDot = name.indexOf("."); 2045 if (firstDot >= 0) { // must have reached the leaf node 2046 childName = name.substring(0, firstDot); 2047 targetName = name.substring(firstDot + 1); 2048 } 2049 Iterator it = nodes.iterator(); 2050 while (it.hasNext()) { 2051 Graph g = (Graph) it.next(); 2052 if (g.name.equals(childName)) { 2053 return g.setPreferred(targetName, value, force); 2054 } 2055 } 2056 // if here, name doesn't exist in the graph - noop 2057 return true; 2058 } 2059 2060 /** 2061 * Count the number of child leaf nodes which 2062 * have a preferred state matching the given value. If this 2063 * is a leaf node, return 1 if the value matches the preferred 2064 * state of this node. 2065 * 2066 * @param value the preferred value to search for 2067 * @return the number of leaf nodes having the given value 2068 */ 2069 synchronized int countPreferred(boolean value) { 2070 if (type == CLASS || type == RESOURCE) { // leaf 2071 return (value == preferred) ? 1 : 0; 2072 } else { 2073 int total = 0; 2074 Iterator it = nodes.iterator(); 2075 while (it.hasNext()) { 2076 Graph g = (Graph) it.next(); 2077 total += g.countPreferred(value); 2078 } 2079 return total; 2080 } 2081 } 2082 2083 /** 2084 * Count the number of child leaf (class) nodes which have a preferred 2085 * state which differs from the value implied by * its ancestors. If 2086 * this is a leaf node and is also a nested class, return 1 if the outer 2087 * class has a different preferred state than this class. If this is a 2088 * leaf node and is not a nested class, return 1 if the implied value 2089 * differs the preferred state for this node. If no value is implied 2090 * by the ancestors, this is interpreted as a mismatch. 2091 * 2092 * @return the number of leaf nodes having preferred values which differ 2093 * from that implied by its ancestors 2094 */ 2095 synchronized int countImpliedFailures() { 2096 if (type == CLASS || type == RESOURCE) { // leaf 2097 if (type == CLASS) { 2098 Graph outer = getOuter(); 2099 if (outer != null) { 2100 return (outer.preferred == preferred) ? 0 : 1; 2101 } 2102 } 2103 return (parent.childPref() != preferred) ? 1 : 0; 2104 } else { 2105 int total = 0; 2106 Iterator it = nodes.iterator(); 2107 while (it.hasNext()) { 2108 Graph g = (Graph) it.next(); 2109 total += g.countImpliedFailures(); 2110 } 2111 return total; 2112 } 2113 } 2114 2115 /** 2116 * Count the number of child leaf nodes. 2117 * 2118 * @return the number of leaf nodes which have this node 2119 * as an ancestor. 2120 */ 2121 synchronized int countLeafNodes() { 2122 int total = 0; 2123 Iterator it = nodes.iterator(); 2124 while (it.hasNext()) { 2125 Graph g = (Graph) it.next(); 2126 if (g.type == CLASS || g.type == RESOURCE) { // leaf node 2127 total++; 2128 } else { 2129 total += g.countLeafNodes(); 2130 } 2131 } 2132 return total; 2133 } 2134 2135 /** 2136 * Return the full name of this node expressed as a relative 2137 * path. No suffixes (".class", "/-", "/*") are appended. 2138 * 2139 * @return the full path name representing this node 2140 */ 2141 String getFullName() { 2142 if (name.length() == 0) { 2143 return ""; 2144 } 2145 String prefix = ""; 2146 if (parent != null) { 2147 prefix = parent.getFullName(); 2148 } 2149 if (prefix.length() > 0) { 2150 prefix += "/"; 2151 } 2152 return prefix + name; 2153 } 2154 2155 /** 2156 * If this node is of type CLASS and is an inner class, mark this node 2157 * as STALE if the outer class has the same preferred value. Print a 2158 * warning to <code>System.err</code> if they do not have the same 2159 * value. If there is no outer class corresponding to the inner class, 2160 * this node is retained. Call this method on all children. 2161 */ 2162 synchronized void markStaleInnerClasses() { 2163 if (type == CLASS) { 2164 Graph outer = getOuter(); 2165 if (outer != null) { 2166 if (outer.preferred == preferred) { 2167 type = STALE; 2168 } else { 2169 print("preflistgen.innerwarn", outer.name, name); 2170 } 2171 } 2172 } else { 2173 for (Iterator it = nodes.iterator(); it.hasNext(); ) { 2174 Graph g = (Graph) it.next(); 2175 g.markStaleInnerClasses(); 2176 } 2177 } 2178 } 2179 2180 /** 2181 * Return the node for the outer class for this class if this is a nested 2182 * class. 2183 * 2184 * @return the graph for the outer class, or <code>null</code> if this is 2185 * not a nested class, or if the outer class is not in the graph 2186 */ 2187 final synchronized Graph getOuter() { 2188 if (type != CLASS) { 2189 throw new IllegalStateException("Attempt to get outer of a non-class"); 2190 } 2191 Graph g = null; 2192 int lastDollar = name.lastIndexOf('$'); 2193 if (lastDollar > 0) { 2194 String outerName = name.substring(0, lastDollar); 2195 g = parent.getChild(outerName); 2196 } 2197 return g; 2198 } 2199 2200 /** 2201 * Recursively remove child nodes of type STALE. 2202 */ 2203 synchronized void deleteStaleNodes() { 2204 for (Iterator it = nodes.iterator(); it.hasNext(); ) { 2205 Graph g = (Graph) it.next(); 2206 if (g.type == STALE) { 2207 it.remove(); 2208 } else { 2209 g.deleteStaleNodes(); 2210 } 2211 } 2212 } 2213 2214 /** 2215 * Get the child node having the given name. 2216 * 2217 * @param name the child name 2218 * @return the child <code>Graph</code> object, or null if the 2219 * no child named <code>name</code> exists 2220 */ 2221 Graph getChild(String name) { 2222 for (Iterator it = nodes.iterator(); it.hasNext(); ) { 2223 Graph g = (Graph) it.next(); 2224 if (g.name.equals(name)) { 2225 return g; 2226 } 2227 } 2228 return null; 2229 } 2230 2231 /** 2232 * Test whether all of the immediate child nodes of this 2233 * node are leaf (class) nodes. 2234 * 2235 * @return <code>true</code> if all a the nodes are leaf nodes 2236 */ 2237 synchronized boolean containsLeavesOnly() { 2238 Iterator it = nodes.iterator(); 2239 while (it.hasNext()) { 2240 Graph g = (Graph) it.next(); 2241 if (g.type != CLASS && g.type != RESOURCE) { // must be subpkg 2242 return false; 2243 } 2244 } 2245 return true; 2246 } 2247 2248 synchronized void addLeaves(Collection leaves) { 2249 if (type == CLASS) { 2250 leaves.add(new PrefData(getFullName() + ".class", 2251 preferred, 2252 sourceJar)); 2253 return; 2254 } 2255 if (type == RESOURCE) { 2256 leaves.add(new PrefData(getFullName(), preferred, sourceJar)); 2257 return; 2258 } 2259 Iterator it = nodes.iterator(); 2260 while (it.hasNext()) { 2261 Graph g = (Graph) it.next(); 2262 g.addLeaves(leaves); 2263 } 2264 return; 2265 } 2266 2267 synchronized void merge(Graph g) { 2268 Collection leaves = new HashSet(); 2269 g.addLeaves(leaves); 2270 Iterator it = leaves.iterator(); 2271 while (it.hasNext()) { 2272 PrefData data = (PrefData) it.next(); 2273 int type = ((data.name.endsWith(".class") ? CLASS : RESOURCE)); 2274 addWithPreference(fileToClass(data.name), 2275 type, 2276 data.preferred, 2277 data.sourceJar); 2278 } 2279 } 2280 2281 /** 2282 * Add preferred list entries (represented by <code>PrefData</code> 2283 * objects) to the given <code>Collection</code>. One of the following 2284 * cases is handled: 2285 * <ul> 2286 * <li>if this is the root node, then <code>addEntries</code> is 2287 * called on each of the child nodes. Otherwise: 2288 * <li>if the preferred state of all leaf nodes (including possibly 2289 * this one) matches the implied state provided by the ancestors, 2290 * then return without adding any entries. Otherwise: 2291 * <li>if this is a leaf node and its preferred state does not 2292 * match the state implied by its parent, add a class style 2293 * entry to the collection and return. Otherwise: 2294 * <li>if this is not a leaf node, and there is a single child 2295 * which is also not a leaf node, then this node represents 2296 * an intermediate component in a package name. Call 2297 * <code>addEntries</code> on the child to generate a more 2298 * qualified entry. Otherwise: 2299 * <li>if this is not a leaf node and all of the child leaf nodes 2300 * have the same preferred value, then make this a package 2301 * wildcard having that value (if all children are leaves) 2302 * or make this a namespace wildcard having that value (if 2303 * some children are not leaves). Otherwise: 2304 * <li>try all variations of package/namespace wildcards, as well 2305 * as no wildcard setting, for this node. Add an entry 2306 * to the collection giving the optimal (minimal) result, then 2307 * call <code>addEntries</code> on all children. 2308 * </ul> 2309 * 2310 */ 2311 synchronized void addEntries(Collection entries) { 2312 if (parent == null) { 2313 Iterator it = nodes.iterator(); 2314 while (it.hasNext()) { 2315 Graph g = (Graph) it.next(); 2316 g.addEntries(entries); 2317 } 2318 return; 2319 } 2320 reset(); // set all non-leaf nodes to type = INHERIT 2321 if (countImpliedFailures() == 0) { 2322 return; 2323 } 2324 if (type == CLASS) { // entry failed previous count test 2325 entries.add(new PrefData(getFullName() + ".class", preferred)); 2326 return; 2327 } 2328 if (type == RESOURCE) { // entry failed previous count test 2329 entries.add(new PrefData(getFullName(), preferred)); 2330 return; 2331 } 2332 // special case - find max qualified variant of pkg name 2333 if (nodes.size() == 1) { 2334 Iterator it = nodes.iterator(); 2335 if (! it.hasNext()) { 2336 throw new IllegalStateException("Inconsistent iterator"); 2337 } 2338 Graph g = (Graph) it.next(); 2339 if (g.type != CLASS && g.type != RESOURCE) { // must be subpkg 2340 g.addEntries(entries); 2341 return; 2342 } 2343 } 2344 /* 2345 * if here, some children have actual preferences which don't match 2346 * their implied preferences. Check whether all children have 2347 * the same preference value, and if so, make this node a 2348 * wildcard package (if all immediate children are leaves) 2349 * or a wildcard namespace otherwise (if at least one child 2350 * is a non-leaf) 2351 */ 2352 boolean gotMatch = false; 2353 boolean matchValue = false; 2354 int leafCount = countLeafNodes(); 2355 if (leafCount == countPreferred(true)) { 2356 gotMatch = true; 2357 matchValue = true; 2358 } else if (leafCount == countPreferred(false)) { 2359 gotMatch = true; 2360 matchValue = false; 2361 } 2362 if (gotMatch) { 2363 if (containsLeavesOnly()) { 2364 entries.add(new PrefData(getFullName() + "/*", matchValue)); 2365 } else { 2366 entries.add(new PrefData(getFullName() + "/-", matchValue)); 2367 } 2368 return; 2369 } 2370 /* 2371 * if here, children have a mix of preferred values. Find 2372 * which variation of wildcard type (if any) to assign to 2373 * this node to optimize the entries generated by children 2374 */ 2375 int lowestValue = countEntries(INHERIT, null); 2376 int bestType = INHERIT; 2377 Boolean bestImpliedPref = null; 2378 // do PKG before NAMESPACE so 'leaf package' always get pkg wildcard 2379 int count = countEntries(PKG, TRUE); 2380 if (count < lowestValue) { 2381 lowestValue = count; 2382 bestType = PKG; 2383 bestImpliedPref = TRUE; 2384 } 2385 2386 count = countEntries(PKG, FALSE); 2387 if (count < lowestValue) { 2388 lowestValue = count; 2389 bestType = PKG; 2390 bestImpliedPref = FALSE; 2391 } 2392 2393 count = countEntries(NAMESPACE, TRUE); 2394 if (count < lowestValue) { 2395 lowestValue = count; 2396 bestType = NAMESPACE; 2397 bestImpliedPref = TRUE; 2398 } 2399 2400 count = countEntries(NAMESPACE, FALSE); 2401 if (count < lowestValue) { 2402 lowestValue = count; 2403 bestType = NAMESPACE; 2404 bestImpliedPref = FALSE; 2405 } 2406 2407 type = bestType; 2408 impliedPref = bestImpliedPref; 2409 if (type == NAMESPACE) { 2410 entries.add(new PrefData(getFullName() + "/-", 2411 impliedPref.booleanValue())); 2412 } 2413 if (type == PKG) { 2414 entries.add(new PrefData(getFullName() + "/*", 2415 impliedPref.booleanValue())); 2416 } 2417 for (Iterator it = nodes.iterator(); it.hasNext(); ) { 2418 Graph g = (Graph) it.next(); 2419 g.addEntries(entries); 2420 } 2421 } 2422 2423 /** 2424 * Count the number of entries which would be created 2425 * by this node for the given node type and preference. The 2426 * <code>type</code> and <code>impliedPref</code> class attributes 2427 * are side-affected by this method. 2428 * 2429 * @param type the node type to assign 2430 * @param pref the node's implied preference value 2431 * @return the number of entries generated by children 2432 */ 2433 synchronized int countEntries(int type, Boolean pref) { 2434 HashSet test = new HashSet(); 2435 this.type = type; 2436 impliedPref = pref; 2437 for (Iterator it = nodes.iterator(); it.hasNext(); ) { 2438 Graph g = (Graph) it.next(); 2439 g.addEntries(test); 2440 } 2441 return test.size(); 2442 } 2443 } 2444 }