<commit_before><?php

/*
 * This file is part of the Pager package.
 *
 * (c) <NAME> <EMAIL>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace KG\Pager\Adapter;

use KG\Pager\AdapterInterface;

/**
 * Caches and attempts to minimize calls made to the wrapped adapter.
 */
final class CachedDecorator implements AdapterInterface
{
    /**
     * @var AdapterInterface
     */
    private $adapter;

    /**
     * @var integer|null
     */
    private $itemCount;

    /**
     * @var array
     */
    private $cached = array();

    /**
     * Position of the last item in the entire paged set. The decorator expects
     * no rows after this position.
     *
     * @var integer|null
     */
    private $lastItemPos;

    /**
     * @param AdapterInterface $adapter
     */
    public function __construct(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;
    }

    /**
     * {@inheritDoc}
     */
    public function getItemCount()
    {
        if (null === $this->itemCount) {
            $this->itemCount = $this->adapter->getItemCount();
        }

        return $this->itemCount;
    }

    /**
     * {@inheritDoc}
     */
    public function getItems($offset, $limit)
    {
        $cached = &$this->cached;

        $begin = $offset;
        $end = $offset + $limit;

        if (null !== $this->lastItemPos && $end > $this->lastItemPos) {
            $end = $this->lastItemPos;
        }

        // Start narrowing both beginning & end indices until an item at the
        // given position is not cached.
        while ($begin < $end && array_key_exists($begin, $cached)) {
            $begin++;
        }

        while ($begin < $end && array_key_exists($end - 1, $cached)) {
            $end--;
        }

        // If non-cached items were found, go get and cache them.
        if ($begin < $end) {
            $i = $begin;

            $nonCachedOffset = $begin;
            $nonCachedLimit = $end - $begin;

            $nonCachedItems = $this->adapter->getItems($nonCachedOffset, $nonCachedLimit);

            foreach ($nonCachedItems as $item) {
                $cached[$i++] = $item;
            }

            if (count($nonCachedItems) < $nonCachedLimit) {
                $this->lastItemPos = $i - 1;
            }
        }

        $items = array();

        for ($i = $offset; $i < ($offset + $limit); $i++) {
            if (!array_key_exists($i, $cached)) {
                break;
            }

            $items[] = $cached[$i];
        }

        return $items;
    }
}<commit_msg>Split CachedDecorator into several smaller methods<commit_after><?php

/*
 * This file is part of the Pager package.
 *
 * (c) <NAME> <EMAIL>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace KG\Pager\Adapter;

use KG\Pager\AdapterInterface;

/**
 * Caches and attempts to minimize calls made to the wrapped adapter.
 */
final class CachedDecorator implements AdapterInterface
{
    /**
     * @var AdapterInterface
     */
    private $adapter;

    /**
     * @var integer|null
     */
    private $itemCount;

    /**
     * @var array
     */
    private $cached = array();

    /**
     * Position of the last item in the entire paged set. The decorator expects
     * no rows after this position.
     *
     * @var integer|null
     */
    private $lastItemPos;

    /**
     * @param AdapterInterface $adapter
     */
    public function __construct(AdapterInterface $adapter)
    {
        $this->adapter = $adapter;
    }

    /**
     * {@inheritDoc}
     */
    public function getItemCount()
    {
        if (null === $this->itemCount) {
            $this->itemCount = $this->adapter->getItemCount();
        }

        return $this->itemCount;
    }

    /**
     * {@inheritDoc}
     */
    public function getItems($offset, $limit)
    {
        list($notCachedOffset, $notCachedLimit) = $this->findNotCachedRange($offset, $limit);

        if ($notCachedLimit > 0) {
            $this->cache($notCachedOffset, $notCachedLimit);
        }

        return $this->fromCache($offset, $limit);
    }

    /**
     * @param integer $offset
     * @param integer $limit
     *
     * @return array
     */
    private function findNotCachedRange($offset, $limit)
    {
        $begin = $offset;
        $end = $offset + $limit;

        if (null !== $this->lastItemPos && $end > $this->lastItemPos) {
            $end = $this->lastItemPos;
        }

        // Start narrowing both beginning & end indices until an item at the
        // given position is not cached.
        while ($begin < $end && array_key_exists($begin, $this->cached)) {
            $begin++;
        }

        while ($begin < $end && array_key_exists($end - 1, $this->cached)) {
            $end--;
        }

        return array($begin, $end - $begin);
    }

    /**
     * Fetches the given range of items from the adapter and stores them in
     * the cache array.
     *
     * @param integer $offset
     * @param integer $limit
     */
    private function cache($offset, $limit)
    {
        $items = $this->adapter->getItems($offset, $limit);
        $i = $offset;

        foreach ($items as $item) {
            $this->cached[$i++] = $item;
        }

        if (count($items) < $limit) {
            $this->lastItemPos = $i - 1;
        }
    }

    /**
     * Returns the range from previously cached items.
     *
     * @param integer $offset
     * @param integer $limit
     *
     * @return array
     */
    private function fromCache($offset, $limit)
    {
        $items = array();

        for ($i = $offset; $i < ($offset + $limit); $i++) {
            if (!array_key_exists($i, $this->cached)) {
                break;
            }

            $items[] = $this->cached[$i];
        }

        return $items;
    }
}
{'fraction_non_alphanumeric': 0.1360738255033557, 'fraction_numerical': 0.0008389261744966443, 'mean_word_length': 1.8211074301940369, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3199277', 'n_tokens_mistral': 1782, 'n_tokens_neox': 1731, 'n_words': 470}