/*
 * Copyright (C) 2009 The Guava Authors
 *
 * 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.google.common.util.concurrent;

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 static com.google.common.util.concurrent.Service.State.FAILED;
import static com.google.common.util.concurrent.Service.State.NEW;
import static com.google.common.util.concurrent.Service.State.RUNNING;
import static com.google.common.util.concurrent.Service.State.STARTING;
import static com.google.common.util.concurrent.Service.State.STOPPING;
import static com.google.common.util.concurrent.Service.State.TERMINATED;

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.util.concurrent.Monitor.Guard;
import com.google.common.util.concurrent.Service.State; // javadoc needs this
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.ForOverride;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import com.google.j2objc.annotations.WeakOuter;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Base class for implementing services that can handle {@link #doStart} and {@link #doStop}
 * requests, responding to them with {@link #notifyStarted()} and {@link #notifyStopped()}
 * callbacks. Its subclasses must manage threads manually; consider {@link
 * AbstractExecutionThreadService} if you need only a single execution thread.
 *
 * @author Jesse Wilson
 * @author Luke Sandberg
 * @since 1.0
 */
@Beta
@GwtIncompatible
public abstract class AbstractService implements Service {
  private static final ListenerCallQueue.Event<Listener> STARTING_EVENT =
      new ListenerCallQueue.Event<Listener>() {
        @Override
        public void call(Listener listener) {
          listener.starting();
        }

        @Override
        public String toString() {
          return "starting()";
        }
      };
  private static final ListenerCallQueue.Event<Listener> RUNNING_EVENT =
      new ListenerCallQueue.Event<Listener>() {
        @Override
        public void call(Listener listener) {
          listener.running();
        }

        @Override
        public String toString() {
          return "running()";
        }
      };
  private static final ListenerCallQueue.Event<Listener> STOPPING_FROM_STARTING_EVENT =
      stoppingEvent(STARTING);
  private static final ListenerCallQueue.Event<Listener> STOPPING_FROM_RUNNING_EVENT =
      stoppingEvent(RUNNING);

  private static final ListenerCallQueue.Event<Listener> TERMINATED_FROM_NEW_EVENT =
      terminatedEvent(NEW);
  private static final ListenerCallQueue.Event<Listener> TERMINATED_FROM_STARTING_EVENT =
      terminatedEvent(STARTING);
  private static final ListenerCallQueue.Event<Listener> TERMINATED_FROM_RUNNING_EVENT =
      terminatedEvent(RUNNING);
  private static final ListenerCallQueue.Event<Listener> TERMINATED_FROM_STOPPING_EVENT =
      terminatedEvent(STOPPING);

  private static ListenerCallQueue.Event<Listener> terminatedEvent(final State from) {
    return new ListenerCallQueue.Event<Listener>() {
      @Override
      public void call(Listener listener) {
        listener.terminated(from);
      }

      @Override
      public String toString() {
        return "terminated({from = " + from + "})";
      }
    };
  }

  private static ListenerCallQueue.Event<Listener> stoppingEvent(final State from) {
    return new ListenerCallQueue.Event<Listener>() {
      @Override
      public void call(Listener listener) {
        listener.stopping(from);
      }

      @Override
      public String toString() {
        return "stopping({from = " + from + "})";
      }
    };
  }

  private final Monitor monitor = new Monitor();

  private final Guard isStartable = new IsStartableGuard();

  @WeakOuter
  private final class IsStartableGuard extends Guard {
    IsStartableGuard() {
      super(AbstractService.this.monitor);
    }

    @Override
    public boolean isSatisfied() {
      return state() == NEW;
    }
  }

  private final Guard isStoppable = new IsStoppableGuard();

  @WeakOuter
  private final class IsStoppableGuard extends Guard {
    IsStoppableGuard() {
      super(AbstractService.this.monitor);
    }

    @Override
    public boolean isSatisfied() {
      return state().compareTo(RUNNING) <= 0;
    }
  }

  private final Guard hasReachedRunning = new HasReachedRunningGuard();

  @WeakOuter
  private final class HasReachedRunningGuard extends Guard {
    HasReachedRunningGuard() {
      super(AbstractService.this.monitor);
    }

    @Override
    public boolean isSatisfied() {
      return state().compareTo(RUNNING) >= 0;
    }
  }

  private final Guard isStopped = new IsStoppedGuard();

  @WeakOuter
  private final class IsStoppedGuard extends Guard {
    IsStoppedGuard() {
      super(AbstractService.this.monitor);
    }

    @Override
    public boolean isSatisfied() {
      return state().isTerminal();
    }
  }

  /** The listeners to notify during a state transition. */
  private final ListenerCallQueue<Listener> listeners = new ListenerCallQueue<>();

  /**
   * The current state of the service. This should be written with the lock held but can be read
   * without it because it is an immutable object in a volatile field. This is desirable so that
   * methods like {@link #state}, {@link #failureCause} and notably {@link #toString} can be run
   * without grabbing the lock.
   *
   *
To update this field correctly the lock must be held to guarantee that the state is
   * consistent.
   */
  private volatile StateSnapshot snapshot = new StateSnapshot(NEW);

  /** Constructor for use by subclasses. */
  protected AbstractService() {}

  /**
   * This method is called by {@link #startAsync} to initiate service startup. The invocation of
   * this method should cause a call to {@link #notifyStarted()}, either during this method's run,
   * or after it has returned. If startup fails, the invocation should cause a call to {@link
   * #notifyFailed(Throwable)} instead.
   *
   *
This method should return promptly; prefer to do work on a different thread where it is
   * convenient. It is invoked exactly once on service startup, even when {@link #startAsync} is
   * called multiple times.
   */
  @ForOverride
  protected abstract void doStart();

  /**
   * This method should be used to initiate service shutdown. The invocation of this method should
   * cause a call to {@link #notifyStopped()}, either during this method's run, or after it has
   * returned. If shutdown fails, the invocation should cause a call to {@link
   * #notifyFailed(Throwable)} instead.
   *
   *
This method should return promptly; prefer to do work on a different thread where it is
   * convenient. It is invoked exactly once on service shutdown, even when {@link #stopAsync} is
   * called multiple times.
   *
   *
If {@link #stopAsync} is called on a {@link State#STARTING} service, this method is not
   * invoked immediately. Instead, it will be deferred until after the service is {@link
   * State#RUNNING}. Services that need to cancel startup work can override {#link #doCancelStart}.
   */
  @ForOverride
  protected abstract void doStop();

  /**
   * This method is called by {@link #stopAsync} when the service is still starting (i.e. {@link
   * #startAsync} has been called but {@link #notifyStarted} has not). Subclasses can override the
   * method to cancel pending work and then call {@link #notifyStopped} to stop the service.
   *
   *
This method should return promptly; prefer to do work on a different thread where it is
   * convenient. It is invoked exactly once on service shutdown, even when {@link #stopAsync} is
   * called multiple times.
   *
   *
When this method is called {@link #state()} will return {@link State#STOPPING}, which is the
   * external state observable by the caller of {@link #stopAsync}.
   *
   * @since 27.0
   */
  @ForOverride
  protected void doCancelStart() {}

  @CanIgnoreReturnValue
  @Override
  public final Service startAsync() {
    if (monitor.enterIf(isStartable)) {
      try {
        snapshot = new StateSnapshot(STARTING);
        enqueueStartingEvent();
        doStart();
      } catch (Throwable startupFailure) {
        notifyFailed(startupFailure);
      } finally {
        monitor.leave();
        dispatchListenerEvents();
      }
    } else {
      throw new IllegalStateException("Service " + this + " has already been started");
    }
    return this;
  }

  @CanIgnoreReturnValue
  @Override
  public final Service stopAsync() {
    if (monitor.enterIf(isStoppable)) {
      try {
        State previous = state();
        switch (previous) {
          case NEW:
            snapshot = new StateSnapshot(TERMINATED);
            enqueueTerminatedEvent(NEW);
            break;
          case STARTING:
            snapshot = new StateSnapshot(STARTING, true, null);
            enqueueStoppingEvent(STARTING);
            doCancelStart();
            break;
          case RUNNING:
            snapshot = new StateSnapshot(STOPPING);
            enqueueStoppingEvent(RUNNING);
            doStop();
            break;
          case STOPPING:
          case TERMINATED:
          case FAILED:
            // These cases are impossible due to the if statement above.
            throw new AssertionError("isStoppable is incorrectly implemented, saw: " + previous);
        }
      } catch (Throwable shutdownFailure) {
        notifyFailed(shutdownFailure);
      } finally {
        monitor.leave();
        dispatchListenerEvents();
      }
    }
    return this;
  }

  @Override
  public final void awaitRunning() {
    monitor.enterWhenUninterruptibly(hasReachedRunning);
    try {
      checkCurrentState(RUNNING);
    } finally {
      monitor.leave();
    }
  }

  @Override
  public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException {
    if (monitor.enterWhenUninterruptibly(hasReachedRunning, timeout, unit)) {
      try {
        checkCurrentState(RUNNING);
      } finally {
        monitor.leave();
      }
    } else {
      // It is possible due to races the we are currently in the expected state even though we
      // timed out. e.g. if we weren't event able to grab the lock within the timeout we would never
      // even check the guard. I don't think we care too much about this use case but it could lead
      // to a confusing error message.
      throw new TimeoutException("Timed out waiting for " + this + " to reach the RUNNING state.");
    }
  }

  @Override
  public final void awaitTerminated() {
    monitor.enterWhenUninterruptibly(isStopped);
    try {
      checkCurrentState(TERMINATED);
    } finally {
      monitor.leave();
    }
  }

  @Override
  public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException {
    if (monitor.enterWhenUninterruptibly(isStopped, timeout, unit)) {
      try {
        checkCurrentState(TERMINATED);
      } finally {
        monitor.leave();
      }
    } else {
      // It is possible due to races the we are currently in the expected state even though we
      // timed out. e.g. if we weren't event able to grab the lock within the timeout we would never
      // even check the guard. I don't think we care too much about this use case but it could lead
      // to a confusing error message.
      throw new TimeoutException(
          "Timed out waiting for "
              + this
              + " to reach a terminal state. "
              + "Current state: "
              + state());
    }
  }

  /** Checks that the current state is equal to the expected state. */
  @GuardedBy("monitor")
  private void checkCurrentState(State expected) {
    State actual = state();
    if (actual != expected) {
      if (actual == FAILED) {
        // Handle this specially so that we can include the failureCause, if there is one.
        throw new IllegalStateException(
            "Expected the service " + this + " to be " + expected + ", but the service has FAILED",
            failureCause());
      }
      throw new IllegalStateException(
          "Expected the service " + this + " to be " + expected + ", but was " + actual);
    }
  }

  /**
   * Implementing classes should invoke this method once their service has started. It will cause
   * the service to transition from {@link State#STARTING} to {@link State#RUNNING}.
   *
   * @throws IllegalStateException if the service is not {@link State#STARTING}.
   */
  protected final void notifyStarted() {
    monitor.enter();
    try {
      // We have to examine the internal state of the snapshot here to properly handle the stop
      // while starting case.
      if (snapshot.state != STARTING) {
        IllegalStateException failure =
            new IllegalStateException(
                "Cannot notifyStarted() when the service is " + snapshot.state);
        notifyFailed(failure);
        throw failure;
      }

      if (snapshot.shutdownWhenStartupFinishes) {
        snapshot = new StateSnapshot(STOPPING);
        // We don't call listeners here because we already did that when we set the
        // shutdownWhenStartupFinishes flag.
        doStop();
      } else {
        snapshot = new StateSnapshot(RUNNING);
        enqueueRunningEvent();
      }
    } finally {
      monitor.leave();
      dispatchListenerEvents();
    }
  }

  /**
   * Implementing classes should invoke this method once their service has stopped. It will cause
   * the service to transition from {@link State#STARTING} or {@link State#STOPPING} to {@link
   * State#TERMINATED}.
   *
   * @throws IllegalStateException if the service is not one of {@link State#STOPPING}, {@link
   *     State#STARTING}, or {@link State#RUNNING}.
   */
  protected final void notifyStopped() {
    monitor.enter();
    try {
      State previous = state();
      switch (previous) {
        case NEW:
        case TERMINATED:
        case FAILED:
          throw new IllegalStateException("Cannot notifyStopped() when the service is " + previous);
        case RUNNING:
        case STARTING:
        case STOPPING:
          snapshot = new StateSnapshot(TERMINATED);
          enqueueTerminatedEvent(previous);
          break;
      }
    } finally {
      monitor.leave();
      dispatchListenerEvents();
    }
  }

  /**
   * Invoke this method to transition the service to the {@link State#FAILED}. The service will
   * not be stopped if it is running. Invoke this method when a service has failed critically
   * or otherwise cannot be started nor stopped.
   */
  protected final void notifyFailed(Throwable cause) {
    checkNotNull(cause);

    monitor.enter();
    try {
      State previous = state();
      switch (previous) {
        case NEW:
        case TERMINATED:
          throw new IllegalStateException("Failed while in state:" + previous, cause);
        case RUNNING:
        case STARTING:
        case STOPPING:
          snapshot = new StateSnapshot(FAILED, false, cause);
          enqueueFailedEvent(previous, cause);
          break;
        case FAILED:
          // Do nothing
          break;
      }
    } finally {
      monitor.leave();
      dispatchListenerEvents();
    }
  }

  @Override
  public final boolean isRunning() {
    return state() == RUNNING;
  }

  @Override
  public final State state() {
    return snapshot.externalState();
  }

  /** @since 14.0 */
  @Override
  public final Throwable failureCause() {
    return snapshot.failureCause();
  }

  /** @since 13.0 */
  @Override
  public final void addListener(Listener listener, Executor executor) {
    listeners.addListener(listener, executor);
  }

  @Override
  public String toString() {
    return getClass().getSimpleName() + " [" + state() + "]";
  }

  /**
   * Attempts to execute all the listeners in {@link #listeners} while not holding the {@link
   * #monitor}.
   */
  private void dispatchListenerEvents() {
    if (!monitor.isOccupiedByCurrentThread()) {
      listeners.dispatch();
    }
  }

  private void enqueueStartingEvent() {
    listeners.enqueue(STARTING_EVENT);
  }

  private void enqueueRunningEvent() {
    listeners.enqueue(RUNNING_EVENT);
  }

  private void enqueueStoppingEvent(final State from) {
    if (from == State.STARTING) {
      listeners.enqueue(STOPPING_FROM_STARTING_EVENT);
    } else if (from == State.RUNNING) {
      listeners.enqueue(STOPPING_FROM_RUNNING_EVENT);
    } else {
      throw new AssertionError();
    }
  }

  private void enqueueTerminatedEvent(final State from) {
    switch (from) {
      case NEW:
        listeners.enqueue(TERMINATED_FROM_NEW_EVENT);
        break;
      case STARTING:
        listeners.enqueue(TERMINATED_FROM_STARTING_EVENT);
        break;
      case RUNNING:
        listeners.enqueue(TERMINATED_FROM_RUNNING_EVENT);
        break;
      case STOPPING:
        listeners.enqueue(TERMINATED_FROM_STOPPING_EVENT);
        break;
      case TERMINATED:
      case FAILED:
        throw new AssertionError();
    }
  }

  private void enqueueFailedEvent(final State from, final Throwable cause) {
    // can't memoize this one due to the exception
    listeners.enqueue(
        new ListenerCallQueue.Event<Listener>() {
          @Override
          public void call(Listener listener) {
            listener.failed(from, cause);
          }

          @Override
          public String toString() {
            return "failed({from = " + from + ", cause = " + cause + "})";
          }
        });
  }

  /**
   * An immutable snapshot of the current state of the service. This class represents a consistent
   * snapshot of the state and therefore it can be used to answer simple queries without needing to
   * grab a lock.
   */
  // @Immutable except that Throwable is mutable (initCause(), setStackTrace(), mutable subclasses).
  private static final class StateSnapshot {
    /**
     * The internal state, which equals external state unless shutdownWhenStartupFinishes is true.
     */
    final State state;

    /** If true, the user requested a shutdown while the service was still starting up. */
    final boolean shutdownWhenStartupFinishes;

    /**
     * The exception that caused this service to fail. This will be {@code null} unless the service
     * has failed.
     */
    final @Nullable Throwable failure;

    StateSnapshot(State internalState) {
      this(internalState, false, null);
    }

    StateSnapshot(
        State internalState, boolean shutdownWhenStartupFinishes, @Nullable Throwable failure) {
      checkArgument(
          !shutdownWhenStartupFinishes || internalState == STARTING,
          "shutdownWhenStartupFinishes can only be set if state is STARTING. Got %s instead.",
          internalState);
      checkArgument(
          !(failure != null ^ internalState == FAILED),
          "A failure cause should be set if and only if the state is failed.  Got %s and %s "
              + "instead.",
          internalState,
          failure);
      this.state = internalState;
      this.shutdownWhenStartupFinishes = shutdownWhenStartupFinishes;
      this.failure = failure;
    }

    /** @see Service#state() */
    State externalState() {
      if (shutdownWhenStartupFinishes && state == STARTING) {
        return STOPPING;
      } else {
        return state;
      }
    }

    /** @see Service#failureCause() */
    Throwable failureCause() {
      checkState(
          state == FAILED,
          "failureCause() is only valid if the service has failed, service is %s",
          state);
      return failure;
    }
  }
}


/*
 * Copyright (C) 2007 The Guava Authors
 *
 * 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.google.common.collect;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.CollectPreconditions.checkNonnegative;
import static com.google.common.collect.ObjectArrays.checkElementNotNull;

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.Ints;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.concurrent.LazyInit;
import com.google.j2objc.annotations.RetainedWith;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

/**
 * A {@link Set} whose contents will never change, with many other important properties detailed at
 * {@link ImmutableCollection}.
 *
 * @since 2.0
 */
@GwtCompatible(serializable = true, emulated = true)
@SuppressWarnings("serial") // we're overriding default serialization
public abstract class ImmutableSet<E> extends ImmutableCollection<E> implements Set<E> {
  /**
   * Returns the empty immutable set. Preferred over {@link Collections#emptySet} for code
   * consistency, and because the return type conveys the immutability guarantee.
   */
  @SuppressWarnings({"unchecked"}) // fully variant implementation (never actually produces any Es)
  public static <E> ImmutableSet<E> of() {
    return (ImmutableSet<E>) RegularImmutableSet.EMPTY;
  }

  /**
   * Returns an immutable set containing {@code element}. Preferred over {@link
   * Collections#singleton} for code consistency, {@code null} rejection, and because the return
   * type conveys the immutability guarantee.
   */
  public static <E> ImmutableSet<E> of(E element) {
    return new SingletonImmutableSet<E>(element);
  }

  /**
   * Returns an immutable set containing the given elements, minus duplicates, in the order each was
   * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except
   * the first are ignored.
   */
  public static <E> ImmutableSet<E> of(E e1, E e2) {
    return construct(2, e1, e2);
  }

  /**
   * Returns an immutable set containing the given elements, minus duplicates, in the order each was
   * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except
   * the first are ignored.
   */
  public static <E> ImmutableSet<E> of(E e1, E e2, E e3) {
    return construct(3, e1, e2, e3);
  }

  /**
   * Returns an immutable set containing the given elements, minus duplicates, in the order each was
   * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except
   * the first are ignored.
   */
  public static <E> ImmutableSet<E> of(E e1, E e2, E e3, E e4) {
    return construct(4, e1, e2, e3, e4);
  }

  /**
   * Returns an immutable set containing the given elements, minus duplicates, in the order each was
   * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except
   * the first are ignored.
   */
  public static <E> ImmutableSet<E> of(E e1, E e2, E e3, E e4, E e5) {
    return construct(5, e1, e2, e3, e4, e5);
  }

  /**
   * Returns an immutable set containing the given elements, minus duplicates, in the order each was
   * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except
   * the first are ignored.
   *
   *
The array {@code others} must not be longer than {@code Integer.MAX_VALUE - 6}.
   *
   * @since 3.0 (source-compatible since 2.0)
   */
  @SafeVarargs // For Eclipse. For internal javac we have disabled this pointless type of warning.
  public static <E> ImmutableSet<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) {
    checkArgument(
        others.length <= Integer.MAX_VALUE - 6, "the total number of elements must fit in an int");
    final int paramCount = 6;
    Object[] elements = new Object[paramCount + others.length];
    elements[0] = e1;
    elements[1] = e2;
    elements[2] = e3;
    elements[3] = e4;
    elements[4] = e5;
    elements[5] = e6;
    System.arraycopy(others, 0, elements, paramCount, others.length);
    return construct(elements.length, elements);
  }

  /**
   * Constructs an {@code ImmutableSet} from the first {@code n} elements of the specified array. If
   * {@code k} is the size of the returned {@code ImmutableSet}, then the unique elements of {@code
   * elements} will be in the first {@code k} positions, and {@code elements[i] == null} for {@code
   * k <= i < n}.
   *
   *
After this method returns, {@code elements} will contain no duplicates, but {@code elements}
   * may be the real array backing the returned set, so do not modify it further.
   *
   *
{@code elements} may contain only values of type {@code E}.
   *
   * @throws NullPointerException if any of the first {@code n} elements of {@code elements} is null
   */
  private static <E> ImmutableSet<E> construct(int n, Object... elements) {
    switch (n) {
      case 0:
        return of();
      case 1:
        @SuppressWarnings("unchecked") // safe; elements contains only E's
        E elem = (E) elements[0];
        return of(elem);
      default:
        // continue below to handle the general case
    }
    int tableSize = chooseTableSize(n);
    Object[] table = new Object[tableSize];
    int mask = tableSize - 1;
    int hashCode = 0;
    int uniques = 0;
    for (int i = 0; i < n; i++) {
      Object element = checkElementNotNull(elements[i], i);
      int hash = element.hashCode();
      for (int j = Hashing.smear(hash); ; j++) {
        int index = j & mask;
        Object value = table[index];
        if (value == null) {
          // Came to an empty slot. Put the element here.
          elements[uniques++] = element;
          table[index] = element;
          hashCode += hash;
          break;
        } else if (value.equals(element)) {
          break;
        }
      }
    }
    Arrays.fill(elements, uniques, n, null);
    if (uniques == 1) {
      // There is only one element or elements are all duplicates
      @SuppressWarnings("unchecked") // we are careful to only pass in E
      E element = (E) elements[0];
      return new SingletonImmutableSet<E>(element, hashCode);
    } else if (chooseTableSize(uniques) < tableSize / 2) {
      // Resize the table when the array includes too many duplicates.
      return construct(uniques, elements);
    } else {
      Object[] uniqueElements =
          shouldTrim(uniques, elements.length) ? Arrays.copyOf(elements, uniques) : elements;
      return new RegularImmutableSet<E>(uniqueElements, hashCode, table, mask, uniques);
    }
  }

  private static boolean shouldTrim(int actualUnique, int expectedUnique) {
    return actualUnique < (expectedUnique >> 1) + (expectedUnique >> 2);
  }

  // We use power-of-2 tables, and this is the highest int that's a power of 2
  static final int MAX_TABLE_SIZE = Ints.MAX_POWER_OF_TWO;

  // Represents how tightly we can pack things, as a maximum.
  private static final double DESIRED_LOAD_FACTOR = 0.7;

  // If the set has this many elements, it will "max out" the table size
  private static final int CUTOFF = (int) (MAX_TABLE_SIZE * DESIRED_LOAD_FACTOR);

  /**
   * Returns an array size suitable for the backing array of a hash table that uses open addressing
   * with linear probing in its implementation. The returned size is the smallest power of two that
   * can hold setSize elements with the desired load factor. Always returns at least setSize + 2.
   */
  @VisibleForTesting
  static int chooseTableSize(int setSize) {
    setSize = Math.max(setSize, 2);
    // Correct the size for open addressing to match desired load factor.
    if (setSize < CUTOFF) {
      // Round up to the next highest power of 2.
      int tableSize = Integer.highestOneBit(setSize - 1) << 1;
      while (tableSize * DESIRED_LOAD_FACTOR < setSize) {
        tableSize <<= 1;
      }
      return tableSize;
    }

    // The table can't be completely full or we'll get infinite reprobes
    checkArgument(setSize < MAX_TABLE_SIZE, "collection too large");
    return MAX_TABLE_SIZE;
  }

  /**
   * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order
   * each appears first in the source collection.
   *
   *
Performance note: This method will sometimes recognize that the actual copy operation
   * is unnecessary; for example, {@code copyOf(copyOf(anArrayList))} will copy the data only once.
   * This reduces the expense of habitually making defensive copies at API boundaries. However, the
   * precise conditions for skipping the copy operation are undefined.
   *
   * @throws NullPointerException if any of {@code elements} is null
   * @since 7.0 (source-compatible since 2.0)
   */
  public static <E> ImmutableSet<E> copyOf(Collection<? extends E> elements) {
    /*
     * TODO(lowasser): consider checking for ImmutableAsList here
     * TODO(lowasser): consider checking for Multiset here
     */
    // Don't refer to ImmutableSortedSet by name so it won't pull in all that code
    if (elements instanceof ImmutableSet && !(elements instanceof SortedSet)) {
      @SuppressWarnings("unchecked") // all supported methods are covariant
      ImmutableSet<E> set = (ImmutableSet<E>) elements;
      if (!set.isPartialView()) {
        return set;
      }
    }
    Object[] array = elements.toArray();
    return construct(array.length, array);
  }

  /**
   * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order
   * each appears first in the source iterable. This method iterates over {@code elements} only
   * once.
   *
   *
Performance note: This method will sometimes recognize that the actual copy operation
   * is unnecessary; for example, {@code copyOf(copyOf(anArrayList))} should copy the data only
   * once. This reduces the expense of habitually making defensive copies at API boundaries.
   * However, the precise conditions for skipping the copy operation are undefined.
   *
   * @throws NullPointerException if any of {@code elements} is null
   */
  public static <E> ImmutableSet<E> copyOf(Iterable<? extends E> elements) {
    return (elements instanceof Collection)
        ? copyOf((Collection<? extends E>) elements)
        : copyOf(elements.iterator());
  }

  /**
   * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order
   * each appears first in the source iterator.
   *
   * @throws NullPointerException if any of {@code elements} is null
   */
  public static <E> ImmutableSet<E> copyOf(Iterator<? extends E> elements) {
    // We special-case for 0 or 1 elements, but anything further is madness.
    if (!elements.hasNext()) {
      return of();
    }
    E first = elements.next();
    if (!elements.hasNext()) {
      return of(first);
    } else {
      return new ImmutableSet.Builder<E>().add(first).addAll(elements).build();
    }
  }

  /**
   * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order
   * each appears first in the source array.
   *
   * @throws NullPointerException if any of {@code elements} is null
   * @since 3.0
   */
  public static <E> ImmutableSet<E> copyOf(E[] elements) {
    switch (elements.length) {
      case 0:
        return of();
      case 1:
        return of(elements[0]);
      default:
        return construct(elements.length, elements.clone());
    }
  }

  ImmutableSet() {}

  /** Returns {@code true} if the {@code hashCode()} method runs quickly. */
  boolean isHashCodeFast() {
    return false;
  }

  @Override
  public boolean equals(@NullableDecl Object object) {
    if (object == this) {
      return true;
    } else if (object instanceof ImmutableSet
        && isHashCodeFast()
        && ((ImmutableSet<?>) object).isHashCodeFast()
        && hashCode() != object.hashCode()) {
      return false;
    }
    return Sets.equalsImpl(this, object);
  }

  @Override
  public int hashCode() {
    return Sets.hashCodeImpl(this);
  }

  // This declaration is needed to make Set.iterator() and
  // ImmutableCollection.iterator() consistent.
  @Override
  public abstract UnmodifiableIterator<E> iterator();

  @LazyInit @RetainedWith @NullableDecl private transient ImmutableList<E> asList;

  @Override
  public ImmutableList<E> asList() {
    ImmutableList<E> result = asList;
    return (result == null) ? asList = createAsList() : result;
  }

  ImmutableList<E> createAsList() {
    return ImmutableList.asImmutableList(toArray());
  }

  /*
   * This class is used to serialize all ImmutableSet instances, except for
   * ImmutableEnumSet/ImmutableSortedSet, regardless of implementation type. It
   * captures their "logical contents" and they are reconstructed using public
   * static factories. This is necessary to ensure that the existence of a
   * particular implementation type is an implementation detail.
   */
  private static class SerializedForm implements Serializable {
    final Object[] elements;

    SerializedForm(Object[] elements) {
      this.elements = elements;
    }

    Object readResolve() {
      return copyOf(elements);
    }

    private static final long serialVersionUID = 0;
  }

  @Override
  Object writeReplace() {
    return new SerializedForm(toArray());
  }

  /**
   * Returns a new builder. The generated builder is equivalent to the builder created by the {@link
   * Builder} constructor.
   */
  public static <E> Builder<E> builder() {
    return new Builder<E>();
  }

  /**
   * Returns a new builder, expecting the specified number of distinct elements to be added.
   *
   *
If {@code expectedSize} is exactly the number of distinct elements added to the builder
   * before {@link Builder#build} is called, the builder is likely to perform better than an unsized
   * {@link #builder()} would have.
   *
   *
It is not specified if any performance benefits apply if {@code expectedSize} is close to,
   * but not exactly, the number of distinct elements added to the builder.
   *
   * @since 23.1
   */
  @Beta
  public static <E> Builder<E> builderWithExpectedSize(int expectedSize) {
    checkNonnegative(expectedSize, "expectedSize");
    return new Builder<E>(expectedSize);
  }

  /**
   * A builder for creating {@code ImmutableSet} instances. Example:
   *
   *
{@code
   * static final ImmutableSet<Color> GOOGLE_COLORS =
   *     ImmutableSet.<Color>builder()
   *         .addAll(WEBSAFE_COLORS)
   *         .add(new Color(0, 191, 255))
   *         .build();
   * }

   *
   *
Elements appear in the resulting set in the same order they were first added to the builder.
   *
   *
Building does not change the state of the builder, so it is still possible to add more
   * elements and to build again.
   *
   * @since 2.0
   */
  public static class Builder<E> extends ImmutableCollection.ArrayBasedBuilder<E> {
    @NullableDecl @VisibleForTesting Object[] hashTable;
    private int hashCode;

    /**
     * Creates a new builder. The returned builder is equivalent to the builder generated by {@link
     * ImmutableSet#builder}.
     */
    public Builder() {
      super(DEFAULT_INITIAL_CAPACITY);
    }

    Builder(int capacity) {
      super(capacity);
      this.hashTable = new Object[chooseTableSize(capacity)];
    }

    /**
     * Adds {@code element} to the {@code ImmutableSet}. If the {@code ImmutableSet} already
     * contains {@code element}, then {@code add} has no effect (only the previously added element
     * is retained).
     *
     * @param element the element to add
     * @return this {@code Builder} object
     * @throws NullPointerException if {@code element} is null
     */
    @CanIgnoreReturnValue
    @Override
    public Builder<E> add(E element) {
      checkNotNull(element);
      if (hashTable != null && chooseTableSize(size) <= hashTable.length) {
        addDeduping(element);
        return this;
      } else {
        hashTable = null;
        super.add(element);
        return this;
      }
    }

    /**
     * Adds each element of {@code elements} to the {@code ImmutableSet}, ignoring duplicate
     * elements (only the first duplicate element is added).
     *
     * @param elements the elements to add
     * @return this {@code Builder} object
     * @throws NullPointerException if {@code elements} is null or contains a null element
     */
    @CanIgnoreReturnValue
    @Override
    public Builder<E> add(E... elements) {
      if (hashTable != null) {
        for (E e : elements) {
          add(e);
        }
      } else {
        super.add(elements);
      }
      return this;
    }

    private void addDeduping(E element) {
      int mask = hashTable.length - 1;
      int hash = element.hashCode();
      for (int i = Hashing.smear(hash); ; i++) {
        i &= mask;
        Object previous = hashTable[i];
        if (previous == null) {
          hashTable[i] = element;
          hashCode += hash;
          super.add(element);
          return;
        } else if (previous.equals(element)) {
          return;
        }
      }
    }

    /**
     * Adds each element of {@code elements} to the {@code ImmutableSet}, ignoring duplicate
     * elements (only the first duplicate element is added).
     *
     * @param elements the {@code Iterable} to add to the {@code ImmutableSet}
     * @return this {@code Builder} object
     * @throws NullPointerException if {@code elements} is null or contains a null element
     */
    @CanIgnoreReturnValue
    @Override
    public Builder<E> addAll(Iterable<? extends E> elements) {
      checkNotNull(elements);
      if (hashTable != null) {
        for (E e : elements) {
          add(e);
        }
      } else {
        super.addAll(elements);
      }
      return this;
    }

    /**
     * Adds each element of {@code elements} to the {@code ImmutableSet}, ignoring duplicate
     * elements (only the first duplicate element is added).
     *
     * @param elements the elements to add to the {@code ImmutableSet}
     * @return this {@code Builder} object
     * @throws NullPointerException if {@code elements} is null or contains a null element
     */
    @CanIgnoreReturnValue
    @Override
    public Builder<E> addAll(Iterator<? extends E> elements) {
      checkNotNull(elements);
      while (elements.hasNext()) {
        add(elements.next());
      }
      return this;
    }

    /**
     * Returns a newly-created {@code ImmutableSet} based on the contents of the {@code Builder}.
     */
    @SuppressWarnings("unchecked")
    @Override
    public ImmutableSet<E> build() {
      switch (size) {
        case 0:
          return of();
        case 1:
          return (ImmutableSet<E>) of(contents[0]);
        default:
          ImmutableSet<E> result;
          if (hashTable != null && chooseTableSize(size) == hashTable.length) {
            Object[] uniqueElements =
                shouldTrim(size, contents.length) ? Arrays.copyOf(contents, size) : contents;
            result =
                new RegularImmutableSet<E>(
                    uniqueElements, hashCode, hashTable, hashTable.length - 1, size);
          } else {
            result = construct(size, contents);
            // construct has the side effect of deduping contents, so we update size
            // accordingly.
            size = result.size();
          }
          forceCopy = true;
          hashTable = null;
          return result;
      }
    }
  }
}


/*
 * Copyright (C) 2013 The Guava Authors
 *
 * 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.google.common.io;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.graph.SuccessorsFunction;
import com.google.common.graph.Traverser;
import com.google.j2objc.annotations.J2ObjCIncompatible;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.SecureDirectoryStream;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Stream;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Static utilities for use with {@link Path} instances, intended to complement {@link Files}.
 *
 *
Many methods provided by Guava's {@code Files} class for {@link java.io.File} instances are
 * now available via the JDK's {@link java.nio.file.Files} class for {@code Path} - check the JDK's
 * class if a sibling method from {@code Files} appears to be missing from this class.
 *
 * @since 21.0
 * @author Colin Decker
 */
@Beta
@GwtIncompatible
@J2ObjCIncompatible // java.nio.file
public final class MoreFiles {

  private MoreFiles() {}

  /**
   * Returns a view of the given {@code path} as a {@link ByteSource}.
   *
   *
Any {@linkplain OpenOption open options} provided are used when opening streams to the file
   * and may affect the behavior of the returned source and the streams it provides. See {@link
   * StandardOpenOption} for the standard options that may be provided. Providing no options is
   * equivalent to providing the {@link StandardOpenOption#READ READ} option.
   */
  public static ByteSource asByteSource(Path path, OpenOption... options) {
    return new PathByteSource(path, options);
  }

  private static final class PathByteSource extends ByteSource {

    private static final LinkOption[] FOLLOW_LINKS = {};

    private final Path path;
    private final OpenOption[] options;
    private final boolean followLinks;

    private PathByteSource(Path path, OpenOption... options) {
      this.path = checkNotNull(path);
      this.options = options.clone();
      this.followLinks = followLinks(this.options);
      // TODO(cgdecker): validate the provided options... for example, just WRITE seems wrong
    }

    private static boolean followLinks(OpenOption[] options) {
      for (OpenOption option : options) {
        if (option == NOFOLLOW_LINKS) {
          return false;
        }
      }
      return true;
    }

    @Override
    public InputStream openStream() throws IOException {
      return Files.newInputStream(path, options);
    }

    private BasicFileAttributes readAttributes() throws IOException {
      return Files.readAttributes(
          path,
          BasicFileAttributes.class,
          followLinks ? FOLLOW_LINKS : new LinkOption[] {NOFOLLOW_LINKS});
    }

    @Override
    public Optional<Long> sizeIfKnown() {
      BasicFileAttributes attrs;
      try {
        attrs = readAttributes();
      } catch (IOException e) {
        // Failed to get attributes; we don't know the size.
        return Optional.absent();
      }

      // Don't return a size for directories or symbolic links; their sizes are implementation
      // specific and they can't be read as bytes using the read methods anyway.
      if (attrs.isDirectory() || attrs.isSymbolicLink()) {
        return Optional.absent();
      }

      return Optional.of(attrs.size());
    }

    @Override
    public long size() throws IOException {
      BasicFileAttributes attrs = readAttributes();

      // Don't return a size for directories or symbolic links; their sizes are implementation
      // specific and they can't be read as bytes using the read methods anyway.
      if (attrs.isDirectory()) {
        throw new IOException("can't read: is a directory");
      } else if (attrs.isSymbolicLink()) {
        throw new IOException("can't read: is a symbolic link");
      }

      return attrs.size();
    }

    @Override
    public byte[] read() throws IOException {
      try (SeekableByteChannel channel = Files.newByteChannel(path, options)) {
        return ByteStreams.toByteArray(Channels.newInputStream(channel), channel.size());
      }
    }

    @Override
    public CharSource asCharSource(Charset charset) {
      if (options.length == 0) {
        // If no OpenOptions were passed, delegate to Files.lines, which could have performance
        // advantages. (If OpenOptions were passed we can't, because Files.lines doesn't have an
        // overload taking OpenOptions, meaning we can't guarantee the same behavior w.r.t. things
        // like following/not following symlinks.
        return new AsCharSource(charset) {
          @SuppressWarnings("FilesLinesLeak") // the user needs to close it in this case
          @Override
          public Stream<String> lines() throws IOException {
            return Files.lines(path, charset);
          }
        };
      }

      return super.asCharSource(charset);
    }

    @Override
    public String toString() {
      return "MoreFiles.asByteSource(" + path + ", " + Arrays.toString(options) + ")";
    }
  }

  /**
   * Returns a view of the given {@code path} as a {@link ByteSink}.
   *
   *
Any {@linkplain OpenOption open options} provided are used when opening streams to the file
   * and may affect the behavior of the returned sink and the streams it provides. See {@link
   * StandardOpenOption} for the standard options that may be provided. Providing no options is
   * equivalent to providing the {@link StandardOpenOption#CREATE CREATE}, {@link
   * StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING} and {@link StandardOpenOption#WRITE
   * WRITE} options.
   */
  public static ByteSink asByteSink(Path path, OpenOption... options) {
    return new PathByteSink(path, options);
  }

  private static final class PathByteSink extends ByteSink {

    private final Path path;
    private final OpenOption[] options;

    private PathByteSink(Path path, OpenOption... options) {
      this.path = checkNotNull(path);
      this.options = options.clone();
      // TODO(cgdecker): validate the provided options... for example, just READ seems wrong
    }

    @Override
    public OutputStream openStream() throws IOException {
      return Files.newOutputStream(path, options);
    }

    @Override
    public String toString() {
      return "MoreFiles.asByteSink(" + path + ", " + Arrays.toString(options) + ")";
    }
  }

  /**
   * Returns a view of the given {@code path} as a {@link CharSource} using the given {@code
   * charset}.
   *
   *
Any {@linkplain OpenOption open options} provided are used when opening streams to the file
   * and may affect the behavior of the returned source and the streams it provides. See {@link
   * StandardOpenOption} for the standard options that may be provided. Providing no options is
   * equivalent to providing the {@link StandardOpenOption#READ READ} option.
   */
  public static CharSource asCharSource(Path path, Charset charset, OpenOption... options) {
    return asByteSource(path, options).asCharSource(charset);
  }

  /**
   * Returns a view of the given {@code path} as a {@link CharSink} using the given {@code charset}.
   *
   *
Any {@linkplain OpenOption open options} provided are used when opening streams to the file
   * and may affect the behavior of the returned sink and the streams it provides. See {@link
   * StandardOpenOption} for the standard options that may be provided. Providing no options is
   * equivalent to providing the {@link StandardOpenOption#CREATE CREATE}, {@link
   * StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING} and {@link StandardOpenOption#WRITE
   * WRITE} options.
   */
  public static CharSink asCharSink(Path path, Charset charset, OpenOption... options) {
    return asByteSink(path, options).asCharSink(charset);
  }

  /**
   * Returns an immutable list of paths to the files contained in the given directory.
   *
   * @throws NoSuchFileException if the file does not exist (optional specific exception)
   * @throws NotDirectoryException if the file could not be opened because it is not a directory
   *     (optional specific exception)
   * @throws IOException if an I/O error occurs
   */
  public static ImmutableList<Path> listFiles(Path dir) throws IOException {
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
      return ImmutableList.copyOf(stream);
    } catch (DirectoryIteratorException e) {
      throw e.getCause();
    }
  }

  /**
   * Returns a {@link Traverser} instance for the file and directory tree. The returned traverser
   * starts from a {@link Path} and will return all files and directories it encounters.
   *
   *
The returned traverser attempts to avoid following symbolic links to directories. However,
   * the traverser cannot guarantee that it will not follow symbolic links to directories as it is
   * possible for a directory to be replaced with a symbolic link between checking if the file is a
   * directory and actually reading the contents of that directory.
   *
   *
If the {@link Path} passed to one of the traversal methods does not exist or is not a
   * directory, no exception will be thrown and the returned {@link Iterable} will contain a single
   * element: that path.
   *
   *
{@link DirectoryIteratorException} may be thrown when iterating {@link Iterable} instances
   * created by this traverser if an {@link IOException} is thrown by a call to {@link
   * #listFiles(Path)}.
   *
   *
Example: {@code MoreFiles.fileTraverser().depthFirstPreOrder(Paths.get("/"))} may return the
   * following paths: {@code ["/", "/etc", "/etc/config.txt", "/etc/fonts", "/home", "/home/alice",
   * ...]}
   *
   * @since 23.5
   */
  public static Traverser<Path> fileTraverser() {
    return Traverser.forTree(FILE_TREE);
  }

  private static final SuccessorsFunction<Path> FILE_TREE =
      new SuccessorsFunction<Path>() {
        @Override
        public Iterable<Path> successors(Path path) {
          return fileTreeChildren(path);
        }
      };

  private static Iterable<Path> fileTreeChildren(Path dir) {
    if (Files.isDirectory(dir, NOFOLLOW_LINKS)) {
      try {
        return listFiles(dir);
      } catch (IOException e) {
        // the exception thrown when iterating a DirectoryStream if an I/O exception occurs
        throw new DirectoryIteratorException(e);
      }
    }
    return ImmutableList.of();
  }

  /**
   * Returns a predicate that returns the result of {@link java.nio.file.Files#isDirectory(Path,
   * LinkOption...)} on input paths with the given link options.
   */
  public static Predicate<Path> isDirectory(LinkOption... options) {
    final LinkOption[] optionsCopy = options.clone();
    return new Predicate<Path>() {
      @Override
      public boolean apply(Path input) {
        return Files.isDirectory(input, optionsCopy);
      }

      @Override
      public String toString() {
        return "MoreFiles.isDirectory(" + Arrays.toString(optionsCopy) + ")";
      }
    };
  }

  /** Returns whether or not the file with the given name in the given dir is a directory. */
  private static boolean isDirectory(
      SecureDirectoryStream<Path> dir, Path name, LinkOption... options) throws IOException {
    return dir.getFileAttributeView(name, BasicFileAttributeView.class, options)
        .readAttributes()
        .isDirectory();
  }

  /**
   * Returns a predicate that returns the result of {@link java.nio.file.Files#isRegularFile(Path,
   * LinkOption...)} on input paths with the given link options.
   */
  public static Predicate<Path> isRegularFile(LinkOption... options) {
    final LinkOption[] optionsCopy = options.clone();
    return new Predicate<Path>() {
      @Override
      public boolean apply(Path input) {
        return Files.isRegularFile(input, optionsCopy);
      }

      @Override
      public String toString() {
        return "MoreFiles.isRegularFile(" + Arrays.toString(optionsCopy) + ")";
      }
    };
  }

  /**
   * Returns true if the files located by the given paths exist, are not directories, and contain
   * the same bytes.
   *
   * @throws IOException if an I/O error occurs
   * @since 22.0
   */
  public static boolean equal(Path path1, Path path2) throws IOException {
    checkNotNull(path1);
    checkNotNull(path2);
    if (Files.isSameFile(path1, path2)) {
      return true;
    }

    /*
     * Some operating systems may return zero as the length for files denoting system-dependent
     * entities such as devices or pipes, in which case we must fall back on comparing the bytes
     * directly.
     */
    ByteSource source1 = asByteSource(path1);
    ByteSource source2 = asByteSource(path2);
    long len1 = source1.sizeIfKnown().or(0L);
    long len2 = source2.sizeIfKnown().or(0L);
    if (len1 != 0 && len2 != 0 && len1 != len2) {
      return false;
    }
    return source1.contentEquals(source2);
  }

  /**
   * Like the unix command of the same name, creates an empty file or updates the last modified
   * timestamp of the existing file at the given path to the current system time.
   */
  @SuppressWarnings("GoodTime") // reading system time without TimeSource
  public static void touch(Path path) throws IOException {
    checkNotNull(path);

    try {
      Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis()));
    } catch (NoSuchFileException e) {
      try {
        Files.createFile(path);
      } catch (FileAlreadyExistsException ignore) {
        // The file didn't exist when we called setLastModifiedTime, but it did when we called
        // createFile, so something else created the file in between. The end result is
        // what we wanted: a new file that probably has its last modified time set to approximately
        // now. Or it could have an arbitrary last modified time set by the creator, but that's no
        // different than if another process set its last modified time to something else after we
        // created it here.
      }
    }
  }

  /**
   * Creates any necessary but nonexistent parent directories of the specified path. Note that if
   * this operation fails, it may have succeeded in creating some (but not all) of the necessary
   * parent directories. The parent directory is created with the given {@code attrs}.
   *
   * @throws IOException if an I/O error occurs, or if any necessary but nonexistent parent
   *     directories of the specified file could not be created.
   */
  public static void createParentDirectories(Path path, FileAttribute<?>... attrs)
      throws IOException {
    // Interestingly, unlike File.getCanonicalFile(), Path/Files provides no way of getting the
    // canonical (absolute, normalized, symlinks resolved, etc.) form of a path to a nonexistent
    // file. getCanonicalFile() can at least get the canonical form of the part of the path which
    // actually exists and then append the normalized remainder of the path to that.
    Path normalizedAbsolutePath = path.toAbsolutePath().normalize();
    Path parent = normalizedAbsolutePath.getParent();
    if (parent == null) {
      // The given directory is a filesystem root. All zero of its ancestors exist. This doesn't
      // mean that the root itself exists -- consider x:\ on a Windows machine without such a
      // drive -- or even that the caller can create it, but this method makes no such guarantees
      // even for non-root files.
      return;
    }

    // Check if the parent is a directory first because createDirectories will fail if the parent
    // exists and is a symlink to a directory... we'd like for this to succeed in that case.
    // (I'm kind of surprised that createDirectories would fail in that case; doesn't seem like
    // what you'd want to happen.)
    if (!Files.isDirectory(parent)) {
      Files.createDirectories(parent, attrs);
      if (!Files.isDirectory(parent)) {
        throw new IOException("Unable to create parent directories of " + path);
      }
    }
  }

  /**
   * Returns the file extension for
   * the file at the given path, or the empty string if the file has no extension. The result does
   * not include the '{@code .}'.
   *
   *
Note: This method simply returns everything after the last '{@code .}' in the file's
   * name as determined by {@link Path#getFileName}. It does not account for any filesystem-specific
   * behavior that the {@link Path} API does not already account for. For example, on NTFS it will
   * report {@code "txt"} as the extension for the filename {@code "foo.exe:.txt"} even though NTFS
   * will drop the {@code ":.txt"} part of the name when the file is actually created on the
   * filesystem due to NTFS's Alternate Data Streams.
   */
  public static String getFileExtension(Path path) {
    Path name = path.getFileName();

    // null for empty paths and root-only paths
    if (name == null) {
      return "";
    }

    String fileName = name.toString();
    int dotIndex = fileName.lastIndexOf('.');
    return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1);
  }

  /**
   * Returns the file name without its file extension or path. This is
   * similar to the {@code basename} unix command. The result does not include the '{@code .}'.
   */
  public static String getNameWithoutExtension(Path path) {
    Path name = path.getFileName();

    // null for empty paths and root-only paths
    if (name == null) {
      return "";
    }

    String fileName = name.toString();
    int dotIndex = fileName.lastIndexOf('.');
    return dotIndex == -1 ? fileName : fileName.substring(0, dotIndex);
  }

  /**
   * Deletes the file or directory at the given {@code path} recursively. Deletes symbolic links,
   * not their targets (subject to the caveat below).
   *
   *
If an I/O exception occurs attempting to read, open or delete any file under the given
   * directory, this method skips that file and continues. All such exceptions are collected and,
   * after attempting to delete all files, an {@code IOException} is thrown containing those
   * exceptions as {@linkplain Throwable#getSuppressed() suppressed exceptions}.
   *
   *
Warning: Security of recursive deletes

   *
   *
On a file system that supports symbolic links and does not support {@link
   * SecureDirectoryStream}, it is possible for a recursive delete to delete files and directories
   * that are outside the directory being deleted. This can happen if, after checking that a
   * file is a directory (and not a symbolic link), that directory is replaced by a symbolic link to
   * an outside directory before the call that opens the directory to read its entries.
   *
   *
By default, this method throws {@link InsecureRecursiveDeleteException} if it can't
   * guarantee the security of recursive deletes. If you wish to allow the recursive deletes anyway,
   * pass {@link RecursiveDeleteOption#ALLOW_INSECURE} to this method to override that behavior.
   *
   * @throws NoSuchFileException if {@code path} does not exist (optional specific exception)
   * @throws InsecureRecursiveDeleteException if the security of recursive deletes can't be
   *     guaranteed for the file system and {@link RecursiveDeleteOption#ALLOW_INSECURE} was not
   *     specified
   * @throws IOException if {@code path} or any file in the subtree rooted at it can't be deleted
   *     for any reason
   */
  public static void deleteRecursively(Path path, RecursiveDeleteOption... options)
      throws IOException {
    Path parentPath = getParentPath(path);
    if (parentPath == null) {
      throw new FileSystemException(path.toString(), null, "can't delete recursively");
    }

    Collection<IOException> exceptions = null; // created lazily if needed
    try {
      boolean sdsSupported = false;
      try (DirectoryStream<Path> parent = Files.newDirectoryStream(parentPath)) {
        if (parent instanceof SecureDirectoryStream) {
          sdsSupported = true;
          exceptions =
              deleteRecursivelySecure((SecureDirectoryStream<Path>) parent, path.getFileName());
        }
      }

      if (!sdsSupported) {
        checkAllowsInsecure(path, options);
        exceptions = deleteRecursivelyInsecure(path);
      }
    } catch (IOException e) {
      if (exceptions == null) {
        throw e;
      } else {
        exceptions.add(e);
      }
    }

    if (exceptions != null) {
      throwDeleteFailed(path, exceptions);
    }
  }

  /**
   * Deletes all files within the directory at the given {@code path} {@linkplain #deleteRecursively
   * recursively}. Does not delete the directory itself. Deletes symbolic links, not their targets
   * (subject to the caveat below). If {@code path} itself is a symbolic link to a directory, that
   * link is followed and the contents of the directory it targets are deleted.
   *
   *
If an I/O exception occurs attempting to read, open or delete any file under the given
   * directory, this method skips that file and continues. All such exceptions are collected and,
   * after attempting to delete all files, an {@code IOException} is thrown containing those
   * exceptions as {@linkplain Throwable#getSuppressed() suppressed exceptions}.
   *
   *
Warning: Security of recursive deletes

   *
   *
On a file system that supports symbolic links and does not support {@link
   * SecureDirectoryStream}, it is possible for a recursive delete to delete files and directories
   * that are outside the directory being deleted. This can happen if, after checking that a
   * file is a directory (and not a symbolic link), that directory is replaced by a symbolic link to
   * an outside directory before the call that opens the directory to read its entries.
   *
   *
By default, this method throws {@link InsecureRecursiveDeleteException} if it can't
   * guarantee the security of recursive deletes. If you wish to allow the recursive deletes anyway,
   * pass {@link RecursiveDeleteOption#ALLOW_INSECURE} to this method to override that behavior.
   *
   * @throws NoSuchFileException if {@code path} does not exist (optional specific exception)
   * @throws NotDirectoryException if the file at {@code path} is not a directory (optional
   *     specific exception)
   * @throws InsecureRecursiveDeleteException if the security of recursive deletes can't be
   *     guaranteed for the file system and {@link RecursiveDeleteOption#ALLOW_INSECURE} was not
   *     specified
   * @throws IOException if one or more files can't be deleted for any reason
   */
  public static void deleteDirectoryContents(Path path, RecursiveDeleteOption... options)
      throws IOException {
    Collection<IOException> exceptions = null; // created lazily if needed
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
      if (stream instanceof SecureDirectoryStream) {
        SecureDirectoryStream<Path> sds = (SecureDirectoryStream<Path>) stream;
        exceptions = deleteDirectoryContentsSecure(sds);
      } else {
        checkAllowsInsecure(path, options);
        exceptions = deleteDirectoryContentsInsecure(stream);
      }
    } catch (IOException e) {
      if (exceptions == null) {
        throw e;
      } else {
        exceptions.add(e);
      }
    }

    if (exceptions != null) {
      throwDeleteFailed(path, exceptions);
    }
  }

  /**
   * Secure recursive delete using {@code SecureDirectoryStream}. Returns a collection of exceptions
   * that occurred or null if no exceptions were thrown.
   */
  private static @Nullable Collection<IOException> deleteRecursivelySecure(
      SecureDirectoryStream<Path> dir, Path path) {
    Collection<IOException> exceptions = null;
    try {
      if (isDirectory(dir, path, NOFOLLOW_LINKS)) {
        try (SecureDirectoryStream<Path> childDir = dir.newDirectoryStream(path, NOFOLLOW_LINKS)) {
          exceptions = deleteDirectoryContentsSecure(childDir);
        }

        // If exceptions is not null, something went wrong trying to delete the contents of the
        // directory, so we shouldn't try to delete the directory as it will probably fail.
        if (exceptions == null) {
          dir.deleteDirectory(path);
        }
      } else {
        dir.deleteFile(path);
      }

      return exceptions;
    } catch (IOException e) {
      return addException(exceptions, e);
    }
  }

  /**
   * Secure method for deleting the contents of a directory using {@code SecureDirectoryStream}.
   * Returns a collection of exceptions that occurred or null if no exceptions were thrown.
   */
  private static @Nullable Collection<IOException> deleteDirectoryContentsSecure(
      SecureDirectoryStream<Path> dir) {
    Collection<IOException> exceptions = null;
    try {
      for (Path path : dir) {
        exceptions = concat(exceptions, deleteRecursivelySecure(dir, path.getFileName()));
      }

      return exceptions;
    } catch (DirectoryIteratorException e) {
      return addException(exceptions, e.getCause());
    }
  }

  /**
   * Insecure recursive delete for file systems that don't support {@code SecureDirectoryStream}.
   * Returns a collection of exceptions that occurred or null if no exceptions were thrown.
   */
  private static @Nullable Collection<IOException> deleteRecursivelyInsecure(Path path) {
    Collection<IOException> exceptions = null;
    try {
      if (Files.isDirectory(path, NOFOLLOW_LINKS)) {
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
          exceptions = deleteDirectoryContentsInsecure(stream);
        }
      }

      // If exceptions is not null, something went wrong trying to delete the contents of the
      // directory, so we shouldn't try to delete the directory as it will probably fail.
      if (exceptions == null) {
        Files.delete(path);
      }

      return exceptions;
    } catch (IOException e) {
      return addException(exceptions, e);
    }
  }

  /**
   * Simple, insecure method for deleting the contents of a directory for file systems that don't
   * support {@code SecureDirectoryStream}. Returns a collection of exceptions that occurred or null
   * if no exceptions were thrown.
   */
  private static @Nullable Collection<IOException> deleteDirectoryContentsInsecure(
      DirectoryStream<Path> dir) {
    Collection<IOException> exceptions = null;
    try {
      for (Path entry : dir) {
        exceptions = concat(exceptions, deleteRecursivelyInsecure(entry));
      }

      return exceptions;
    } catch (DirectoryIteratorException e) {
      return addException(exceptions, e.getCause());
    }
  }

  /**
   * Returns a path to the parent directory of the given path. If the path actually has a parent
   * path, this is simple. Otherwise, we need to do some trickier things. Returns null if the path
   * is a root or is the empty path.
   */
  private static @Nullable Path getParentPath(Path path) {
    Path parent = path.getParent();

    // Paths that have a parent:
    if (parent != null) {
      // "/foo" ("/")
      // "foo/bar" ("foo")
      // "C:\foo" ("C:\")
      // "\foo" ("\" - current drive for process on Windows)
      // "C:foo" ("C:" - working dir of drive C on Windows)
      return parent;
    }

    // Paths that don't have a parent:
    if (path.getNameCount() == 0) {
      // "/", "C:\", "\" (no parent)
      // "" (undefined, though typically parent of working dir)
      // "C:" (parent of working dir of drive C on Windows)
      //
      // For working dir paths ("" and "C:"), return null because:
      //   A) it's not specified that "" is the path to the working directory.
      //   B) if we're getting this path for recursive delete, it's typically not possible to
      //      delete the working dir with a relative path anyway, so it's ok to fail.
      //   C) if we're getting it for opening a new SecureDirectoryStream, there's no need to get
      //      the parent path anyway since we can safely open a DirectoryStream to the path without
      //      worrying about a symlink.
      return null;
    } else {
      // "foo" (working dir)
      return path.getFileSystem().getPath(".");
    }
  }

  /** Checks that the given options allow an insecure delete, throwing an exception if not. */
  private static void checkAllowsInsecure(Path path, RecursiveDeleteOption[] options)
      throws InsecureRecursiveDeleteException {
    if (!Arrays.asList(options).contains(RecursiveDeleteOption.ALLOW_INSECURE)) {
      throw new InsecureRecursiveDeleteException(path.toString());
    }
  }

  /**
   * Adds the given exception to the given collection, creating the collection if it's null. Returns
   * the collection.
   */
  private static Collection<IOException> addException(
      @Nullable Collection<IOException> exceptions, IOException e) {
    if (exceptions == null) {
      exceptions = new ArrayList<>(); // don't need Set semantics
    }
    exceptions.add(e);
    return exceptions;
  }

  /**
   * Concatenates the contents of the two given collections of exceptions. If either collection is
   * null, the other collection is returned. Otherwise, the elements of {@code other} are added to
   * {@code exceptions} and {@code exceptions} is returned.
   */
  private static @Nullable Collection<IOException> concat(
      @Nullable Collection<IOException> exceptions, @Nullable Collection<IOException> other) {
    if (exceptions == null) {
      return other;
    } else if (other != null) {
      exceptions.addAll(other);
    }
    return exceptions;
  }

  /**
   * Throws an exception indicating that one or more files couldn't be deleted. The thrown exception
   * contains all the exceptions in the given collection as suppressed exceptions.
   */
  private static void throwDeleteFailed(Path path, Collection<IOException> exceptions)
      throws FileSystemException {
    // TODO(cgdecker): Should there be a custom exception type for this?
    // Also, should we try to include the Path of each file we may have failed to delete rather
    // than just the exceptions that occurred?
    FileSystemException deleteFailed =
        new FileSystemException(
            path.toString(),
            null,
            "failed to delete one or more files; see suppressed exceptions for details");
    for (IOException e : exceptions) {
      deleteFailed.addSuppressed(e);
    }
    throw deleteFailed;
  }
}