public static boolean haveSeen(final List<TokenType> tokenTypeSequence,
                                   final boolean contiguous,
                                   final TokenType... tokenTypes) {
        if (NullSafe.isEmptyCollection(tokenTypeSequence)) {
            return false;
        } else if (NullSafe.isEmptyArray(tokenTypes)) {
            return false;
        } else if (tokenTypeSequence.size() < tokenTypes.length) {
            return false;
        } else {
            int startIdx = 0;
            int seenCount = 0;
            for (final TokenType requiredType : tokenTypes) {
                Objects.requireNonNull(requiredType);
                boolean foundType = false;
                for (int j = startIdx; j < tokenTypeSequence.size(); j++) {
                    final TokenType seenType = tokenTypeSequence.get(j);
                    if (requiredType == seenType) {
                        foundType = true;
                        startIdx = j + 1;
                        seenCount++;
                        break;
                    } else if (contiguous && seenCount > 0 && tokenTypes.length > 1) {
                        break;
                    }
                }
                if (!foundType) {
                    return false;
                }
            }
            return true;
        }
    }
@param contiguous If true, tokenTypes must be seen as a contiguous block rather
                  than separated by other {@link TokenType}s.
@return True if tokenTypes have been seen in order in lastKeywordSequence
stroom-query/stroom-query-api/src/main/java/stroom/query/api/token/TokenType.java