public static Predicate<String> createFuzzyMatchPredicate(final String userInput,
                                                              final Pattern separatorCharacterClass) {
        // TODO should we trim the input to remove leading/trailing spaces?

        // TODO @AT Need to hold the received input in an obj along with the match mode and input stripped of
        //  all pre/suffixes and available via getStrippedInput() and getLowerCaseStrippedInput().
        //  This will make it clearer as to what input you are dealing with.

        // TODO @AT Rather than constructing a predicate for each match mode we ought to build a mapping func
        //  that will map the input in its original form, i.e. T to an obj containing the original T item,
        //  the result of the match (true/false) and the match quality (match length, match position, matched chars).
        //  The caller would need to provide an extractor func <T, String> so we can get the string to filer on.
        //  Internally we can then map (to our obj), filter (on its true false) and sort (on the match quality).
        //  The caller can then do something like
        //    filterItems(itemStream, extractorFunc).map(FilterResult::getItem).collect(....)
        //  or make use of the filterResult to show the user why a thing matched
        //  All this would save us from having to run a regex on each match result in the compare stage, which is
        //  super inefficient.

        LOGGER.trace("Creating predicate for userInput [{}] and separators {}", userInput, separatorCharacterClass);

        String modifiedInput = userInput;
        boolean isNegated = false;

        Predicate<String> predicate;
        if (modifiedInput == null || modifiedInput.isEmpty()) {
            LOGGER.trace("Creating null input predicate");
            // No input so get everything
            predicate = stringUnderTest -> true;
        } else {
            if (modifiedInput.startsWith(NOT_OPERATOR_STR)) {
                modifiedInput = modifiedInput.substring(1);
                LOGGER.debug("Input after NOT operator removal [{}]", modifiedInput);
                isNegated = true;
            }

            if (modifiedInput.isEmpty()) {
                LOGGER.trace("Creating null input predicate");
                // No input so get everything
                predicate = stringUnderTest -> true;
            } else if (modifiedInput.startsWith(REGEX_PREFIX)) {
                // We must test for this prefix first as you might have '/foobar$' which could be confused
                // with ends with matching
                // remove the / marker char from the beginning
                predicate = createRegexPredicate(modifiedInput.substring(1));
            } else if (modifiedInput.startsWith(CHARS_ANYWHERE_PREFIX)) {
                // remove the ~ marker char from the beginning
                predicate = createCharsAnywherePredicate(modifiedInput.substring(1));
            } else if (modifiedInput.startsWith(WORD_BOUNDARY_PREFIX)) {
                // remove the ? marker char from the beginning
                predicate = createWordBoundaryPredicate(modifiedInput.substring(1), separatorCharacterClass);
                // remove the = marker char from the beginning
            } else if (modifiedInput.startsWith(EXACT_MATCH_PREFIX)) {
                predicate = createCaseInsensitiveExactMatchPredicate(modifiedInput.substring(1));
            } else if (modifiedInput.startsWith(ENDS_WITH_PREFIX)) {
                // remove the $ marker char from the beginning
                predicate = createCaseInsensitiveEndsWithPredicate(modifiedInput.substring(1));
            } else if (modifiedInput.startsWith(STARTS_WITH_PREFIX)) {
                // remove the ^ marker char from the beginning
                predicate = createCaseInsensitiveStartsWithPredicate(modifiedInput.substring(1));
            } else if (modifiedInput.contains(WILDCARD_STR)) {
                // This is intended for expression term values, e.g. specifing a set of feeds in a processor
                // filter expression. The aim is to show the user what values are represented by a wild-carded
                // string, e.g. if they enter 'FEED_*' it can show 'FEED_1', 'FEED_2', etc.
                // This is different behaviour to entering a quick filter term to select a single value.
                // Also, it is not ideal as it is complete match and case sens which is inconsistent with the rest.
                // It is not at all obvious to the user what is going on.
                predicate = createWildCardedPredicate(modifiedInput);
            } else {
                // Would be nice to use chars anywhere for the default but that needs ranked matches which
                // we can't do when filtering the trees
                predicate = createCaseInsensitiveContainsPredicate(modifiedInput);
            }
        }

        if (isNegated) {
            predicate = predicate.negate();
            LOGGER.debug("Negating predicate");
        }

        if (LOGGER.isTraceEnabled()) {
            return toLoggingPredicate(predicate);
        } else {
            return predicate;
        }
    }
Creates a fuzzy match {@link Predicate<String>} for userInput.
Null userInput results in an always true predicate.
Broadly it has five match modes:
Regex match: "/(wo|^)man" matches "a woman", "manly"
Word boundary match: "?OTheiM" matches "on the mat" in "the cat sat on their mat", but not
"the cat sat on there mat"
Starts with: "^prefix" matches "PrefixToSomeText" (case insensitive)
Ends with "suffix$" matches "TextWithSuffix" (case insensitive)
Exact match: "^sometext$" matches "sometext" (case insensitive)
Chars anywhere (in order): "aid" matches "A big dog" (case insensitive)
See TestStringPredicateFactory for more examples of how the
matching works.

@param separatorCharacterClass A regex character class, e.g. [ \-_] that defines the separators
                               between words in the string(s) under test.
stroom-query/stroom-query-common/src/main/java/stroom/query/common/v2/StringPredicateFactory.java