function add($selector, $callbackOrFilterName = null, $options = null, $recursive = false, $priority = 0)
    {
        /**
         * $selector is actually an array with filters
         *
         * @example $filtrator->add(array(
         *          'title' => array('trim', array('truncate', '{"limit":100}'))
         *          'description' => array('trim')
         *          ));
         */
        if (is_array($selector)) {
            foreach ($selector as $key => $filters) {
                $this->add($key, $filters);
            }
            return $this;
        }

        if (! is_string($selector)) {
            throw new \InvalidArgumentException('The data selector for filtering must be a string');
        }


        if (is_string($callbackOrFilterName)) {
            // rule was supplied like 'trim' or 'trim | nullify'
            if (strpos($callbackOrFilterName, ' | ') !== false) {
                return $this->add($selector, explode(' | ', $callbackOrFilterName));
            }
            // rule was supplied like this 'trim(limit=10)(true)(10)'
            if (strpos($callbackOrFilterName, '(') !== false) {
                list ($callbackOrFilterName, $options, $recursive, $priority) = $this->parseRule($callbackOrFilterName);
            }
        }

        /**
         * The $callbackOrFilterName is an array of filters
         *
         * @example $filtrator->add('title', array(
         *          'trim',
         *          array('truncate', '{"limit":100}')
         *          ));
         */
        if (is_array($callbackOrFilterName) && ! is_callable($callbackOrFilterName)) {
            foreach ($callbackOrFilterName as $filter) {
                // $filter is something like array('truncate', '{"limit":100}')
                if (is_array($filter) && ! is_callable($filter)) {
                    $args = $filter;
                    array_unshift($args, $selector);
                    call_user_func_array(array(
                        $this,
                        'add'
                    ), $args);
                } elseif (is_string($filter) || is_callable($filter)) {
                    $this->add($selector, $filter);
                }
            }
            return $this;
        }

        $filter = $this->filterFactory->createFilter($callbackOrFilterName, $options, $recursive);
        if (! array_key_exists($selector, $this->filters)) {
            $this->filters[$selector] = new FilterSet();
        }
        /* @var $filterSet FilterSet */
        $filterSet = $this->filters[$selector];
        $filterSet->insert($filter, $priority);
        return $this;
    }