View Javadoc
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 }