/**
   * Checks if address can be reached using one argument InetAddress.isReachable() version or ping
   * command if failed.
   *
   * @param addr Address to check.
   * @param reachTimeout Timeout for the check.
   * @return {@code True} if address is reachable.
   */
  public static boolean reachableByPing(InetAddress addr, int reachTimeout) {
    try {
      if (addr.isReachable(reachTimeout)) return true;

      String cmd = String.format("ping -%s 1 %s", U.isWindows() ? "n" : "c", addr.getHostAddress());

      Process myProc = Runtime.getRuntime().exec(cmd);

      myProc.waitFor();

      return myProc.exitValue() == 0;
    } catch (IOException ignore) {
      return false;
    } catch (InterruptedException ignored) {
      Thread.currentThread().interrupt();

      return false;
    }
  }
/**
 * This adepter designed to support stores with bulk loading from stream-like source.
 *
 * <p>This class processes input data in the following way:
 *
 * <ul>
 *   <li>Iterator of input record obtained from user-defined {@link #inputIterator(Object...)}.
 *   <li>Iterator continuously queried for input records and they are grouped into batches of {@link
 *       #batchSize}.
 *   <li>Batch is placed into processing queue and puled by one of {@link #threadsCnt} working
 *       threads.
 *   <li>Each record in batch is passed to user-defined {@link #parse(Object, Object...)} method and
 *       result is stored into cache.
 * </ul>
 *
 * <p>Two methods should be implemented by inheritants:
 *
 * <ul>
 *   <li>{@link #inputIterator(Object...)}. It should open underlying data source and iterate all
 *       record available in it. Individual records could be in very raw form, like text lines for
 *       CSV files.
 *   <li>{@link #parse(Object, Object...)}. This method should process input records and transform
 *       them into key-value pairs for cache.
 * </ul>
 *
 * <p>
 *
 * @param <K> Key type.
 * @param <V> Value type.
 * @param <I> Input type.
 * @author @java.author
 * @version @java.version
 */
public abstract class GridCacheLoadOnlyStoreAdapter<K, V, I> implements GridCacheStore<K, V> {
  /**
   * Default batch size (number of records read with {@link #inputIterator(Object...)} and then
   * submitted to internal pool at a time).
   */
  public static final int DFLT_BATCH_SIZE = 100;

  /** Default batch queue size (max batches count to limit memory usage). */
  public static final int DFLT_BATCH_QUEUE_SIZE = 100;

  /** Default number of working threads (equal to the number of available processors). */
  public static final int DFLT_THREADS_COUNT = Runtime.getRuntime().availableProcessors();

  /** Auto-injected logger. */
  @GridLoggerResource private GridLogger log;

  /** Batch size. */
  private int batchSize = DFLT_BATCH_SIZE;

  /** Size of queue of batches to process. */
  private int batchQueueSize = DFLT_BATCH_QUEUE_SIZE;

  /** Number fo working threads. */
  private int threadsCnt = DFLT_THREADS_COUNT;

  /**
   * Returns iterator of input records.
   *
   * <p>Note that returned iterator doesn't have to be thread-safe. Thus it could operate on raw
   * streams, DB connections, etc. without additional synchronization.
   *
   * @param args Arguments passes into {@link GridCache#loadCache(GridBiPredicate, long, Object...)}
   *     method.
   * @return Iterator over input records.
   * @throws GridException If iterator can't be created with the given arguments.
   */
  protected abstract Iterator<I> inputIterator(@Nullable Object... args) throws GridException;

  /**
   * This method should transform raw data records into valid key-value pairs to be stored into
   * cache.
   *
   * <p>If {@code null} is returned then this record will be just skipped.
   *
   * @param rec A raw data record.
   * @param args Arguments passed into {@link GridCache#loadCache(GridBiPredicate, long, Object...)}
   *     method.
   * @return Cache entry to be saved in cache or {@code null} if no entry could be produced from
   *     this record.
   */
  @Nullable
  protected abstract GridBiTuple<K, V> parse(I rec, @Nullable Object... args);

  /** {@inheritDoc} */
  @Override
  public void loadCache(GridBiInClosure<K, V> c, @Nullable Object... args) throws GridException {
    ExecutorService exec =
        new ThreadPoolExecutor(
            threadsCnt,
            threadsCnt,
            0L,
            MILLISECONDS,
            new ArrayBlockingQueue<Runnable>(batchQueueSize),
            new BlockingRejectedExecutionHandler());

    Iterator<I> iter = inputIterator(args);

    Collection<I> buf = new ArrayList<>(batchSize);

    try {
      while (iter.hasNext()) {
        if (Thread.currentThread().isInterrupted()) {
          U.warn(log, "Working thread was interrupted while loading data.");

          break;
        }

        buf.add(iter.next());

        if (buf.size() == batchSize) {
          exec.submit(new Worker(c, buf, args));

          buf = new ArrayList<>(batchSize);
        }
      }

      if (!buf.isEmpty()) exec.submit(new Worker(c, buf, args));
    } catch (RejectedExecutionException ignored) {
      // Because of custom RejectedExecutionHandler.
      assert false : "RejectedExecutionException was thrown while it shouldn't.";
    } finally {
      exec.shutdown();

      try {
        exec.awaitTermination(Long.MAX_VALUE, MILLISECONDS);
      } catch (InterruptedException ignored) {
        U.warn(log, "Working thread was interrupted while waiting for put operations to complete.");

        Thread.currentThread().interrupt();
      }
    }
  }

  /**
   * Returns batch size.
   *
   * @return Batch size.
   */
  public int getBatchSize() {
    return batchSize;
  }

  /**
   * Sets batch size.
   *
   * @param batchSize Batch size.
   */
  public void setBatchSize(int batchSize) {
    this.batchSize = batchSize;
  }

  /**
   * Returns batch queue size.
   *
   * @return Batch queue size.
   */
  public int getBatchQueueSize() {
    return batchQueueSize;
  }

  /**
   * Sets batch queue size.
   *
   * @param batchQueueSize Batch queue size.
   */
  public void setBatchQueueSize(int batchQueueSize) {
    this.batchQueueSize = batchQueueSize;
  }

  /**
   * Returns number of worker threads.
   *
   * @return Number of worker threads.
   */
  public int getThreadsCount() {
    return threadsCnt;
  }

  /**
   * Sets number of worker threads.
   *
   * @param threadsCnt Number of worker threads.
   */
  public void setThreadsCount(int threadsCnt) {
    this.threadsCnt = threadsCnt;
  }

  /** {@inheritDoc} */
  @Override
  public V load(@Nullable GridCacheTx tx, K key) throws GridException {
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public void loadAll(
      @Nullable GridCacheTx tx, @Nullable Collection<? extends K> keys, GridBiInClosure<K, V> c)
      throws GridException {
    // No-op.
  }

  /** {@inheritDoc} */
  @Override
  public void put(@Nullable GridCacheTx tx, K key, @Nullable V val) throws GridException {
    // No-op.
  }

  /** {@inheritDoc} */
  @Override
  public void putAll(@Nullable GridCacheTx tx, @Nullable Map<? extends K, ? extends V> map)
      throws GridException {
    // No-op.
  }

  /** {@inheritDoc} */
  @Override
  public void remove(@Nullable GridCacheTx tx, K key) throws GridException {
    // No-op.
  }

  /** {@inheritDoc} */
  @Override
  public void removeAll(@Nullable GridCacheTx tx, @Nullable Collection<? extends K> keys)
      throws GridException {
    // No-op.
  }

  /** {@inheritDoc} */
  @Override
  public void txEnd(GridCacheTx tx, boolean commit) throws GridException {
    // No-op.
  }

  /** Worker. */
  private class Worker implements Runnable {
    /** */
    private final GridBiInClosure<K, V> c;

    /** */
    private final Collection<I> buf;

    /** */
    private final Object[] args;

    /**
     * @param c Closure for loaded entries.
     * @param buf Set of input records to process.
     * @param args Arguments passed into {@link GridCache#loadCache(GridBiPredicate, long,
     *     Object...)} method.
     */
    Worker(GridBiInClosure<K, V> c, Collection<I> buf, Object[] args) {
      this.c = c;
      this.buf = buf;
      this.args = args;
    }

    /** {@inheritDoc} */
    @Override
    public void run() {
      for (I rec : buf) {
        GridBiTuple<K, V> entry = parse(rec, args);

        if (entry != null) c.apply(entry.getKey(), entry.getValue());
      }
    }
  }

  /**
   * This handler blocks the caller thread until free space will be available in tasks queue. If the
   * executor is shut down than it throws {@link RejectedExecutionException}.
   *
   * <p>It is save to apply this policy when:
   *
   * <ol>
   *   <li>{@code shutdownNow} is not used on the pool.
   *   <li>{@code shutdown} is called from the thread where all submissions where performed.
   * </ol>
   */
  private class BlockingRejectedExecutionHandler implements RejectedExecutionHandler {
    /** {@inheritDoc} */
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
      try {
        if (executor.isShutdown()) throw new RejectedExecutionException();
        else executor.getQueue().put(r);
      } catch (InterruptedException ignored) {
        U.warn(log, "Working thread was interrupted while loading data.");

        Thread.currentThread().interrupt();
      }
    }
  }
}