/** * Copyright 2015 Palantir Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.palantir.giraffe.command; import static com.google.common.base.Preconditions.checkArgument; import java.nio.file.Path; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.palantir.giraffe.file.UniformPath; /** * Provides command-independent information that influences how commands * execute. For example, the context controls the working directory in which * commands execute and the values of environment variables. * <p> * This class provides several static factory methods for creating common * contexts. For more complex contexts that set multiple properties, use the * {@linkplain #builder() builder}. * <p> * Instances of this class are immutable. * * @author bkeyes */ public final class CommandContext { private static final CommandContext EMPTY_CONTEXT = builder().build(); /** * Builds {@link CommandContext} objects. */ @SuppressWarnings({ "hiding", "checkstyle:hiddenfield" }) public static final class Builder { private CommandEnvironment environment = CommandEnvironment.defaultEnvironment(); private Predicate<Integer> exitStatusVerifier = Predicates.equalTo(0); private Optional<UniformPath> workingDir = Optional.absent(); private Optional<Integer> stdoutWindow = Optional.absent(); private Optional<Integer> stderrWindow = Optional.absent(); private Builder() { // use static builder() method externally } /** * Sets the {@linkplain CommandEnvironment environment} for commands * executed with this context. * * @param environment the environment * * @return this builder */ public Builder environment(CommandEnvironment environment) { this.environment = environment; return this; } /** * Sets the required exit status for commands executed with this * context. If the command exits with a different exit status, a * {@link CommandException} is thrown. * <p> * By default, an exit status of {@code 0} is required. * * @param status the required exit status * * @return this builder */ public Builder requireExitStatus(int status) { return requireExitStatus(Predicates.equalTo(status)); } /** * Allows any exit status for commands executed with this context. When * this is set, {@link CommandException} is never thrown. * * @return this builder */ public Builder ignoreExitStatus() { return requireExitStatus(Predicates.<Integer>alwaysTrue()); } /** * Indicates what the permissible exit status of a command execution is * through a Predicate. If the Predicate returns true, that is implied * to mean that the exit status is permissible. * * @param exitStatusVerifier The Predicate used to verify the exit * status * * @return this builder */ public Builder requireExitStatus(Predicate<Integer> exitStatusVerifier) { this.exitStatusVerifier = exitStatusVerifier; return this; } /** * Sets the working directory for commands executed with this context. * * @param path the working directory path * * @return this builder */ public Builder workingDirectory(UniformPath path) { workingDir = Optional.of(path); return this; } /** * Sets the working directory for commands executed with this context. * <p> * This method is equivalent to calling * {@link #workingDirectory(UniformPath)} after converting the path to a * {@code UniformPath}. * * @param path the working directory path * * @return this builder * * @see UniformPath#fromPath(Path) */ public Builder workingDirectory(Path path) { return workingDirectory(UniformPath.fromPath(path)); } /** * Discards all output from commands. Equivalent to * {@link #outputWindowSizes(int, int) outputWindowSizes(0, 0)}. * * @return this builder */ public Builder discardOutput() { return outputWindowSizes(0, 0); } /** * Sets the window sizes for the process output streams. * <p> * The size of the window determines the amount of recent output * buffered for reading. If data is written beyond the window size * without corresponding reads, data older than the window is discarded. * A window size of 0 discards all data. * <p> * By default, there is no window and buffering is limited by the * maximum size of the buffer array (effectively a window of size * {@code Integer.MAX_VALUE}). * * @param stdoutWindow the window for the output stream * @param stderrWindow the window for the error stream * * @return this builder */ public Builder outputWindowSizes(int stdoutWindow, int stderrWindow) { checkArgument(stdoutWindow >= 0, "stdoutWindow must be non-negative"); checkArgument(stderrWindow >= 0, "stderrWindow must be non-negative"); this.stdoutWindow = Optional.of(stdoutWindow); this.stderrWindow = Optional.of(stderrWindow); return this; } /** * Creates a new {@code CommandContext} using the settings configured by * this builder. The builder may be reused to create more contexts after * calling this method. */ public CommandContext build() { return new CommandContext(this); } } private final CommandEnvironment environment; private final Predicate<Integer> exitStatusVerifier; private final Optional<UniformPath> workingDir; private final Optional<Integer> stdoutWindow; private final Optional<Integer> stderrWindow; private CommandContext(Builder builder) { this.environment = builder.environment.copy(); this.exitStatusVerifier = builder.exitStatusVerifier; this.workingDir = builder.workingDir; this.stdoutWindow = builder.stdoutWindow; this.stderrWindow = builder.stderrWindow; } /** * Returns a new {@code CommandContext} builder. */ public static Builder builder() { return new Builder(); } /** * Returns the default context object. The default context uses the default * environment, the default working directory, and requires that commands * exit with status {@code 0}. */ public static CommandContext defaultContext() { return EMPTY_CONTEXT; } /** * Returns a context that ignores the exit status of commands. * * @see Builder#ignoreExitStatus() */ public static CommandContext ignoreExitStatus() { return builder().ignoreExitStatus().build(); } /** * Returns a context that requires commands exit with the given status. * * @param status the required exit status * * @see Builder#requireExitStatus(int) */ public static CommandContext requireExitStatus(int status) { return builder().requireExitStatus(status).build(); } /** * Returns a context that sets the given environment before commands * execute. * * @param environment the environment * * @see Builder#environment(CommandEnvironment) */ public static CommandContext withEnvironment(CommandEnvironment environment) { return builder().environment(environment).build(); } /** * Returns a context that executes commands in the given directory. * * @param path the working directory path * * @see Builder#workingDirectory(UniformPath) */ public static CommandContext workingDirectory(UniformPath path) { return builder().workingDirectory(path).build(); } /** * Returns a context that executes commands in the given directory. * * @param path the working directory path * * @see Builder#workingDirectory(Path) */ public static CommandContext workingDirectory(Path path) { return workingDirectory(UniformPath.fromPath(path)); } /** * Returns a copy of this context's {@code CommandEnvironment}. */ public CommandEnvironment getEnvironment() { return environment.copy(); } /** * Returns the predicate this context uses to verify the exit status of * commands. */ public Predicate<Integer> getExitStatusVerifier() { return exitStatusVerifier; } /** * Returns this context's working directory. If the returned * {@code Optional} is not present, this context uses the system-dependent * default working directory. */ public Optional<UniformPath> getWorkingDirectory() { return workingDir; } /** * Returns this context's output window. If the returned {@code Optional} is * not present, the window size is unlimited. */ public Optional<Integer> getStdoutWindowSize() { return stdoutWindow; } /** * Returns this context's error window. If the returned {@code Optional} is * not present, the window size is unlimited. */ public Optional<Integer> getStderrWindowSize() { return stderrWindow; } }