All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.util.CommandLineTool Maven / Gradle / Ivy

/*
 * Copyright 2008-2017 UnboundID Corp.
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2008-2017 UnboundID Corp.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.util;



import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;

import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.BooleanArgument;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.SubCommand;

import static com.unboundid.util.Debug.*;
import static com.unboundid.util.StaticUtils.*;
import static com.unboundid.util.UtilityMessages.*;



/**
 * This class provides a framework for developing command-line tools that use
 * the argument parser provided as part of the UnboundID LDAP SDK for Java.
 * This tool adds a "-H" or "--help" option, which can be used to display usage
 * information for the program, and may also add a "-V" or "--version" option,
 * which can display the tool version.
 * 

* Subclasses should include their own {@code main} method that creates an * instance of a {@code CommandLineTool} and should invoke the * {@link CommandLineTool#runTool} method with the provided arguments. For * example: *
 *   public class ExampleCommandLineTool
 *          extends CommandLineTool
 *   {
 *     public static void main(String[] args)
 *     {
 *       ExampleCommandLineTool tool = new ExampleCommandLineTool();
 *       ResultCode resultCode = tool.runTool(args);
 *       if (resultCode != ResultCode.SUCCESS)
 *       {
 *         System.exit(resultCode.intValue());
 *       }
 *     |
 *
 *     public ExampleCommandLineTool()
 *     {
 *       super(System.out, System.err);
 *     }
 *
 *     // The rest of the tool implementation goes here.
 *     ...
 *   }
 * 
. *

* Note that in general, methods in this class are not threadsafe. However, the * {@link #out(Object...)} and {@link #err(Object...)} methods may be invoked * concurrently by any number of threads. */ @Extensible() @ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE) public abstract class CommandLineTool { // The print stream that was originally used for standard output. It may not // be the current standard output stream if an output file has been // configured. private final PrintStream originalOut; // The print stream that was originally used for standard error. It may not // be the current standard error stream if an output file has been configured. private final PrintStream originalErr; // The print stream to use for messages written to standard output. private volatile PrintStream out; // The print stream to use for messages written to standard error. private volatile PrintStream err; // The argument used to indicate that the tool should append to the output // file rather than overwrite it. private BooleanArgument appendToOutputFileArgument = null; // The argument used to request tool help. private BooleanArgument helpArgument = null; // The argument used to request help about SASL authentication. private BooleanArgument helpSASLArgument = null; // The argument used to request help information about all of the subcommands. private BooleanArgument helpSubcommandsArgument = null; // The argument used to request interactive mode. private BooleanArgument interactiveArgument = null; // The argument used to indicate that output should be written to standard out // as well as the specified output file. private BooleanArgument teeOutputArgument = null; // The argument used to request the tool version. private BooleanArgument versionArgument = null; // The argument used to specify the output file for standard output and // standard error. private FileArgument outputFileArgument = null; /** * Creates a new instance of this command-line tool with the provided * information. * * @param outStream The output stream to use for standard output. It may be * {@code System.out} for the JVM's default standard output * stream, {@code null} if no output should be generated, * or a custom output stream if the output should be sent * to an alternate location. * @param errStream The output stream to use for standard error. It may be * {@code System.err} for the JVM's default standard error * stream, {@code null} if no output should be generated, * or a custom output stream if the output should be sent * to an alternate location. */ public CommandLineTool(final OutputStream outStream, final OutputStream errStream) { if (outStream == null) { out = NullOutputStream.getPrintStream(); } else { out = new PrintStream(outStream); } if (errStream == null) { err = NullOutputStream.getPrintStream(); } else { err = new PrintStream(errStream); } originalOut = out; originalErr = err; } /** * Performs all processing for this command-line tool. This includes: *
    *
  • Creating the argument parser and populating it using the * {@link #addToolArguments} method.
  • *
  • Parsing the provided set of command line arguments, including any * additional validation using the {@link #doExtendedArgumentValidation} * method.
  • *
  • Invoking the {@link #doToolProcessing} method to do the appropriate * work for this tool.
  • *
* * @param args The command-line arguments provided to this program. * * @return The result of processing this tool. It should be * {@link ResultCode#SUCCESS} if the tool completed its work * successfully, or some other result if a problem occurred. */ public final ResultCode runTool(final String... args) { final ArgumentParser parser; try { parser = createArgumentParser(); if (supportsInteractiveMode() && defaultsToInteractiveMode() && ((args == null) || (args.length == 0))) { // We'll skip argument parsing in this case because no arguments were // provided, and the tool may not allow no arguments to be provided in // non-interactive mode. } else { parser.parse(args); } final File generatedPropertiesFile = parser.getGeneratedPropertiesFile(); if (supportsPropertiesFile() && (generatedPropertiesFile != null)) { wrapOut(0, StaticUtils.TERMINAL_WIDTH_COLUMNS - 1, INFO_CL_TOOL_WROTE_PROPERTIES_FILE.get( generatedPropertiesFile.getAbsolutePath())); return ResultCode.SUCCESS; } if (helpArgument.isPresent()) { out(parser.getUsageString(StaticUtils.TERMINAL_WIDTH_COLUMNS - 1)); displayExampleUsages(parser); return ResultCode.SUCCESS; } if ((helpSASLArgument != null) && helpSASLArgument.isPresent()) { out(SASLUtils.getUsageString(StaticUtils.TERMINAL_WIDTH_COLUMNS - 1)); return ResultCode.SUCCESS; } if ((helpSubcommandsArgument != null) && helpSubcommandsArgument.isPresent()) { final TreeMap subCommands = getSortedSubCommands(parser); for (final SubCommand sc : subCommands.values()) { final StringBuilder nameBuffer = new StringBuilder(); final Iterator nameIterator = sc.getNames().iterator(); while (nameIterator.hasNext()) { nameBuffer.append(nameIterator.next()); if (nameIterator.hasNext()) { nameBuffer.append(", "); } } out(nameBuffer.toString()); for (final String descriptionLine : wrapLine(sc.getDescription(), (StaticUtils.TERMINAL_WIDTH_COLUMNS - 3))) { out(" " + descriptionLine); } out(); } wrapOut(0, (StaticUtils.TERMINAL_WIDTH_COLUMNS - 1), INFO_CL_TOOL_USE_SUBCOMMAND_HELP.get(getToolName())); return ResultCode.SUCCESS; } if ((versionArgument != null) && versionArgument.isPresent()) { out(getToolVersion()); return ResultCode.SUCCESS; } boolean extendedValidationDone = false; if (interactiveArgument != null) { if (interactiveArgument.isPresent() || (defaultsToInteractiveMode() && ((args == null) || (args.length == 0)))) { final CommandLineToolInteractiveModeProcessor interactiveProcessor = new CommandLineToolInteractiveModeProcessor(this, parser); try { interactiveProcessor.doInteractiveModeProcessing(); extendedValidationDone = true; } catch (final LDAPException le) { debugException(le); final String message = le.getMessage(); if ((message != null) && (message.length() > 0)) { err(message); } return le.getResultCode(); } } } if (! extendedValidationDone) { doExtendedArgumentValidation(); } } catch (ArgumentException ae) { debugException(ae); err(ae.getMessage()); return ResultCode.PARAM_ERROR; } if ((outputFileArgument != null) && outputFileArgument.isPresent()) { final File outputFile = outputFileArgument.getValue(); final boolean append = ((appendToOutputFileArgument != null) && appendToOutputFileArgument.isPresent()); final PrintStream outputFileStream; try { final FileOutputStream fos = new FileOutputStream(outputFile, append); outputFileStream = new PrintStream(fos, true, "UTF-8"); } catch (final Exception e) { debugException(e); err(ERR_CL_TOOL_ERROR_CREATING_OUTPUT_FILE.get( outputFile.getAbsolutePath(), getExceptionMessage(e))); return ResultCode.LOCAL_ERROR; } if ((teeOutputArgument != null) && teeOutputArgument.isPresent()) { out = new PrintStream(new TeeOutputStream(out, outputFileStream)); err = new PrintStream(new TeeOutputStream(err, outputFileStream)); } else { out = outputFileStream; err = outputFileStream; } } // If any values were selected using a properties file, then display // information about them. final List argsSetFromPropertiesFiles = parser.getArgumentsSetFromPropertiesFile(); if (! argsSetFromPropertiesFiles.isEmpty()) { for (final String line : wrapLine( INFO_CL_TOOL_ARGS_FROM_PROPERTIES_FILE.get( parser.getPropertiesFileUsed().getPath()), (TERMINAL_WIDTH_COLUMNS - 3))) { out("# ", line); } final StringBuilder buffer = new StringBuilder(); for (final String s : argsSetFromPropertiesFiles) { if (s.startsWith("-")) { if (buffer.length() > 0) { out(buffer); buffer.setLength(0); } buffer.append("# "); buffer.append(s); } else { if (buffer.length() == 0) { // This should never happen. buffer.append("# "); } else { buffer.append(' '); } buffer.append(StaticUtils.cleanExampleCommandLineArgument(s)); } } if (buffer.length() > 0) { out(buffer); } out(); } CommandLineToolShutdownHook shutdownHook = null; final AtomicReference exitCode = new AtomicReference(); if (registerShutdownHook()) { shutdownHook = new CommandLineToolShutdownHook(this, exitCode); Runtime.getRuntime().addShutdownHook(shutdownHook); } try { exitCode.set(doToolProcessing()); } catch (Exception e) { debugException(e); err(getExceptionMessage(e)); exitCode.set(ResultCode.LOCAL_ERROR); } finally { if (shutdownHook != null) { Runtime.getRuntime().removeShutdownHook(shutdownHook); } } return exitCode.get(); } /** * Retrieves a sorted map of subcommands for the provided argument parser, * alphabetized by primary name. * * @param parser The argument parser for which to get the sorted * subcommands. * * @return The sorted map of subcommands. */ private static TreeMap getSortedSubCommands( final ArgumentParser parser) { final TreeMap m = new TreeMap(); for (final SubCommand sc : parser.getSubCommands()) { m.put(sc.getPrimaryName(), sc); } return m; } /** * Writes example usage information for this tool to the standard output * stream. * * @param parser The argument parser used to process the provided set of * command-line arguments. */ private void displayExampleUsages(final ArgumentParser parser) { final LinkedHashMap examples; if ((parser != null) && (parser.getSelectedSubCommand() != null)) { examples = parser.getSelectedSubCommand().getExampleUsages(); } else { examples = getExampleUsages(); } if ((examples == null) || examples.isEmpty()) { return; } out(INFO_CL_TOOL_LABEL_EXAMPLES); final int wrapWidth = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; for (final Map.Entry e : examples.entrySet()) { out(); wrapOut(2, wrapWidth, e.getValue()); out(); final StringBuilder buffer = new StringBuilder(); buffer.append(" "); buffer.append(getToolName()); final String[] args = e.getKey(); for (int i=0; i < args.length; i++) { buffer.append(' '); // If the argument has a value, then make sure to keep it on the same // line as the argument name. This may introduce false positives due to // unnamed trailing arguments, but the worst that will happen that case // is that the output may be wrapped earlier than necessary one time. String arg = args[i]; if (arg.startsWith("-")) { if ((i < (args.length - 1)) && (! args[i+1].startsWith("-"))) { ExampleCommandLineArgument cleanArg = ExampleCommandLineArgument.getCleanArgument(args[i+1]); arg += ' ' + cleanArg.getLocalForm(); i++; } } else { ExampleCommandLineArgument cleanArg = ExampleCommandLineArgument.getCleanArgument(arg); arg = cleanArg.getLocalForm(); } if ((buffer.length() + arg.length() + 2) < wrapWidth) { buffer.append(arg); } else { buffer.append('\\'); out(buffer.toString()); buffer.setLength(0); buffer.append(" "); buffer.append(arg); } } out(buffer.toString()); } } /** * Retrieves the name of this tool. It should be the name of the command used * to invoke this tool. * * @return The name for this tool. */ public abstract String getToolName(); /** * Retrieves a human-readable description for this tool. * * @return A human-readable description for this tool. */ public abstract String getToolDescription(); /** * Retrieves a version string for this tool, if available. * * @return A version string for this tool, or {@code null} if none is * available. */ public String getToolVersion() { return null; } /** * Retrieves the minimum number of unnamed trailing arguments that must be * provided for this tool. If a tool requires the use of trailing arguments, * then it must override this method and the {@link #getMaxTrailingArguments} * arguments to return nonzero values, and it must also override the * {@link #getTrailingArgumentsPlaceholder} method to return a * non-{@code null} value. * * @return The minimum number of unnamed trailing arguments that may be * provided for this tool. A value of zero indicates that the tool * may be invoked without any trailing arguments. */ public int getMinTrailingArguments() { return 0; } /** * Retrieves the maximum number of unnamed trailing arguments that may be * provided for this tool. If a tool supports trailing arguments, then it * must override this method to return a nonzero value, and must also override * the {@link CommandLineTool#getTrailingArgumentsPlaceholder} method to * return a non-{@code null} value. * * @return The maximum number of unnamed trailing arguments that may be * provided for this tool. A value of zero indicates that trailing * arguments are not allowed. A negative value indicates that there * should be no limit on the number of trailing arguments. */ public int getMaxTrailingArguments() { return 0; } /** * Retrieves a placeholder string that should be used for trailing arguments * in the usage information for this tool. * * @return A placeholder string that should be used for trailing arguments in * the usage information for this tool, or {@code null} if trailing * arguments are not supported. */ public String getTrailingArgumentsPlaceholder() { return null; } /** * Indicates whether this tool should provide support for an interactive mode, * in which the tool offers a mode in which the arguments can be provided in * a text-driven menu rather than requiring them to be given on the command * line. If interactive mode is supported, it may be invoked using the * "--interactive" argument. Alternately, if interactive mode is supported * and {@link #defaultsToInteractiveMode()} returns {@code true}, then * interactive mode may be invoked by simply launching the tool without any * arguments. * * @return {@code true} if this tool supports interactive mode, or * {@code false} if not. */ public boolean supportsInteractiveMode() { return false; } /** * Indicates whether this tool defaults to launching in interactive mode if * the tool is invoked without any command-line arguments. This will only be * used if {@link #supportsInteractiveMode()} returns {@code true}. * * @return {@code true} if this tool defaults to using interactive mode if * launched without any command-line arguments, or {@code false} if * not. */ public boolean defaultsToInteractiveMode() { return false; } /** * Indicates whether this tool supports the use of a properties file for * specifying default values for arguments that aren't specified on the * command line. * * @return {@code true} if this tool supports the use of a properties file * for specifying default values for arguments that aren't specified * on the command line, or {@code false} if not. */ public boolean supportsPropertiesFile() { return false; } /** * Indicates whether this tool should provide arguments for redirecting output * to a file. If this method returns {@code true}, then the tool will offer * an "--outputFile" argument that will specify the path to a file to which * all standard output and standard error content will be written, and it will * also offer a "--teeToStandardOut" argument that can only be used if the * "--outputFile" argument is present and will cause all output to be written * to both the specified output file and to standard output. * * @return {@code true} if this tool should provide arguments for redirecting * output to a file, or {@code false} if not. */ protected boolean supportsOutputFile() { return false; } /** * Creates a parser that can be used to to parse arguments accepted by * this tool. * * @return ArgumentParser that can be used to parse arguments for this * tool. * * @throws ArgumentException If there was a problem initializing the * parser for this tool. */ public final ArgumentParser createArgumentParser() throws ArgumentException { final ArgumentParser parser = new ArgumentParser(getToolName(), getToolDescription(), getMinTrailingArguments(), getMaxTrailingArguments(), getTrailingArgumentsPlaceholder()); addToolArguments(parser); if (supportsInteractiveMode()) { interactiveArgument = new BooleanArgument(null, "interactive", INFO_CL_TOOL_DESCRIPTION_INTERACTIVE.get()); interactiveArgument.setUsageArgument(true); parser.addArgument(interactiveArgument); } if (supportsOutputFile()) { outputFileArgument = new FileArgument(null, "outputFile", false, 1, null, INFO_CL_TOOL_DESCRIPTION_OUTPUT_FILE.get(), false, true, true, false); outputFileArgument.addLongIdentifier("output-file"); outputFileArgument.setUsageArgument(true); parser.addArgument(outputFileArgument); appendToOutputFileArgument = new BooleanArgument(null, "appendToOutputFile", 1, INFO_CL_TOOL_DESCRIPTION_APPEND_TO_OUTPUT_FILE.get( outputFileArgument.getIdentifierString())); appendToOutputFileArgument.addLongIdentifier("append-to-output-file"); appendToOutputFileArgument.setUsageArgument(true); parser.addArgument(appendToOutputFileArgument); teeOutputArgument = new BooleanArgument(null, "teeOutput", 1, INFO_CL_TOOL_DESCRIPTION_TEE_OUTPUT.get( outputFileArgument.getIdentifierString())); teeOutputArgument.addLongIdentifier("tee-output"); teeOutputArgument.setUsageArgument(true); parser.addArgument(teeOutputArgument); parser.addDependentArgumentSet(appendToOutputFileArgument, outputFileArgument); parser.addDependentArgumentSet(teeOutputArgument, outputFileArgument); } helpArgument = new BooleanArgument('H', "help", INFO_CL_TOOL_DESCRIPTION_HELP.get()); helpArgument.addShortIdentifier('?'); helpArgument.setUsageArgument(true); parser.addArgument(helpArgument); if (! parser.getSubCommands().isEmpty()) { helpSubcommandsArgument = new BooleanArgument(null, "helpSubcommands", 1, INFO_CL_TOOL_DESCRIPTION_HELP_SUBCOMMANDS.get()); helpSubcommandsArgument.addLongIdentifier("help-subcommands"); helpSubcommandsArgument.setUsageArgument(true); parser.addArgument(helpSubcommandsArgument); } final String version = getToolVersion(); if ((version != null) && (version.length() > 0) && (parser.getNamedArgument("version") == null)) { final Character shortIdentifier; if (parser.getNamedArgument('V') == null) { shortIdentifier = 'V'; } else { shortIdentifier = null; } versionArgument = new BooleanArgument(shortIdentifier, "version", INFO_CL_TOOL_DESCRIPTION_VERSION.get()); versionArgument.setUsageArgument(true); parser.addArgument(versionArgument); } if (supportsPropertiesFile()) { parser.enablePropertiesFileSupport(); } return parser; } /** * Specifies the argument that is used to retrieve usage information about * SASL authentication. * * @param helpSASLArgument The argument that is used to retrieve usage * information about SASL authentication. */ void setHelpSASLArgument(final BooleanArgument helpSASLArgument) { this.helpSASLArgument = helpSASLArgument; } /** * Retrieves a set containing the long identifiers used for usage arguments * injected by this class. * * @param tool The tool to use to help make the determination. * * @return A set containing the long identifiers used for usage arguments * injected by this class. */ static Set getUsageArgumentIdentifiers(final CommandLineTool tool) { final LinkedHashSet ids = new LinkedHashSet(9); ids.add("help"); ids.add("version"); ids.add("helpSubcommands"); if (tool.supportsInteractiveMode()) { ids.add("interactive"); } if (tool.supportsPropertiesFile()) { ids.add("propertiesFilePath"); ids.add("generatePropertiesFile"); ids.add("noPropertiesFile"); } if (tool.supportsOutputFile()) { ids.add("outputFile"); ids.add("appendToOutputFile"); ids.add("teeOutput"); } return Collections.unmodifiableSet(ids); } /** * Adds the command-line arguments supported for use with this tool to the * provided argument parser. The tool may need to retain references to the * arguments (and/or the argument parser, if trailing arguments are allowed) * to it in order to obtain their values for use in later processing. * * @param parser The argument parser to which the arguments are to be added. * * @throws ArgumentException If a problem occurs while adding any of the * tool-specific arguments to the provided * argument parser. */ public abstract void addToolArguments(final ArgumentParser parser) throws ArgumentException; /** * Performs any necessary processing that should be done to ensure that the * provided set of command-line arguments were valid. This method will be * called after the basic argument parsing has been performed and immediately * before the {@link CommandLineTool#doToolProcessing} method is invoked. * Note that if the tool supports interactive mode, then this method may be * invoked multiple times to allow the user to interactively fix validation * errors. * * @throws ArgumentException If there was a problem with the command-line * arguments provided to this program. */ public void doExtendedArgumentValidation() throws ArgumentException { // No processing will be performed by default. } /** * Performs the core set of processing for this tool. * * @return A result code that indicates whether the processing completed * successfully. */ public abstract ResultCode doToolProcessing(); /** * Indicates whether this tool should register a shutdown hook with the JVM. * Shutdown hooks allow for a best-effort attempt to perform a specified set * of processing when the JVM is shutting down under various conditions, * including: *
    *
  • When all non-daemon threads have stopped running (i.e., the tool has * completed processing).
  • *
  • When {@code System.exit()} or {@code Runtime.exit()} is called.
  • *
  • When the JVM receives an external kill signal (e.g., via the use of * the kill tool or interrupting the JVM with Ctrl+C).
  • *
* Shutdown hooks may not be invoked if the process is forcefully killed * (e.g., using "kill -9", or the {@code System.halt()} or * {@code Runtime.halt()} methods). *

* If this method is overridden to return {@code true}, then the * {@link #doShutdownHookProcessing(ResultCode)} method should also be * overridden to contain the logic that will be invoked when the JVM is * shutting down in a manner that calls shutdown hooks. * * @return {@code true} if this tool should register a shutdown hook, or * {@code false} if not. */ protected boolean registerShutdownHook() { return false; } /** * Performs any processing that may be needed when the JVM is shutting down, * whether because tool processing has completed or because it has been * interrupted (e.g., by a kill or break signal). *

* Note that because shutdown hooks run at a delicate time in the life of the * JVM, they should complete quickly and minimize access to external * resources. See the documentation for the * {@code java.lang.Runtime.addShutdownHook} method for recommendations and * restrictions about writing shutdown hooks. * * @param resultCode The result code returned by the tool. It may be * {@code null} if the tool was interrupted before it * completed processing. */ protected void doShutdownHookProcessing(final ResultCode resultCode) { throw new LDAPSDKUsageException( ERR_COMMAND_LINE_TOOL_SHUTDOWN_HOOK_NOT_IMPLEMENTED.get( getToolName())); } /** * Retrieves a set of information that may be used to generate example usage * information. Each element in the returned map should consist of a map * between an example set of arguments and a string that describes the * behavior of the tool when invoked with that set of arguments. * * @return A set of information that may be used to generate example usage * information. It may be {@code null} or empty if no example usage * information is available. */ @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) public LinkedHashMap getExampleUsages() { return null; } /** * Retrieves the print stream that will be used for standard output. * * @return The print stream that will be used for standard output. */ public final PrintStream getOut() { return out; } /** * Retrieves the print stream that may be used to write to the original * standard output. This may be different from the current standard output * stream if an output file has been configured. * * @return The print stream that may be used to write to the original * standard output. */ public final PrintStream getOriginalOut() { return originalOut; } /** * Writes the provided message to the standard output stream for this tool. *

* This method is completely threadsafe and my be invoked concurrently by any * number of threads. * * @param msg The message components that will be written to the standard * output stream. They will be concatenated together on the same * line, and that line will be followed by an end-of-line * sequence. */ @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) public final synchronized void out(final Object... msg) { write(out, 0, 0, msg); } /** * Writes the provided message to the standard output stream for this tool, * optionally wrapping and/or indenting the text in the process. *

* This method is completely threadsafe and my be invoked concurrently by any * number of threads. * * @param indent The number of spaces each line should be indented. A * value less than or equal to zero indicates that no * indent should be used. * @param wrapColumn The column at which to wrap long lines. A value less * than or equal to two indicates that no wrapping should * be performed. If both an indent and a wrap column are * to be used, then the wrap column must be greater than * the indent. * @param msg The message components that will be written to the * standard output stream. They will be concatenated * together on the same line, and that line will be * followed by an end-of-line sequence. */ @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) public final synchronized void wrapOut(final int indent, final int wrapColumn, final Object... msg) { write(out, indent, wrapColumn, msg); } /** * Writes the provided message to the standard output stream for this tool, * optionally wrapping and/or indenting the text in the process. *

* This method is completely threadsafe and my be invoked concurrently by any * number of threads. * * @param firstLineIndent The number of spaces the first line should be * indented. A value less than or equal to zero * indicates that no indent should be used. * @param subsequentLineIndent The number of spaces each line except the * first should be indented. A value less than * or equal to zero indicates that no indent * should be used. * @param wrapColumn The column at which to wrap long lines. A * value less than or equal to two indicates * that no wrapping should be performed. If * both an indent and a wrap column are to be * used, then the wrap column must be greater * than the indent. * @param endWithNewline Indicates whether a newline sequence should * follow the last line that is printed. * @param msg The message components that will be written * to the standard output stream. They will be * concatenated together on the same line, and * that line will be followed by an end-of-line * sequence. */ final synchronized void wrapStandardOut(final int firstLineIndent, final int subsequentLineIndent, final int wrapColumn, final boolean endWithNewline, final Object... msg) { write(out, firstLineIndent, subsequentLineIndent, wrapColumn, endWithNewline, msg); } /** * Retrieves the print stream that will be used for standard error. * * @return The print stream that will be used for standard error. */ public final PrintStream getErr() { return err; } /** * Retrieves the print stream that may be used to write to the original * standard error. This may be different from the current standard error * stream if an output file has been configured. * * @return The print stream that may be used to write to the original * standard error. */ public final PrintStream getOriginalErr() { return originalErr; } /** * Writes the provided message to the standard error stream for this tool. *

* This method is completely threadsafe and my be invoked concurrently by any * number of threads. * * @param msg The message components that will be written to the standard * error stream. They will be concatenated together on the same * line, and that line will be followed by an end-of-line * sequence. */ @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) public final synchronized void err(final Object... msg) { write(err, 0, 0, msg); } /** * Writes the provided message to the standard error stream for this tool, * optionally wrapping and/or indenting the text in the process. *

* This method is completely threadsafe and my be invoked concurrently by any * number of threads. * * @param indent The number of spaces each line should be indented. A * value less than or equal to zero indicates that no * indent should be used. * @param wrapColumn The column at which to wrap long lines. A value less * than or equal to two indicates that no wrapping should * be performed. If both an indent and a wrap column are * to be used, then the wrap column must be greater than * the indent. * @param msg The message components that will be written to the * standard output stream. They will be concatenated * together on the same line, and that line will be * followed by an end-of-line sequence. */ @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE) public final synchronized void wrapErr(final int indent, final int wrapColumn, final Object... msg) { write(err, indent, wrapColumn, msg); } /** * Writes the provided message to the given print stream, optionally wrapping * and/or indenting the text in the process. * * @param stream The stream to which the message should be written. * @param indent The number of spaces each line should be indented. A * value less than or equal to zero indicates that no * indent should be used. * @param wrapColumn The column at which to wrap long lines. A value less * than or equal to two indicates that no wrapping should * be performed. If both an indent and a wrap column are * to be used, then the wrap column must be greater than * the indent. * @param msg The message components that will be written to the * standard output stream. They will be concatenated * together on the same line, and that line will be * followed by an end-of-line sequence. */ private static void write(final PrintStream stream, final int indent, final int wrapColumn, final Object... msg) { write(stream, indent, indent, wrapColumn, true, msg); } /** * Writes the provided message to the given print stream, optionally wrapping * and/or indenting the text in the process. * * @param stream The stream to which the message should be * written. * @param firstLineIndent The number of spaces the first line should be * indented. A value less than or equal to zero * indicates that no indent should be used. * @param subsequentLineIndent The number of spaces all lines after the * first should be indented. A value less than * or equal to zero indicates that no indent * should be used. * @param wrapColumn The column at which to wrap long lines. A * value less than or equal to two indicates * that no wrapping should be performed. If * both an indent and a wrap column are to be * used, then the wrap column must be greater * than the indent. * @param endWithNewline Indicates whether a newline sequence should * follow the last line that is printed. * @param msg The message components that will be written * to the standard output stream. They will be * concatenated together on the same line, and * that line will be followed by an end-of-line * sequence. */ private static void write(final PrintStream stream, final int firstLineIndent, final int subsequentLineIndent, final int wrapColumn, final boolean endWithNewline, final Object... msg) { final StringBuilder buffer = new StringBuilder(); for (final Object o : msg) { buffer.append(o); } if (wrapColumn > 2) { boolean firstLine = true; for (final String line : wrapLine(buffer.toString(), (wrapColumn - firstLineIndent), (wrapColumn - subsequentLineIndent))) { final int indent; if (firstLine) { indent = firstLineIndent; firstLine = false; } else { stream.println(); indent = subsequentLineIndent; } if (indent > 0) { for (int i=0; i < indent; i++) { stream.print(' '); } } stream.print(line); } } else { if (firstLineIndent > 0) { for (int i=0; i < firstLineIndent; i++) { stream.print(' '); } } stream.print(buffer.toString()); } if (endWithNewline) { stream.println(); } stream.flush(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy