public static @Nonnull ParsedArguments parseArguments(@Nonnull List<String> args, @Nonnull List<String> allAllowedFields, @Nonnull List<String> allAllowedHeaders) {
        ParsedArguments parsedArguments = new ParsedArguments();

        int i = 0;
        while (i < args.size()) {
            String argument = args.get(i);
            if (argument == null) {
                if (i == 0) {
                    // First argument can be null --> null useragent
                    parsedArguments.requestHeaders.put(USERAGENT_HEADER, null);
                    i++;
                    continue;
                } else {
                    // This is in general NOT allowed
                    // except when all remaining arguments are also null then we ignore the rest of the list
                    int locationOfFirstNull = i;
                    while (i < args.size()) {
                        if (args.get(i) != null) {
                            throw new IllegalArgumentException("Null argument provided to ParseUserAgent (Argument #" + locationOfFirstNull + " [0=first]).");
                        }
                        i++;
                    }
                    // Apparently we have a list of trailing null values which we all ignore.
                    return parsedArguments;
                }
            } else {
                if (args.size() == 1) {
                    // Only a single non-null value is always assumed to be the User-Agent header
                    parsedArguments.requestHeaders.put(USERAGENT_HEADER, args.get(i));
                    return parsedArguments;
                }
            }
            if (allAllowedFields.stream().anyMatch(argument::equalsIgnoreCase)) {
                parsedArguments.wantedFields.add(argument);
                i++;
                continue;
            }
            if (allAllowedHeaders.stream().anyMatch(argument::equalsIgnoreCase)) {
                String value;
                if (i + 1 >= args.size()) {
                    throw new IllegalArgumentException("Invalid last element in argument list (was a header name which requires a value to follow)");
                } else {
                    value = args.get(i + 1);
                    i++;
                }
                parsedArguments.requestHeaders.put(argument, value);
                i++;
                continue;
            }
            if (i == 0) {
                parsedArguments.requestHeaders.put(USERAGENT_HEADER, argument);
                i++;
                continue;
            }
            if (argument.isEmpty()) {
                throw new IllegalArgumentException("Empty argument provided to ParseUserAgent (Argument #" + i + " [0=first]).");
            }
            throw new IllegalArgumentException("Unknown argument provided to ParseUserAgent (Argument #" + i + " [0=first]).");
        }

        return parsedArguments;
    }
In several situations (mostly SQL class UDFs) we see the same need: A sequence of strings must be analyzed and
converted into parameters for the UserAgentAnalyzer.
Supported format patterns this parser will detect:
- useragent
- useragent [ headername, headervalue ]+
- [ headername, headervalue ]+
<p>
Notes:
- The useragent and any of the headervalues can be null or empty.
- A trailing list of null values will be ignored
- When not having the useragent as the first value the Analyzers will expect a "User-Agent" header in the list.
<p>
@param args The list of arguments passed from the user
@param allAllowedFields The list of all supported fieldnames (i.e. all possible outputs)
@param allAllowedHeaders The list of all supported headers (i.e. all supported inputs)
@return The list of request headers and the list of wanted fields. It is up to the calling system to choose which to use.
analyzer/src/main/java/nl/basjes/parse/useragent/AnalyzerUtilities.java