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  
19  package org.apache.river.tool;
20  
21  import java.io.BufferedInputStream;
22  import java.io.BufferedReader;
23  import java.io.ByteArrayInputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.InputStreamReader;
30  import java.io.PrintStream;
31  import java.io.Reader;
32  import java.lang.reflect.Array;
33  import java.lang.reflect.Constructor;
34  import java.lang.reflect.InvocationTargetException;
35  import java.lang.reflect.Method;
36  import java.net.MalformedURLException;
37  import java.net.URL;
38  import java.net.URLClassLoader;
39  import java.text.MessageFormat;
40  import java.util.Arrays;
41  import java.util.Enumeration;
42  import java.util.MissingResourceException;
43  import java.util.Properties;
44  import java.util.ResourceBundle;
45  import java.util.Set;
46  import java.util.StringTokenizer;
47  import net.jini.config.Configuration;
48  import net.jini.config.ConfigurationException;
49  import net.jini.config.ConfigurationFile;
50  import net.jini.config.ConfigurationProvider;
51  import org.apache.river.api.net.RFC3986URLClassLoader;
52  
53  /**
54   * Checks the format of the source for a {@link ConfigurationFile}. The source
55   * is specified with either a file, URL, or standard input, as well as with
56   * override options. The checks include syntax and static type checking, and
57   * require access to any application types mentioned in the source. <p>
58   *
59   * The following items are discussed below:
60   *
61   * <ul>
62   * <li> <a href="#entry_desc">Entry description files</a>
63   * <li> {@linkplain #main Command line options}
64   * <li> <a href="#examples">Examples for running CheckConfigurationFile</a>
65   * </ul> <p>
66   *
67   * <h3><a name="entry_desc">
68   * Entry description files
69   * </a></h3>
70   *
71   * Checking of the source can be controlled by specifying one or more entry
72   * description files, each listing the names and types of entries that are
73   * allowed to appear in the source. Each entry description file is treated as a
74   * {@link Properties} source file, where each key is the fully qualified name
75   * of an entry (<code><i>component</i>.<i>name</i></code>) and each value
76   * specifies the expected type for that entry. Types should be specified in
77   * normal source code format, except that whitespace is not permitted between
78   * tokens. Types in the <code>java.lang</code> package may be unqualified, but
79   * fully qualified names must be used for other types (<code>import</code>
80   * statements are not supported).  If any entry description files are supplied,
81   * then any public entry that appears in the source being checked, whose fully
82   * qualified name does not appear in any entry description file, or whose
83   * actual type is not assignable to the expected type, is treated as an
84   * error. <p>
85   *
86   * Entry description files for all of the JGDMS release services and utilities
87   * are provided in the <code>configentry</code> subdirectory beneath the
88   * top-level directory of the JGDMS release installation. <p>
89   *
90   * Here is a sample entry description file:
91   *
92   * <blockquote>
93   * <pre>
94   * comp.foo Integer[]
95   * comp.bar net.jini.core.constraint.MethodConstraints
96   * comp.baz long
97   * </pre>
98   * </blockquote>
99   *
100  * Here is an associated sample configuration file:
101  *
102  * <blockquote>
103  * <pre>
104  * import net.jini.constraint.*;
105  * import net.jini.core.constraint.*;
106  * comp {
107  *     foo = new Integer[] { Integer.valueOf(3) };
108  *     bar = new BasicMethodConstraints(
109  *               new InvocationConstraints(Integrity.YES, null));
110  *     baz = 33L;
111  * }
112  * </pre>
113  * </blockquote> <p>
114  *
115  * <a name="examples"></a>
116  * <h3>Examples for running CheckConfigurationFile</h3>
117  *
118  * This utility can be run from the {@linkplain #main command line}, or by
119  * calling the {@link #check(String, ClassLoader, String[], String,
120  * PrintStream)} or {@link #check(ConfigurationFile, Properties, ClassLoader,
121  * PrintStream)} methods. <p>
122  *
123  * An example command line usage is:
124  *
125  * <blockquote>
126  * <pre>
127  * java -jar <var><b>install_dir</b></var>/lib/checkconfigurationfile.jar \
128  *      -cp <var><b>install_dir</b></var>/lib/norm.jar:<var><b>install_dir</b></var>/lib/jsk-platform.jar \
129  *      -entries <var><b>install_dir</b></var>/configentry/norm-transient \
130  *      <var><b>your-norm.config</b></var>
131  * </pre>
132  * </blockquote>
133  * 
134  * where <var><b>install_dir</b></var> is the directory where the JGDMS release
135  * is installed, and <var><b>your-norm.config</b></var> is a configuration
136  * source file intended for use with the transient <code>
137  * org.apache.river.norm Norm </code> service implementation. This command will print out
138  * any problems that it detects in the configuration file, including entries
139  * that are not recognized or have the wrong type for the Norm service.
140  *
141  * @author Sun Microsystems, Inc.
142  * @see ConfigurationFile
143  * @since 2.0
144  */
145 public class CheckConfigurationFile  {
146 
147     /** The primitive types */
148     private static final Class[] primitives = {
149 	Boolean.TYPE, Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE,
150 	Long.TYPE, Float.TYPE, Double.TYPE
151     };
152 
153     private static final String RESOURCE =
154 	"META-INF/services/" + Configuration.class.getName();
155 
156     private static ResourceBundle resources;
157     private static boolean resinit = false;
158 
159     /* This class is not instantiable */
160     private CheckConfigurationFile() { }
161 
162     /**
163      * Command line interface for checking the format of source and override
164      * options for a {@link ConfigurationFile}, printing messages to
165      * <code>System.err</code> for any errors found. If errors are found,
166      * continues to check the rest of the source and overrides, and then calls
167      * <code>System.exit</code> with a non-<code>zero</code> argument. <p>
168      *
169      * The command line arguments are:
170      *
171      * <pre>
172      * [ -cp <var><b>classpath</b></var> ] [ -entries <var><b>entrydescs</b></var> ] <var><b>location</b></var> [ <var><b>option</b></var>... ]
173      * </pre>
174      * or
175      * <pre>
176      * [ -cp <var><b>classpath</b></var> ] [ -entries <var><b>entrydescs</b></var> ] -stdin [ <var><b>location</b></var> [ <var><b>option</b></var>... ] ]
177      * </pre>
178      * or
179      * <pre>
180      * -help
181      * </pre>
182      *
183      * If the only argument is <code>-help</code>, a usage message is
184      * printed. <p>
185      *
186      * The <var><b>classpath</b></var> value for the <code>-cp</code> option
187      * specifies one or more directories and zip/JAR files, separated by the
188      * {@linkplain File#pathSeparatorChar path separator character}, where the
189      * application classes are located.  A class loader that loads classes from
190      * this path will be created, with the extension class loader as its
191      * parent. If this option is not specified, the system class loader is used
192      * instead. <p>
193      *
194      * The <var><b>entrydescs</b></var> value for the <code>-entries</code>
195      * option specifies one or more entry description files, separated by the
196      * path separator character. <p>
197      *
198      * The <var><b>location</b></var> argument specifies the source file to be
199      * checked. If the <code>-stdin</code> option is used, then the actual
200      * source data will be read from standard input, and any
201      * <var><b>location</b></var> argument is simply used for identification
202      * purposes in error messages. <p>
203      *
204      * The remaining arguments specify any entry override values that should be
205      * passed to the <code>ConfigurationFile</code> constructor. <p>
206      *
207      * The class loader obtained above is used to resolve all expected types
208      * specified in the entry description files, and to obtain the
209      * configuration provider. The configuration provider class is found from
210      * the class loader in the same manner as specified by {@link
211      * ConfigurationProvider}.  The resulting class must be {@link
212      * ConfigurationFile} or a subclass; if it is a subclass, it must have a
213      * public constructor with three parameters of type: {@link Reader},
214      * <code>String[]</code>, and {@link ClassLoader}. An instance of the
215      * provider is created by passing that constructor a <code>Reader</code>
216      * for the source file to be checked, the location and entry override
217      * values, and the class loader.
218      * @param args array of string arguments.
219      */
220     public static void main(String[] args) {
221 	if (args.length == 0) {
222 	    usage();
223 	} else if (args.length == 1 && "-help".equals(args[0])) {
224 	    print(System.err, "checkconfig.usage", File.pathSeparator);
225 	    return;
226 	}
227 	String classPath = null;
228 	String entriesPath = null;
229 	boolean stdin = false;
230 	int i = 0;
231 	while (i < args.length) {
232 	    if ("-cp".equals(args[i])) {
233 		if (args.length < i + 2) {
234 		    usage();
235 		}
236 		classPath = args[i + 1];
237 		i += 2;
238 	    } else if ("-entries".equals(args[i])) {
239 		if (args.length < i + 2) {
240 		    usage();
241 		}
242 		entriesPath = args[i + 1];
243 		i += 2;
244 	    } else if ("-stdin".equals(args[i])) {
245 		stdin = true;
246 		i++;
247 	    } else {
248 		break;
249 	    }
250 	}
251 	if (!stdin && i == args.length) {
252 	    usage();
253 	}
254 	ClassLoader loader = ClassLoader.getSystemClassLoader();
255 	if (classPath != null) {
256 	    loader = loader.getParent();
257 	}
258 	String[] configOptions = new String[args.length - i];
259 	System.arraycopy(args, i, configOptions, 0, configOptions.length);
260 	boolean ok = check(classPath, loader, stdin, configOptions,
261 			   entriesPath, System.err);
262 	if (!ok) {
263 	    System.exit(1);
264 	}
265     }
266 
267     private static void usage() {
268 	print(System.err, "checkconfig.usage", File.pathSeparator);
269 	System.exit(1);
270     }
271 
272     /**
273      * Returns information about the specified permitted entries, returning
274      * null if there is a problem loading properties from the files.
275      */
276     private static Properties getEntries(String files, PrintStream err) {
277 	Properties entries = new Properties();
278 	StringTokenizer tokens =
279 	    new StringTokenizer(files, File.pathSeparator);
280 	while (tokens.hasMoreTokens()) {
281 	    String file = tokens.nextToken();
282 	    InputStream in = null;
283 	    try {
284 		in = new BufferedInputStream(new FileInputStream(file));
285 		entries.load(in);
286 	    } catch (FileNotFoundException e) {
287 		print(err, "checkconfig.notfound", file);
288 		return null;
289 	    } catch (Throwable t) {
290 		print(err, "checkconfig.read.err",
291 		      new String[]{file, t.getClass().getName(),
292 				   t.getLocalizedMessage()});
293 		t.printStackTrace(err);
294 		return null;
295 	    } finally {
296 		if (in != null) {
297 		    try {
298 			in.close();
299 		    } catch (IOException e) {
300 		    }
301 		}
302 	    }
303 	}
304 	return entries;
305     }
306 
307     /**
308      * Checks the format of a configuration source file. Returns
309      * <code>true</code> if there are no errors, and <code>false</code>
310      * otherwise. <p>
311      *
312      * The <code>classPath</code> argument specifies one or more directories
313      * and zip/JAR files, separated by the {@linkplain File#pathSeparatorChar
314      * path separator character}, where the application classes are located.  A
315      * class loader that loads classes from this path will be created, with
316      * <code>loader</code> as its parent. The <code>ConfigurationFile</code> is
317      * created with this class loader, and all expected types specified in
318      * entry description files are resolved in this class loader. If
319      * <code>classPath</code> is <code>null</code>, then <code>loader</code> is
320      * used instead. <p>
321      *
322      * The class loader is used to resolve all expected types specified in the
323      * entry description files, and to obtain the configuration provider. The
324      * configuration provider class is found from the class loader in the same
325      * manner as specified by {@link ConfigurationProvider}.  The resulting
326      * class must be {@link ConfigurationFile} or a subclass; if it is a
327      * subclass, it must have a public constructor with three parameters of
328      * type: {@link Reader}, <code>String[]</code>, and {@link
329      * ClassLoader}. An instance of the provider is created by passing that
330      * constructor a <code>Reader</code> for the source file to be checked,
331      * the location and entry override values, and the class loader.
332      *
333      * @param classPath the search path for application classes, or
334      * <code>null</code> to use the specified class loader
335      * @param loader the parent class loader to use for application classes if
336      * <code>classPath</code> is not <code>null</code>, otherwise the class
337      * loader to use for resolving application classes
338      * @param configOptions the configuration source file to check, plus any
339      * entry overrides
340      * @param entriesPath one or more entry description files, separated by the
341      * path separator character, or <code>null</code>
342      * @param err the stream to use for printing errors
343      * @return <code>true</code> if there are no errors, <code>false</code>
344      * otherwise
345      * @throws NullPointerException if <code>loader</code>,
346      * <code>configOptions</code>, or <code>err</code> is <code>null</code>
347      * @see #check(ConfigurationFile, Properties, ClassLoader, PrintStream)
348      */
349     public static boolean check(String classPath,
350 				ClassLoader loader,
351 				String[] configOptions,
352 				String entriesPath,
353 				PrintStream err)
354     {
355 	if (loader == null || configOptions == null || err == null) {
356 	    throw new NullPointerException();
357 	}
358 	return check(classPath, loader, false, configOptions, entriesPath,
359 		     err);
360     }
361 
362     /**
363      * Checks the format of a configuration source file, using classes loaded
364      * from classPath and loader, requiring entries to match the names and
365      * values from entriesPath (unless entriesPath is null).  If stdin is
366      * true, reads the configuration source from standard input.
367      */
368     private static boolean check(String classPath,
369 				 ClassLoader loader,
370 				 boolean stdin,
371 				 String[] configOptions,
372 				 String entriesPath,
373 				 PrintStream err)
374     {
375 	if (classPath != null) {
376 	    StringTokenizer st = new StringTokenizer(classPath,
377 						     File.pathSeparator);
378 	    URL[] urls = new URL[st.countTokens()];
379 	    for (int i = 0; st.hasMoreTokens(); i++) {
380 		String elt = st.nextToken();
381 		try {
382 		    urls[i] = new File(elt).toURI().toURL();
383 		} catch (MalformedURLException e) {
384 		    print(err, "checkconfig.classpath", elt);
385 		    return false;
386 		}
387 	    }
388 	    loader = RFC3986URLClassLoader.newInstance(urls, loader);
389 	}
390 	Properties entries = null;
391 	if (entriesPath != null) {
392 	    entries = getEntries(entriesPath, err);
393 	    if (entries == null) {
394 		return false;
395 	    }
396 	}
397 	String location;
398 	if (configOptions.length == 0) {
399 	    location = "(stdin)";
400 	} else {
401 	    configOptions = (String[]) configOptions.clone();
402 	    location = configOptions[0];
403 	    configOptions[0] = "-";
404 	}
405 	Constructor configCons = getProviderConstructor(loader, err);
406 	if (configCons == null) {
407 	    return false;
408 	}
409 	try {
410 	    InputStream in;
411 	    if (stdin) {
412 		in = System.in;
413 	    } else if ("-".equals(location)) {
414 		in = new ByteArrayInputStream(new byte[0]);
415 	    } else {
416 		try {
417 		    URL url = new URL(location);
418 		    in = url.openStream();
419 		} catch (MalformedURLException e) {
420 		    in = new FileInputStream(location);
421 		}
422 	    }
423 	    Object config;
424 	    try {
425 		config = configCons.newInstance(
426 				  new Object[]{new InputStreamReader(in),
427 					       configOptions,
428 					       loader});
429 	    } finally {
430 		if (!stdin) {
431 		    try {
432 			in.close();
433 		    } catch (IOException e) {
434 		    }
435 		}
436 	    }
437 	    return check(config, entries, loader, err);
438 	} catch (FileNotFoundException e) {
439 	    print(err, "checkconfig.notfound", location);
440 	} catch (Throwable t) {
441 	    print(err, "checkconfig.read", location, t);
442 	}
443 	return false;
444     }
445 
446     /**
447      * Returns the (Reader, String[], ClassLoader) constructor for the
448      * resource-specified configuration provider in loader.
449      */
450     private static Constructor getProviderConstructor(ClassLoader loader,
451 						      PrintStream err)
452     {
453 	URL resource = null;
454 	try {
455 	    for (Enumeration providers = loader.getResources(RESOURCE);
456 		 providers.hasMoreElements(); )
457 	    {
458 		resource = (URL) providers.nextElement();
459 	    }
460 	} catch (IOException e) {
461 	    print(err, "checkconfig.resources", "", e);
462 	}
463 	String classname = (resource == null ?
464 			    ConfigurationFile.class.getName() :
465 			    getProviderName(resource, err));
466 	if (classname == null) {
467 	    return null;
468 	}
469 	try {
470 	    Class provider = Class.forName(classname, false, loader);
471 	    try {
472 		if (!Class.forName(ConfigurationFile.class.getName(),
473 				   false, loader).isAssignableFrom(provider))
474 		{
475 		    print(err, "checkconfig.notsubclass", classname);
476 		    return null;
477 		}
478 		return provider.getConstructor(new Class[]{Reader.class,
479 							   String[].class,
480 							   ClassLoader.class});
481 	    } catch (ClassNotFoundException e) {
482 		print(err, "checkconfig.notsubclass", classname);
483 	    } catch (NoSuchMethodException e) {
484 		print(err, "checkconfig.noconstructor", classname);
485 	    }
486 	} catch (ClassNotFoundException e) {
487 	    print(err, "checkconfig.noprovider", classname);
488 	} catch (Throwable t) {
489 	    print(err, "checkconfig.provider", classname, t);
490 	}
491 	return null;
492     }
493 
494     /**
495      * Returns the configuration provider class name specified in the contents
496      * of the URL.
497      */
498     private static String getProviderName(URL url, PrintStream err) {
499 	InputStream in = null;
500 	try {
501 	    in = url.openStream();
502 	    BufferedReader reader =
503 		new BufferedReader(new InputStreamReader(in, "utf-8"));
504 	    String result = null;
505 	    while (true) {
506 		String line = reader.readLine();
507 		if (line == null) {
508 		    break;
509 		}
510 		int commentPos = line.indexOf('#');
511 		if (commentPos >= 0) {
512 		    line = line.substring(0, commentPos);
513 		}
514 		line = line.trim();
515 		int len = line.length();
516 		if (len != 0) {
517 		    if (result != null) {
518 			print(err, "checkconfig.multiproviders",
519 			      url.toString());
520 			return null;
521 		    }
522 		    result = line;
523 		}
524 	    }
525 	    if (result == null) {
526 		print(err, "checkconfig.missingprovider", url.toString());
527 		return null;
528 	    }
529 	    return result;
530 	} catch (IOException e) {
531 	    print(err, "configconfig.read", url.toString(), e);
532 	    return null;
533 	} finally {
534 	    if (in != null) {
535 		try {
536 		    in.close();
537 		} catch (IOException e) {
538 		}
539 	    }
540 	}
541     }
542 
543     /**
544      * Checks the format of a <code>ConfigurationFile</code>. Returns
545      * <code>true</code> if there are no errors, and <code>false</code>
546      * otherwise.
547      *
548      * @param config the <code>ConfigurationFile</code> to check
549      * @param entries the entry descriptions to use (where each key is a fully
550      * qualified entry name and each value is the expected type), or
551      * <code>null</code>
552      * @param loader the class loader to use for resolving type names used in
553      * the entry descriptions
554      * @param err the stream to use for printing errors
555      * @return <code>true</code> if there are no errors, <code>false</code>
556      * otherwise
557      * @throws NullPointerException if <code>config</code>,
558      * <code>loader</code>, or <code>err</code> is <code>null</code>
559      * @see #check(String, ClassLoader, String[], String, PrintStream)
560      */
561     public static boolean check(ConfigurationFile config,
562 				Properties entries,
563 				ClassLoader loader,
564 				PrintStream err)
565     {
566 	if (config == null || loader == null || err == null) {
567 	    throw new NullPointerException();
568 	}
569 	return check((Object) config, entries, loader, err);
570     }
571 
572     /**
573      * Checks the entries in config against the descriptions in entries,
574      * resolving expected types in loader.
575      */
576     private static boolean check(Object config,
577 				 Properties entries,
578 				 ClassLoader loader,
579 				 PrintStream err)
580     {
581 	Method getType;
582 	String[] entryNames;
583 	try {
584 	    getType = config.getClass().getMethod("getEntryType",
585 						  new Class[]{String.class,
586 							      String.class});
587 	    Method getNames = config.getClass().getMethod("getEntryNames",
588 							  null);
589 	    Set entrySet = (Set) getNames.invoke(config, new Object[0]);
590 	    entryNames =
591 		(String[]) entrySet.toArray(new String[entrySet.size()]);
592 	} catch (Throwable t) {
593 	    print(err, "checkconfig.unexpected", "", t);
594 	    return false;
595 	}
596 	Arrays.sort(entryNames);
597 	boolean ok = true;
598 	for (int i = 0; i < entryNames.length; i++) {
599 	    String entryName = entryNames[i];
600 	    String expectedTypeName = entries != null
601 		? entries.getProperty(entryName) : null;
602 	    if (entries != null && expectedTypeName == null) {
603 		print(err, "checkconfig.unknown", entryName);
604 		ok = false;
605 	    }
606 	    try {
607 		int dot = entryName.lastIndexOf('.');
608 		String component = entryName.substring(0, dot);
609 		String name = entryName.substring(dot + 1);
610 		Class type = (Class) getType.invoke(config,
611 						    new Object[]{component,
612 								 name});
613 		if (expectedTypeName != null) {
614 		    try {
615 			Class expectedType =
616 			    findClass(expectedTypeName, loader);
617 			if (!isAssignableFrom(expectedType, type)) {
618 			    print(err, "checkconfig.mismatch",
619 				  new String[]{entryName, typeName(type),
620 					       typeName(expectedType)});
621 			    ok = false;
622 			}
623 		    } catch (ClassNotFoundException e) {
624 			print(err, "checkconfig.expect.fail",
625 			      new String[]{entryName, e.getMessage()});
626 			ok = false;
627 		    } catch (Throwable t) {
628 			print(err, "checkconfig.expect.err",
629 			      new String[]{entryName, expectedTypeName,
630 					   t.getClass().getName(),
631 					   t.getLocalizedMessage()});
632 			t.printStackTrace(err);
633 			ok = false;
634 		    }
635 		}
636 	    } catch (Throwable t) {
637 		print(err, "checkconfig.actual", entryName, t);
638 		ok = false;
639 	    }
640 	}
641 	return ok;
642     }
643 
644     /**
645      * Returns the type with the specified name, including primitives and
646      * arrays, and checking the java.lang package for unqualified names.  This
647      * method supports both internal names ('[I', '[Ljava.lang.Integer;',
648      * 'java.util.Map$Entry') and source level name ('int[]', 'Integer[]',
649      * 'java.util.Map.Entry').
650      */
651     private static Class findClass(String name, ClassLoader loader)
652 	throws ClassNotFoundException
653     {
654 	if (name.indexOf('.') < 0) {
655 	    for (int i = primitives.length; --i >= 0; ) {
656 		if (name.equals(primitives[i].getName())) {
657 		    return primitives[i];
658 		}
659 	    }
660 	}
661 	try {
662 	    return Class.forName(name, false, loader);
663 	} catch (ClassNotFoundException notFound) {
664 	    int bracket = name.indexOf('[');
665 	    if (bracket > 0) {
666 		/* Array */
667 		int dims = 0;
668 		int len = name.length();
669 		for (int i = bracket; i < len; i += 2, dims++) {
670 		    if (name.charAt(i) != '['
671 			|| i + 1 >= len
672 			|| name.charAt(i + 1) != ']')
673 		    {
674 			/* Invalid array class name */
675 			throw notFound;
676 		    }
677 		}
678 		try {
679 		    Class base = findClass(name.substring(0, bracket), loader);
680 		    return Array.newInstance(base, new int[dims]).getClass();
681 		} catch (ClassNotFoundException e) {
682 		}
683 	    } else if (name.indexOf('.') < 0) {
684 		/* Try java.lang package */
685 		try {
686 		    return findClass("java.lang." + name, loader);
687 		} catch (ClassNotFoundException e) {
688 		}
689 	    } else {
690 		/* Try substituting '$' for '.' to look for nested classes */
691 		int dot;
692 		while ((dot = name.lastIndexOf('.')) >= 0) {
693 		    name = name.substring(0, dot) + '$' +
694 			name.substring(dot + 1);
695 		    try {
696 			return Class.forName(name, false, loader);
697 		    } catch (ClassNotFoundException e) {
698 		    }
699 		}
700 	    }
701 	    throw notFound;
702 	}
703     }
704 
705     /** Returns the name of a type, in source code format. */
706     private static String typeName(Class type) {
707 	if (type == null) {
708 	    return "null";
709 	}
710 	StringBuffer buf = new StringBuffer();
711 	if (type.isArray()) {
712 	    Class component;
713 	    while ((component = type.getComponentType()) != null) {
714 		buf.append("[]");
715 		type = component;
716 	    }
717 	}
718 	return type.getName().replace('$', '.') + buf;
719     }
720 
721     /**
722      * Checks if an object of type source can be assigned to a variable of type
723      * dest, where source is null for a null object.
724      */
725     private static boolean isAssignableFrom(Class dest, Class source) {
726 	if (dest.isPrimitive()) {
727 	    return source == dest;
728 	} else {
729 	    return source == null || dest.isAssignableFrom(source);
730 	}
731     }
732 
733     /**
734      * Returns a message from the resource bundle.
735      */
736     private static synchronized String getString(String key, PrintStream err) {
737 	if (!resinit) {
738 	    try {
739 		resinit = true;
740 		resources = ResourceBundle.getBundle(
741 			       "org.apache.river.tool.resources.checkconfig");
742 	    } catch (MissingResourceException e) {
743 		e.printStackTrace(err);
744 	    }
745 	}
746 	try {
747 	    return resources != null ? resources.getString(key) : null;
748 	} catch (MissingResourceException e) {
749 	    return null;
750 	}
751     }
752 
753     /**
754      * If t is a ConfigurationException, uses the key keyPrefix+".fail",
755      * with source as the value for {0} and the localized message of t
756      * as the value for {1}. Otherwise, uses the key keyPrefix+".err",
757      * with source as the value for {0}, the exception class name as the
758      * value for {1}, and the localized message of t as the value for {2}.
759      */
760     private static void print(PrintStream err,
761 			      String keyPrefix,
762 			      String source,
763 			      Throwable t)
764     {
765 	if (t instanceof InvocationTargetException) {
766 	    t = t.getCause();
767 	}
768 	if (t.getClass().getName().equals(
769 				      ConfigurationException.class.getName()))
770 	{
771 	    if (t.getCause() == null) {
772 		print(err, keyPrefix + ".fail",
773 		      new String[]{source, t.getLocalizedMessage()});
774 		return;
775 	    } else {
776 		t = t.getCause();
777 	    }
778 	}
779 	print(err, keyPrefix + ".err",
780 	      new String[]{source, t.getClass().getName(),
781 			   t.getLocalizedMessage()});
782 	t.printStackTrace(err);
783     }
784 
785     private static void print(PrintStream err, String key, String val) {
786 	print(err, key, new String[]{val});
787     }
788 
789     private static void print(PrintStream err, String key, String[] vals) {
790 	String fmt = getString(key, err);
791 	if (fmt == null)
792 	    fmt = "no text found: \"" + key + "\" {0}";
793 	err.println(MessageFormat.format(fmt, vals));
794     }
795 }