001 /*
002 * Copyright 2008-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.util;
022
023
024
025 import java.io.File;
026 import java.io.OutputStream;
027 import java.io.PrintStream;
028 import java.util.Collections;
029 import java.util.LinkedHashMap;
030 import java.util.LinkedHashSet;
031 import java.util.Map;
032 import java.util.Set;
033 import java.util.concurrent.atomic.AtomicReference;
034
035 import com.unboundid.ldap.sdk.LDAPException;
036 import com.unboundid.ldap.sdk.ResultCode;
037 import com.unboundid.util.args.ArgumentException;
038 import com.unboundid.util.args.ArgumentParser;
039 import com.unboundid.util.args.BooleanArgument;
040
041 import static com.unboundid.util.Debug.*;
042 import static com.unboundid.util.StaticUtils.*;
043 import static com.unboundid.util.UtilityMessages.*;
044
045
046
047 /**
048 * This class provides a framework for developing command-line tools that use
049 * the argument parser provided as part of the UnboundID LDAP SDK for Java.
050 * This tool adds a "-H" or "--help" option, which can be used to display usage
051 * information for the program, and may also add a "-V" or "--version" option,
052 * which can display the tool version.
053 * <BR><BR>
054 * Subclasses should include their own {@code main} method that creates an
055 * instance of a {@code CommandLineTool} and should invoke the
056 * {@link CommandLineTool#runTool} method with the provided arguments. For
057 * example:
058 * <PRE>
059 * public class ExampleCommandLineTool
060 * extends CommandLineTool
061 * {
062 * public static void main(String[] args)
063 * {
064 * ExampleCommandLineTool tool = new ExampleCommandLineTool();
065 * ResultCode resultCode = tool.runTool(args);
066 * if (resultCode != ResultCode.SUCCESS)
067 * {
068 * System.exit(resultCode.intValue());
069 * }
070 * |
071 *
072 * public ExampleCommandLineTool()
073 * {
074 * super(System.out, System.err);
075 * }
076 *
077 * // The rest of the tool implementation goes here.
078 * ...
079 * }
080 * </PRE>.
081 * <BR><BR>
082 * Note that in general, methods in this class are not threadsafe. However, the
083 * {@link #out(Object...)} and {@link #err(Object...)} methods may be invoked
084 * concurrently by any number of threads.
085 */
086 @Extensible()
087 @ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
088 public abstract class CommandLineTool
089 {
090 // The print stream to use for messages written to standard output.
091 private final PrintStream out;
092
093 // The print stream to use for messages written to standard error.
094 private final PrintStream err;
095
096 // The argument used to request tool help.
097 private BooleanArgument helpArgument = null;
098
099 // The argument used to request help about SASL authentication.
100 private BooleanArgument helpSASLArgument = null;
101
102 // The argument used to request interactive mode.
103 private BooleanArgument interactiveArgument = null;
104
105 // The argument used to request the tool version.
106 private BooleanArgument versionArgument = null;
107
108
109
110 /**
111 * Creates a new instance of this command-line tool with the provided
112 * information.
113 *
114 * @param outStream The output stream to use for standard output. It may be
115 * {@code System.out} for the JVM's default standard output
116 * stream, {@code null} if no output should be generated,
117 * or a custom output stream if the output should be sent
118 * to an alternate location.
119 * @param errStream The output stream to use for standard error. It may be
120 * {@code System.err} for the JVM's default standard error
121 * stream, {@code null} if no output should be generated,
122 * or a custom output stream if the output should be sent
123 * to an alternate location.
124 */
125 public CommandLineTool(final OutputStream outStream,
126 final OutputStream errStream)
127 {
128 if (outStream == null)
129 {
130 out = NullOutputStream.getPrintStream();
131 }
132 else
133 {
134 out = new PrintStream(outStream);
135 }
136
137 if (errStream == null)
138 {
139 err = NullOutputStream.getPrintStream();
140 }
141 else
142 {
143 err = new PrintStream(errStream);
144 }
145 }
146
147
148
149 /**
150 * Performs all processing for this command-line tool. This includes:
151 * <UL>
152 * <LI>Creating the argument parser and populating it using the
153 * {@link #addToolArguments} method.</LI>
154 * <LI>Parsing the provided set of command line arguments, including any
155 * additional validation using the {@link #doExtendedArgumentValidation}
156 * method.</LI>
157 * <LI>Invoking the {@link #doToolProcessing} method to do the appropriate
158 * work for this tool.</LI>
159 * </UL>
160 *
161 * @param args The command-line arguments provided to this program.
162 *
163 * @return The result of processing this tool. It should be
164 * {@link ResultCode#SUCCESS} if the tool completed its work
165 * successfully, or some other result if a problem occurred.
166 */
167 public final ResultCode runTool(final String... args)
168 {
169 try
170 {
171 final ArgumentParser parser = createArgumentParser();
172 if (supportsInteractiveMode() && defaultsToInteractiveMode() &&
173 ((args == null) || (args.length == 0)))
174 {
175 // We'll skip argument parsing in this case because no arguments were
176 // provided, and the tool may not allow no arguments to be provided in
177 // non-interactive mode.
178 }
179 else
180 {
181 parser.parse(args);
182 }
183
184 final File generatedPropertiesFile = parser.getGeneratedPropertiesFile();
185 if (supportsPropertiesFile() && (generatedPropertiesFile != null))
186 {
187 wrapOut(0, StaticUtils.TERMINAL_WIDTH_COLUMNS - 1,
188 INFO_CL_TOOL_WROTE_PROPERTIES_FILE.get(
189 generatedPropertiesFile.getAbsolutePath()));
190 return ResultCode.SUCCESS;
191 }
192
193 if (helpArgument.isPresent())
194 {
195 out(parser.getUsageString(StaticUtils.TERMINAL_WIDTH_COLUMNS - 1));
196 displayExampleUsages();
197 return ResultCode.SUCCESS;
198 }
199
200 if ((helpSASLArgument != null) && helpSASLArgument.isPresent())
201 {
202 out(SASLUtils.getUsageString(StaticUtils.TERMINAL_WIDTH_COLUMNS - 1));
203 return ResultCode.SUCCESS;
204 }
205
206 if ((versionArgument != null) && versionArgument.isPresent())
207 {
208 out(getToolVersion());
209 return ResultCode.SUCCESS;
210 }
211
212 boolean extendedValidationDone = false;
213 if (interactiveArgument != null)
214 {
215 if (interactiveArgument.isPresent() ||
216 (defaultsToInteractiveMode() &&
217 ((args == null) || (args.length == 0))))
218 {
219 final CommandLineToolInteractiveModeProcessor interactiveProcessor =
220 new CommandLineToolInteractiveModeProcessor(this, parser);
221 try
222 {
223 interactiveProcessor.doInteractiveModeProcessing();
224 extendedValidationDone = true;
225 }
226 catch (final LDAPException le)
227 {
228 debugException(le);
229
230 final String message = le.getMessage();
231 if ((message != null) && (message.length() > 0))
232 {
233 err(message);
234 }
235
236 return le.getResultCode();
237 }
238 }
239 }
240
241 if (! extendedValidationDone)
242 {
243 doExtendedArgumentValidation();
244 }
245 }
246 catch (ArgumentException ae)
247 {
248 debugException(ae);
249 err(ae.getMessage());
250 return ResultCode.PARAM_ERROR;
251 }
252
253
254 final AtomicReference<ResultCode> exitCode =
255 new AtomicReference<ResultCode>();
256 if (registerShutdownHook())
257 {
258 final CommandLineToolShutdownHook shutdownHook =
259 new CommandLineToolShutdownHook(this, exitCode);
260 Runtime.getRuntime().addShutdownHook(shutdownHook);
261 }
262
263 try
264 {
265 exitCode.set(doToolProcessing());
266 }
267 catch (Exception e)
268 {
269 debugException(e);
270 err(getExceptionMessage(e));
271 exitCode.set(ResultCode.LOCAL_ERROR);
272 }
273
274 return exitCode.get();
275 }
276
277
278
279 /**
280 * Writes example usage information for this tool to the standard output
281 * stream.
282 */
283 private void displayExampleUsages()
284 {
285 final LinkedHashMap<String[],String> examples = getExampleUsages();
286 if ((examples == null) || examples.isEmpty())
287 {
288 return;
289 }
290
291 out(INFO_CL_TOOL_LABEL_EXAMPLES);
292
293 final int wrapWidth = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
294 for (final Map.Entry<String[],String> e : examples.entrySet())
295 {
296 out();
297 wrapOut(2, wrapWidth, e.getValue());
298 out();
299
300 final StringBuilder buffer = new StringBuilder();
301 buffer.append(" ");
302 buffer.append(getToolName());
303
304 final String[] args = e.getKey();
305 for (int i=0; i < args.length; i++)
306 {
307 buffer.append(' ');
308
309 // If the argument has a value, then make sure to keep it on the same
310 // line as the argument name. This may introduce false positives due to
311 // unnamed trailing arguments, but the worst that will happen that case
312 // is that the output may be wrapped earlier than necessary one time.
313 String arg = args[i];
314 if (arg.startsWith("-"))
315 {
316 if ((i < (args.length - 1)) && (! args[i+1].startsWith("-")))
317 {
318 ExampleCommandLineArgument cleanArg =
319 ExampleCommandLineArgument.getCleanArgument(args[i+1]);
320 arg += ' ' + cleanArg.getLocalForm();
321 i++;
322 }
323 }
324 else
325 {
326 ExampleCommandLineArgument cleanArg =
327 ExampleCommandLineArgument.getCleanArgument(arg);
328 arg = cleanArg.getLocalForm();
329 }
330
331 if ((buffer.length() + arg.length() + 2) < wrapWidth)
332 {
333 buffer.append(arg);
334 }
335 else
336 {
337 buffer.append('\\');
338 out(buffer.toString());
339 buffer.setLength(0);
340 buffer.append(" ");
341 buffer.append(arg);
342 }
343 }
344
345 out(buffer.toString());
346 }
347 }
348
349
350
351 /**
352 * Retrieves the name of this tool. It should be the name of the command used
353 * to invoke this tool.
354 *
355 * @return The name for this tool.
356 */
357 public abstract String getToolName();
358
359
360
361 /**
362 * Retrieves a human-readable description for this tool.
363 *
364 * @return A human-readable description for this tool.
365 */
366 public abstract String getToolDescription();
367
368
369
370 /**
371 * Retrieves a version string for this tool, if available.
372 *
373 * @return A version string for this tool, or {@code null} if none is
374 * available.
375 */
376 public String getToolVersion()
377 {
378 return null;
379 }
380
381
382
383 /**
384 * Retrieves the minimum number of unnamed trailing arguments that must be
385 * provided for this tool. If a tool requires the use of trailing arguments,
386 * then it must override this method and the {@link #getMaxTrailingArguments}
387 * arguments to return nonzero values, and it must also override the
388 * {@link #getTrailingArgumentsPlaceholder} method to return a
389 * non-{@code null} value.
390 *
391 * @return The minimum number of unnamed trailing arguments that may be
392 * provided for this tool. A value of zero indicates that the tool
393 * may be invoked without any trailing arguments.
394 */
395 public int getMinTrailingArguments()
396 {
397 return 0;
398 }
399
400
401
402 /**
403 * Retrieves the maximum number of unnamed trailing arguments that may be
404 * provided for this tool. If a tool supports trailing arguments, then it
405 * must override this method to return a nonzero value, and must also override
406 * the {@link CommandLineTool#getTrailingArgumentsPlaceholder} method to
407 * return a non-{@code null} value.
408 *
409 * @return The maximum number of unnamed trailing arguments that may be
410 * provided for this tool. A value of zero indicates that trailing
411 * arguments are not allowed. A negative value indicates that there
412 * should be no limit on the number of trailing arguments.
413 */
414 public int getMaxTrailingArguments()
415 {
416 return 0;
417 }
418
419
420
421 /**
422 * Retrieves a placeholder string that should be used for trailing arguments
423 * in the usage information for this tool.
424 *
425 * @return A placeholder string that should be used for trailing arguments in
426 * the usage information for this tool, or {@code null} if trailing
427 * arguments are not supported.
428 */
429 public String getTrailingArgumentsPlaceholder()
430 {
431 return null;
432 }
433
434
435
436 /**
437 * Indicates whether this tool should provide support for an interactive mode,
438 * in which the tool offers a mode in which the arguments can be provided in
439 * a text-driven menu rather than requiring them to be given on the command
440 * line. If interactive mode is supported, it may be invoked using the
441 * "--interactive" argument. Alternately, if interactive mode is supported
442 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then
443 * interactive mode may be invoked by simply launching the tool without any
444 * arguments.
445 *
446 * @return {@code true} if this tool supports interactive mode, or
447 * {@code false} if not.
448 */
449 public boolean supportsInteractiveMode()
450 {
451 return false;
452 }
453
454
455
456 /**
457 * Indicates whether this tool defaults to launching in interactive mode if
458 * the tool is invoked without any command-line arguments. This will only be
459 * used if {@link #supportsInteractiveMode()} returns {@code true}.
460 *
461 * @return {@code true} if this tool defaults to using interactive mode if
462 * launched without any command-line arguments, or {@code false} if
463 * not.
464 */
465 public boolean defaultsToInteractiveMode()
466 {
467 return false;
468 }
469
470
471
472 /**
473 * Indicates whether this tool supports the use of a properties file for
474 * specifying default values for arguments that aren't specified on the
475 * command line.
476 *
477 * @return {@code true} if this tool supports the use of a properties file
478 * for specifying default values for arguments that aren't specified
479 * on the command line, or {@code false} if not.
480 */
481 public boolean supportsPropertiesFile()
482 {
483 return false;
484 }
485
486
487
488 /**
489 * Creates a parser that can be used to to parse arguments accepted by
490 * this tool.
491 *
492 * @return ArgumentParser that can be used to parse arguments for this
493 * tool.
494 *
495 * @throws ArgumentException If there was a problem initializing the
496 * parser for this tool.
497 */
498 public final ArgumentParser createArgumentParser()
499 throws ArgumentException
500 {
501 final ArgumentParser parser = new ArgumentParser(getToolName(),
502 getToolDescription(), getMinTrailingArguments(),
503 getMaxTrailingArguments(), getTrailingArgumentsPlaceholder());
504
505 addToolArguments(parser);
506
507 if (supportsInteractiveMode())
508 {
509 interactiveArgument = new BooleanArgument(null, "interactive",
510 INFO_CL_TOOL_DESCRIPTION_INTERACTIVE.get());
511 interactiveArgument.setUsageArgument(true);
512 parser.addArgument(interactiveArgument);
513 }
514
515 helpArgument = new BooleanArgument('H', "help",
516 INFO_CL_TOOL_DESCRIPTION_HELP.get());
517 helpArgument.addShortIdentifier('?');
518 helpArgument.setUsageArgument(true);
519 parser.addArgument(helpArgument);
520
521 final String version = getToolVersion();
522 if ((version != null) && (version.length() > 0) &&
523 (parser.getNamedArgument("version") == null))
524 {
525 final Character shortIdentifier;
526 if (parser.getNamedArgument('V') == null)
527 {
528 shortIdentifier = 'V';
529 }
530 else
531 {
532 shortIdentifier = null;
533 }
534
535 versionArgument = new BooleanArgument(shortIdentifier, "version",
536 INFO_CL_TOOL_DESCRIPTION_VERSION.get());
537 versionArgument.setUsageArgument(true);
538 parser.addArgument(versionArgument);
539 }
540
541 if (supportsPropertiesFile())
542 {
543 parser.enablePropertiesFileSupport();
544 }
545
546 return parser;
547 }
548
549
550
551 /**
552 * Specifies the argument that is used to retrieve usage information about
553 * SASL authentication.
554 *
555 * @param helpSASLArgument The argument that is used to retrieve usage
556 * information about SASL authentication.
557 */
558 void setHelpSASLArgument(final BooleanArgument helpSASLArgument)
559 {
560 this.helpSASLArgument = helpSASLArgument;
561 }
562
563
564
565 /**
566 * Retrieves a set containing the long identifiers used for LDAP-related
567 * arguments injected by this class.
568 *
569 * @return A set containing the long identifiers used for LDAP-related
570 * arguments injected by this class.
571 */
572 static Set<String> getUsageArgumentIdentifiers()
573 {
574 final LinkedHashSet<String> ids = new LinkedHashSet<String>(21);
575
576 ids.add("interactive");
577 ids.add("help");
578 ids.add("version");
579
580 return Collections.unmodifiableSet(ids);
581 }
582
583
584
585 /**
586 * Adds the command-line arguments supported for use with this tool to the
587 * provided argument parser. The tool may need to retain references to the
588 * arguments (and/or the argument parser, if trailing arguments are allowed)
589 * to it in order to obtain their values for use in later processing.
590 *
591 * @param parser The argument parser to which the arguments are to be added.
592 *
593 * @throws ArgumentException If a problem occurs while adding any of the
594 * tool-specific arguments to the provided
595 * argument parser.
596 */
597 public abstract void addToolArguments(final ArgumentParser parser)
598 throws ArgumentException;
599
600
601
602 /**
603 * Performs any necessary processing that should be done to ensure that the
604 * provided set of command-line arguments were valid. This method will be
605 * called after the basic argument parsing has been performed and immediately
606 * before the {@link CommandLineTool#doToolProcessing} method is invoked.
607 * Note that if the tool supports interactive mode, then this method may be
608 * invoked multiple times to allow the user to interactively fix validation
609 * errors.
610 *
611 * @throws ArgumentException If there was a problem with the command-line
612 * arguments provided to this program.
613 */
614 public void doExtendedArgumentValidation()
615 throws ArgumentException
616 {
617 // No processing will be performed by default.
618 }
619
620
621
622 /**
623 * Performs the core set of processing for this tool.
624 *
625 * @return A result code that indicates whether the processing completed
626 * successfully.
627 */
628 public abstract ResultCode doToolProcessing();
629
630
631
632 /**
633 * Indicates whether this tool should register a shutdown hook with the JVM.
634 * Shutdown hooks allow for a best-effort attempt to perform a specified set
635 * of processing when the JVM is shutting down under various conditions,
636 * including:
637 * <UL>
638 * <LI>When all non-daemon threads have stopped running (i.e., the tool has
639 * completed processing).</LI>
640 * <LI>When {@code System.exit()} or {@code Runtime.exit()} is called.</LI>
641 * <LI>When the JVM receives an external kill signal (e.g., via the use of
642 * the kill tool or interrupting the JVM with Ctrl+C).</LI>
643 * </UL>
644 * Shutdown hooks may not be invoked if the process is forcefully killed
645 * (e.g., using "kill -9", or the {@code System.halt()} or
646 * {@code Runtime.halt()} methods).
647 * <BR><BR>
648 * If this method is overridden to return {@code true}, then the
649 * {@link #doShutdownHookProcessing(ResultCode)} method should also be
650 * overridden to contain the logic that will be invoked when the JVM is
651 * shutting down in a manner that calls shutdown hooks.
652 *
653 * @return {@code true} if this tool should register a shutdown hook, or
654 * {@code false} if not.
655 */
656 protected boolean registerShutdownHook()
657 {
658 return false;
659 }
660
661
662
663 /**
664 * Performs any processing that may be needed when the JVM is shutting down,
665 * whether because tool processing has completed or because it has been
666 * interrupted (e.g., by a kill or break signal).
667 * <BR><BR>
668 * Note that because shutdown hooks run at a delicate time in the life of the
669 * JVM, they should complete quickly and minimize access to external
670 * resources. See the documentation for the
671 * {@code java.lang.Runtime.addShutdownHook} method for recommendations and
672 * restrictions about writing shutdown hooks.
673 *
674 * @param resultCode The result code returned by the tool. It may be
675 * {@code null} if the tool was interrupted before it
676 * completed processing.
677 */
678 protected void doShutdownHookProcessing(final ResultCode resultCode)
679 {
680 throw new LDAPSDKUsageException(
681 ERR_COMMAND_LINE_TOOL_SHUTDOWN_HOOK_NOT_IMPLEMENTED.get(
682 getToolName()));
683 }
684
685
686
687 /**
688 * Retrieves a set of information that may be used to generate example usage
689 * information. Each element in the returned map should consist of a map
690 * between an example set of arguments and a string that describes the
691 * behavior of the tool when invoked with that set of arguments.
692 *
693 * @return A set of information that may be used to generate example usage
694 * information. It may be {@code null} or empty if no example usage
695 * information is available.
696 */
697 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
698 public LinkedHashMap<String[],String> getExampleUsages()
699 {
700 return null;
701 }
702
703
704
705 /**
706 * Retrieves the print writer that will be used for standard output.
707 *
708 * @return The print writer that will be used for standard output.
709 */
710 public final PrintStream getOut()
711 {
712 return out;
713 }
714
715
716
717 /**
718 * Writes the provided message to the standard output stream for this tool.
719 * <BR><BR>
720 * This method is completely threadsafe and my be invoked concurrently by any
721 * number of threads.
722 *
723 * @param msg The message components that will be written to the standard
724 * output stream. They will be concatenated together on the same
725 * line, and that line will be followed by an end-of-line
726 * sequence.
727 */
728 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
729 public final synchronized void out(final Object... msg)
730 {
731 write(out, 0, 0, msg);
732 }
733
734
735
736 /**
737 * Writes the provided message to the standard output stream for this tool,
738 * optionally wrapping and/or indenting the text in the process.
739 * <BR><BR>
740 * This method is completely threadsafe and my be invoked concurrently by any
741 * number of threads.
742 *
743 * @param indent The number of spaces each line should be indented. A
744 * value less than or equal to zero indicates that no
745 * indent should be used.
746 * @param wrapColumn The column at which to wrap long lines. A value less
747 * than or equal to two indicates that no wrapping should
748 * be performed. If both an indent and a wrap column are
749 * to be used, then the wrap column must be greater than
750 * the indent.
751 * @param msg The message components that will be written to the
752 * standard output stream. They will be concatenated
753 * together on the same line, and that line will be
754 * followed by an end-of-line sequence.
755 */
756 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
757 public final synchronized void wrapOut(final int indent, final int wrapColumn,
758 final Object... msg)
759 {
760 write(out, indent, wrapColumn, msg);
761 }
762
763
764
765 /**
766 * Writes the provided message to the standard output stream for this tool,
767 * optionally wrapping and/or indenting the text in the process.
768 * <BR><BR>
769 * This method is completely threadsafe and my be invoked concurrently by any
770 * number of threads.
771 *
772 * @param firstLineIndent The number of spaces the first line should be
773 * indented. A value less than or equal to zero
774 * indicates that no indent should be used.
775 * @param subsequentLineIndent The number of spaces each line except the
776 * first should be indented. A value less than
777 * or equal to zero indicates that no indent
778 * should be used.
779 * @param wrapColumn The column at which to wrap long lines. A
780 * value less than or equal to two indicates
781 * that no wrapping should be performed. If
782 * both an indent and a wrap column are to be
783 * used, then the wrap column must be greater
784 * than the indent.
785 * @param endWithNewline Indicates whether a newline sequence should
786 * follow the last line that is printed.
787 * @param msg The message components that will be written
788 * to the standard output stream. They will be
789 * concatenated together on the same line, and
790 * that line will be followed by an end-of-line
791 * sequence.
792 */
793 final synchronized void wrapStandardOut(final int firstLineIndent,
794 final int subsequentLineIndent,
795 final int wrapColumn,
796 final boolean endWithNewline,
797 final Object... msg)
798 {
799 write(out, firstLineIndent, subsequentLineIndent, wrapColumn,
800 endWithNewline, msg);
801 }
802
803
804
805 /**
806 * Retrieves the print writer that will be used for standard error.
807 *
808 * @return The print writer that will be used for standard error.
809 */
810 public final PrintStream getErr()
811 {
812 return err;
813 }
814
815
816
817 /**
818 * Writes the provided message to the standard error stream for this tool.
819 * <BR><BR>
820 * This method is completely threadsafe and my be invoked concurrently by any
821 * number of threads.
822 *
823 * @param msg The message components that will be written to the standard
824 * error stream. They will be concatenated together on the same
825 * line, and that line will be followed by an end-of-line
826 * sequence.
827 */
828 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
829 public final synchronized void err(final Object... msg)
830 {
831 write(err, 0, 0, msg);
832 }
833
834
835
836 /**
837 * Writes the provided message to the standard error stream for this tool,
838 * optionally wrapping and/or indenting the text in the process.
839 * <BR><BR>
840 * This method is completely threadsafe and my be invoked concurrently by any
841 * number of threads.
842 *
843 * @param indent The number of spaces each line should be indented. A
844 * value less than or equal to zero indicates that no
845 * indent should be used.
846 * @param wrapColumn The column at which to wrap long lines. A value less
847 * than or equal to two indicates that no wrapping should
848 * be performed. If both an indent and a wrap column are
849 * to be used, then the wrap column must be greater than
850 * the indent.
851 * @param msg The message components that will be written to the
852 * standard output stream. They will be concatenated
853 * together on the same line, and that line will be
854 * followed by an end-of-line sequence.
855 */
856 @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
857 public final synchronized void wrapErr(final int indent, final int wrapColumn,
858 final Object... msg)
859 {
860 write(err, indent, wrapColumn, msg);
861 }
862
863
864
865 /**
866 * Writes the provided message to the given print stream, optionally wrapping
867 * and/or indenting the text in the process.
868 *
869 * @param stream The stream to which the message should be written.
870 * @param indent The number of spaces each line should be indented. A
871 * value less than or equal to zero indicates that no
872 * indent should be used.
873 * @param wrapColumn The column at which to wrap long lines. A value less
874 * than or equal to two indicates that no wrapping should
875 * be performed. If both an indent and a wrap column are
876 * to be used, then the wrap column must be greater than
877 * the indent.
878 * @param msg The message components that will be written to the
879 * standard output stream. They will be concatenated
880 * together on the same line, and that line will be
881 * followed by an end-of-line sequence.
882 */
883 private static void write(final PrintStream stream, final int indent,
884 final int wrapColumn, final Object... msg)
885 {
886 write(stream, indent, indent, wrapColumn, true, msg);
887 }
888
889
890
891 /**
892 * Writes the provided message to the given print stream, optionally wrapping
893 * and/or indenting the text in the process.
894 *
895 * @param stream The stream to which the message should be
896 * written.
897 * @param firstLineIndent The number of spaces the first line should be
898 * indented. A value less than or equal to zero
899 * indicates that no indent should be used.
900 * @param subsequentLineIndent The number of spaces all lines after the
901 * first should be indented. A value less than
902 * or equal to zero indicates that no indent
903 * should be used.
904 * @param wrapColumn The column at which to wrap long lines. A
905 * value less than or equal to two indicates
906 * that no wrapping should be performed. If
907 * both an indent and a wrap column are to be
908 * used, then the wrap column must be greater
909 * than the indent.
910 * @param endWithNewline Indicates whether a newline sequence should
911 * follow the last line that is printed.
912 * @param msg The message components that will be written
913 * to the standard output stream. They will be
914 * concatenated together on the same line, and
915 * that line will be followed by an end-of-line
916 * sequence.
917 */
918 private static void write(final PrintStream stream, final int firstLineIndent,
919 final int subsequentLineIndent,
920 final int wrapColumn,
921 final boolean endWithNewline, final Object... msg)
922 {
923 final StringBuilder buffer = new StringBuilder();
924 for (final Object o : msg)
925 {
926 buffer.append(o);
927 }
928
929 if (wrapColumn > 2)
930 {
931 boolean firstLine = true;
932 for (final String line :
933 wrapLine(buffer.toString(), (wrapColumn - firstLineIndent),
934 (wrapColumn - subsequentLineIndent)))
935 {
936 final int indent;
937 if (firstLine)
938 {
939 indent = firstLineIndent;
940 firstLine = false;
941 }
942 else
943 {
944 stream.println();
945 indent = subsequentLineIndent;
946 }
947
948 if (indent > 0)
949 {
950 for (int i=0; i < indent; i++)
951 {
952 stream.print(' ');
953 }
954 }
955 stream.print(line);
956 }
957 }
958 else
959 {
960 if (firstLineIndent > 0)
961 {
962 for (int i=0; i < firstLineIndent; i++)
963 {
964 stream.print(' ');
965 }
966 }
967 stream.print(buffer.toString());
968 }
969
970 if (endWithNewline)
971 {
972 stream.println();
973 }
974 stream.flush();
975 }
976 }