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 }