@NotNull
    public static Predicate<String> createWordBoundaryPredicate(
            final String userInput,
            final Pattern separatorCharacterClass) {

        LOGGER.trace("creating word boundary predicate");
        // Has some uppercase so use word boundaries
        // An upper case letter means the start of a word
        // a lower case letter means the continuation of a word

        // We can use the separator based predicate for both camel case and separated
        // strings as long as we modify the camel case ones first.

        final Predicate<String> separatorPredicate = createSeparatedWordBoundaryPredicate(
                userInput, separatorCharacterClass);

        return toNullSafePredicate(false, stringUnderTest -> {

            // First split the string being tested on the separators, then see if any of the
            // parts are camel case and if so add spaces to split the camel case parts.
            // e.g. stroom.someProp.maxFileSize => "stroom some prop max file size"
            // This allows us to deal with strings that are a mix of delimited and camel case.
            final String[] separatedParts = separatorCharacterClass.split(stringUnderTest);
            final String cleanedString = Arrays.stream(separatedParts)
                    .map(StringPredicateFactory::cleanStringForWordBoundaryMatching)
                    .collect(Collectors.joining(" "));

            LOGGER.trace("cleaned stringUnderTest [{}] => [{}] has word separators",
                    stringUnderTest, cleanedString);
            return separatorPredicate.test(cleanedString);
        });
    }
For a given user input this method will return a function that can evaluate a string to see how
good the match is. The quality of the match is determined by the length of the matched region
(shorter == better) and its distance from the start of the string (closer == better).
stroom-query/stroom-query-common/src/main/java/stroom/query/common/v2/StringPredicateFactory.java