<?php

namespace ilateral\SilverStripe\Searchable\Extensions;

use SilverStripe\Core\Extension;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\Form;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\FormAction;
use ilateral\SilverStripe\Searchable\Searchable;

/**
 * Add SearchForm to controllers
 *
 * @package Searchable
 */
class ControllerExtension extends Extension
{

    /**
     * Add a site search form to all controllers that links to the
     * results controller.
     *
     * @return Form
     */
    public function SearchForm()
    {
        // If we have setup objects to search
        if (count(Searchable::config()->objects)) {
            $searchText =  "";

            if ($this->owner->request && $this->owner->request->getVar('Search')) {
                $searchText = $this->owner->request->getVar('Search');
            }

            $fields = FieldList::create(
                TextField::create('Search', false, $searchText)
                    ->setAttribute("placeholder", _t('Searchable.Search', 'Search'))
            );

            $actions = FieldList::create(
                FormAction::create('results', _t('Searchable.Go', 'Go'))
            );

            $template_class = Searchable::config()->template_class;
            $results_page = Injector::inst()->create($template_class);

            $form = Form::create(
                $this->owner,
                'SearchForm',
                $fields,
                $actions
            )->setFormMethod('get')
            ->setFormAction($results_page->Link())
            ->setTemplate('ilateral\SilverStripe\Searchable\Includes\SearchForm')
            ->disableSecurityToken();

            $this->owner->extend("updateSearchForm", $form);

            return $form;
        }
    }
}


<?php
declare(strict_types=1);

namespace precore\util;

use precore\lang\Comparable;

/**
 * @package precore\util
 *
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
abstract class Arrays
{
    private function __construct()
    {
    }

    /**
     * Sorts the specified array according to the order induced by the specified comparator.
     * All elements in the array must be mutually comparable using the specified comparator
     * (that is, c.compare(e1, e2) must not throw a ClassCastException for any elements e1 and e2 in the array).
     *
     * <p>
     * If the specified comparator is null, this method sorts the specified array into ascending order,
     * according to the natural ordering of its elements. All elements in the array must implement
     * the {@link Comparable} interface. Furthermore, all elements in the array must be mutually comparable
     * (that is, e1.compareTo(e2) must not throw a ClassCastException for any elements e1 and e2 in the array).
     *
     * @param Comparable[] $list
     * @param Comparator $comparator
     */
    public static function sort(array &$list, Comparator $comparator = null) : void
    {
        if ($comparator === null) {
            $comparator = ComparableComparator::instance();
        }
        usort($list, Collections::compareFunctionFor($comparator));
    }
}


<?php
declare(strict_types=1);

namespace precore\lang;

use InvalidArgumentException;
use precore\util\Preconditions;
use ReflectionProperty;

/**
 * Extending this class will simulate an enum known in several languages.
 * The following rules must be followed:
 *  - use public static variables
 *  - call init() method right after the class definition
 *
 * All public static variables will be an instance of your class.
 * The instances stores the given name as a string
 * which can be obtained through name() method.
 *
 * For example:
 *
 * <pre>
 * class Color extends Enum
 * {
 *     public static $RED;
 *     public static $BLUE;
 * }
 * Color::init();
 * </pre>
 *
 * In this case Color::$RED will be a Color instance and its name() method will return 'RED'.
 *
 * All public static variables declared in your class will be used as a possible enum constant!
 *
 * Initializing objects can be done in constructor, which is recommended to defined as private or protected.
 * Constructor parameters (if any) can be defined by constructorArgs() static method.
 *
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
abstract class Enum extends BaseObject implements Comparable
{
    private static $cache = [];
    private static $ordinals = [];

    private $name;

    /**
     * Override if you want to pass any arguments to the constructor defined in your class.
     *
     * @return array Keys must be the names, values are arrays storing constructor parameters
     *
     * For example:
     *
     * class Color extends Enum
     * {
     *     public static $RED;
     *
     *     private $hexValue;
     *
     *     protected function __construct($hexValue)
     *     {
     *         $this->hexValue = $hexValue;
     *     }
     *
     *     protected static function constructorArgs()
     *     {
     *         return array('RED' => array('#ff0000'));
     *     }
     * }
     */
    protected static function constructorArgs()
    {
        return [];
    }

    /**
     * @param $name
     * @param array $constructorArgs
     * @return static
     */
    private static function newInstance($name, array $constructorArgs) : self
    {
        $reflectionClass = self::objectClass();
        $obj = $reflectionClass->newInstanceWithoutConstructor();
        $obj->name = $name;
        $constructor = $reflectionClass->getConstructor();
        if ($constructor !== null) {
            $constructor->setAccessible(true);
            $numOfParams = $constructor->getNumberOfParameters();
            if ($numOfParams == 0) {
                $constructor->invoke($obj);
            } else {
                Preconditions::checkArgument(
                    array_key_exists($name, $constructorArgs) && is_array($constructorArgs[$name])
                        && $numOfParams === count($constructorArgs[$name]),
                    'Invalid arguments are provided for constructor in %s:$%s',
                    static::class,
                    $name
                );
                $constructor->invokeArgs($obj, $constructorArgs[$name]);
            }
        }
        return $obj;
    }

    /**
     * Must be called after your class definition!
     */
    final public static function init() : void
    {
        $className = static::class;
        self::$cache[$className] = [];
        $reflectionClass = self::objectClass();
        $constructorParams = static::constructorArgs();
        $ordinal = 0;
        foreach ($reflectionClass->getProperties(ReflectionProperty::IS_STATIC) as $property) {
            if ($property->isPublic()) {
                $name = $property->getName();
                $instance = self::newInstance($name, $constructorParams);
                $property->setValue($instance);
                self::$cache[$className][$name] = $instance;
                self::$ordinals[$className][$name] = $ordinal++;
            }
        }
    }

    /**
     * You can obtain the appropriate instance by their name.
     *
     * @param string $name
     * @return static
     * @throws InvalidArgumentException
     */
    final public static function valueOf($name) : self
    {
        $className = static::class;
        Preconditions::checkArgument(
            array_key_exists($className, self::$cache) && array_key_exists($name, self::$cache[$className]),
            "The enum '%s' type has no constant with name '%s'",
            $className,
            $name
        );
        return self::$cache[$className][$name];
    }

    /**
     * @return static[]
     */
    final public static function values() : array
    {
        $className = static::class;
        return array_key_exists($className, self::$cache)
            ? self::$cache[$className]
            : [];
    }

    /**
     * The type and the name equality is being checked. Although the reference should be the same,
     * it can differ if one of the two objects have been deserialized.
     *
     * @param ObjectInterface $object
     * @return bool
     */
    public function equals(ObjectInterface $object = null) : bool
    {
        return $object instanceof static && $object->name === $this->name;
    }

    /**
     * Returns the name of this enum constant, exactly as declared in its enum declaration.
     * Most programmers should use the toString() method in preference to this one, as the toString method
     * may return a more user-friendly name. This method is designed primarily for use in specialized situations
     * where correctness depends on getting the exact name, which will not vary from release to release.
     *
     * @return string
     */
    final public function name() : string
    {
        return $this->name;
    }

    /**
     * Returns the ordinal of this enumeration constant
     * (its position in its enum declaration, where the initial constant is assigned an ordinal of zero).
     * Most programmers will have no use for this method.
     *
     * @return int
     */
    final public function ordinal() : int
    {
        return self::$ordinals[$this->getClassName()][$this->name()];
    }

    /**
     * Returns the name of this enum constant, as contained in the declaration. This method may be overridden,
     * though it typically isn't necessary or desirable. An enum type should override this method
     * when a more "programmer-friendly" string form exists.
     *
     * @return string
     */
    public function toString() : string
    {
        return $this->name;
    }

    /**
     * @param $object
     * @return int a negative integer, zero, or a positive integer
     *         as this object is less than, equal to, or greater than the specified object.
     * @throws ClassCastException - if the specified object's type prevents it from being compared to this object.
     * @throws NullPointerException if the specified object is null
     */
    public function compareTo($object) : int
    {
        Preconditions::checkNotNull($object, 'The given object is null');
        if (!($object instanceof static)) {
            throw new ClassCastException('The given object is not instance of this class');
        }
        /* @var $object Enum */
        return $this->ordinal() - $object->ordinal();
    }
}


<?php
declare(strict_types=1);

namespace precore\util;

/**
 * Contains {@link Profiler} objects.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class ProfilerRegistry
{
    /**
     * @var ProfilerRegistry
     */
    private static $instance;

    /**
     * @var Profiler[]
     */
    private $profilers = [];

    public static function init() : void
    {
        self::$instance = new ProfilerRegistry();
    }

    public static function instance() : ProfilerRegistry
    {
        return self::$instance;
    }

    /**
     * @param $name
     * @return Profiler
     * @throws \OutOfBoundsException if there is no registered profiler with $name
     */
    public function get(string $name) : Profiler
    {
        return Preconditions::checkElementExists(
            $this->profilers, $name,
            "There is no registered profiler with name '%s'",
            $name
        );
    }

    /**
     * Should not be called directly. Intended to use from {@link Profiler::startNested()}.
     *
     * @param string $name
     * @param Profiler $profiler
     */
    public function register(string $name, Profiler $profiler) : void
    {
        $this->profilers[$name] = $profiler;
    }
}
ProfilerRegistry::init();


<?php
declare(strict_types=1);

namespace precore\util;

use ArrayIterator;
use EmptyIterator;
use Iterator;
use IteratorAggregate;
use OuterIterator;
use OutOfBoundsException;
use Traversable;

/**
 * Helper class for {@link Iterator} objects.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class Iterators
{
    private function __construct()
    {
    }

    /**
     * Returns an iterator for the given traversable.
     *
     * @param Traversable $traversable
     * @return Iterator
     */
    public static function from(Traversable $traversable) : Iterator
    {
        Preconditions::checkArgument($traversable instanceof Iterator || $traversable instanceof IteratorAggregate);
        return $traversable instanceof Iterator
            ? $traversable
            : Iterators::from($traversable->getIterator());
    }

    /**
     * Returns an iterator containing the elements of array in order.
     *
     * @param array $array
     * @return Iterator
     */
    public static function forArray(array $array) : Iterator
    {
        return new ArrayIterator($array);
    }

    /**
     * Returns an iterator containing only value.
     *
     * @param mixed $value
     * @return Iterator
     */
    public static function singletonIterator($value) : Iterator
    {
        return self::forArray([$value]);
    }

    /**
     * Returns the elements of unfiltered that satisfy a predicate.
     *
     * @param Iterator $unfiltered
     * @param callable $predicate
     * @return Iterator
     */
    public static function filter(Iterator $unfiltered, callable $predicate) : Iterator
    {
        return new FilterIterator($unfiltered, $predicate);
    }

    /**
     * Returns all instances of class type in unfiltered.
     * The returned iterator has elements whose class is type or a subclass of type.
     *
     * @param Iterator $unfiltered
     * @param string $className
     * @return Iterator
     */
    public static function filterBy(Iterator $unfiltered, string $className) : Iterator
    {
        return self::filter($unfiltered, Predicates::instance($className));
    }

    /**
     * Combines two iterators into a single iterator. The returned iterator iterates across the elements in a,
     * followed by the elements in b. The source iterators are not polled until necessary.
     *
     * @param Iterator $a
     * @param Iterator $b
     * @return Iterator
     */
    public static function concat(Iterator $a, Iterator $b) : Iterator
    {
        return self::concatIterators(new ArrayIterator([$a, $b]));
    }

    /**
     * Combines multiple iterators into a single iterator. The returned iterator iterates across
     * the elements of each iterator in inputs. The input iterators are not polled until necessary.
     *
     * @param Iterator $inputs
     * @return Iterator
     */
    public static function concatIterators(Iterator $inputs) : Iterator
    {
        return new ConcatIterator($inputs);
    }

    /**
     * Returns true if one or more elements returned by iterator satisfy the given predicate.
     *
     * @param Iterator $iterator
     * @param callable $predicate
     * @return boolean
     */
    public static function any(Iterator $iterator, callable $predicate) : bool
    {
        return self::indexOf($iterator, $predicate) !== -1;
    }

    /**
     * Returns true if every element returned by iterator satisfies the given predicate.
     * If iterator is empty, true is returned.
     *
     * @param Iterator $iterator
     * @param callable $predicate
     * @return boolean
     */
    public static function all(Iterator $iterator, callable $predicate) : bool
    {
        while ($iterator->valid()) {
            if (!Predicates::call($predicate, $iterator->current())) {
                return false;
            }
            $iterator->next();
        }
        return true;
    }

    /**
     * Returns the index in iterator of the first element that satisfies the provided predicate,
     * or -1 if the Iterator has no such elements.
     *
     * @param Iterator $iterator
     * @param callable $predicate
     * @return int
     */
    public static function indexOf(Iterator $iterator, callable $predicate) : int
    {
        $i = 0;
        while ($iterator->valid()) {
            if (Predicates::call($predicate, $iterator->current())) {
                return $i;
            }
            $i++;
            $iterator->next();
        }
        return -1;
    }

    /**
     * Returns an iterator that applies transformer to each element of fromIterator.
     *
     * @param Iterator $fromIterator
     * @param callable $transformer
     * @return Iterator
     */
    public static function transform(Iterator $fromIterator, callable $transformer) : Iterator
    {
        return new TransformerIterator($fromIterator, $transformer);
    }

    /**
     * Creates an iterator returning the first limitSize elements of the given iterator.
     * If the original iterator does not contain that many elements,
     * the returned iterator will have the same behavior as the original iterator.
     *
     * @param Iterator $iterator
     * @param int $limitSize
     * @return Iterator
     * @throws \InvalidArgumentException if $limit is negative
     */
    public static function limit(Iterator $iterator, int $limitSize) : Iterator
    {
        Preconditions::checkArgument(0 <= $limitSize);
        return new NoRewindNecessaryLimitIterator($iterator, $limitSize);
    }

    /**
     * @param Iterator $iterator
     * @param $numberToSkip
     * @return integer
     * @throws \InvalidArgumentException if $numberToSkip is < 0
     */
    public static function advance(Iterator $iterator, int $numberToSkip) : int
    {
        Preconditions::checkArgument(0 <= $numberToSkip);
        for ($i = 0; $i < $numberToSkip && $iterator->valid(); $i++) {
            $iterator->next();
        }
        return $i;
    }

    /**
     * Advances iterator position times, returning the element at the positionth position.
     *
     * @param Iterator $iterator
     * @param int $position
     * @return mixed
     * @throws OutOfBoundsException if position does not exist
     */
    public static function get(Iterator $iterator, int $position)
    {
        Iterators::advance($iterator, $position);
        if (!$iterator->valid()) {
            throw new OutOfBoundsException("The requested index '{$position}' is invalid");
        }
        return $iterator->current();
    }

    /**
     * Returns the number of elements in iterator.
     *
     * @param Iterator $iterator
     * @return int
     */
    public static function size(Iterator $iterator)
    {
        $result = 0;
        Iterators::each($iterator, function () use (&$result) {
            $result++;
        });
        return $result;
    }

    /**
     * @param Iterator $iterator
     * @param callable $function
     * @return void
     */
    public static function each(Iterator $iterator, callable $function)
    {
        while ($iterator->valid()) {
            Functions::call($function, $iterator->current());
            $iterator->next();
        }
    }

    /**
     * Returns true if iterator contains element. The check based on {@link Objects::equal()}.
     *
     * @param Iterator $iterator
     * @param $element
     * @return boolean
     */
    public static function contains(Iterator $iterator, $element) : bool
    {
        while ($iterator->valid()) {
            if (Objects::equal($iterator->current(), $element)) {
                return true;
            }
            $iterator->next();
        }
        return false;
    }

    /**
     * Returns the number of elements in the specified iterator that equal the specified object.
     * The check based on {@link Objects::equal()}.
     *
     * @param Iterator $iterator
     * @param $element
     * @return int
     */
    public static function frequency(Iterator $iterator, $element) : int
    {
        $frequency = 0;
        Iterators::each($iterator, function ($item) use (&$frequency, $element) {
            if (Objects::equal($element, $item)) {
                $frequency++;
            }
        });
        return $frequency;
    }

    /**
     * Divides an iterator into unmodifiable sublists of the given size (the final list may be smaller).
     *
     * For example, partitioning an iterator containing [a, b, c, d, e] with a partition size
     * of 3 yields [[a, b, c], [d, e]] -- an outer iterator containing two inner lists of three and two elements,
     * all in the original order.
     *
     * @param Iterator $iterator
     * @param int $size
     * @return Iterator
     * @throws \InvalidArgumentException if $limit is non positive
     */
    public static function partition(Iterator $iterator, int $size) : Iterator
    {
        Preconditions::checkArgument(0 < $size);
        return new PartitionIterator($iterator, $size);
    }

    /**
     * Divides an iterator into unmodifiable sublists of the given size,
     * padding the final iterator with null values if necessary.
     *
     * For example, partitioning an iterator containing [a, b, c, d, e] with a partition size
     * of 3 yields [[a, b, c], [d, e, null]] -- an outer iterator containing two inner lists of three elements each,
     * all in the original order.
     *
     * @param Iterator $iterator
     * @param int $size
     * @return Iterator
     * @throws \InvalidArgumentException if $limit is non positive
     */
    public static function paddedPartition(Iterator $iterator, int $size) : Iterator
    {
        Preconditions::checkArgument(0 < $size);
        return new PartitionIterator($iterator, $size, true);
    }

    /**
     * Returns the first element in iterator that satisfies the given predicate. If there is no such an element,
     * it returns $defaultValue.
     *
     * @param Iterator $iterator
     * @param callable $predicate
     * @param null $defaultValue
     * @return mixed
     */
    public static function find(Iterator $iterator, callable $predicate, $defaultValue = null)
    {
        $result = $defaultValue;
        while ($iterator->valid()) {
            $current = $iterator->current();
            if (Predicates::call($predicate, $current)) {
                $result = $current;
                break;
            }
            $iterator->next();
        }
        return $result;
    }

    /**
     * Returns an Optional containing the first element in iterator that satisfies the given predicate,
     * if such an element exists. If no such element is found, an empty Optional will be returned from this method.
     *
     * @param Iterator $iterator
     * @param callable $predicate
     * @return Optional
     */
    public static function tryFind(Iterator $iterator, callable $predicate) : Optional
    {
        return Optional::ofNullable(self::find($iterator, $predicate));
    }

    /**
     * @param Iterator $iterator
     * @return boolean
     */
    public static function isEmpty(Iterator $iterator) : bool
    {
        return !$iterator->valid();
    }

    /**
     * Returns the current element of the iterator if it's valid and calls {@link Iterator::next} on it.
     * If the iterator is not valid, it returns $defaultValue.
     *
     * @param Iterator $iterator
     * @param null $defaultValue
     * @return mixed|null
     */
    public static function getNext(Iterator $iterator, $defaultValue = null)
    {
        $result = $defaultValue;
        if ($iterator->valid()) {
            $result = $iterator->current();
            $iterator->next();
        }
        return $result;
    }

    /**
     * Returns the last element provided by $iterator. If the iterator is not valid, returns $defaultValue.
     *
     * @param Iterator $iterator
     * @param null $defaultValue
     * @return mixed|null
     */
    public static function getLast(Iterator $iterator, $defaultValue = null)
    {
        $last = $defaultValue;
        Iterators::each($iterator, function ($element) use (&$last) {
            $last = $element;
        });
        return $last;
    }

    /**
     * Checks whether the elements provided by the given iterators are equal correspondingly.
     * It uses {@link Objects::equal()} for equality check.
     *
     * @param Iterator $iterator1
     * @param Iterator $iterator2
     * @return bool
     */
    public static function elementsEqual(Iterator $iterator1, Iterator $iterator2) : bool
    {
        while ($iterator1->valid() && $iterator2->valid()) {
            if (!Objects::equal($iterator1->current(), $iterator2->current())) {
                return false;
            }
            $iterator1->next();
            $iterator2->next();
        }
        return !$iterator1->valid() && !$iterator2->valid();
    }

    /**
     * Returns a string representation of iterator, with the format [e1, e2, ..., en].
     *
     * @param Iterator $iterator
     * @return string
     */
    public static function toString(Iterator $iterator) : string
    {
        return '[' . Collections::standardJoiner()->join($iterator) . ']';
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
class NoRewindNecessaryIterator implements OuterIterator
{
    /**
     * @var Iterator
     */
    private $iterator;

    /**
     * FilterIterator constructor.
     * @param Iterator $iterator
     */
    public function __construct(Iterator $iterator)
    {
        $this->iterator = $iterator;
    }

    public function valid()
    {
        return $this->iterator->valid();
    }

    public function current()
    {
        return $this->iterator->current();
    }

    public function next()
    {
        $this->iterator->next();
    }

    public function key()
    {
        return $this->iterator->key();
    }

    public function rewind()
    {
        $this->iterator->rewind();
    }

    public function getInnerIterator()
    {
        return $this->iterator;
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class FilterIterator extends NoRewindNecessaryIterator
{
    /**
     * @var callable
     */
    private $filter;
    private $key = 0;

    /**
     * FilterIterator constructor.
     * @param Iterator $iterator
     * @param callable $filter
     */
    public function __construct(Iterator $iterator, callable $filter)
    {
        parent::__construct($iterator);
        $this->filter = $filter;
    }

    public function rewind()
    {
        parent::rewind();
        $this->key = 0;
    }

    public function key()
    {
        return $this->key;
    }

    public function current()
    {
        $this->findNext();
        return parent::current();
    }

    public function next()
    {
        parent::next();
        $this->findNext();
        $this->key++;
    }

    public function valid()
    {
        $this->findNext();
        return parent::valid();
    }

    private function findNext()
    {
        while (parent::valid() && !Predicates::call($this->filter, parent::current())) {
            parent::next();
        }
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class TransformerIterator extends NoRewindNecessaryIterator
{
    /**
     * @var callable
     */
    private $transformer;

    /**
     * @param Iterator $iterator
     * @param callable $transformer
     */
    public function __construct(Iterator $iterator, callable $transformer)
    {
        parent::__construct($iterator);
        $this->transformer = $transformer;
    }

    public function current()
    {
        return Functions::call($this->transformer, parent::current());
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class ConcatIterator extends NoRewindNecessaryIterator
{
    /**
     * @var Iterator
     */
    private $current;
    private $key = 0;

    public function __construct(Iterator $iterator)
    {
        parent::__construct($iterator);
        $this->current = new EmptyIterator();
    }

    public function rewind()
    {
        parent::rewind();
        $this->current = new EmptyIterator();
        $this->key = 0;
    }

    public function current()
    {
        if (!$this->current->valid()) {
            $this->findNextIterator();
        }
        return $this->current->current();
    }

    public function valid()
    {
        if (!$this->current->valid()) {
            $this->findNextIterator();
        }
        return $this->current->valid();
    }

    public function next()
    {
        $this->current->next();
        if (!$this->current->valid()) {
            $this->findNextIterator();
        }
        $this->key++;
    }

    public function key()
    {
        return $this->key;
    }

    private function findNextIterator()
    {
        while (!$this->current->valid() && parent::valid()) {
            $this->current = parent::current();
            parent::next();
        }
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class NoRewindNecessaryLimitIterator extends NoRewindNecessaryIterator
{
    /**
     * @var int
     */
    private $limit;

    private $count = 0;

    public function __construct(Iterator $iterator, $limit)
    {
        parent::__construct($iterator);
        $this->limit = $limit;
    }

    public function rewind()
    {
        parent::rewind();
        $this->count = 0;
    }

    public function valid()
    {
        return $this->count < $this->limit && parent::valid();
    }

    public function next()
    {
        parent::next();
        $this->count++;
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class PartitionIterator extends NoRewindNecessaryIterator
{
    private $size;

    /**
     * @var Iterator
     */
    private $currentIterator;

    /**
     * @var bool
     */
    private $padded;

    private $key = 0;

    /**
     * @param Iterator $iterator
     * @param int $size
     * @param bool $padded
     */
    public function __construct(Iterator $iterator, $size, $padded = false)
    {
        parent::__construct($iterator);
        $this->size = $size;
        $this->padded = $padded;
    }

    public function current()
    {
        if ($this->currentIterator === null) {
            $this->findNext();
        }
        return $this->currentIterator;
    }

    public function next()
    {
        $this->findNext();
        $this->key++;
    }

    public function key()
    {
        return $this->key;
    }

    public function valid()
    {
        if ($this->currentIterator === null) {
            $this->findNext();
        }
        return $this->currentIterator !== null;
    }

    private function findNext()
    {
        $array = [];
        for ($i = 0; $i < $this->size && $this->getInnerIterator()->valid(); $this->getInnerIterator()->next(), $i++) {
            $array[] = $this->getInnerIterator()->current();
        }
        if ($this->padded) {
            $array = array_pad($array, $this->size, null);
        }
        $this->currentIterator = 0 < $i
            ? new ArrayIterator($array)
            : null;
        return $this->currentIterator;
    }
}


<?php
/**
 * Timestamp
 *
 * Copyright (C) 2012 Tyler Romeo <tylerromeo@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 * @author Tyler Romeo <tylerromeo@gmail.com>
 */

namespace Wikimedia\Timestamp;

use DateInterval;
use DateTime;
use DateTimeZone;
use Exception;

/**
 * Library for creating, parsing, and converting timestamps.
 */
class ConvertibleTimestamp {
	/**
	 * Standard gmdate() formats for the different timestamp types.
	 */
	private static $formats = [
		TS_UNIX => 'U',
		TS_MW => 'YmdHis',
		TS_DB => 'Y-m-d H:i:s',
		TS_ISO_8601 => 'Y-m-d\TH:i:s\Z',
		TS_ISO_8601_BASIC => 'Ymd\THis\Z',
		TS_EXIF => 'Y:m:d H:i:s', // This shouldn't ever be used, but is included for completeness
		TS_RFC2822 => 'D, d M Y H:i:s',
		TS_ORACLE => 'd-m-Y H:i:s.u', // Was 'd-M-y h.i.s A' . ' +00:00' before r51500
		TS_POSTGRES => 'Y-m-d H:i:s+00', // Formerly 'Y-m-d H:i:s' . ' GMT'
		TS_UNIX_MICRO => 'U.u',
	];

	/**
	 * @var callback|null
	 * @see setFakeTime()
	 */
	protected static $fakeTimeCallback = null;

	/**
	 * Get the current time in the same form that PHP's built-in time() function uses.
	 *
	 * This is used by now() get setTimestamp( false ) instead of the built in time() function.
	 * The output of this method can be overwritten for testing purposes by calling setFakeTime().
	 *
	 * @return int UNIX epoch
	 */
	public static function time() {
		return static::$fakeTimeCallback ? call_user_func( static::$fakeTimeCallback ) : \time();
	}

	/**
	 * Set a fake time value or clock callback.
	 *
	 * @param callable|string|int|false $fakeTime a fixed time string, or an integer Unix time, or
	 *   a callback() returning an int representing a UNIX epoch, or false to disable fake time and
	 *   go back to real time.
	 *
	 * @return callable|null the previous fake time callback, if any.
	 */
	public static function setFakeTime( $fakeTime ) {
		if ( is_string( $fakeTime ) ) {
			$fakeTime = (int)static::convert( TS_UNIX, $fakeTime );
		}

		if ( is_int( $fakeTime ) ) {
			$fakeTime = function () use ( $fakeTime ) {
				return $fakeTime;
			};
		}

		$old = static::$fakeTimeCallback;
		static::$fakeTimeCallback = $fakeTime ? $fakeTime : null;
		return $old;
	}

	/**
	 * The actual timestamp being wrapped (DateTime object).
	 * @var DateTime
	 */
	public $timestamp;

	/**
	 * Make a new timestamp and set it to the specified time,
	 * or the current time if unspecified.
	 *
	 * @param bool|string|int|float|DateTime $timestamp Timestamp to set, or false for current time
	 */
	public function __construct( $timestamp = false ) {
		if ( $timestamp instanceof DateTime ) {
			$this->timestamp = $timestamp;
		} else {
			$this->setTimestamp( $timestamp );
		}
	}

	/**
	 * Set the timestamp to the specified time, or the current time if unspecified.
	 *
	 * Parse the given timestamp into either a DateTime object or a Unix timestamp,
	 * and then store it.
	 *
	 * @param string|bool $ts Timestamp to store, or false for now
	 * @throws TimestampException
	 */
	public function setTimestamp( $ts = false ) {
		$m = [];
		$da = [];
		$strtime = '';

		// We want to catch 0, '', null... but not date strings starting with a letter.
		if ( !$ts || $ts === "\0\0\0\0\0\0\0\0\0\0\0\0\0\0" ) {
			$uts = self::time();
			$strtime = "@$uts";
		} elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
			# TS_DB
		} elseif ( preg_match( '/^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/D', $ts, $da ) ) {
			# TS_EXIF
		} elseif ( preg_match( '/^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/D', $ts, $da ) ) {
			# TS_MW
		} elseif ( preg_match( '/^(-?\d{1,13})$/D', $ts, $m ) ) {
			# TS_UNIX
			$strtime = "@{$m[1]}"; // http://php.net/manual/en/datetime.formats.compound.php
		} elseif ( preg_match( '/^(-?\d{1,13})(\.\d+)$/D', $ts, $m ) ) {
			# TS_UNIX_MICRO
			// Not supported with @, so we need a hack
			$strtime = 'unixmicro';
		} elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
			# TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
			$strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
				str_replace( '+00:00', 'UTC', $ts ) );
		} elseif ( preg_match(
			'/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.*\d*)?Z?$/',
			$ts,
			$da
		) ) {
			# TS_ISO_8601
		} elseif ( preg_match(
			'/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(?:\.*\d*)?Z?$/',
			$ts,
			$da
		) ) {
			# TS_ISO_8601_BASIC
		} elseif ( preg_match(
			'/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d*[\+\- ](\d\d)$/',
			$ts,
			$da
		) ) {
			# TS_POSTGRES
		} elseif ( preg_match(
			'/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/',
			$ts,
			$da
		) ) {
			# TS_POSTGRES
		} elseif ( preg_match(
			# Day of week
			'/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' .
			# dd Mon yyyy
			'\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' .
			# hh:mm:ss
			'[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d[ \t\r\n]*:[ \t\r\n]*\d\d/S',
			$ts
		) ) {
			# TS_RFC2822, accepting a trailing comment.
			# See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html / r77171
			# The regex is a superset of rfc2822 for readability
			$strtime = strtok( $ts, ';' );
		} elseif ( preg_match( '/^[A-Z][a-z]{5,8}, \d\d-[A-Z][a-z]{2}-\d{2} \d\d:\d\d:\d\d/', $ts ) ) {
			# TS_RFC850
			$strtime = $ts;
		} elseif ( preg_match( '/^[A-Z][a-z]{2} [A-Z][a-z]{2} +\d{1,2} \d\d:\d\d:\d\d \d{4}/', $ts ) ) {
			# asctime
			$strtime = $ts;
		} else {
			throw new TimestampException( __METHOD__ . ": Invalid timestamp - $ts" );
		}

		if ( !$strtime ) {
			$da = array_map( 'intval', $da );
			$da[0] = "%04d-%02d-%02dT%02d:%02d:%02d.00+00:00";
			$strtime = call_user_func_array( "sprintf", $da );
		}

		try {
			if ( $strtime === 'unixmicro' ) {
				$final = DateTime::createFromFormat( 'U.u', $ts, new DateTimeZone( 'GMT' ) );
			} else {
				$final = new DateTime( $strtime, new DateTimeZone( 'GMT' ) );
			}
		} catch ( Exception $e ) {
			throw new TimestampException( __METHOD__ . ': Invalid timestamp format.', $e->getCode(), $e );
		}

		if ( $final === false ) {
			throw new TimestampException( __METHOD__ . ': Invalid timestamp format.' );
		}

		$this->timestamp = $final;
	}

	/**
	 * Convert a timestamp string to a given format.
	 *
	 * @param int $style Constant Output format for timestamp
	 * @param string|int|float|bool $ts Timestamp
	 * @return string|bool Formatted timestamp or false on failure
	 */
	public static function convert( $style = TS_UNIX, $ts ) {
		try {
			$ct = new static( $ts );
			return $ct->getTimestamp( $style );
		} catch ( TimestampException $e ) {
			return false;
		}
	}

	/**
	 * Get the current time in the given format
	 *
	 * @param int $style Constant Output format for timestamp
	 * @return string
	 */
	public static function now( $style = TS_MW ) {
		return static::convert( $style, false );
	}

	/**
	 * Get the timestamp represented by this object in a certain form.
	 *
	 * Convert the internal timestamp to the specified format and then
	 * return it.
	 *
	 * @param int $style Constant Output format for timestamp
	 * @throws TimestampException
	 * @return string The formatted timestamp
	 */
	public function getTimestamp( $style = TS_UNIX ) {
		if ( !isset( self::$formats[$style] ) ) {
			throw new TimestampException( __METHOD__ . ': Illegal timestamp output type.' );
		}

		$output = $this->timestamp->format( self::$formats[$style] );

		if ( $style == TS_RFC2822 ) {
			$output .= ' GMT';
		}

		if ( $style == TS_MW && strlen( $output ) !== 14 ) {
			throw new TimestampException( __METHOD__ . ': The timestamp cannot be represented in ' .
				'the specified format' );
		}

		return $output;
	}

	/**
	 * @return string
	 */
	public function __toString() {
		return $this->getTimestamp();
	}

	/**
	 * Calculate the difference between two ConvertibleTimestamp objects.
	 *
	 * @param ConvertibleTimestamp $relativeTo Base time to calculate difference from
	 * @return DateInterval|bool The DateInterval object representing the
	 *   difference between the two dates or false on failure
	 */
	public function diff( ConvertibleTimestamp $relativeTo ) {
		return $this->timestamp->diff( $relativeTo->timestamp );
	}

	/**
	 * Set the timezone of this timestamp to the specified timezone.
	 *
	 * @param string $timezone Timezone to set
	 * @throws TimestampException
	 */
	public function setTimezone( $timezone ) {
		try {
			$this->timestamp->setTimezone( new DateTimeZone( $timezone ) );
		} catch ( Exception $e ) {
			throw new TimestampException( __METHOD__ . ': Invalid timezone.', $e->getCode(), $e );
		}
	}

	/**
	 * Get the timezone of this timestamp.
	 *
	 * @return DateTimeZone The timezone
	 */
	public function getTimezone() {
		return $this->timestamp->getTimezone();
	}

	/**
	 * Format the timestamp in a given format.
	 *
	 * @param string $format Pattern to format in
	 * @return string The formatted timestamp
	 */
	public function format( $format ) {
		return $this->timestamp->format( $format );
	}
}


<?php
declare(strict_types=1);

namespace precore\util;

use ArrayIterator;
use Iterator;
use IteratorAggregate;

/**
 * An {@link IteratorAggregate} which provides the ability to iterate over dynamically loaded data.
 *
 *

 * The used {@link ChunkProvider}s responsibility is to load and return the proper chunk of data
 * according to the given offset. The number of the expected items can be limited and the result can be filtered.
 * It is handy when the result is huge, because only the current chunk need to be stored in the memory.
 *

 *
 *

 * The data loading will be stopped in the following cases:
 *

 *
The {@link ChunkProvider} returns {@link Optional::absent()}

 *
A limit is defined and reached

 *
The chunk provider call limit reached

 *

 *

 *
 *
Note that the provider call limit is 1 by default to avoid infinite loop. Always use a reasonable number!

 *
 *

 *   $userProvider = function ($offset) {
 *       return Optional::ofNullable($userRepository->get($offset, 10));
 *   }
 *   $adminFilter = function ($user) {
 *       return $user->isAdmin();
 *   }
 *   $top100AdminUsers = BufferedIterable::withChunkFunction($userProvider)
 *       ->filter($adminFilter)
 *       ->providerCallLimit(40)
 *       ->limit(100);
 *   foreach ($top100AdminUsers as $admin) {
 *       // do something
 *   }
 *

 * In the above example we always load 10 users through a repository until we get 100 admin users.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class BufferedIterable implements IteratorAggregate
{
    /**
     * @var callable
     */
    private $filter;

    /**
     * @var ChunkProvider
     */
    private $chunkProvider;

    /**
     * @var int
     */
    private $limit;

    /**
     * @var int
     */
    private $providerCallLimit;

    private function __construct(
        ChunkProvider $chunkProvider,
        callable $filter = null,
        $limit = PHP_INT_MAX,
        $providerCallLimit = 1
    ) {
        $this->chunkProvider = $chunkProvider;
        if ($filter === null) {
            $filter = Predicates::alwaysTrue();
        }
        $this->filter = $filter;
        $this->limit = $limit;
        $this->providerCallLimit = $providerCallLimit;
    }

    /**
     * Creates a {@link BufferedIterable} with the given {@link ChunkProvider}.
     *
     * @param ChunkProvider $chunkProvider
     * @return BufferedIterable
     */
    public static function withChunkProvider(ChunkProvider $chunkProvider) : BufferedIterable
    {
        return new BufferedIterable($chunkProvider);
    }

    /**
     * Creates a {@link BufferedIterable} with the given function as chunk provider.
     * The function will get the offset as parameter.
     *
     * @param callable $function
     * @return BufferedIterable
     */
    public static function withChunkFunction(callable $function) : BufferedIterable
    {
        return self::withChunkProvider(new FunctionalChunkProvider($function));
    }

    /**
     * Limits the size of the result.
     *
     * @param int $limit
     * @return BufferedIterable
     * @throws \InvalidArgumentException if $limit is not a number or <= 0
     */
    public function limit(int $limit) : BufferedIterable
    {
        Preconditions::checkArgument(0 < $limit, 'Limit must be a positive integer!');
        return new BufferedIterable($this->chunkProvider, $this->filter, $limit, $this->providerCallLimit);
    }

    /**
     * Limits the number of chunk provider calls. The default value is 1.
     *
     * @param int $limit
     * @return BufferedIterable
     * @throws \InvalidArgumentException if $limit is <= 0
     */
    public function providerCallLimit(int $limit) : BufferedIterable
    {
        Preconditions::checkArgument(0 < $limit, 'Limit must be a positive integer!');
        return new BufferedIterable($this->chunkProvider, $this->filter, $this->limit, $limit);
    }

    /**
     * Filters the result list with the given predicate.
     *
     * @param callable $predicate
     * @return BufferedIterable
     */
    public function filter(callable $predicate) : BufferedIterable
    {
        return new BufferedIterable($this->chunkProvider, $predicate, $this->limit, $this->providerCallLimit);
    }

    /**
     * @return Iterator
     */
    public function getIterator()
    {
        return new BufferedIterator($this->chunkProvider, $this->filter, $this->limit, $this->providerCallLimit);
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class BufferedIterator implements Iterator
{
    /**
     * @var ChunkProvider final
     */
    private $chunkProvider;

    /**
     * @var callable final
     */
    private $filter;

    /**
     * @var int final
     */
    private $limit;

    /**
     * @var int final
     */
    private $providerCallLimit;

    /**
     * @var Iterator
     */
    private $currentChunk;

    /**
     * @var int
     */
    private $remaining;

    private $providerCalls = 0;

    private $offset = 0;

    /**
     * BufferedIterator constructor.
     * @param ChunkProvider $chunkProvider
     * @param callable $filter
     * @param int $limit
     * @param $providerCallLimit
     */
    public function __construct(ChunkProvider $chunkProvider, callable $filter, int $limit, int $providerCallLimit)
    {
        $this->chunkProvider = $chunkProvider;
        $this->filter = $filter;
        $this->limit = $limit;
        $this->remaining = $limit;
        $this->providerCallLimit = $providerCallLimit;
    }

    public function current()
    {
        return $this->currentChunk->current();
    }

    public function next()
    {
        $this->currentChunk->next();
    }

    public function key()
    {
        $this->currentChunk->key();
    }

    public function valid()
    {
        if ($this->providerCalls < $this->providerCallLimit
            && 0 < $this->remaining
            && ($this->currentChunk === null || !$this->currentChunk->valid())) {

            $this->callNext();
        }
        return $this->currentChunk->valid();
    }

    public function rewind()
    {
        $this->offset = 0;
        $this->remaining = $this->limit;
        $this->providerCalls = 0;
        $this->callNext();
    }

    private function callNext()
    {
        $chunk = $this->chunkProvider->getChunk($this->offset)->orElse(Collections::emptyIterator());
        $filteredChunk = FluentIterable::from($chunk)
            ->filter($this->filter)
            ->limit($this->remaining)
            ->toArray();
        $this->currentChunk = new ArrayIterator($filteredChunk);
        $chunkSize = count($filteredChunk);
        $this->offset += $chunkSize;
        $this->remaining -= $chunkSize;
        $this->providerCalls++;
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class FunctionalChunkProvider implements ChunkProvider
{
    /**
     * @var callable
     */
    private $function;

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

    /**
     * @param $offset
     * @return Optional
     */
    public function getChunk($offset) : Optional
    {
        return Functions::call($this->function, $offset);
    }
}


<?php

declare(strict_types=1);

namespace precore\util\concurrent;

use DateInterval;
use precore\lang\Enum;
use precore\lang\IllegalStateException;
use precore\util\Preconditions;

class TimeUnit extends Enum
{
    const C0 = 1;
    const C1 = 1000;
    const C2 = 1000000;
    const C3 = 60000000;
    const C4 = 3600000000;
    const C5 = 86400000000;

    /**
     * Does not support toDateInterval()
     *
     * @var TimeUnit
     */
    public static $MICROSECONDS;

    /**
     * Does not support toDateInterval()
     *
     * @var TimeUnit
     */
    public static $MILLISECONDS;

    /**
     * @var TimeUnit
     */
    public static $SECONDS;

    /**
     * @var TimeUnit
     */
    public static $MINUTES;

    /**
     * @var TimeUnit
     */
    public static $HOURS;

    /**
     * @var TimeUnit
     */
    public static $DAYS;

    private $inMicros;
    private $dateIntervalFormat;

    protected static function constructorArgs()
    {
        return [
            'MICROSECONDS' => [self::C0, null],
            'MILLISECONDS' => [self::C1, null],
            'SECONDS' => [self::C2, 'PT%dS'],
            'MINUTES' => [self::C3, 'PT%dM'],
            'HOURS' => [self::C4, 'PT%dH'],
            'DAYS' => [self::C5, 'P%dD']
        ];
    }

    /**
     * @param $inMicros float The amount of microseconds of this unit.
     * @param $dateIntervalFormat string the format character for DateInterval
     */
    protected function __construct($inMicros, $dateIntervalFormat)
    {
        $this->inMicros = $inMicros;
        $this->dateIntervalFormat = $dateIntervalFormat;
    }

    /**
     * Equivalent to TimeUnit::$MICROSECONDS->convert($duration, $this).
     *
     * @param $duration float
     * @return float
     */
    public function toMicros(float $duration) : float
    {
        return TimeUnit::$MICROSECONDS->convert($duration, $this);
    }

    /**
     * Equivalent to TimeUnit::$MILLISECONDS->convert($duration, $this).
     *
     * @param $duration float
     * @return float
     */
    public function toMillis(float $duration) : float
    {
        return TimeUnit::$MILLISECONDS->convert($duration, $this);
    }

    /**
     * Equivalent to TimeUnit::$SECONDS->convert(duration, $this).
     *
     * @param $duration float
     * @return float
     */
    public function toSeconds(float $duration) : float
    {
        return TimeUnit::$SECONDS->convert($duration, $this);
    }

    /**
     * Equivalent to TimeUnit::$MINUTES->convert(duration, $this).
     *
     * @param $duration float
     * @return float
     */
    public function toMinutes(float $duration) : float
    {
        return TimeUnit::$MINUTES->convert($duration, $this);
    }

    /**
     * Equivalent to TimeUnit::$HOURS->convert(duration, $this).
     *
     * @param $duration float
     * @return float
     */
    public function toHours(float $duration) : float
    {
        return TimeUnit::$HOURS->convert($duration, $this);
    }

    /**
     * Equivalent to TimeUnit::$DAYS->convert($duration, $this).
     *
     * @param $duration float
     * @return float
     */
    public function toDays(float $duration) : float
    {
        return TimeUnit::$DAYS->convert($duration, $this);
    }

    /**
     * Creates a DateInterval with the given duration and this unit.
     *
     * @param $duration float
     * @throws \Exception if this object does not have proper DateInterval format string
     * @return DateInterval
     */
    public function toDateInterval(float $duration) : DateInterval
    {
        Preconditions::checkState($this->dateIntervalFormat !== null, '[%s] does not support toDateInterval()', $this);
        return new DateInterval(sprintf($this->dateIntervalFormat, $duration));
    }

    /**
     * Convert the given time duration in the given unit to this unit.
     *
     * For example, to convert 10 minutes to milliseconds, use: TimeUnit::$MILLISECONDS->convert(10, TimeUnit::$MINUTES)
     *
     * @param $duration float The time duration in the given sourceUnit
     * @param TimeUnit $timeUnit the unit of the sourceDuration argument
     * @return float the converted duration in this unit
     */
    public function convert(float $duration, TimeUnit $timeUnit) : float
    {
        return $duration * ($timeUnit->inMicros / $this->inMicros);
    }

    /**
     * Sleeps the process using this TimeUnit.
     *
     * @param $duration
     */
    public function sleep(float $duration) : void
    {
        usleep(intval($this->toMicros($duration)));
    }
}
TimeUnit::init();


<?php
declare(strict_types=1);

namespace precore\util;

use precore\lang\BaseObject;

/**
 * <p> Helps collection execution times in a programmatic way.
 *
 * <p> All the time the start() method is called, a new stopwatch will be started, and the previous one will be stopped.
 * Calling the stop() method stops the whole profiler, does not start another stopwatch.
 *
 * <p> Obtaining the result can be accomplished via the log() or the printout() method.
 *
 * <p> Nested profiler can be started by the startNested() method. The currently active entry will include
 * the total time of the nested profiler. All nested profilers are registered to the {@link ProfilerRegistry}
 * and can be got from there.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class Profiler extends BaseObject
{
    const ENTRY_FORMAT = "%-13s%30s%10s.";
    const ENTRY_PREFIX = ' |-- ';
    const HEAD_PREFIX = ' ';

    /**
     * @var string
     */
    private $name;

    /**
     * @var string
     */
    private $currentName;

    /**
     * All items can be cast to string.
     *
     * @var object[]
     */
    private $entries = [];

    /**
     * @var Stopwatch
     */
    private $globalStopwatch;

    /**
     * @var Stopwatch
     */
    private $entryStopwatch;

    /**
     * @var int
     */
    private $level = 1;

    /**
     * @var Profiler|null
     */
    private $parent = null;

    /**
     * @var null|Ticker
     */
    private $ticker;

    /**
     * @param string $name The name of this profile
     * @param Ticker $ticker should not be used in production code
     */
    public function __construct(string $name, Ticker $ticker = null)
    {
        $this->name = $name;
        if ($ticker === null) {
            $this->globalStopwatch = Stopwatch::createUnstarted();
            $this->entryStopwatch = Stopwatch::createUnstarted();
        } else {
            $this->globalStopwatch = Stopwatch::createUnstartedWith($ticker);
            $this->entryStopwatch = Stopwatch::createUnstartedWith($ticker);
        }
        $this->ticker = $ticker;
    }

    /**
     * Creates and starts a new entry.
     *
     * @param string $name The name of the next entry
     * @return $this
     */
    public function start($name) : Profiler
    {
        if (!$this->globalStopwatch->isRunning()) {
            $this->globalStopwatch->start();
        }
        if ($this->entryStopwatch->isRunning()) {
            $this->recordEntry();
        }
        $this->currentName = $name;
        $this->entryStopwatch->start();
        return $this;
    }

    /**
     * Stops the whole profiler.
     *
     * @return $this
     */
    public function stop() : Profiler
    {
        $this->recordEntry();
        $this->globalStopwatch->stop();
        return $this;
    }

    /**
     * Creates a nested profiler, which can be obtained from {@link ProfilerRegistry} by its name.
     *
     * @param string $name
     * @return $this
     */
    public function startNested($name) : Profiler
    {
        $nestedProfiler = new Profiler($name, $this->ticker);
        $nestedProfiler->parent = $this;
        $nestedProfiler->level = $this->level + 1;
        $this->entries[] = $nestedProfiler;
        ProfilerRegistry::instance()->register($name, $nestedProfiler);
        return $this;
    }

    public function toString() : string
    {
        $res = sprintf('%' . (($this->level - 1) * 4) . "s+ Profiler [%s]" . PHP_EOL, self::HEAD_PREFIX, $this->name);
        foreach ($this->entries as $entry) {
            $res .= $entry;
        }
        $total = $this->parent === null ? 'Total' : 'Subtotal';
        return $res . $this->entryString($total, '[' . $this->name . ']', $this->globalStopwatch);
    }

    /**
     * Sends the {@link Profiler::toString()} to the standard output.
     */
    public function printOut() : void
    {
        echo $this->toString();
    }

    /**
     * Creates a log entry on DEBUG level.
     */
    public function log() : void
    {
        self::getLogger()->debug("Profiler output:{}{}", [PHP_EOL, $this]);
    }

    private function recordEntry() : void
    {
        $this->entryStopwatch->stop();
        $this->entries[] = $this->entryString('elapsed time', '[' . $this->currentName . ']', $this->entryStopwatch);
        $this->entryStopwatch->reset();
    }

    private function entryString($title, $stopwatchName, Stopwatch $stopwatch) : string
    {
        $format = '%' . ($this->level * 4) . 's' . self::ENTRY_FORMAT . PHP_EOL;
        return sprintf($format, self::ENTRY_PREFIX, $title, $stopwatchName, $stopwatch);
    }
}


<?php
declare(strict_types=1);

namespace precore\util;

use precore\lang\ClassCastException;
use Traversable;

/**
 * A comparator, with additional methods to support common operations.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class Ordering implements Comparator
{
    private static $NATURAL;
    private static $TO_STRING;

    /**
     * @var Comparator
     */
    private $comparator;

    /**
     * Ordering constructor.
     * @param Comparator $comparator
     */
    private function __construct(Comparator $comparator)
    {
        $this->comparator = $comparator;
    }

    public static function init() : void
    {
        self::$NATURAL = Ordering::from(ComparableComparator::instance());
        self::$TO_STRING = Ordering::from(StringComparator::$BINARY)->onResultOf(Functions::toStringFunction());
    }

    /**
     * Construct an {@link Ordering} from the passed comparator.
     *
     * @param Comparator $comparator
     * @return Ordering
     */
    public static function from(Comparator $comparator) : Ordering
    {
        return new Ordering($comparator);
    }

    /**
     * Creates a {@link Ordering} based on natural ordering which means all sorted elements
     * have to implement {@link Comparable} interface and must be compatible with each other.
     *
     * @return Ordering
     */
    public static function natural() : Ordering
    {
        return self::$NATURAL;
    }

    /**
     * The created {@link Ordering} will compare the string representation
     * of all elements with {@link StringComparator::$BINARY}.
     *
     * @return Ordering
     */
    public static function usingToString() : Ordering
    {
        return self::$TO_STRING;
    }

    /**
     * Returns the reverse of this ordering.
     *
     * @return Ordering
     */
    public function reverse() : Ordering
    {
        return Ordering::from(Collections::reverseOrder($this));
    }

    /**
     * Returns an ordering that treats null as less than all other values and uses this to compare non-null values.
     *
     * @return Ordering
     */
    public function nullsFirst() : Ordering
    {
        return Ordering::from(Collections::comparatorFrom(
            function ($object1, $object2) {
                return $object1 === null
                    ? -1
                    : ($object2 === null
                        ? 1
                        : $this->compare($object1, $object2));
            }
        ));
    }

    /**
     * Returns an ordering that treats null as greater than all other values
     * and uses this ordering to compare non-null values.
     *
     * @return Ordering
     */
    public function nullsLast() : Ordering
    {
        return Ordering::from(Collections::comparatorFrom(
            function ($object1, $object2) {
                return $object1 === null
                    ? 1
                    : ($object2 === null
                        ? -1
                        : $this->compare($object1, $object2));
            }
        ));
    }

    /**
     * Returns a new ordering which orders elements by first applying a function to them,
     * then comparing those results using this.
     *
     * @param callable $function
     * @return Ordering
     */
    public function onResultOf(callable $function) : Ordering
    {
        return Ordering::from(Collections::comparatorFrom(
            function ($object1, $object2) use ($function) {
                return $this->compare(
                    Functions::call($function, $object1),
                    Functions::call($function, $object2)
                );
            }
        ));
    }

    /**
     * Returns an ordering which first uses the ordering this, but which in the event of a "tie",
     * then delegates to secondaryComparator.
     *
     * @param Comparator $secondaryComparator
     * @return Ordering
     */
    public function compound(Comparator $secondaryComparator) : Ordering
    {
        return Ordering::from(Collections::comparatorFrom(
            function ($object1, $object2) use ($secondaryComparator) {
                $res = $this->compare($object1, $object2);
                return $res !== 0 ? $res : $secondaryComparator->compare($object1, $object2);
            }
        ));
    }

    /**
     * Returns the least of the specified values according to this ordering.
     *
     * @param Traversable $traversable
     * @return mixed
     * @throws \OutOfBoundsException if $traversable is empty
     */
    public function min(Traversable $traversable)
    {
        $array = iterator_to_array($traversable, false);
        Arrays::sort($array, $this);
        return Preconditions::checkElementExists($array, 0);
    }

    /**
     * Returns the greatest of the specified values according to this ordering.
     *
     * @param Traversable $traversable
     * @return mixed
     * @throws \OutOfBoundsException if $traversable is empty
     */
    public function max(Traversable $traversable)
    {
        return $this->reverse()->min($traversable);
    }

    /**
     * @param $object1
     * @param $object2
     * @return int a negative integer, zero, or a positive integer
     *         as the first argument is less than, equal to, or greater than the second.
     * @throws ClassCastException - if the arguments' types prevent them from being compared by this comparator.
     */
    public function compare($object1, $object2) : int
    {
        return $this->comparator->compare($object1, $object2);
    }
}
Ordering::init();


<?php

namespace TafDecoder;

use TafDecoder\Entity\DecodedTaf;
use TafDecoder\Exception\ChunkDecoderException;
use TafDecoder\ChunkDecoder\ReportTypeChunkDecoder;
use TafDecoder\ChunkDecoder\IcaoChunkDecoder;
use TafDecoder\ChunkDecoder\DatetimeChunkDecoder;
use TafDecoder\ChunkDecoder\ForecastPeriodChunkDecoder;
use TafDecoder\ChunkDecoder\SurfaceWindChunkDecoder;
use TafDecoder\ChunkDecoder\VisibilityChunkDecoder;
use TafDecoder\ChunkDecoder\WeatherChunkDecoder;
use TafDecoder\ChunkDecoder\CloudChunkDecoder;
use TafDecoder\ChunkDecoder\TemperatureChunkDecoder;
use TafDecoder\ChunkDecoder\EvolutionChunkDecoder;

class TafDecoder
{
    private $decoder_chain;

    private $strict_parsing = false;

    protected $global_strict_parsing = false;

    public function __construct()
    {
        $this->decoder_chain = array(
            new ReportTypeChunkDecoder(),
            new IcaoChunkDecoder(),
            new DatetimeChunkDecoder(),
            new ForecastPeriodChunkDecoder(),
            new SurfaceWindChunkDecoder(),
            new VisibilityChunkDecoder(),
            new WeatherChunkDecoder(),
            new CloudChunkDecoder(),
            new TemperatureChunkDecoder(),
        );
    }

    /**
     * Set global parsing mode (strict/not strict) for the whole object
     */
    public function setStrictParsing($is_strict)
    {
        $this->global_strict_parsing = $is_strict;
    }

    /**
     * Decode a full taf string into a complete taf object
     * while using global strict option
     */
    public function parse($raw_taf)
    {
        return $this->parseWithMode($raw_taf, $this->global_strict_parsing);
    }

    /**
     * Decode a full taf string into a complete taf object
     * with strict option, meaning decoding will stop as soon as
     * a non-compliance is detected
     */
    public function parseStrict($raw_taf)
    {
        return $this->parseWithMode($raw_taf, true);
    }

    /**
     * Decode a full taf string into a complete taf object
     * with strict option disabled, meaning that decoding will
     * continue even if taf is not compliant
     */
    public function parseNotStrict($raw_taf)
    {
        return $this->parseWithMode($raw_taf, false);
    }

    /**
     * Decode a full taf string into a complete taf object
     * @param $raw_taf
     * @param $strict
     * @return DecodedTaf
     */
    private function parseWithMode($raw_taf, $strict)
    {
        // prepare decoding inputs/outputs: (trim, remove linefeeds and returns, no more than one space)
        $clean_taf = trim($raw_taf);
        $clean_taf = preg_replace("#\n+#", ' ', $clean_taf);
        $clean_taf = preg_replace("#\r+#", ' ', $clean_taf);
        $clean_taf = preg_replace('#[ ]{2,}#', ' ', $clean_taf) . ' ';
        $clean_taf = strtoupper($clean_taf);
        if (strpos($clean_taf, 'CNL') === false) {
            // appending END to it is necessary to detect the last line of evolution
            // but only when the TAF wasn't cancelled (CNL)
            $remaining_taf = trim($clean_taf) . ' END';
        } else {
            $remaining_taf = $clean_taf;
        }
        $decoded_taf = new DecodedTaf($clean_taf);
        $with_cavok = false;

        // call each decoder in the chain and use results to populate decoded taf
        foreach ($this->decoder_chain as $chunk_decoder) {
            try {
                // try to parse a chunk with current chunk decoder
                $decoded = $chunk_decoder->parse($remaining_taf, $with_cavok);

                // map obtained fields (if any) to the final decoded object
                $result = $decoded['result'];
                if ($result != null) {
                    foreach ($result as $key => $value) {
                        if ($value !== null) {
                            $setter_name = 'set'.ucfirst($key);
                            $decoded_taf->$setter_name($value);
                        }
                    }
                }

                // update remaining taf for next round
                $remaining_taf = $decoded['remaining_taf'];
            } catch (ChunkDecoderException $cde) {
                // log error in decoded taf and abort decoding if in strict mode
                $decoded_taf->addDecodingException($cde);
                // abort decoding if strict mode is activated, continue otherwise
                if ($strict) {
                    break;
                }
                // update remaining taf for next round
                $remaining_taf = $cde->getRemainingTaf();
            }

             // hook for CAVOK decoder, keep CAVOK information in memory
            if ($chunk_decoder instanceof VisibilityChunkDecoder) {
                $with_cavok = $decoded_taf->getCavok();
            }
        }

        // weather evolutions
        $evolutionDecoder = new EvolutionChunkDecoder($strict, $with_cavok);
        while ($remaining_taf != null && trim($remaining_taf) != 'END') {
            $evolutionDecoder->parse($remaining_taf, $decoded_taf);
            $remaining_taf = $evolutionDecoder->getRemaining();
        }

        return $decoded_taf;
    }
}


<?php
declare(strict_types=1);

namespace precore\util;

use ArrayIterator;
use Iterator;
use precore\lang\BaseObject;
use precore\lang\ObjectInterface;
use RuntimeException;
use Traversable;

/**
 * An object that divides strings into substrings, by recognizing a separator (a.k.a. "delimiter")
 * which can be expressed as a string, regular expression, or by using a fixed substring length.
 * This class provides the complementary functionality to {@link Joiner}.
 *
 *
Here is the most basic example of {@link Splitter} usage:
 *

 *   Splitter::on(',').split('foo,bar')
 *

 *
 * This invocation returns an {@link \Traversable} containing "foo" and "bar", in that order.
 *
 *
By default {@link Splitter}'s behavior is very simplistic:
 *

 *   Splitter::on(',').split('foo,,bar, quux')
 *

 *
 * This returns a {@link \Traversable} containing ["foo", "", "bar", " quux"].
 *
 * Notice that the splitter does not assume that you want empty strings removed,
 * or that you wish to trim whitespace. If you want features like these, simply
 * ask for them:
 *

 *   $splitter = Splitter::on(',')
 *       ->trimResults()
 *       ->omitEmptyStrings();
 *

 *
 * Now $splitter.split("foo, ,bar, quux,") returns a {@link \Traversable}
 * containing just ["foo", "bar", "quux"]. Note that the order in which
 * the configuration methods are called is never significant; for instance,
 * trimming is always applied first before checking for an empty result,
 * regardless of the order in which the {@link Splitter::trimResults()} and
 * {@link Splitter::omitEmptyStrings()} methods were invoked.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
abstract class Splitter extends BaseObject
{
    const UTF_8 = 'UTF-8';

    protected $omitEmptyStrings;
    protected $trimResults;

    /**
     * @param $delimiter
     * @return SimpleSplitter
     */
    public static function on(string $delimiter) : SimpleSplitter
    {
        return new SimpleSplitter($delimiter, false, false);
    }

    /**
     * @param string $pattern
     * @return Splitter
     */
    public static function onPattern(string $pattern) : Splitter
    {
        return new PatternSplitter($pattern, false, false);
    }

    /**
     * @param int $length
     * @return Splitter
     */
    public static function fixedLength(int $length) : Splitter
    {
        return new FixedLengthSplitter($length, false, false);
    }

    protected function __construct(bool $trimResult, bool $omitEmptyStrings)
    {
        $this->trimResults = $trimResult;
        $this->omitEmptyStrings = $omitEmptyStrings;
    }

    /**
     * @return static
     */
    protected abstract function copy() : Splitter;

    /**
     * @param $input
     * @return Iterator
     */
    protected abstract function rawSplitIterator(string $input) : Iterator;

    /**
     * Returns a splitter that behaves equivalently to this splitter, but
     * automatically removes leading and trailing whitespace characters
     * from each returned substring. For example
     *

     *     Splitter::on(',')->trimResults()->split(' a, b ,c ')
     *

     * returns a Traversable containing ["a", "b", "c"].
     *
     * @return static
     */
    public final function trimResults() : Splitter
    {
        $splitter = $this->copy();
        $splitter->omitEmptyStrings = $this->omitEmptyStrings;
        $splitter->trimResults = true;
        return $splitter;
    }

    /**
     * Returns a splitter that behaves equivalently to this splitter, but
     * automatically omits empty strings from the results. For example
     *

     *     Splitter::on(',')->omitEmptyStrings()->split(',a,,,b,c,,')
     *

     * returns a Traversable containing only ["a", "b", "c"].
     *
     * @return static
     */
    public final function omitEmptyStrings() : Splitter
    {
        $splitter = $this->copy();
        $splitter->omitEmptyStrings = true;
        $splitter->trimResults = $this->trimResults;
        return $splitter;
    }

    /**
     * @param string $input
     * @return Traversable
     */
    public final function split(string $input) : Traversable
    {
        return FluentIterable::from($this->rawSplitIterator($input))
            ->transform(
                function ($element) {
                    return $this->trimResults ? trim($element) : $element;
                }
            )
            ->filter(
                function ($element) {
                    return !$this->omitEmptyStrings || $element !== '';
                }
            );
    }

    public function equals(ObjectInterface $object = null) : bool
    {
        /* @var $object Splitter */
        return $object !== null
            && $this->getClassName() === $object->getClassName()
            && $this->omitEmptyStrings === $object->omitEmptyStrings
            && $this->trimResults === $object->trimResults;
    }

    public function toString() : string
    {
        return Objects::toStringHelper($this)
            ->add('trimResults', $this->trimResults)
            ->add('omitEmptyStrings', $this->omitEmptyStrings)
            ->toString();
    }

}

/**
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class SimpleSplitter extends Splitter
{
    /**
     * @var string
     */
    private $delimiter;

    /**
     * @var bool
     */
    private $eager;

    /**
     * @param string $delimiter
     * @param $trimResult
     * @param $omitEmptyStrings
     * @param boolean $eager
     * @throws \InvalidArgumentException if $delimiter is not a string
     */
    public function __construct(string $delimiter, bool $trimResult, bool $omitEmptyStrings, $eager = false)
    {
        parent::__construct($trimResult, $omitEmptyStrings);
        $this->delimiter = $delimiter;
        $this->eager = $eager;
    }

    /**
     * @return SimpleSplitter
     */
    public function eager() : SimpleSplitter
    {
        return new SimpleSplitter($this->delimiter, $this->trimResults, $this->omitEmptyStrings, true);
    }

    /**
     * @return Splitter
     */
    protected function copy() : Splitter
    {
        return new SimpleSplitter($this->delimiter, $this->trimResults, $this->omitEmptyStrings, $this->eager);
    }

    /**
     * @param $input
     * @return Iterator
     */
    protected function rawSplitIterator(string $input) : Iterator
    {
        return $this->eager
            ? new ArrayIterator(explode($this->delimiter, $input))
            : new SimpleSplitIterator($this->delimiter, $input);
    }

    public function equals(ObjectInterface $object = null) : bool
    {
        /* @var $object SimpleSplitter */
        return parent::equals($object)
            && $this->eager === $object->eager
            && $this->delimiter === $object->delimiter;
    }

    public function toString() : string
    {
        return Objects::toStringHelper($this)
            ->add('parent', parent::toString())
            ->add('delimiter', $this->delimiter)
            ->add('eager', $this->eager)
            ->toString();
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class PatternSplitter extends Splitter
{
    /**
     * @var string
     */
    private $pattern;

    /**
     * @param string $pattern
     * @param $trimResult
     * @param $omitEmptyStrings
     * @throws \InvalidArgumentException if $pattern is not a string
     */
    public function __construct(string $pattern, bool $trimResult, bool $omitEmptyStrings)
    {
        parent::__construct($trimResult, $omitEmptyStrings);
        $this->pattern = $pattern;
    }

    /**
     * @return Splitter
     */
    protected function copy() : Splitter
    {
        return new PatternSplitter($this->pattern, $this->trimResults, $this->omitEmptyStrings);
    }

    /**
     * @param $input
     * @return Iterator
     */
    protected function rawSplitIterator(string $input) : Iterator
    {
        return new ArrayIterator(preg_split($this->pattern, $input));
    }

    public function equals(ObjectInterface $object = null) : bool
    {
        /* @var $object PatternSplitter */
        return parent::equals($object)
            && $this->pattern === $object->pattern;
    }

    public function toString() : string
    {
        return Objects::toStringHelper($this)
            ->add('parent', parent::toString())
            ->add('pattern', $this->pattern)
            ->toString();
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class FixedLengthSplitter extends Splitter
{
    /**
     * @var int
     */
    private $length;

    /**
     * @param int $length
     * @param $trimResult
     * @param $omitEmptyStrings
     * @throws \InvalidArgumentException if $length is not a positive integer
     */
    public function __construct(int $length, bool $trimResult, bool $omitEmptyStrings)
    {
        parent::__construct($trimResult, $omitEmptyStrings);
        Preconditions::checkArgument(0 < $length, 'length must be a positive integer');
        $this->length = $length;
    }

    /**
     * @return Splitter
     */
    protected function copy() : Splitter
    {
        return new FixedLengthSplitter($this->length, $this->trimResults, $this->omitEmptyStrings);
    }

    /**
     * @param $input
     * @return Iterator
     */
    protected function rawSplitIterator(string $input) : Iterator
    {
        return new FixedLengthSplitIterator($this->length, $input);
    }

    public function equals(ObjectInterface $object = null) : bool
    {
        /* @var $object FixedLengthSplitter */
        return parent::equals($object)
            && $this->length === $object->length;
    }

    public function toString() : string
    {
        return Objects::toStringHelper($this)
            ->add('parent', parent::toString())
            ->add('length', $this->length)
            ->toString();
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class SimpleSplitIterator implements Iterator
{
    private $delimiter;
    private $delimiterLength;
    private $input;
    private $end = false;
    private $current;
    private $started = false;

    public function __construct(string $delimiter, string $input)
    {
        $this->delimiter = $delimiter;
        $this->delimiterLength = mb_strlen($delimiter, Splitter::UTF_8);
        $this->input = $input;
    }

    public function current()
    {
        return $this->current;
    }

    public function next()
    {
        if (!$this->end) {
            $this->calculateCurrent();
        } else {
            $this->current = null;
        }
    }

    public function key()
    {
        return null;
    }

    public function valid()
    {
        return $this->current !== null;
    }

    public function rewind()
    {
        if (!$this->started) {
            $this->started = true;
            $this->calculateCurrent();
        } else {
            throw new RuntimeException('this iterator cannot be rewound');
        }
    }

    private function calculateCurrent() : void
    {
        $delimiterPos = mb_strpos($this->input, $this->delimiter, 0, Splitter::UTF_8);
        if ($delimiterPos === false) {
            $this->end = true;
            $this->current = $this->input;
        } else {
            $this->current = mb_substr($this->input, 0, $delimiterPos, Splitter::UTF_8);
            $this->input = mb_substr($this->input, $delimiterPos + $this->delimiterLength, null, Splitter::UTF_8);
        }
    }
}

/**
 * It is not intended to be used in your code.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class FixedLengthSplitIterator implements Iterator
{
    private $fullLength;
    private $limitLength;
    private $input;
    private $current;
    private $pos = 0;

    public function __construct(int $limitLength, string $input)
    {
        $this->fullLength = mb_strlen($input, Splitter::UTF_8);
        $this->limitLength = $limitLength;
        $this->input = $input;
    }

    public function current()
    {
        return $this->current;
    }

    public function next()
    {
        $this->pos += $this->limitLength;
        $this->calculateCurrent();
    }

    public function key()
    {
        return null;
    }

    public function valid()
    {
        return $this->pos < $this->fullLength;
    }

    public function rewind()
    {
        $this->pos = 0;
        $this->calculateCurrent();
    }

    private function calculateCurrent()
    {
        $this->current = mb_substr($this->input, $this->pos, $this->limitLength, Splitter::UTF_8);
    }
}


<?php
declare(strict_types=1);

namespace precore\util;

/**
 * Utility class to create common functions.
 *
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class Functions
{
    private static $IDENTITY;
    private static $TO_STRING;

    private function __construct()
    {
    }

    public static function init() : void
    {
        self::$IDENTITY = function ($input) {
            return $input;
        };
        self::$TO_STRING = function ($input) {
            return (string) $input;
        };
    }

    /**
     * Creates a function that returns value for any input.
     *
     * @param $value
     * @return callable
     */
    public static function constant($value) : callable
    {
        return function () use ($value) {
            return $value;
        };
    }

    /**
     * Returns the identity function.
     *
     * @return callable
     */
    public static function identity() : callable
    {
        return self::$IDENTITY;
    }

    /**
     * Returns a function that casts its argument to string.
     *
     * @return callable
     */
    public static function toStringFunction() : callable
    {
        return self::$TO_STRING;
    }

    /**
     * Returns the composition of two functions.
     *
     * For f: A->B and g: B->C, composition is defined as the function h such that h(a) == g(f(a)) for each a.
     *
     * @param callable $g
     * @param callable $f
     * @return callable
     */
    public static function compose(callable $g, callable $f) : callable
    {
        return function ($input) use ($g, $f) {
            return Functions::call($g, Functions::call($f, $input));
        };
    }

    /**
     * Returns a function which performs a map lookup.
     *
     * @param array $map
     * @return callable
     * @throws \InvalidArgumentException if given a key that does not exist in the map
     */
    public static function forMap(array $map) : callable
    {
        return function ($index) use ($map) {
            Preconditions::checkArgument(
                array_key_exists($index, $map),
                "The given key '%s' does not exist in the map",
                $index
            );
            return $map[$index];
        };
    }

    /**
     * Returns a function which performs a map lookup with a default value.
     *
     * @param array $map source map that determines the function behavior
     * @param mixed $default the value to return for inputs that aren't map keys
     * @return callable
     */
    public static function forMapOr(array $map, $default) : callable
    {
        return function ($index) use ($map, $default) {
            return array_key_exists($index, $map)
                ? $map[$index]
                : $default;
        };
    }

    /**
     * @param callable $function
     * @param $params
     * @return mixed
     */
    public static function call(callable $function, $params = null)
    {
        $args = func_get_args();
        array_shift($args);
        return call_user_func_array($function, $args);
    }
}
Functions::init();


<?php

namespace ilateral\SilverStripe\Searchable;

use SilverStripe\View\ViewableData;
use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\ArrayList;
use ilateral\SilverStripe\Searchable\Control\SearchResults;

class Searchable extends ViewableData
{

    /**
     * Cache of objects added via Searchable::add. This is used to
     * determine if the SearchForm is usable
     *
     * @var array
     * @config
     */
    private static $objects = [];

    public static function getObjects()
    {
        Deprecation::notice(1.1, "Get objects is no longer being supported, use 'Searchable.objects' instead");

        return self::config()->objects;
    }

    /**
     * Specify how many items should appear per page of results.
     *
     * @var int
     * @config
     */
    private static $page_length = 10;

    /**
     * Specify how many items should appear per object on the results
     * dashboard.
     *
     * @var int
     * @config
     */
    private static $dashboard_items = 5;

    /**
     * Specify a list of custom filters that can be associated against
     * an object we are searching.
     *
     * For example if we are searching a "Product" object and want to
     * only show objects that have Disabled set to 0, we would add the
     * following to our _config.php
     *
     * Searchable::config()->custom_filters = array(
     *     "Product" => array(
     *         "Disabled" => 0
     *     )
     * );
     *
     * @var array
     * @config
     */
    private static $custom_filters = array();

    /**
     *
     *
     * @var string
     * @config
     */
    private static $template_class = SearchResults::class;

    /**
     * Add an object to the Searchable module, this object will
     * automatically be added to the results page dashboard
     *
     * @param $classname Classname of the object we want to search
     * @param $columns An array of database column names we will search
     * @param $title The title of this object (that will appear in the dashboard)
     *
     */
    public static function add($classname, $columns = array(), $title = null)
    {
        if ($title) {
            Deprecation::notice(1.1, "Title is no longer used, instead set ClassName.PluralName in translations");
        }

        self::config()->objects[$classname] = $columns;

        $cols_string = '"' . implode('","', $columns) . '"';
    }

    /**
     * Return DataList of the results using $_REQUEST to get search info
     * Wraps around {@link searchEngine()}.
     *
     * Results also checks to see if there is a custom filter set in
     * configuration and adds it.
     *
     * @param $classname Name of the object we will be filtering
     * @param $columns an array of the column names we will be sorting
     * @param $query the current search query
     *
     * @return SS_List
     */
     public static function Results($classname, $columns, $keywords, $limit = 0)
     {
        $cols_string = implode('","', $columns);
        $custom_filters = Searchable::config()->custom_filters;
        $results = ArrayList::create();

        $filter = [];

        foreach ($columns as $col) {
            $filter["{$col}:PartialMatch"] = $keywords;
        }

        $search = $classname::get()
            ->filterAny($filter);

        if (is_array($custom_filters) && array_key_exists($classname, $custom_filters) && is_array($custom_filters[$classname])) {
            $search = $search->filter($custom_filters[$classname]);
        }

        $searchable = Searchable::create();

        if ($searchable->hasMethod('filterResultsByCallback')) {
            $search = $searchable->filterResultsByCallback($search, $classname);
        }

        if ($limit) {
            $search = $search->limit($limit);
        }

        foreach ($search as $result) {
            if ($result->canView() || (isset($result->ShowInSearch) && $result->ShowInSearch)) {
                $results->add($result);
            }
        }

        return $results;
     }
}


<?php
declare(strict_types=1);

namespace precore\util;

use Countable;
use Iterator;
use IteratorAggregate;
use OutOfBoundsException;
use precore\lang\BaseObject;
use Traversable;

/**
 * {@link FluentIterable} provides a flexible way to filter, transform and limit {@link IteratorAggregate}s.
 * All of it is managed in a lazy manner, thus constructing an object is cheap. All methods return a new instance.
 *
 *

 * $topAdminUserNames = FluentIterable::from($repository->getUsers())
 *   ->filter($hasAdminRoleFilter)
 *   ->transform($userNameTransformer)
 *   ->limit(10);
 *

 *
 * In the above example, $hasAdminRoleFilter is a predicate that accept a user if that is an administrator,
 * $userNameTransformer returns the name of the input user. Iterating over $topAdminUserNames results 10 user names.
 *
 * @see Predicates
 * @package precore\util
 * @author Janos Szurovecz <szjani@szjani.hu>
 */
final class FluentIterable extends BaseObject implements IteratorAggregate, Countable
{
    /**
     * @var IteratorAggregate
     */
    private $iterable;

    private function __construct(IteratorAggregate $iterable)
    {
        $this->iterable = $iterable;
    }

    /**
     * Creates an object from the given array.
     *
     * @param array $array
     * @return FluentIterable
     */
    public static function of(array $array) : FluentIterable
    {
        return self::from(Iterables::fromArray($array));
    }

    /**
     * Creates an object from the given {@link Traversable}.
     *
     * @param Traversable $traversable
     * @return FluentIterable
     */
    public static function from(Traversable $traversable) : FluentIterable
    {
        return new FluentIterable(Iterables::from($traversable));
    }

    /**
     * Returns a fluent iterable whose iterators traverse first the elements
     * of this fluent iterable, followed by those of other.
     *
     * @param IteratorAggregate $other
     * @return FluentIterable
     */
    public function append(IteratorAggregate $other) : FluentIterable
    {
        return self::from(Iterables::concat($this, $other));
    }

    /**
     * Returns the elements from this fluent iterable that satisfy a predicate.
     *
     * @param callable $predicate
     * @return FluentIterable
     */
    public function filter(callable $predicate) : FluentIterable
    {
        return self::from(Iterables::filter($this, $predicate));
    }

    /**
     * Returns the elements from this fluent iterable that are instances of class className.
     *
     * @param string $className
     * @return FluentIterable
     */
    public function filterBy(string $className) : FluentIterable
    {
        return self::from(Iterables::filterBy($this, $className));
    }

    /**
     * Returns true if any element in this fluent iterable satisfies the predicate.
     *
     * @param callable $predicate
     * @return boolean
     */
    public function anyMatch(callable $predicate) : bool
    {
        return Iterables::any($this, $predicate);
    }

    /**
     * Returns true if every element in this fluent iterable satisfies the predicate.
     * If this fluent iterable is empty, true is returned.
     *
     * @param callable $predicate
     * @return boolean
     */
    public function allMatch(callable $predicate) : bool
    {
        return Iterables::all($this, $predicate);
    }

    /**
     * Returns a fluent iterable that applies transformer to each element of this fluent iterable.
     *
     * @param callable $transformer
     * @return FluentIterable
     */
    public function transform(callable $transformer) : FluentIterable
    {
        return self::from(Iterables::transform($this, $transformer));
    }

    /**
     * Returns a fluent iterable that applies mapper to each element of this fluent iterable.
     *
     * @param callable $mapper
     * @return FluentIterable
     */
    public function map(callable $mapper) : FluentIterable
    {
        return $this->transform($mapper);
    }

    /**
     * Applies function to each element of this fluent iterable and returns a fluent iterable
     * with the concatenated combination of results. Transformer returns a Traversable of results.
     *
     * @param callable $transformer
     * @return FluentIterable
     * @see flatMap
     */
    public function transformAndConcat(callable $transformer) : FluentIterable
    {
        return self::from(Iterables::concatIterables($this->transform($transformer)));
    }

    /**
     * Applies function to each element of this fluent iterable and returns a fluent iterable
     * with the concatenated combination of results. Transformer returns a Traversable of results.
     *
     * @param callable $transformer
     * @return FluentIterable
     */
    public function flatMap(callable $transformer) : FluentIterable
    {
        return $this->transformAndConcat($transformer);
    }

    /**
     * Creates a fluent iterable with the first size elements of this fluent iterable.
     * If this fluent iterable does not contain that many elements,
     * the returned fluent iterable will have the same behavior as this fluent iterable.
     *
     * @param $limit
     * @return FluentIterable
     */
    public function limit($limit) : FluentIterable
    {
        return self::from(Iterables::limit($this, $limit));
    }

    /**
     * Returns a view of this fluent iterable that skips its first numberToSkip elements.
     * If this fluent iterable contains fewer than numberToSkip elements,
     * the returned fluent iterable skips all of its elements.
     *
     * @param $numberToSkip
     * @return FluentIterable
     */
    public function skip($numberToSkip) : FluentIterable
    {
        return self::from(Iterables::skip($this, $numberToSkip));
    }

    /**
     * Returns a string containing all of the elements of this fluent iterable joined with joiner.
     *
     * @param Joiner $joiner
     * @return string
     */
    public function join(Joiner $joiner) : string
    {
        return $joiner->join($this);
    }

    /**
     * Returns an Optional containing the first element in this fluent iterable.
     *
     * @return Optional
     */
    public function first() : Optional
    {
        try {
            return Optional::ofNullable($this->get(0));
        } catch (OutOfBoundsException $e) {
            return Optional::absent();
        }
    }

    /**
     * Returns an Optional containing the first element in this fluent iterable
     * that satisfies the given predicate, if such an element exists.
     *
     * @param callable $predicate
     * @return Optional
     */
    public function firstMatch(callable $predicate) : Optional
    {
        return $this->filter($predicate)->first();
    }

    /**
     * @return Optional
     */
    public function last() : Optional
    {
        return Optional::ofNullable(Iterators::getLast($this->iterator()));
    }

    /**
     * Returns the element at the specified position in this fluent iterable.
     *
     * @param $index
     * @return mixed
     * @throws OutOfBoundsException if $index is invalid
     */
    public function get($index)
    {
        return Iterables::get($this, $index);
    }

    /**
     * Determines whether this fluent iterable is empty.
     *
     * @return boolean
     */
    public function isEmpty() : bool
    {
        return Iterables::isEmpty($this);
    }

    /**
     * Returns true if this fluent iterable contains any object for which Objects::equal(element, object) is true.
     *
     * @param $element
     * @return boolean
     */
    public function contains($element) : bool
    {
        return Iterables::contains($this, $element);
    }

    /**
     * Returns the number of elements in this fluent iterable.
     *
     * @return int
     */
    public function size() : int
    {
        return Iterators::size($this->iterator());
    }

    /**
     * Returns an iterator provided by the inner {@link IteratorAggregate}.
     *
     * @return Iterator
     */
    public function iterator() : Iterator
    {
        return Iterators::from($this->iterable);
    }

    /**
     * Executes the given function on all elements.
     *
     * @param callable $function
     * @return void
     */
    public function each(callable $function) : void
    {
        Iterators::each($this->iterator(), $function);
    }

    /**
     * Sorts the elements in the order specified by comparator.
     *
     * @param Comparator $comparator
     * @return FluentIterable
     */
    public function sorted(Comparator $comparator) : FluentIterable
    {
        $array = $this->toArray();
        Arrays::sort($array, $comparator);
        return self::of($array);
    }

    /**
     * @param $size
     * @return FluentIterable
     */
    public function partition($size) : FluentIterable
    {
        return FluentIterable::from(Iterables::partition($this, $size));
    }

    /**
     * Converts this object to an array. This means it will iterate over this object.
     * It should be used only if it is reasonable.
     *
     * @return array
     */
    public function toArray() : array
    {
        $res = [];
        Iterators::each($this->iterator(), function ($element) use (&$res) {
            $res[] = $element;
        });
        return $res;
    }

    /**
     * (PHP 5 >= 5.0.0)

     * Retrieve an external iterator
     * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
     * @return Traversable An instance of an object implementing Iterator or
     * Traversable
     */
    public function getIterator()
    {
        return $this->iterator();
    }

    /**
     * (PHP 5 >= 5.1.0)

     * Count elements of an object
     * @link http://php.net/manual/en/countable.count.php
     * @return int The custom count as an integer.
     *

     *

     * The return value is cast to an integer.
     */
    public function count()
    {
        return $this->size();
    }

    public function toString() : string
    {
        return Iterables::toString($this);
    }
}