惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Blog — PlanetScale
Blog — PlanetScale
爱范儿
爱范儿
MongoDB | Blog
MongoDB | Blog
腾讯CDC
aimingoo的专栏
aimingoo的专栏
月光博客
月光博客
Engineering at Meta
Engineering at Meta
C
Check Point Blog
N
Netflix TechBlog - Medium
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
IT之家
IT之家
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
The Blog of Author Tim Ferriss
Last Week in AI
Last Week in AI
B
Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
WordPress大学
WordPress大学
博客园 - 司徒正美

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Building CLIAR — A simple drop-in Java class for parsing ...
onebitwonder · 2026-05-12 · via DEV Community

Welcome back to the third part of my rolling blog on building CLIAR, a lightweight, transparent command‑line argument parser for Java.
In Part 2, we added support for short options, long options, and key–value pairs.
This chapter focuses on two major improvements:

  • separating concerns inside the parser
  • introducing a formal option‑registration system so CLIAR can validate user input

This is the point where CLIAR becomes a real utility rather than a loose collection of parsing rules.
You can find current snapshots of the code on my GitHub page.

Defining rules for option names

Before we can validate user input, we need clear rules for declared option names — the names the developer defines in code.
To keep CLIAR predictable and easy to read:

  • Short option names must be single alphabetic characters.
  • Long option names must start with a letter and may contain letters, digits, or hyphens.
  • Values and positional arguments remain unrestricted. These rules apply only to declared options, not to the raw command‑line arguments.

With naming rules established, we can now look at how CLIAR processes the actual arguments supplied by the user.

Parsing command‑line arguments with dedicated methods

CLIAR does not parse option names directly. It parses arguments, then maps them to declared options.
To keep the logic clean, we delegate parsing to two methods:

  • parseShortOption(String arg, Cliar cliar)
  • parseLongOption(String arg, Cliar cliar)

The main loop becomes:

for (String arg : args) {
    if (arg.startsWith("--") && arg.length() > 2) {
        parseLongOption(arg.substring(2), cliar);
    } else if (arg.startsWith("-") && !arg.startsWith("--") && arg.length() > 1) {
        // NOTE: "-" becomes a positional argument
        parseShortOption(arg.substring(1), cliar);
    } else {
        cliar.positionalArguments.add(arg);
    }
}

Enter fullscreen mode Exit fullscreen mode

Parsing Short Options

Short options are simple: each character is a boolean flag.

for (char chr : arg.toCharArray()) {
    if (Character.isLetter(chr)) {
        // ...
    } else {
        throw new IllegalArgumentException(
            String.format("Invalid option '%c' in argument %s.", chr, arg)
        );
    }
}

Enter fullscreen mode Exit fullscreen mode

CLIAR treats grouped flags (-abc) as three separate options.

Parsing Long Options

Long options may include a value:

  • --verbose
  • --output=file.txt

We split the argument:

int index = arg.indexOf('=');
String key = arg;
String val = null;

if (index != -1) {
    key = arg.substring(0, index);
    val = arg.substring(index + 1);
}

Enter fullscreen mode Exit fullscreen mode

Then validate the key:

if (key.isEmpty()) {
    throw new IllegalArgumentException("Long option name is empty.");
}

if (!Character.isLetter(key.charAt(0))) {
    throw new IllegalArgumentException("Long option must start with a letter.");
}

for (int i = 1; i < key.length(); i++) {
    char chr = key.charAt(i);

    if (!(Character.isLetter(chr) || Character.isDigit(chr) || chr == '-')) {
        throw new IllegalArgumentException(
            String.format("Illegal character %c in option %s at position %d.", chr, key, i)
        );
    }
}

Enter fullscreen mode Exit fullscreen mode

Declaring Options with the Option Class

CLIAR can now parse arguments, but it still doesn’t know which options are allowed.
This information must be declared in code.

We encapsulate a declared option in an inner class:

public static class Option {

    public Option(String shortOption, String longOption, String description,
                  boolean required, boolean expectsValue) { }

    public String getShortOption() { }
    public boolean hasShortOption() { }

    public String getLongOption() { }
    public boolean hasLongOption() { }

    public String getDescription() { }

    public boolean isRequired() { }
    public boolean expectsValue() { }
}

Enter fullscreen mode Exit fullscreen mode

Constructor expectations:

  • At least one of shortOption or longOption must be provided.
  • Short options must be a single character.
  • Short options cannot expect values.
  • Long options may expect values.

Validation:

if ((shortOption == null || shortOption.isBlank()) &&
    (longOption == null || longOption.isBlank())) {
    throw new IllegalArgumentException("Option requires short or long name.");
}

if (shortOption != null && shortOption.length() != 1) {
    throw new IllegalArgumentException("Short option must be a single character.");
}

if (shortOption != null && expectsValue) {
    throw new IllegalArgumentException("Short options cannot expect values.");
}

Enter fullscreen mode Exit fullscreen mode

Registering Options

To track declared options and parsed arguments, CLIAR maintains four collections:

private final Map<String, Option> shortOptions = new HashMap<>();
private final Map<String, Option> longOptions  = new HashMap<>();
private final Map<Option, String> parsedOptions = new HashMap<>();
private final List<String> positionalArguments = new ArrayList<>();

Enter fullscreen mode Exit fullscreen mode

and the factory method now accepts a list of declared options:

Cliar cliar = new Cliar();

for (Option opt : options) {
    if (opt == null) {
        throw new IllegalArgumentException("Option must not be null.");
    }

    if (opt.hasShortOption()) {
        String key = opt.getShortOption();
        if (cliar.shortOptions.containsKey(key)) {
            throw new IllegalArgumentException("Duplicate short option: -" + key);
        }
        cliar.shortOptions.put(key, opt);
    }

    if (opt.hasLongOption()) {
        String key = opt.getLongOption();
        if (cliar.longOptions.containsKey(key)) {
            throw new IllegalArgumentException("Duplicate long option: --" + key);
        }
        cliar.longOptions.put(key, opt);
    }
}

Enter fullscreen mode Exit fullscreen mode

This ensures CLIAR knows exactly which options are valid.

Validating Parsed Options

This is where argument parsing and option validation meet: each parsed argument is checked against the set of declared options, and only valid, non‑duplicate options are accepted.

For short options, each character (or each character in a grouped short option) is looked up in the map of declared short options:

for (char chr : arg.toCharArray()) {
    if (Character.isLetter(chr)) {
        Option opt = cliar.shortOptions.get(String.valueOf(chr));

        if (opt == null) {
            throw new IllegalArgumentException(
                String.format("Invalid option '%c' in argument %s.", chr, arg)
            );
        }

        if (cliar.parsedOptions.containsKey(opt)) {
            throw new IllegalArgumentException("Duplicate short option: -" + opt.getShortOption());
        }

        cliar.parsedOptions.put(opt, null);
    } else {
        throw new IllegalArgumentException(
            String.format("Invalid option '%c' in argument %s.", chr, arg)
        );
    }
}

Enter fullscreen mode Exit fullscreen mode

And similarly for long options, where the parser validates the option name, checks whether a value is required, and ensures no duplicates are supplied:

Option opt = cliar.longOptions.get(key);

if (opt == null) {
    throw new IllegalArgumentException(String.format("Invalid option '%s'.", key));
}

if (opt.expectsValue() && (index == -1 || val.isBlank())) {
    throw new IllegalArgumentException(String.format("Option '%s' requires a value.", key));
}

if (cliar.parsedOptions.containsKey(opt)) {
    throw new IllegalArgumentException("Duplicate long option: --" + opt.getLongOption());
}

cliar.parsedOptions.put(opt, val);

Enter fullscreen mode Exit fullscreen mode

Many frustrations come from unspoken expectations

At this stage, CLIAR has verified that all supplied arguments correspond to declared options — but it has not yet checked whether all required options were actually provided. This final validation step happens after parsing and ensures that every option marked as required is present:

for (Option opt : options) {
    if (opt.isRequired() && !cliar.parsedOptions.containsKey(opt)) {
        String name = opt.hasLongOption()
            ? "--" + opt.getLongOption()
            : "-" + opt.getShortOption();

        throw new IllegalArgumentException(
            String.format("Missing required option %s.", name)
        );
    }
}

Enter fullscreen mode Exit fullscreen mode

with this, CLIAR contains:

  • all valid parsed options
  • all positional arguments
  • and has rejected any invalid or missing required options

Conclusion

In this chapter, we turned CLIAR into a structured, declarative command‑line parser.
We introduced:

- dedicated parsing methods
- strict naming rules
- the Option class
- option registration
- duplicate detection
- required‑option enforcement

Enter fullscreen mode Exit fullscreen mode

With these foundations in place, CLIAR is now robust and predictable.

In Part 4, we will expose the parsed options to the consumer, refine the API, and address a few remaining edge cases.


This article was written with the help of an LLM for structuring and wording. All technical content reflects my own understanding and decisions.