/**
   * @param entry Entry to touch.
   * @return {@code True} if queue has been changed by this call.
   */
  private boolean touch(GridCacheEntry<K, V> entry) {
    Node<GridCacheEntry<K, V>> node = entry.meta(meta);

    // Entry has not been enqueued yet.
    if (node == null) {
      while (true) {
        node = queue.offerLastx(entry);

        if (entry.putMetaIfAbsent(meta, node) != null) {
          // Was concurrently added, need to clear it from queue.
          queue.unlinkx(node);

          // Queue has not been changed.
          return false;
        } else if (node.item() != null) {
          if (!entry.isCached()) {
            // Was concurrently evicted, need to clear it from queue.
            queue.unlinkx(node);

            return false;
          }

          return true;
        }
        // If node was unlinked by concurrent shrink() call, we must repeat the whole cycle.
        else if (!entry.removeMeta(meta, node)) return false;
      }
    }

    // Entry is already in queue.
    return false;
  }
  /**
   * @param entry Entry.
   * @param bound Value bound.
   * @throws Exception If failed.
   */
  private void checkEntry(GridCacheEntry<String, Integer> entry, int bound) throws Exception {
    assert entry != null;

    checkKey(entry.getKey());
    checkValue(entry.getValue(), bound);
    checkValue(entry.get(), bound);
  }
  /** Shrinks queue to maximum allowed size. */
  private void shrink() {
    long maxSize = this.maxSize;
    int maxBlocks = this.maxBlocks;

    int cnt = queue.sizex();

    for (int i = 0;
        i < cnt
            && (maxBlocks > 0 && queue.sizex() > maxBlocks
                || maxSize > 0 && curSize.longValue() > maxSize);
        i++) {
      GridCacheEntry<GridGgfsBlockKey, byte[]> entry = queue.poll();

      if (entry == null) break; // Queue is empty.

      byte[] val = entry.peek();

      if (val != null)
        changeSize(-val.length); // Change current size as we polled entry from the queue.

      if (!entry.evict()) {
        // Reorder entries which we failed to evict.
        entry.removeMeta(META_NODE);

        touch(entry);
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  public void onEntryAccessed(boolean rmv, GridCacheEntry<K, V> entry) {
    if (!rmv) {
      if (!entry.isCached()) return;

      if (touch(entry)) shrink();
    } else {
      Node<GridCacheEntry<K, V>> node = entry.removeMeta(meta);

      if (node != null) queue.unlinkx(node);
    }
  }
  /** {@inheritDoc} */
  @Override
  public void onEntryAccessed(boolean rmv, GridCacheEntry<GridGgfsBlockKey, byte[]> entry) {
    if (!rmv) {
      if (!entry.isCached()) return;

      if (touch(entry)) shrink();
    } else {
      MetaEntry meta = entry.removeMeta(META_NODE);

      if (meta != null && queue.unlinkx(meta.node())) changeSize(-meta.size());
    }
  }
  /** Shrinks FIFO queue to maximum allowed size. */
  private void shrink() {
    int max = this.max;

    int startSize = queue.sizex();

    for (int i = 0; i < startSize && queue.sizex() > max; i++) {
      GridCacheEntry<K, V> entry = queue.poll();

      if (entry == null) break;

      if (!entry.evict()) {
        entry.removeMeta(meta);

        touch(entry);
      }
    }
  }
  /**
   * Checks entry for empty value.
   *
   * @param entry Entry to check.
   * @return {@code True} if entry is empty.
   */
  private boolean empty(GridCacheEntry<K, V> entry) {
    try {
      return entry.peek(F.asList(GLOBAL)) == null;
    } catch (GridException e) {
      U.error(null, e.getMessage(), e);

      assert false : "Should never happen: " + e;

      return false;
    }
  }
    /** {@inheritDoc} */
    @Override
    public void remove() {
      if (currEntry == null) throw new IllegalStateException();

      assert currIter != null;

      currIter.remove();

      try {
        GridNearCache.this.remove(currEntry.getKey(), CU.<K, V>empty());
      } catch (GridException e) {
        throw new GridRuntimeException(e);
      }
    }
  /** {@inheritDoc} */
  @Override
  public boolean evictAllowed(GridCacheEntry entry) {
    Object key = entry.getKey();

    return !(key instanceof GridGgfsBlockKey && ((GridGgfsBlockKey) key).evictExclude());
  }
  /**
   * @param entry Entry to touch.
   * @return {@code True} if new node has been added to queue by this call.
   */
  private boolean touch(GridCacheEntry<GridGgfsBlockKey, byte[]> entry) {
    byte[] val = entry.peek();

    int blockSize = val != null ? val.length : 0;

    MetaEntry meta = entry.meta(META_NODE);

    // Entry has not been enqueued yet.
    if (meta == null) {
      while (true) {
        Node<GridCacheEntry<GridGgfsBlockKey, byte[]>> node = queue.offerLastx(entry);

        meta = new MetaEntry(node, blockSize);

        if (entry.putMetaIfAbsent(META_NODE, meta) != null) {
          // Was concurrently added, need to clear it from queue.
          queue.unlinkx(node);

          // Queue has not been changed.
          return false;
        } else if (node.item() != null) {
          if (!entry.isCached()) {
            // Was concurrently evicted, need to clear it from queue.
            queue.unlinkx(node);

            return false;
          }

          // Increment current size.
          changeSize(blockSize);

          return true;
        }
        // If node was unlinked by concurrent shrink() call, we must repeat the whole cycle.
        else if (!entry.removeMeta(META_NODE, node)) return false;
      }
    } else {
      int oldBlockSize = meta.size();

      Node<GridCacheEntry<GridGgfsBlockKey, byte[]>> node = meta.node();

      if (queue.unlinkx(node)) {
        // Move node to tail.
        Node<GridCacheEntry<GridGgfsBlockKey, byte[]>> newNode = queue.offerLastx(entry);

        int delta = blockSize - oldBlockSize;

        if (!entry.replaceMeta(META_NODE, meta, new MetaEntry(newNode, blockSize))) {
          // Was concurrently added, need to clear it from queue.
          if (queue.unlinkx(newNode)) delta -= blockSize;
        }

        if (delta != 0) {
          changeSize(delta);

          if (delta > 0)
            // Total size increased, so shrinking could be needed.
            return true;
        }
      }
    }

    // Entry is already in queue.
    return false;
  }