# (DD03) NumericFilter — Class Detailed Design [41 LOC]

| Field | Value |
|-------|-------|
| Fully Qualified Name | `com.github.blaxk3.ui.NumericFilter` |
| Layer | Controller (Swing UI) |
| Module | `com.github.blaxk3.ui` |

## Class Overview

`NumericFilter` is an inner class of `UI` that extends `javax.swing.text.DocumentFilter`. It serves as an input validator for a `JTextField`, restricting user input to numeric characters only (digits 0-9 and a single decimal point). This filter intercepts all text insertion and replacement operations on the associated document, validating the resulting text before allowing the change to proceed. It ensures the text field only ever contains valid numeric representations, preventing non-numeric keystrokes, paste operations, or programmatic modifications that would produce invalid content.

---

## Method: isValid(String text)

### 1. Role
Validates whether a given string represents a valid numeric value for the input field. Accepts empty strings (to allow the user to clear the field), strings containing only digits, and strings containing exactly one decimal point among digits.

### 2. Processing Pattern

```mermaid
flowchart TD
    Start(["Start: isValid(text)"]) --> Empty{Is text empty?}
    Empty -->|true| True["Return true"]
    Empty -->|false| Init["Set decimalCount = 0"]
    Init --> Loop["Iterate chars from i=0 to length"]
    Loop --> CharCheck{Is char '.'?}
    CharCheck -->|yes| DotCheck{decimalCount == 1?}
    DotCheck -->|yes| Skip["Skip - second dot found"]
    DotCheck -->|no| Inc["Increment decimalCount"]
    Skip --> Next["i++"]
    Inc --> Next
    CharCheck -->|no| DigitCheck{Is digit?}
    DigitCheck -->|no| False["Return false"]
    DigitCheck -->|yes| Next
    Next --> More{More chars?}
    More -->|yes| Loop
    More -->|no| True
```

### 3. Parameter Analysis

| Parameter | Type | Description |
|-----------|------|-------------|
| `text` | `String` | The full text to validate (current document content after proposed change) |

**Return**: `boolean` — `true` if the text is a valid numeric string or empty; `false` otherwise.

### 4. CRUD Operations / Called Services

| Operation | Target | Details |
|-----------|--------|---------|
| `isEmpty()` | `String` | Early exit optimization for empty input |
| `length()` | `String` | Loop bound for character iteration |
| `charAt(int)` | `String` | Access each character sequentially |
| `Character.isDigit(char)` | `java.lang.Character` | Verify characters are numeric digits |

### 5. Dependency Trace

| Caller | File | Context |
|--------|------|---------|
| `insertString()` | `UI.java` | Validates document text after insertion |
| `replace()` | `UI.java` | Validates document text after replacement |

### 6. Per-Branch Detail Blocks

**Branch 1: Empty string (L197-199)**
- If `text.isEmpty()` returns `true`, the method immediately returns `true`.
- Rationale: allows the user to clear the field completely; an empty field is a valid state that lets subsequent input proceed.

**Branch 2: Character iteration (L201-209)**
- Iterates every character in the string with a simple index loop.
- Maintains `decimalCount` as a `byte` to track decimal point occurrences.
- **Dot rule** (L203-205): When a `'.'` is encountered, it is accepted only if `decimalCount != 1`. If accepted, `decimalCount` is incremented. This ensures at most one decimal point exists in the string.
- **Non-digit rule** (L206-207): Any character that is not a digit and not already handled by the dot branch immediately triggers `return false`, rejecting the entire text.
- This means letters, symbols, negative signs, whitespace, and a second decimal point are all rejected.

**Branch 3: Acceptance (L210)**
- If the loop completes without finding any invalid character, returns `true`.
- The text is a valid non-negative numeric string (with optional single decimal point).

---

## Method: insertString(FilterBypass fb, int offset, String string, AttributeSet attr)

### 1. Role
Intercepts `insertString` operations on the associated `JTextField` document. It constructs the resulting document text after the insertion, validates it via `isValid()`, and only applies the change if the result is valid. This prevents any non-numeric characters from being inserted at any position in the text field.

### 2. Processing Pattern

```mermaid
flowchart TD
    Start(["Start: insertString called"]) --> Build["Build full document text"]
    Build --> InsertStr["Insert new string at offset"]
    InsertStr --> Validate["isValid text call"]
    Validate --> Check{Valid?}
    Check -->|true| Super["super.insertString applied"]
    Check -->|false| Reject["Silently reject change"]
    Super --> EndNode["End"]
    Reject --> EndNode
```

### 3. Parameter Analysis

| Parameter | Type | Description |
|-----------|------|-------------|
| `fb` | `FilterBypass` | Bypasses filter to access the underlying `Document` directly |
| `offset` | `int` | Position in the document where text will be inserted |
| `string` | `String` | The text being inserted (may contain non-numeric characters) |
| `attr` | `AttributeSet` | Attribute set for the insertion (style metadata, unused in validation) |

**Throws**: `BadLocationException` if offset is invalid.

### 4. CRUD Operations / Called Services

| Operation | Target | Details |
|-----------|--------|---------|
| `getDocument()` | `FilterBypass` | Access the underlying `Document` |
| `getText(int, int)` | `Document` | Read current document content (from 0 to full length) |
| `StringBuilder.append(String)` | `java.lang` | Append existing document text |
| `StringBuilder.insert(int, String)` | `java.lang` | Insert new text at offset |
| `isValid(String)` | `NumericFilter` (inner) | Validate the constructed text |
| `super.insertString(...)` | `DocumentFilter` | Apply the insertion to the document |

### 5. Dependency Trace

| Caller | File | Context |
|--------|------|---------|
| `textField()` (via `setDocumentFilter`) | `UI.java` | Registered on `JTextField` in the UI builder method |

### 6. Per-Branch Detail Blocks

**Branch 1: Build full document text (L172-175)**
- Retrieves the underlying `Document` from the `FilterBypass`.
- Constructs a `StringBuilder` with the current document content by reading from offset 0 to the document's full length.
- Inserts the incoming `string` at the specified `offset` position.
- This simulates what the document would look like after the insertion without actually modifying it yet.

**Branch 2: Validate and conditionally apply (L177-179)**
- Calls `isValid()` with the simulated full document text.
- **If valid**: Delegates to `super.insertString(fb, offset, string, attr)`, which applies the insertion through the `FilterBypass` directly to the document, bypassing the filter recursion.
- **If invalid**: The method returns without calling `super`, silently discarding the insertion. The user sees no change.

---

## Method: replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)

### 1. Role
Intercepts `replace` operations on the associated `JTextField` document, which cover both text replacement and deletion scenarios. Similar to `insertString`, it constructs the resulting document text after the proposed replacement, validates it via `isValid()`, and conditionally applies the change. This ensures that paste operations, backspace, delete, and programmatic text replacement all produce valid numeric content.

### 2. Processing Pattern

```mermaid
flowchart TD
    Start(["Start: replace called"]) --> Build["Build full document text"]
    Build --> ReplaceTxt["Replace range at offset with text"]
    ReplaceTxt --> Validate["isValid text call"]
    Validate --> Check{Valid?}
    Check -->|true| Super["super.replace applied"]
    Check -->|false| Reject["Silently reject change"]
    Super --> EndNode["End"]
    Reject --> EndNode
```

### 3. Parameter Analysis

| Parameter | Type | Description |
|-----------|------|-------------|
| `fb` | `FilterBypass` | Bypasses filter to access the underlying `Document` directly |
| `offset` | `int` | Start position in the document where replacement begins |
| `length` | `int` | Number of characters to delete (0 for insertion-only replacement) |
| `text` | `String` | The replacement text (may contain non-numeric characters) |
| `attrs` | `AttributeSet` | Attribute set for the replacement (style metadata, unused in validation) |

**Throws**: `BadLocationException` if offset or length is invalid.

### 4. CRUD Operations / Called Services

| Operation | Target | Details |
|-----------|--------|---------|
| `getDocument()` | `FilterBypass` | Access the underlying `Document` |
| `getText(int, int)` | `Document` | Read current document content (from 0 to full length) |
| `StringBuilder.append(String)` | `java.lang` | Append existing document text |
| `StringBuilder.replace(int, int, String)` | `java.lang` | Replace a range with new text |
| `isValid(String)` | `NumericFilter` (inner) | Validate the constructed text |
| `super.replace(...)` | `DocumentFilter` | Apply the replacement to the document |

### 5. Dependency Trace

| Caller | File | Context |
|--------|------|---------|
| `textField()` (via `setDocumentFilter`) | `UI.java` | Registered on `JTextField` in the UI builder method |

### 6. Per-Branch Detail Blocks

**Branch 1: Build full document text (L185-187)**
- Retrieves the underlying `Document` from the `FilterBypass`.
- Constructs a `StringBuilder` with the current document content.
- Replaces the character range `[offset, offset + length)` with the new `text`.
- When `length` is 0, this behaves like an insertion at `offset`.
- When `text` is empty, this behaves like a deletion of the range.

**Branch 2: Validate and conditionally apply (L189-191)**
- Calls `isValid()` with the simulated full document text.
- **If valid**: Delegates to `super.replace(fb, offset, length, text, attrs)`, which applies the replacement through the bypass.
- **If invalid**: Returns without calling `super`, silently rejecting the change. This covers scenarios like pasting non-numeric text, backspacing to produce invalid state, or replacing valid text with invalid content.

---

## Design Notes

### Key Design Patterns
- **DocumentFilter pattern**: Extends `javax.swing.text.DocumentFilter` to intercept all mutations on a `JTextField` document. This is the standard Swing approach for input validation on text components.
- **Simulate-then-validate strategy**: Rather than validating the incoming chunk in isolation, the method constructs the hypothetical full document state and validates that. This catches contextual errors (e.g., inserting a second `.` when one already exists) that chunk-level validation would miss.
- **Bypass-based mutation**: Uses `FilterBypass` to re-apply validated changes without triggering recursive filter invocations, avoiding infinite loops.

### Validation Rules
| Rule | Behavior |
|------|----------|
| Empty string | Always accepted (allows clearing the field) |
| Digits (0-9) | Always accepted |
| Single `.` | Accepted (decimal point allowed) |
| Multiple `.` | Rejected |
| Negative sign `-` | Rejected (no support for negative numbers) |
| Letters, symbols, whitespace | Rejected |

### Constructor
No explicit constructor is defined. The default no-arg constructor is inherited from `DocumentFilter`, used when instantiated as `new NumericFilter()` in `textField()`.

### Architecture
`NumericFilter` is a static nested class (inner class) of `UI`, tightly coupled to the UI module. It is instantiated once in the `textField()` method and attached via `setDocumentFilter()`, making it the sole gatekeeper for input to the associated `JTextField`.
