java/com/google/iosdevicecontrol/util/FluentLogger.java
public final class FluentLogger {
  /** Returns a generic fluent logger for a class. */
  public static FluentLogger forEnclosingClass() {
    StackTraceElement caller = new Throwable().getStackTrace()[1];
    Logger logger = Logger.getLogger(caller.getClassName());
    logger.setUseParentHandlers(false);
    logger.addHandler(new ConsoleHandler());
    return new FluentLogger(logger, Level.OFF, Optional.empty());
  }

  private final Logger logger;
  private final Level level;
  private final Optional<Throwable> cause;

  private FluentLogger(Logger logger, Level level, Optional<Throwable> cause) {
    this.level = level;
    this.logger = logger;
    this.cause = cause;
  }

  /** Returns a fluent logger with the specified cause. */
  public FluentLogger withCause(Throwable cause) {
    return new FluentLogger(logger, level, Optional.of(cause));
  }

  /** Convenience method for at({@link Level#INFO}). */
  public FluentLogger atInfo() {
    return at(Level.INFO);
  }

  /** Convenience method for at({@link Level#SEVERE}). */
  public FluentLogger atSevere() {
    return at(Level.SEVERE);
  }

  /** Convenience method for at({@link Level#WARNING}). */
  public FluentLogger atWarning() {
    return at(Level.WARNING);
  }

  /** Convenience method for at({@link Level#FINE}). */
  public FluentLogger atFine() {
    return at(Level.FINE);
  }

  /** Returns a fluent logger with the specified logging level. */
  public FluentLogger at(Level level) {
    return new FluentLogger(logger, level, cause);
  }

  /** Print a log of the {@link FluentLogger#cause}. */
  public void log() {
    log("");
  }

  /** Print a formatted log message. */
  public void log(String message, Object... params) {
    StackTraceElement caller = new Throwable().getStackTrace()[1];
    String formatMsg = String.format(message, params);
    if (cause.isPresent()) {
      logger.logp(level, logger.getName(), caller.getMethodName(), formatMsg, cause.get());
    } else {
      logger.logp(level, logger.getName(), caller.getMethodName(), formatMsg);
    }
  }
}
java/com/google/iosdevicecontrol/command/Command.java
@AutoValue
public abstract class Command {
  /**
   * The default command environment: equal to {@link System#getenv()}.
   */
  public static final ImmutableMap<String, String> SYSTEM_ENVIRONMENT =
      ImmutableMap.copyOf(System.getenv());

  /**
   * The default command executor: runs commands on the native OS.
   */
  public static final CommandExecutor NATIVE_EXECUTOR = NativeProcess.EXECUTOR;

  private static final Predicate<CommandResult> HAS_EXIT_CODE_ZERO =
      new Predicate<CommandResult>() {
        @Override
        public boolean apply(CommandResult result) {
          return result.exitCode() == 0;
        }
      };

  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  /**
   * Returns a command with the specified executable and arguments.
   */
  public static Command command(String executable, String... args) {
    return new AutoValue_Command(
        executable,
        ImmutableList.copyOf(args),
        SYSTEM_ENVIRONMENT,
        Optional.<Path>absent(),
        InputSource.fromProcess(),
        OutputSink.toProcessOut(),
        OutputSink.toProcessErr(),
        HAS_EXIT_CODE_ZERO,
        NATIVE_EXECUTOR);
  }

  /**
   * Returns a command with the specified executable path and arguments.
   */
  public static Command command(Path executable, String... args) {
    return command(sanitizePath(executable), args);
  }

  /**
   * Need to prefix paths that are just a filename with a ./, otherwise it will be treated as a
   * executable on the PATH, and fail.
   */
  private static String sanitizePath(Path executable) {
    return executable.equals(executable.getFileName())
        ? executable.getFileSystem().getPath(".").resolve(executable).toString()
        : executable.toString();
  }

  //
  // Accessor methods.
  //

  /**
   * Returns the executable.
   */
  public abstract String executable();

  /**
   * Returns the list of arguments to the executable.
   */
  public abstract ImmutableList<String> arguments();

  /**
   * Returns the map of environment variables under which the executable will run.
   */
  public abstract ImmutableMap<String, String> environment();

  /**
   * Returns the working directory where to run the command, or absent for the working directory of
   * the current Java process.
   */
  public abstract Optional<Path> workingDirectory();

  /**
   * Returns the source of input that will be piped to this command's standard input.
   */
  public abstract InputSource stdinSource();

  /**
   * Returns the sink to which output from the command's standard output will be piped.
   */
  public abstract OutputSink stdoutSink();

  /**
   * Returns the sink to which output from the command's standard error will be piped.
   */
  public abstract OutputSink stderrSink();

  /**
   * Returns the predicate on a command's result that determines whether a command's result is
   * successful.
   */
  public abstract Predicate<CommandResult> successCondition();

  /**
   * Returns the executor to run the command.
   */
  public abstract CommandExecutor executor();

  //
  // Fluent methods.
  //

  /**
   * Returns a command that behaves equivalently to this command, but with the specified executable
   * in place of the current executable.
   */
  public final Command withExecutable(Path executable) {
    return withExecutable(sanitizePath(executable));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified executable
   * in place of the current executable.
   */
  public final Command withExecutable(String executable) {
    return new AutoValue_Command(
        executable,
        arguments(),
        environment(),
        workingDirectory(),
        stdinSource(),
        stdoutSink(),
        stderrSink(),
        successCondition(),
        executor());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified arguments
   * in place of the current arguments.
   */
  public final Command withArguments(List<String> arguments) {
    return new AutoValue_Command(
        executable(),
        ImmutableList.copyOf(arguments),
        environment(),
        workingDirectory(),
        stdinSource(),
        stdoutSink(),
        stderrSink(),
        successCondition(),
        executor());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified arguments
   * in place of the current arguments.
   */
  public final Command withArguments(String first, String... rest) {
    return withArguments(listFromVarArgs(first, rest));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified arguments
   * appended to the current arguments.
   */
  public final Command withArgumentsAppended(List<String> arguments) {
    return withArguments(
        ImmutableList.<String>builder().addAll(arguments()).addAll(arguments).build());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified arguments
   * appended to the current arguments.
   */
  public final Command withArgumentsAppended(String first, String... rest) {
    return withArgumentsAppended(listFromVarArgs(first, rest));
  }

  private static List<String> listFromVarArgs(String first, String... rest) {
    return ImmutableList.<String>builder().add(first).add(rest).build();
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified environment
   * in place of the current environment.
   */
  public final Command withEnvironment(Map<String, String> environment) {
    return new AutoValue_Command(
        executable(),
        arguments(),
        ImmutableMap.copyOf(environment),
        workingDirectory(),
        stdinSource(),
        stdoutSink(),
        stderrSink(),
        successCondition(),
        executor());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified key-value
   * entries in place of the current environment.
   *
   * @throws IllegalArgumentException - if an odd number of key value arguments are given
   */
  public final Command withEnvironment(String firstKey, String firstValue, String... rest) {
    return withEnvironment(mapFromVarArgs(firstKey, firstValue, rest));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the current environment
   * updated by the specified environment.
   */
  public final Command withEnvironmentUpdated(Map<String, String> environment) {
    // This isn't as simple as ImmutableMap.builder().putAll(environment()).putAll(environment),
    // because the ImmutableMap builder prohibits duplicate keys.
    ImmutableMap.Builder<String, String> builder =
        ImmutableMap.<String, String>builder().putAll(environment);
    for (Entry<String, String> e : environment().entrySet()) {
      if (!environment.containsKey(e.getKey())) {
        builder.put(e);
      }
    }
    return withEnvironment(builder.build());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the current environment
   * updated by the specified key-value entries.
   *
   * @throws IllegalArgumentException - if an odd number of key/value arguments are given
   */
  public final Command withEnvironmentUpdated(String firstKey, String firstValue, String... rest) {
    return withEnvironmentUpdated(mapFromVarArgs(firstKey, firstValue, rest));
  }

  private static Map<String, String> mapFromVarArgs(
      String firstKey, String firstValue, String... rest) {
    checkArgument(rest.length % 2 == 0, "odd number of key/value arguments");
    ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
    builder.put(firstKey, firstValue);
    for (int i = 0; i < rest.length; i += 2) {
      builder.put(rest[i], rest[i + 1]);
    }
    return builder.build();
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified working
   * directory. An absent value signifies the working directory of the current Java process.
   */
  public final Command withWorkingDirectory(Optional<Path> directory) {
    return new AutoValue_Command(
        executable(),
        arguments(),
        environment(),
        directory,
        stdinSource(),
        stdoutSink(),
        stderrSink(),
        successCondition(),
        executor());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified working
   * directory on the default FileSystem.
   */
  public final Command withWorkingDirectory(String directory) {
    return withWorkingDirectory(Paths.get(directory));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified working
   * directory.
   */
  public final Command withWorkingDirectory(Path directory) {
    return withWorkingDirectory(Optional.of(directory));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stdin
   * source.
   */
  public final Command withStdinFrom(Path file) {
    return withStdinFrom(InputSource.fromFile(file));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stdin
   * source.
   */
  public final Command withStdinFromUtf8(String string) {
    return withStdinFrom(string, StandardCharsets.UTF_8);
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stdin
   * source.
   */
  public final Command withStdinFrom(String string, Charset charset) {
    return withStdinFrom(CharSource.wrap(string).asByteSource(charset));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stdin
   * source.
   */
  public final Command withStdinFrom(ByteSource byteSource) {
    return withStdinFrom(InputSource.fromStream(byteSource));
  }

  /**
   * Returns a command that behaves equivalently to this command, but whose stdin source is the
   * same as the JVM's (effectively {@link System#in}).
   */
  public final Command withStdinFromJvm() {
    return withStdinFrom(InputSource.fromJvm());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stdin
   * source.
   */
  public final Command withStdinFrom(InputSource stdinSource) {
    return new AutoValue_Command(
        executable(),
        arguments(),
        environment(),
        workingDirectory(),
        stdinSource,
        stdoutSink(),
        stderrSink(),
        successCondition(),
        executor());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stdout
   * sink.
   */
  public final Command withStdoutTo(Path file) {
    return withStdoutTo(OutputSink.toFile(file));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stdout
   * sink.
   */
  public final Command withStdoutTo(ByteSink byteSink) {
    return withStdoutTo(OutputSink.toStream(byteSink));
  }

  /**
   * Returns a command that behaves equivalently to this command, but whose stdout sink is the same
   * as the JVM's (effectively {@link System#out}).
   */
  public final Command withStdoutToJvm() {
    return withStdoutTo(OutputSink.toJvmOut());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stdout
   * sink.
   */
  public final Command withStdoutTo(OutputSink outputSink) {
    return new AutoValue_Command(
        executable(),
        arguments(),
        environment(),
        workingDirectory(),
        stdinSource(),
        outputSink,
        stderrSink(),
        successCondition(),
        executor());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stderr
   * sink.
   */
  public final Command withStderrTo(Path file) {
    return withStderrTo(OutputSink.toFile(file));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stderr
   * sink.
   */
  public final Command withStderrTo(ByteSink byteSink) {
    return withStderrTo(OutputSink.toStream(byteSink));
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stderr
   * sink.
   */
  public final Command withStderrToJvm() {
    return withStderrTo(OutputSink.toJvmErr());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified stderr
   * sink.
   */
  public final Command withStderrTo(OutputSink outputSink) {
    return new AutoValue_Command(
        executable(),
        arguments(),
        environment(),
        workingDirectory(),
        stdinSource(),
        stdoutSink(),
        outputSink,
        successCondition(),
        executor());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with a success condition that
   * tests whether the result has one of the specified exit codes.
   */
  public final Command withSuccessExitCodes(int first, int... rest) {
    ImmutableSet.Builder<Integer> builder = ImmutableSet.<Integer>builder().add(first);
    for (int exitCode : rest) {
      builder.add(exitCode);
    }
    final ImmutableSet<Integer> exitCodeSet = builder.build();
    return withSuccessCondition(
        new Predicate<CommandResult>() {
          @Override
          public boolean apply(CommandResult result) {
            return exitCodeSet.contains(result.exitCode());
          }
        });
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified success
   * condition.
   */
  public final Command withSuccessCondition(Predicate<CommandResult> condition) {
    return new AutoValue_Command(
        executable(),
        arguments(),
        environment(),
        workingDirectory(),
        stdinSource(),
        stdoutSink(),
        stderrSink(),
        condition,
        executor());
  }

  /**
   * Returns a command that behaves equivalently to this command, but with the specified executor
   * in place of the current executor.
   */
  public final Command withExecutor(CommandExecutor executor) {
    return new AutoValue_Command(
        executable(),
        arguments(),
        environment(),
        workingDirectory(),
        stdinSource(),
        stdoutSink(),
        stderrSink(),
        successCondition(),
        executor);
  }

  //
  // Execution methods.
  //

  /**
   * Starts the command running asynchronously.
   *
   * @throws CommandStartException - if there was an error starting the command
   */
  public final CommandProcess start() throws CommandStartException {
    logger.atFine().log("Started command: %s", this);
    return executor().start(this);
  }

  /**
   * Starts the command and blocks until it has completed.
   *
   * <p>Blocks until the command completes. It returns the command result if the result satisfies
   * the command's success condition (by default a zero exit code), and throws {@link
   * CommandFailureException} otherwise. If this thread is interrupted, this method kills the
   * process and throws {@link InterruptedException}.
   *
   * @throws CommandStartException - if there was an error starting the command
   * @throws CommandFailureException - if the result fails the command's success condition
   * @throws InterruptedException - if the execution is interrupted
   */
  @CanIgnoreReturnValue
  public final CommandResult execute()
      throws CommandStartException, CommandFailureException, InterruptedException {
    CommandProcess process = start();
    try {
      return process.await();
    } catch (InterruptedException e) {
      logger.atFine().log("Thread interrupted; killing command: %s", this);
      process.kill();
      throw e;
    }
  }
}
java/com/google/iosdevicecontrol/IosAppBundleId.java
public final class IosAppBundleId {
  // Implementation note: I didn't use @AutoValue, because I didn't want to add an additional
  // property method to this class beyond toString. See: https://github.com/google/auto/issues/357

  private static final Pattern VALID_UTI_PATTERN = Pattern.compile("^[\\w-\\.]+$");

  /**
   * Returns the bundle identifier read from a ".ipa" application archive file. This is equivalent
   * to {@code IosAppInfo.readFromIpa(ipaPath).bundleId()}.
   */
  public static IosAppBundleId readFromIpa(Path ipaPath) throws IOException {
    return IosAppInfo.readFromPath(ipaPath).bundleId();
  }

  private final String string;

  /**
   * Constructs a bundle ID from the given string. This constructor checks that the specified string
   * is a valid Apple Uniform Type Identifier (UTI), meaning it contains only alphanumeric
   * (A-Z,a-z,0-9), hyphen (-), and period (.) characters.
   *
   * @throws IllegalArgumentException if the string is not a valid Apple UTI.
   */
  public IosAppBundleId(String string) {
    checkArgument(VALID_UTI_PATTERN.matcher(string).matches());
    this.string = string;
  }

  @Override
  public String toString() {
    return string;
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(string);
  }

  @Override
  public boolean equals(Object o) {
    return o instanceof IosAppBundleId && string.equals(((IosAppBundleId) o).string);
  }
}
java/com/google/iosdevicecontrol/command/CommandResult.java
public final class CommandResult {
  private final int exitCode;
  private final CapturingOutputStream stdout;
  private final CapturingOutputStream stderr;

  CommandResult(int exitCode, CapturingOutputStream stdout, CapturingOutputStream stderr) {
    this.exitCode = exitCode;
    this.stdout = stdout;
    this.stderr = stderr;
  }

  /** Returns the exit code of the command execution. */
  public int exitCode() {
    return exitCode;
  }

  /** Returns the standard output of the command execution as a new array of bytes. */
  public byte[] stdoutBytes() {
    return stdout.toByteArray();
  }

  /** Returns the standard error of the command execution as a new array of bytes. */
  public byte[] stderrBytes() {
    return stderr.toByteArray();
  }

  /** Returns the standard output of the command execution as a string. */
  public String stdoutString(Charset cs) {
    return stdout.toString(cs);
  }

  /** Returns the standard error of the command execution as a string. */
  public String stderrString(Charset cs) {
    return stderr.toString(cs);
  }

  /** Returns the standard output of the command execution as a UTF-8 string. */
  public String stdoutStringUtf8() {
    return stdoutString(UTF_8);
  }

  /** Returns the standard error of the command execution as a UTF-8 string. */
  public String stderrStringUtf8() {
    return stderrString(UTF_8);
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .add("exit code", exitCode)
        .add("stdout", stdoutStringUtf8())
        .add("stderr", stderrStringUtf8())
        .toString();
  }
}
java/com/google/iosdevicecontrol/util/RetryCallable.java
public final class RetryCallable<V, X extends Exception> implements CheckedCallable<V, X> {
  /** Default number of maximum attempts: 3. */
  public static final int DEFAULT_MAX_ATTEMPTS = 3;

  /**
   * Creates a callable that retries the specified task up to the default number of attempts, with
   * no delay between attempts and a noop exception handler.
   */
  public static <V, X extends Exception> RetryCallable<V, X> retry(
      CheckedCallable<? extends V, ? extends X> task) {
    return new RetryCallable<>(
        task, DEFAULT_MAX_ATTEMPTS, Duration.ZERO, false, ExceptionHandler.NOOP);
  }

  /**
   * Creates a callable that retries the specified task up to the default number of attempts, with
   * no delay between attempts and a noop exception handler.
   */
  public static <X extends Exception> RetryCallable<Void, X> retry(VoidCallable<? extends X> task) {
    return retry(CheckedCallables.fromVoidCallable(task));
  }

  private final CheckedCallable<? extends V, ? extends X> task;
  private final int maxAttempts;
  private final Duration delay;
  private final boolean delayedFirstAttempt;
  private final ExceptionHandler<? super X> exceptionHandler;

  private RetryCallable(
      CheckedCallable<? extends V, ? extends X> task,
      int maxAttempts,
      Duration delay,
      boolean delayedFirstAttempt,
      ExceptionHandler<? super X> exceptionHandler) {
    this.task = checkNotNull(task);
    checkArgument(maxAttempts > 0);
    this.maxAttempts = maxAttempts;
    this.delay = checkNotNull(delay);
    this.delayedFirstAttempt = delayedFirstAttempt;
    this.exceptionHandler = checkNotNull(exceptionHandler);
  }

  /**
   * Returns a callable equivalent to this one but with the specified number of maximum attempts.
   */
  public RetryCallable<V, X> withMaxAttempts(int maxAttempts) {
    return new RetryCallable<>(task, maxAttempts, delay, delayedFirstAttempt, exceptionHandler);
  }

  /** Returns a callable equivalent to this one but with the specified delay between attempts. */
  public RetryCallable<V, X> withDelay(Duration delay) {
    return new RetryCallable<>(task, maxAttempts, delay, delayedFirstAttempt, exceptionHandler);
  }

  /** Returns a callable equivalent to this one but with the delay before the first attempt. */
  public RetryCallable<V, X> withDelayedFirstAttempt() {
    return new RetryCallable<>(task, maxAttempts, delay, delayedFirstAttempt, exceptionHandler);
  }

  /** Returns a callable equivalent to this one but with the specified exception handler. */
  public RetryCallable<V, X> withExceptionHandler(ExceptionHandler<? super X> exceptionHandler) {
    return new RetryCallable<>(task, maxAttempts, delay, delayedFirstAttempt, exceptionHandler);
  }

  /**
   * Repeatedly attempts a task up to the specified (or default) number of maximum attempts,
   * handling each checked exception thrown by the task with the specified (or default) exception
   * handler, and then waiting the specified (or default) delay between attempts.
   *
   * <p>If the maximum number of attempts has been reached, the first exception is thrown with all
   * remaining exceptions added as suppressed exceptions to the original. If the exception handler
   * throws a checked exception, it is added as a suppressed exception to the original, and the
   * original exception is thrown immediately with no further retries attempted. If this thread is
   * interrupted, an {@link InterruptedException} is added as suppressed to the original, and the
   * original exception is thrown immediately with no further retries attempted. If the task or the
   * exception handler throws an unchecked exception, this unchecked exception is propagated
   * immediately with no retry.
   */
  @Override
  public V call() throws X {
    if (delayedFirstAttempt) {
      Uninterruptibles.sleepUninterruptibly(delay.getMillis(), MILLISECONDS);
    }

    X cause = null;
    for (int i = 0; i < maxAttempts; i++) {
      try {
        return task.call();
      } catch (RuntimeException e) {
        // Propagate unchecked exceptions immediately.
        throw e;
      } catch (Exception e) {
        // If it's a checked exception, it's necessarily of type X.
        @SuppressWarnings("unchecked")
        X failure = (X) e;
        if (cause == null) {
          cause = failure;
        } else {
          cause.addSuppressed(failure);
        }

        // Handle the failure.
        try {
          exceptionHandler.handle(failure);
        } catch (RuntimeException e2) {
          // Propagate unchecked exceptions immediately.
          throw e2;
        } catch (Exception e2) {
          // A checked exception is marked suppressed and the call ends immediately.
          if (!e2.equals(failure)) {
            cause.addSuppressed(e2);
          }
          break;
        }

        // Sleep for the specified delay.
        try {
          Thread.sleep(delay.getMillis());
        } catch (InterruptedException ie) {
          Thread.currentThread().interrupt();
          cause.addSuppressed(ie);
          break;
        }
      }
    }

    throw Verify.verifyNotNull(cause);
  }
}
java/com/google/iosdevicecontrol/util/ForwardingSocket.java
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public class ForwardingSocket extends Socket {
  private final Socket delegate;

  public ForwardingSocket(Socket delegate) {
    this.delegate = checkNotNull(delegate);
  }

  @Override
  public void connect(SocketAddress endpoint) throws IOException {
    delegate.connect(endpoint);
  }

  @Override
  public void connect(SocketAddress endpoint, int timeout) throws IOException {
    delegate.connect(endpoint, timeout);
  }

  @Override
  public void bind(SocketAddress bindpoint) throws IOException {
    delegate.bind(bindpoint);
  }

  @Override
  public InetAddress getInetAddress() {
    return delegate.getInetAddress();
  }

  @Override
  public InetAddress getLocalAddress() {
    return delegate.getLocalAddress();
  }

  @Override
  public int getPort() {
    return delegate.getPort();
  }

  @Override
  public int getLocalPort() {
    return delegate.getLocalPort();
  }

  @Override
  public SocketAddress getRemoteSocketAddress() {
    return delegate.getRemoteSocketAddress();
  }

  @Override
  public SocketAddress getLocalSocketAddress() {
    return delegate.getLocalSocketAddress();
  }

  @Override
  public SocketChannel getChannel() {
    return delegate.getChannel();
  }

  @Override
  public InputStream getInputStream() throws IOException {
    return delegate.getInputStream();
  }

  @Override
  public OutputStream getOutputStream() throws IOException {
    return delegate.getOutputStream();
  }

  @Override
  public void setTcpNoDelay(boolean on) throws SocketException {
    delegate.setTcpNoDelay(on);
  }

  @Override
  public boolean getTcpNoDelay() throws SocketException {
    return delegate.getTcpNoDelay();
  }

  @Override
  public void setSoLinger(boolean on, int linger) throws SocketException {
    delegate.setSoLinger(on, linger);
  }

  @Override
  public int getSoLinger() throws SocketException {
    return delegate.getSoLinger();
  }

  @Override
  public void sendUrgentData(int data) throws IOException {
    delegate.sendUrgentData(data);
  }

  @Override
  public void setOOBInline(boolean on) throws SocketException {
    delegate.setOOBInline(on);
  }

  @Override
  public boolean getOOBInline() throws SocketException {
    return delegate.getOOBInline();
  }

  @Override
  public void setSoTimeout(int timeout) throws SocketException {
    delegate.setSoTimeout(timeout);
  }

  @Override
  public int getSoTimeout() throws SocketException {
    return delegate.getSoTimeout();
  }

  @Override
  public void setSendBufferSize(int size) throws SocketException {
    delegate.setSendBufferSize(size);
  }

  @Override
  public int getSendBufferSize() throws SocketException {
    return delegate.getSendBufferSize();
  }

  @Override
  public void setReceiveBufferSize(int size) throws SocketException {
    delegate.setReceiveBufferSize(size);
  }

  @Override
  public int getReceiveBufferSize() throws SocketException {
    return delegate.getReceiveBufferSize();
  }

  @Override
  public void setKeepAlive(boolean on) throws SocketException {
    delegate.setKeepAlive(on);
  }

  @Override
  public boolean getKeepAlive() throws SocketException {
    return delegate.getKeepAlive();
  }

  @Override
  public void setTrafficClass(int tc) throws SocketException {
    delegate.setTrafficClass(tc);
  }

  @Override
  public int getTrafficClass() throws SocketException {
    return delegate.getTrafficClass();
  }

  @Override
  public void setReuseAddress(boolean on) throws SocketException {
    delegate.setReuseAddress(on);
  }

  @Override
  public boolean getReuseAddress() throws SocketException {
    return delegate.getReuseAddress();
  }

  @Override
  public void close() throws IOException {
    delegate.close();
  }

  @Override
  public void shutdownInput() throws IOException {
    delegate.shutdownInput();
  }

  @Override
  public void shutdownOutput() throws IOException {
    delegate.shutdownOutput();
  }

  @Override
  public boolean isConnected() {
    return delegate.isConnected();
  }

  @Override
  public boolean isBound() {
    return delegate.isBound();
  }

  @Override
  public boolean isClosed() {
    return delegate.isClosed();
  }

  @Override
  public boolean isInputShutdown() {
    return delegate.isInputShutdown();
  }

  @Override
  public boolean isOutputShutdown() {
    return delegate.isOutputShutdown();
  }

  @Override
  public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
    delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
  }

  @Override
  public String toString() {
    return delegate.toString();
  }
}
java/com/google/iosdevicecontrol/util/CheckedCallable.java
public interface CheckedCallable<V, X extends Exception> {
  @CanIgnoreReturnValue
  V call() throws X;
}
java/com/google/iosdevicecontrol/IosAppInfo.java
@AutoValue
public abstract class IosAppInfo {
  /** Returns the application info read from either an app folder or ipa archive */
  public static IosAppInfo readFromPath(Path ipaOrAppPath) throws IOException {
    NSObject plistDict;
    if (Files.isDirectory(ipaOrAppPath)) {
      plistDict = PlistParser.fromPath(ipaOrAppPath.resolve("Info.plist"));
    } else {
      try (FileSystem ipaFs = FileSystems.newFileSystem(ipaOrAppPath, null)) {
        Path appPath =
            MoreFiles.listFiles(ipaFs.getPath("Payload"))
                .stream()
                // Can't use Files.isDirectory, because no entry is a "directory" in a zip.
                .filter(e -> e.toString().endsWith(".app/"))
                .collect(MoreCollectors.onlyElement());
        plistDict = PlistParser.fromPath(appPath.resolve("Info.plist"));
      }
    }
    return readFromPlistDictionary((NSDictionary) plistDict);
  }

  /** Returns the application info read from a plist dictionary. */
  public static IosAppInfo readFromPlistDictionary(NSDictionary dict) {
    return builder()
        .bundleId(new IosAppBundleId(dict.get("CFBundleIdentifier").toString()))
        .build();
  }

  /** Returns a new builder. */
  public static Builder builder() {
    return new AutoValue_IosAppInfo.Builder();
  }

  /** A builder for {@code IosAppInfo}. */
  @AutoValue.Builder
  public abstract static class Builder {
    public abstract Builder bundleId(IosAppBundleId bundleId);

    public abstract IosAppInfo build();
  }

  /** The bundle identifier of the application. */
  public abstract IosAppBundleId bundleId();
}
java/com/google/iosdevicecontrol/command/CommandProcess.java
public abstract class CommandProcess {
  private final Command command;
  private final RawProcess rawProcess;
  private final CapturingOutputStream stdoutStream;
  private final CapturingOutputStream stderrStream;
  private final Optional<AsyncCopier> stdinPump;
  private final Optional<AsyncCopier> stdoutPump;
  private final Optional<AsyncCopier> stderrPump;

  private volatile CommandResult result = null;

  protected CommandProcess(Command command, RawProcess rawProcess) throws CommandStartException {
    this.command = checkNotNull(command);
    this.rawProcess = checkNotNull(rawProcess);
    stdoutStream = new CapturingOutputStream();
    stderrStream = new CapturingOutputStream();

    Supplier<Level> ioLogLevel = new Supplier<Level>() {
      @Override
      public Level get() {
        return CommandProcess.this.rawProcess.isAlive() ? Level.WARNING : Level.FINE;
      }
    };

    try {
      stdinPump =
          maybeStartCopyFromSourceToPipe(command.stdinSource(), rawProcess.stdinPipe(), ioLogLevel);
      stdoutPump =
          maybeStartCopyFromPipeToSink(
              rawProcess.stdoutPipe(),
              command.stdoutSink(),
              stdoutStream,
              stderrStream,
              ioLogLevel);
      stderrPump =
          maybeStartCopyFromPipeToSink(
              rawProcess.stderrPipe(),
              command.stderrSink(),
              stdoutStream,
              stderrStream,
              ioLogLevel);
    } catch (IOException e) {
      throw new CommandStartException(command, e);
    }
  }

  private static Optional<AsyncCopier> maybeStartCopyFromSourceToPipe(
      InputSource inputSource, Optional<OutputStream> pipe, Supplier<Level> ioLogLevel)
      throws IOException {
    if (pipe.isPresent() && !inputSource.kind().equals(InputSource.Kind.PROCESS)) {
      InputStream sourceStream = inputSource.byteSource().get().openStream();
      return Optional.of(AsyncCopier.start(sourceStream, pipe.get(), ioLogLevel));
    } else {
      return Optional.absent();
    }
  }

  private static Optional<AsyncCopier> maybeStartCopyFromPipeToSink(
      Optional<InputStream> pipe,
      OutputSink outputSink,
      OutputStream stdoutStream,
      OutputStream stderrStream,
      Supplier<Level> ioLogLevel)
      throws IOException {
    if (pipe.isPresent()) {
      OutputStream sinkStream;
      switch (outputSink.kind()) {
        case PROCESS_OUT:
          sinkStream = stdoutStream;
          break;
        case PROCESS_ERR:
          sinkStream = stderrStream;
          break;
        default:
          sinkStream = outputSink.byteSink().get().openStream();
      }
      return Optional.of(AsyncCopier.start(pipe.get(), sinkStream, ioLogLevel));
    } else {
      return Optional.absent();
    }
  }

  /** The command that started this process. */
  public final Command command() {
    return command;
  }

  /** Returns whether the process has not yet terminated. */
  public final boolean isAlive() {
    return rawProcess.isAlive();
  }

  /**
   * Blocks until the command completes. It returns the command result if the result satisfies the
   * command's success condition (by default a zero exit code), and throws {@link
   * CommandFailureException} otherwise.
   *
   * <p>If this thread is interrupted, an {@link InterruptedException} is thrown, but the <i>process
   * will continue running</i>. If you wish the process to be killed in this case, catch this
   * exception and call {@link CommandProcess#kill} on the running process.
   *
   * @throws CommandFailureException - if the result fails the command's success condition
   * @throws InterruptedException - if the execution is interrupted
   */
  @CanIgnoreReturnValue
  public final CommandResult await() throws CommandFailureException, InterruptedException {
    rawProcess.await(); // returns immediately if process already complete
    return processFinished();
  }

  /**
   * Blocks until the command completes or the timeout is reached. If the timeout is reached before
   * the command finishes, {@link TimeoutException} is thrown. It returns the command result if
   * the result satisfies the command's success condition (by default a zero exit code), and throws
   * {@link CommandFailureException} otherwise.
   *
   * <p>If this thread is interrupted, an {@link InterruptedException} is thrown, but the <i>process
   * will continue running</i>. If you wish the process to be killed in this case, catch this
   * exception and call {@link CommandProcess#kill} on the running process.
   *
   * @throws CommandFailureException - if the result fails the command's success condition
   * @throws InterruptedException - if the execution is interrupted
   */
  @CanIgnoreReturnValue
  public final CommandResult await(long timeout, TimeUnit unit)
      throws CommandFailureException, InterruptedException, TimeoutException {
    if (rawProcess.await(timeout, unit)) {
      return processFinished();
    }
    throw new TimeoutException(
        String.format("%s did not complete after %d %s.", command, timeout, unit));
  }

  /**
   * A helper method for cleaning up once the process has finished. This method should only be
   * called once the caller knows the process has terminated (e.g. by calling {link #await}).
   *
   * @throws CommandFailureException if the command fails.
   * @throws InterruptedException if the raw process is interrupted.
   */
  private CommandResult processFinished()
      throws CommandFailureException, InterruptedException {
    // Double-checked locking to compute the result at most once.
    if (result == null) {
      synchronized (this) {
        if (result == null) {
          verify(!rawProcess.isAlive());
          int exitCode = rawProcess.await();
          // The process has ended, so there is no need to wait indefinitely for the stdin pump,
          // which may in theory be blocked or simply unbounded; just force stop it now.
          if (stdinPump.isPresent()) {
            stdinPump.get().stop();
          }
          // However, wait indefinitely for the stdout and stderr pumps before returning. The
          // default case of writing to a CapturingOutputStream will never block forever; if the
          // user provides a custom output stream, the burden is on them that it doesn't either.
          if (stdoutPump.isPresent()) {
            stdoutPump.get().await();
          }
          if (stderrPump.isPresent()) {
            stderrPump.get().await();
          }
          result = new CommandResult(exitCode, stdoutStream, stderrStream);
        }
      }
    }

    if (command.successCondition().apply(result)) {
      return result;
    } else {
      throw new CommandFailureException(command, result);
    }
  }

  /**
   * Sends a signal to terminate the process and returns immediately. Killing a process that has
   * already exited has no effect. To wait for the process to be killed, use {@code kill().await()}.
   *
   * <p>This method makes a best-effort attempt to read in any data already written to stdout or
   * stderr before the process is terminated. Reading output from a process as you kill it is
   * inherently racy, so if there's any output you expect to see it should be read explicitly via
   * {@link #stdoutStream}/{@link #stderrStream} before calling this method.
   *
   * <p><b>Implementation detail:</b> manual benchmarking suggests {@link Process} will buffer up to
   * 60KB internally, which this method should be able to retrieve.
   */
  @CanIgnoreReturnValue
  public final CommandProcess kill() {
    rawProcess.kill();
    return this;
  }

  /** Returns a new {@link InputStream} connected to the standard output of the process. */
  public final InputStream stdoutStream() {
    return stdoutStream.openInputStream();
  }

  /** Returns a new {@link InputStream} connected to the standard error of the process. */
  public final InputStream stderrStream() {
    return stderrStream.openInputStream();
  }

  /** Returns a new {@link Reader} connected to the standard output of the process. */
  public final Reader stdoutReader(Charset cs) {
    return new InputStreamReader(stdoutStream(), cs);
  }

  /** Returns a new {@link Reader} connected to the standard error of the process. */
  public final Reader stderrReader(Charset cs) {
    return new InputStreamReader(stderrStream(), cs);
  }

  /**
   * Returns a new {@link Reader} connected to the standard output of the process, assuming the
   * output uses UTF-8 encoding.
   */
  public final Reader stdoutReaderUtf8() {
    return stdoutReader(UTF_8);
  }

  /**
   * Returns a new {@link Reader} connected to the standard error of the process, assuming the
   * output uses UTF-8 encoding.
   */
  public final Reader stderrReaderUtf8() {
    return stderrReader(UTF_8);
  }

  @Override
  public final int hashCode() {
    // All implementations must use reference equality.
    return super.hashCode();
  }

  @Override
  public final boolean equals(Object o) {
    // All implementations must use reference equality.
    return super.equals(o);
  }

  @Override
  public final String toString() {
    return MoreObjects.toStringHelper(this)
        .add("command", command)
        .add("alive", isAlive())
        .toString();
  }

  /**
   * A low-level process API used to implement {@link CommandProcess}. This is a bare-bones
   * "strategy" object that executors use to create instances of the more user-friendly
   * CommandProcess type. The alternative to the strategy object design is a complicated combination
   * of constructor args and abstract hook methods on CommandProcess. Also, unlike hook methods, a
   * strategy object allows us to safely invoke these methods in the CommandProcess constructor, a
   * feature which CommandProcess uses to start a StdinPump, for example.
   */
  protected interface RawProcess {
    /**
     * Returns an output stream that writes to the standard input of the process; absent when the
     * process has redirected its stdin and, therefore, isn't reading input over a pipe.
     */
    Optional<OutputStream> stdinPipe();

    /**
     * Returns an input stream that reads from the standard output of the process; absent when the
     * process has redirected its stdout and, therefore, isn't writing output over a pipe.
     */
    Optional<InputStream> stdoutPipe();

    /**
     * Returns an input stream that reads from the standard error of the process; absent when the
     * process has redirected its stderr and, therefore, isn't writing error over a pipe.
     */
    Optional<InputStream> stderrPipe();

    /** Returns whether the process has not yet terminated. */
    boolean isAlive();

    /**
     * Waits for the process to terminate and returns its exit code.
     *
     * @throws InterruptedException - if the execution is interrupted
     */
    @CanIgnoreReturnValue
    int await() throws InterruptedException;

    /**
     * Waits up to timeout for the process to terminate, returning true if it did so.
     *
     * @throws InterruptedException - if the execution is interrupted
     */
    boolean await(long timeout, TimeUnit unit) throws InterruptedException;

    /** Sends a signal to terminate the process and returns immediately. */
    void kill();
  }
}
java/com/google/iosdevicecontrol/IosDeviceSocket.java
public abstract class IosDeviceSocket extends IosDeviceResource {
  /** Wraps an iOS device and a Java socket as an IosDeviceSocket. */
  public static IosDeviceSocket wrap(IosDevice device, Socket socket) throws IosDeviceException {
    return new SocketWrapper(device, socket);
  }

  protected IosDeviceSocket(IosDevice device) {
    super(device);
  }

  /**
   * Read bytes from the socket until the array is full or EOF is reached, returning the number of
   * bytes read. The returned value is less than the length of the array only when EOF is reached.
   */
  public abstract int read(byte[] bytes) throws IosDeviceException;

  /** Writes bytes to the socket. */
  public abstract void write(byte[] bytes) throws IosDeviceException;

  /** Wraps a device and a Java socket as an IosDeviceSocket. */
  private static class SocketWrapper extends IosDeviceSocket {
    private final InputStream socketIn;
    private final OutputStream socketOut;
    private final Closeable socketClose;

    private SocketWrapper(IosDevice device, Socket socket) throws IosDeviceException {
      super(device);
      try {
        socketIn = socket.getInputStream();
        socketOut = socket.getOutputStream();
      } catch (IOException | RuntimeException e) {
        try {
          socket.close();
        } catch (IOException ce) {
          e.addSuppressed(ce);
        }
        throw new IosDeviceException(device, e);
      }
      socketClose = socket::close;
    }

    @Override
    public int read(byte[] bytes) throws IosDeviceException {
      try {
        return ByteStreams.read(socketIn, bytes, 0, bytes.length);
      } catch (IOException e) {
        throw new IosDeviceException(device(), e);
      }
    }

    @Override
    public void write(byte[] bytes) throws IosDeviceException {
      try {
        socketOut.write(bytes);
      } catch (IOException e) {
        throw new IosDeviceException(device(), e);
      }
    }

    @Override
    public void close() throws IosDeviceException {
      try {
        socketClose.close();
      } catch (IOException e) {
        throw new IosDeviceException(device(), e);
      }
    }
  }
}
java/com/google/iosdevicecontrol/IosAppProcess.java
public interface IosAppProcess {
  /**
   * Kills the process and returns immediately. To kill and then wait for the process to terminate,
   * use kill().await().
   */
  IosAppProcess kill();

  /** Waits for the app to terminate and returns its output. */
  String await() throws IosDeviceException, InterruptedException;

  /** Waits for the app to terminate up to the specified timeout and returns its output. */
  String await(Duration timeout) throws IosDeviceException, InterruptedException, TimeoutException;

  /** Returns a new reader to the UTF-8 encoded streamed output. */
  Reader outputReader();
}
java/com/google/iosdevicecontrol/util/CheckedCallables.java
public final class CheckedCallables {
  /**
   * Returns a a {@link CheckedCallable} that always returns <code>instance</code>.
   */
  public static <V, X extends Exception> CheckedCallable<V, X> returning(V instance) {
    return new CheckedCallableReturning<>(instance);
  }

  private static class CheckedCallableReturning<V, X extends Exception>
      implements CheckedCallable<V, X>, Serializable {
    private final V instance;

    private CheckedCallableReturning(V instance) {
      this.instance = instance;
    }

    @Override
    public V call() {
      return instance;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof CheckedCallableReturning) {
        CheckedCallableReturning<?, ?> that = (CheckedCallableReturning<?, ?>) obj;
        return Objects.equals(instance, that.instance);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(instance);
    }

    @Override
    public String toString() {
      return "CheckedCallables.returning(" + instance + ")";
    }

    private static final long serialVersionUID = 0;
  }

  /**
   * Wraps a {@link Callable} as a {@link CheckedCallable}.
   */
  @SuppressWarnings("unchecked")
  public static <V> CheckedCallable<V, Exception> fromCallable(Callable<? extends V> c) {
    return c instanceof CheckedCallable
        ? (CheckedCallable<V, Exception>) c
        : new CheckedCallableFromCallable<V>(c);
  }

  private static class CheckedCallableFromCallable<V>
      implements CheckedCallable<V, Exception>, Serializable {
    private final Callable<? extends V> delegate;

    private CheckedCallableFromCallable(Callable<? extends V> delegate) {
      this.delegate = checkNotNull(delegate);
    }

    @Override
    public V call() throws Exception {
      return delegate.call();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof CheckedCallableFromCallable) {
        CheckedCallableFromCallable<?> that = (CheckedCallableFromCallable<?>) obj;
        return delegate.equals(that.delegate);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(delegate);
    }

    @Override
    public String toString() {
      return "CheckedCallables.fromCallable(" + delegate + ")";
    }

    private static final long serialVersionUID = 0;
  }

  /**
   * Wraps a {@link VoidCallable} as a {@link CheckedCallable}.
   */
  public static <X extends Exception> CheckedCallable<Void, X> fromVoidCallable(
      VoidCallable<? extends X> c) {
    return new CheckedCallableFromVoidCallable<X>(c);
  }

  private static class CheckedCallableFromVoidCallable<X extends Exception>
      implements CheckedCallable<Void, X>, Serializable {
    private final VoidCallable<? extends X> delegate;

    private CheckedCallableFromVoidCallable(VoidCallable<? extends X> delegate) {
      this.delegate = checkNotNull(delegate);
    }

    @Override
    public Void call() throws X {
      delegate.call();
      return null;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof CheckedCallableFromVoidCallable) {
        CheckedCallableFromVoidCallable<?> that = (CheckedCallableFromVoidCallable<?>) obj;
        return delegate.equals(that.delegate);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(delegate);
    }

    @Override
    public String toString() {
      return "CheckedCallables.fromVoidCallable(" + delegate + ")";
    }

    private static final long serialVersionUID = 0;
  }

  /**
   * Returns a {@link CheckedCallable} which caches the instance retrieved or exception thrown
   * during the first call and returns that value or throws that exception on subsequent calls.
   */
  public static <V, X extends Exception> CheckedCallable<V, X> memoize(CheckedCallable<V, X> c) {
    return c instanceof MemoizingCheckedCallable ? c : new MemoizingCheckedCallable<V, X>(c);
  }

  private static class MemoizingCheckedCallable<V, X extends Exception>
      implements CheckedCallable<V, X>, Serializable {
    private final CheckedCallable<V, X> delegate;

    private transient volatile boolean initialized;
    // The "value" and "exception" fields do not need to be volatile; their visibility piggy-backs
    // on volatile read of "initialized".
    private transient V value;
    private transient Exception exception;

    private MemoizingCheckedCallable(CheckedCallable<V, X> delegate) {
      this.delegate = checkNotNull(delegate);
    }

    @Override
    @SuppressWarnings("unchecked")
    public V call() throws X {
      // Double Checked Locking.
      if (!initialized) {
        synchronized (this) {
          if (!initialized) {
            try {
              value = delegate.call();
            } catch (Exception e) {
              exception = e;
            }
            initialized = true;
          }
        }
      }

      if (exception == null) {
        return value;
      } else {
        // The exception either be of generic type X or a RuntimeException, but a generic cast will
        // succeed in either case due to type erasure.
        throw (X) exception;
      }
    }

    @Override
    public String toString() {
      return "CheckedCallables.memoize(" + delegate + ")";
    }

    private static final long serialVersionUID = 0;
  }

  private CheckedCallables() {}
}
java/com/google/iosdevicecontrol/IosDeviceException.java
public class IosDeviceException extends Exception {
  /** Remedies to some kinds of iOS device errors. */
  public enum Remedy {
    DISMISS_DIALOG,
    REINSTALL_APP,
    RESTART_DEVICE,
  }

  private final IosDevice device;
  private final Optional<Remedy> remedy;

  public IosDeviceException(IosDevice device, String message) {
    this(device, device + ": " + message, null, Optional.<Remedy>absent());
  }

  public IosDeviceException(IosDevice device, Throwable cause) {
    this(device, device.toString(), checkNotNull(cause), Optional.<Remedy>absent());
  }

  public IosDeviceException(IosDevice device, Throwable cause, Remedy remedy) {
    this(device, device.toString(), checkNotNull(cause), Optional.of(remedy));
  }

  private IosDeviceException(
      IosDevice device, String message, @Nullable Throwable cause, Optional<Remedy> remedy) {
    super(message, cause);
    this.device = checkNotNull(device);
    this.remedy = remedy;
  }

  /** Returns the device on which the error occurred. */
  public final IosDevice device() {
    return device;
  }

  /** Suggested remedy that <i>may</i> resolve the cause of the error. */
  public final Optional<Remedy> remedy() {
    return remedy;
  }
}
java/com/google/iosdevicecontrol/real/DevDiskImages.java
@AutoValue
abstract static class DiskImage {
  abstract Path imagePath();
  abstract Path signaturePath();
}
java/com/google/iosdevicecontrol/IosVersion.java
@AutoValue
public abstract class IosVersion {
  public static Builder builder() {
    return new AutoValue_IosVersion.Builder();
  }

  /** The build version, e.g. "12H321". */
  public abstract String buildVersion();

  /** The product version, e.g. "8.4.1". */
  public abstract String productVersion();

  /** The major version number, e.g. 8. */
  public final int majorVersion() {
    return Integer.parseInt(productVersion().substring(0, productVersion().indexOf('.')));
  }

  public abstract Builder toBuilder();

  /**
   * IosVersion builder.
   */
  @AutoValue.Builder
  public abstract static class Builder {
    public abstract Builder buildVersion(String buildVersion);

    public abstract Builder productVersion(String productVersion);

    public abstract IosVersion build();
  }
}
java/com/google/iosdevicecontrol/IosDeviceResource.java
public abstract class IosDeviceResource implements AutoCloseable {
  private final IosDevice device;

  protected IosDeviceResource(IosDevice device) {
    this.device = checkNotNull(device);
  }

  /** Returns the device to which the resource belongs. */
  public final IosDevice device() {
    return device;
  }

  @Override
  public abstract void close() throws IosDeviceException;
}
java/com/google/iosdevicecontrol/IosDevice.java
public interface IosDevice {
  /** Returns the udid of the device. */
  String udid();

  /** Returns whether the device is responsive to communication from the host. */
  boolean isResponsive();

  /**
   * Returns the model of the device.
   *
   * @throws IosDeviceException - if there is an error communicating with the device
   */
  IosModel model() throws IosDeviceException;

  /**
   * Returns the version of the device.
   *
   * @throws IosDeviceException - if there is an error communicating with the device
   */
  IosVersion version() throws IosDeviceException;

  /**
   * Lists the application information of all installed applications on the device.
   *
   * @throws IosDeviceException - if there is an error communicating with the device
   */
  ImmutableSet<IosAppInfo> listApplications() throws IosDeviceException;

  /**
   * Returns whether an application with the specified bundle ID is installed.
   *
   * @throws IosDeviceException - if there is an error communicating with the device
   */
  boolean isApplicationInstalled(IosAppBundleId bundleId) throws IosDeviceException;

  /**
   * Installs the specified application from a .app folder or .ipa archive on the device. If an
   * application with the same bundle ID is already installed, it will be overwritten.
   *
   * @throws IosDeviceException - if there is an error communicating with the device
   */
  void installApplication(Path ipaOrAppPath) throws IosDeviceException;

  /**
   * Uninstall the application with the specified bundle ID on the device. If an application with
   * this bundle ID is not installed, this is a noop.
   *
   * @throws IosDeviceException - if there is an error communicating with the device
   */
  void uninstallApplication(IosAppBundleId bundleId) throws IosDeviceException;

  /**
   * Runs the specified iOS application with the specified args and returns immediately with a
   * {@link IosAppProcess}, which is a future to the text output of the running application.
   *
   * @throws IosDeviceException - if there is an error communicating with the device
   */
  IosAppProcess runApplication(IosAppBundleId bundleId, String... args) throws IosDeviceException;

  /**
   * Starts capturing the system log for the device and writes it to the provided path. Returns an
   * {@link IosDeviceResource}, which can be used in a try-with-resources block.
   *
   * @throws IosDeviceException - if there was an error communicating with the device
   * @throws IllegalStateException - if system log capturing is already started
   */
  IosDeviceResource startSystemLogger(Path logPath) throws IosDeviceException;

  /**
   * Copies the crash logs to the specified directory and removes them from the device.
   *
   * @throws IosDeviceException - if there was an error communicating with the device
   */
  void pullCrashLogs(Path directory) throws IosDeviceException;

  /**
   * Clears the crash logs on the device.
   *
   * @throws IosDeviceException - if there was an error communicating with the device
   */
  void clearCrashLogs() throws IosDeviceException;

  /**
   * Opens a socket to communicate with the web inspector.
   *
   * @throws IosDeviceException - if there was an error communicating with the device
   */
  IosDeviceSocket openWebInspectorSocket() throws IosDeviceException;

  /**
   * Restarts the device and waits for it to be responsive.
   *
   * @throws IosDeviceException - if there is an error communicating with the device
   */
  void restart() throws IosDeviceException;

  /**
   * Takes a screenshot in PNG format and returns the bytes.
   *
   * @throws IosDeviceException - if there is an error communicating with the device
   */
  byte[] takeScreenshot() throws IosDeviceException;
}
java/com/google/iosdevicecontrol/command/CommandFailureException.java
public final class CommandFailureException extends CommandException {
  private final CommandResult result;

  CommandFailureException(Command command, CommandResult result) {
    super(command, checkNotNull(result, command).toString());
    Verify.verify(!command().successCondition().apply(result), result.toString());
    this.result = result;
  }

  public CommandResult result() {
    return result;
  }
}
java/com/google/iosdevicecontrol/util/PlistParser.java
public class PlistParser {
  /**
   * Parses the content of a plist file as UTF-8 encoded XML.
   *
   * @throws PlistParseException - if there is an error parsing the content.
   */
  public static NSObject fromPath(Path plist) {
    try {
      return PropertyListParser.parse(Files.readAllBytes(plist));
    } catch (ParserConfigurationException
        | ParseException
        | PropertyListFormatException
        | IOException
        | SAXException e) {
      throw new PlistParseException(e);
    }
  }

  /**
   * Parses an XML string to a plist object.
   *
   * @throws PlistParseException - if there is an error parsing the string.
   */
  public static NSObject fromXml(String xml) {
    try {
      return XMLPropertyListParser.parse(xml.getBytes(UTF_8));
    } catch (ParserConfigurationException
        | ParseException
        | PropertyListFormatException
        | IOException
        | SAXException e) {
      throw new PlistParseException(e);
    }
  }

  /**
   * Parses a byte array to a plist object.
   *
   * @throws PlistParseException - if there is an error parsing the bytes.
   */
  public static NSObject fromBinary(byte[] bytes) {
    try {
      return BinaryPropertyListParser.parse(bytes);
    } catch (UnsupportedEncodingException | PropertyListFormatException e) {
      throw new PlistParseException(e);
    }
  }

  /** Thrown if a parsing error occurs. */
  public static class PlistParseException extends RuntimeException {
    private PlistParseException(Exception cause) {
      super(cause);
    }
  }

  private PlistParser() {}
}
java/com/google/iosdevicecontrol/IosModel.java
@AutoValue
public abstract class IosModel {
  private static final Splitter PRODUCT_CLASS_SPLITTER = Splitter.on(' ').limit(2);

  /** The class of device. */
  public enum DeviceClass {
    IPAD,
    IPHONE,
    IPOD;

    private static final StringEnumMap<DeviceClass> STRING_TO_DEVICE_CLASS =
        new StringEnumMap<>(DeviceClass.class);

    /**
     * Returns the device class for the specified string, e.g. returns <code>IPHONE</code> for the
     * string "iPhone".
     */
    public static DeviceClass forString(String s) {
      return STRING_TO_DEVICE_CLASS.get(s);
    }

    private final String string;

    private DeviceClass() {
      // e.g. "IPHONE" becomes "iPhone";
      string = "iP" + name().substring(2).toLowerCase();
    }

    @Override
    public String toString() {
      return string;
    }
  }

  /** CPU architecture of an iOS device. */
  public enum Architecture {
    ARM64,
    ARMV7,
    ARMV7F,
    ARMV7K,
    ARMV7S,
    I386,
    X86_64;

    private static final StringEnumMap<Architecture> STRING_TO_ARCHITECTURE =
        new StringEnumMap<>(Architecture.class);

    /**
     * Returns the architecture for the specified string, e.g. returns <code>ARMV7</code> for the
     * string "armv7".
     */
    public static Architecture forString(String s) {
      return STRING_TO_ARCHITECTURE.get(s);
    }

    private final String string;

    private Architecture() {
      string = name().toLowerCase();
    }

    @Override
    public String toString() {
      return string;
    }
  }

  public static Builder builder() {
    return new AutoValue_IosModel.Builder();
  }

  private final Supplier<DeviceClass> deviceClass =
      Suppliers.memoize(
          () ->
              DeviceClass.forString(PRODUCT_CLASS_SPLITTER.split(productName()).iterator().next()));

  /** Model identifier, e.g. "iPhone5,1". */
  public abstract String identifier();

  /** Product string of this model, e.g. "iPhone 5". */
  public abstract String productName();

  /** Architecture of the device, e.g. "arm64". */
  public abstract Architecture architecture();

  /** Device class of this model, e.g. "iPad". */
  public final DeviceClass deviceClass() {
    return deviceClass.get();
  }

  /** IosModel builder. */
  @AutoValue.Builder
  public abstract static class Builder {
    public abstract Builder identifier(String identifier);

    public abstract Builder productName(String productName);

    public abstract Builder architecture(Architecture architecture);

    /** Equivalent to <code>architecture(Architecture.fromString(archString))</code>. */
    public final Builder architecture(String archString) {
      return architecture(Architecture.forString(archString));
    }

    public abstract IosModel build();
  }
}


Question: import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.dd.plist.NSArray;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSNumber;
import com.dd.plist.NSObject;
import com.dd.plist.NSString;
import com.dd.plist.PropertyListFormatException;
import com.dd.plist.XMLPropertyListParser;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.iosdevicecontrol.util.FluentLogger;
import com.google.common.io.MoreFiles;
import com.google.common.io.RecursiveDeleteOption;
import com.google.iosdevicecontrol.command.Command;
import com.google.iosdevicecontrol.command.CommandFailureException;
import com.google.iosdevicecontrol.command.CommandProcess;
import com.google.iosdevicecontrol.command.CommandResult;
import com.google.iosdevicecontrol.IosAppBundleId;
import com.google.iosdevicecontrol.IosAppInfo;
import com.google.iosdevicecontrol.IosAppProcess;
import com.google.iosdevicecontrol.IosDevice;
import com.google.iosdevicecontrol.IosDeviceException;
import com.google.iosdevicecontrol.IosDeviceResource;
import com.google.iosdevicecontrol.IosDeviceSocket;
import com.google.iosdevicecontrol.IosModel;
import com.google.iosdevicecontrol.IosVersion;
import com.google.iosdevicecontrol.real.DevDiskImages.DiskImage;
import com.google.iosdevicecontrol.util.CheckedCallable;
import com.google.iosdevicecontrol.util.CheckedCallables;
import com.google.iosdevicecontrol.util.ForwardingSocket;
import com.google.iosdevicecontrol.util.PlistParser;
import com.google.iosdevicecontrol.util.RetryCallable;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.xml.parsers.ParserConfigurationException;
import org.joda.time.Duration;
import org.xml.sax.SAXException;
            })
        .call();
  }

  /** Thrown if the developer image doesn't appear mounted when running an app. */
  private class NoDeveloperImageMountedException extends IosDeviceException {
    private NoDeveloperImageMountedException() {
      super(RealDeviceImpl.this, "Cannot run apps without mounting a developer image");
    }
  }

  @SuppressWarnings("Finally") // b/64321948
  @Override
  public IosDeviceSocket openWebInspectorSocket() throws IosDeviceException {
    int inspectorPort;
    try (ServerSocket socket = new ServerSocket(0)) {
      inspectorPort = socket.getLocalPort();
    } catch (IOException e) {
      throw new IosDeviceException(this, e);
    }

    CommandProcess inspectorProcess = idevice.webinspectorproxy(Integer.toString(inspectorPort));
    Closeable closeProcess =
        () -> {
          try {
            inspectorProcess.kill().await();
          } catch (CommandFailureException e) {
            throw new IOException(e);
          } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException(e);
          }
        };

    // Socket may not be open right away, so we retry.
    Socket socket;
    try {
      socket =
          RetryCallable.retry(() -> new Socket("localhost", inspectorPort))
              .withDelay(Duration.standardSeconds(1))
              .withMaxAttempts(15)
              .call();
    } catch (IOException e) {
      IosDeviceException deviceEx = new IosDeviceException(this, e);
      try {
        closeProcess.close();
      } catch (Throwable suppressed) {
        deviceEx.addSuppressed(suppressed);
      } finally {
        throw deviceEx;
      }
    }

    // Wrap the socket with an augmented close method that also closes the process.
    return IosDeviceSocket.wrap(
        this,
        new ForwardingSocket(socket) {
          @Override
          public synchronized void close() throws IOException {
            try {
              closeProcess.close();
            } finally {
              super.close();
            }
          }
        });
  }

  @Override
  public void restart() throws IosDeviceException {
    await(idevice.diagnostics("restart"));

    restarting = true;
    try {
      // Device should take at least 30 seconds to reboot.
      sleep(Duration.standardSeconds(30));
      // Now check periodically for the device to become responsive again.
      retryResponsiveAfterReboot.call();
    } finally {
      restarting = false;
    }
  }

  private final RetryCallable<Void, IosDeviceException> retryResponsiveAfterReboot =
      RetryCallable.retry(
              () -> {
                if (!isResponsive()) {
                  throw new IosDeviceException(
                      RealDeviceImpl.this, "Device unresponsive after reboot");
                }
              })
          .withMaxAttempts(12)
          .withDelay(Duration.standardSeconds(5));

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(IosDevice.class).add("udid", udid).toString();
  }

  private NSDictionary callIdeviceInfo() throws IosDeviceException {
    String infoText = await(idevice.info("-x"));
    byte[] infoBytes = infoText.getBytes(StandardCharsets.UTF_8);
    try {
      return (NSDictionary) XMLPropertyListParser.parse(infoBytes);
    } catch (ParserConfigurationException
        | ParseException
        | PropertyListFormatException
        | IOException
        | SAXException e) {
      throw new IosDeviceException(RealDeviceImpl.this, e);
    }
  }

  /**
   * Waits for a process to terminate and returns the result of the execution. Optionally specify
   * expected process exit codes, which if not specified, is assumed to be only zero. If the exit
   * code is not one of the expected values, this method throws an IosDeviceException.
   */
  private String await(CommandProcess process, Integer... expectedExitCodes)
      throws IosDeviceException {
Answer:     CommandResult result = awaitResult(process);