# UI — Class Detailed Design [91 LOC]

| Field | Value |
|-------|-------|
| Fully Qualified Name | `com.github.blaxk3.ui.UI` |
| Layer | Controller / View |
| Module | `com.github.blaxk3.ui` |

## Class Overview

The `UI` class is the primary graphical user interface for the Currency Converter application. It extends `javax.swing.JFrame` to create a standalone window that displays a conversion form with an input text field, two currency dropdowns (source and target), a result label, and three action buttons (Convert, Swap, Clear). The class uses SwingWorker (`CurrencyCode` inner class) to fetch available currency codes asynchronously from the `CurrencyRateAPI`, and a `DocumentFilter` (`NumericFilter` inner class) to restrict user input to valid numeric values only. It is launched by `CurrencyConverter` via `SwingUtilities.invokeLater(UI::new)`.

---

## Method: button()

### 1. Role

Creates and configures three `JButton` components (Convert, Swap, Clear), attaches action listeners that implement the core business logic, and returns them as a `Component[]` array. This is the largest method in the class and contains the primary conversion workflow, the currency-pair swapping logic, and the form reset behavior.

### 2. Processing Pattern

```mermaid
flowchart TD
    A["button()"] --> B["Create JButton[] {Convert, Swap, Clear}"]
    B --> C["Set PreferredSize 200x35 on each button"]
    C --> D["Instantiate CurrencyRateAPI"]
    D --> E["Attach Convert ActionListener to button[0]"]
    E --> F{"TextField empty or \".\"?"}
    F -->|No| G["Parse amount from textField"]
    G --> H["Call rate.convert(source, target, amount)"]
    H --> I["Format result with DecimalFormat"]
    I --> J["setLabel(formatted result)"]
    F -->|Yes| K["showMessageDialog Error Alert"]
    D --> L["Attach Swap ActionListener to button[1]"]
    L --> M["Read comboBox1 selected item"]
    M --> N["Set comboBox1 = comboBox2 selection"]
    N --> O["Set comboBox2 = saved value"]
    D --> P["Attach Clear ActionListener to button[2]"]
    P --> Q["setTextField(\"\")"]
    Q --> R["setLabel(\"\")"]
    R --> S["return button array"]
```

### 3. Parameter Analysis

| Parameter | Type | Direction | Mandatory | Description |
|-----------|------|-----------|-----------|-------------|
| *(none)* | — | — | — | Factory method; returns configured buttons |

### 4. CRUD Operations / Called Services

| Service / API | Method | Direction | Description |
|---------------|--------|-----------|-------------|
| `CurrencyRateAPI` | `convert(String, String, BigDecimal)` | Read | Fetches exchange rate and computes converted amount |
| `DecimalFormat` | `format(Number)` | — | Formats the numeric result with thousand separators |
| `javax.swing.JOptionPane` | `showMessageDialog(...)` | Write | Displays error dialog when input is invalid |
| `UI` | `setTextField(String)` | Write | Clears the input text field |
| `UI` | `setLabel(String)` | Write | Updates the result label |
| `UI` | `getComboBox1()` | Read | Retrieves source currency dropdown |
| `UI` | `getComboBox2()` | Read | Retrieves target currency dropdown |
| `UI` | `getTextField()` | Read | Retrieves the amount text field |

### 5. Dependency Trace

| Caller | Context |
|--------|---------|
| `panel()` | Calls `button()` at L88 and iterates `button()[0]`, `button()[1]`, `button()[2]` to add buttons to the UI |

### 6. Per-Branch Detail Blocks

**Block 1 — Convert Button (button[0])**

- **Purpose**: Perform currency conversion when user clicks the Convert button.
- **Flow**:
  1. Reads the text from `textField` via `getTextField().getText()`.
  2. Validates: if the text is **not empty** AND **not equal to `"."`**, proceeds with conversion.
  3. Parses the amount as `Double.parseDouble(...)` and wraps in `BigDecimal.valueOf(...)`.
  4. Resolves selected currency strings from both combo boxes via `getSelectedItem()` with `Objects.requireNonNull()` guard.
  5. Calls `rate.convert(foreignCurrency1, foreignCurrency2, amount)` which performs an HTTP GET to the Exchangerate API.
  6. The raw result is a String; it is cast to `(Number)` and parsed by `Double.parseDouble()` before being fed to `DecimalFormat("#,###.###")`.
  7. The formatted string is set as the label text via `setLabel()`.
- **Exception Handling**: Catches `MalformedURLException` and `URISyntaxException` from `rate.convert()` and re-throws as `RuntimeException`.
- **Alternative Path**: If the text field is empty or equals `"."`, a `JOptionPane.showMessageDialog` error dialog is shown with message "Please enter the amount you need" and title "Error".

**Block 2 — Swap Button (button[1])**

- **Purpose**: Swap the selected currencies between the two combo boxes.
- **Flow**:
  1. Reads the currently selected item from `comboBox1` into a `boxItem` string.
  2. Sets `comboBox1`'s selection to `comboBox2`'s current selection.
  3. Sets `comboBox2`'s selection to the saved `boxItem`.
- **Null Safety**: Uses `Objects.requireNonNull()` on both `getSelectedItem()` calls; throws `NullPointerException` if either is null.

**Block 3 — Clear Button (button[2])**

- **Purpose**: Reset the form to its initial empty state.
- **Flow**:
  1. Calls `setTextField("")` to clear the input amount.
  2. Calls `setLabel("")` to clear the result label.

---

## Method: panel()

### 1. Role

Assembles the main content panel of the application window. It creates a vertically-boxed `JPanel` containing two sub-panels: the input panel (amount field + source currency) and the output panel (result label + target currency + action buttons). The panel is returned as a `Component` and added to the frame in the constructor.

### 2. Processing Pattern

```mermaid
flowchart TD
    A["panel()"] --> B["Create framePanel (BoxLayout Y_AXIS)"]
    B --> C["Create panelFramePanel1 (FlowLayout CENTER)"]
    C --> D["Set background DARK_GRAY"]
    D --> E["Add textField() result"]
    E --> F["Add comboBox1() result"]
    C --> G["Create panelFramePanel2 (FlowLayout CENTER)"]
    G --> H["Set background DARK_GRAY"]
    H --> I["Add label() result"]
    I --> J["Add comboBox2() result"]
    J --> K["Add button()[0] (Convert)"]
    K --> L["Add button()[1] (Swap)"]
    L --> M["Add button()[2] (Clear)"]
    G --> N["Add panelFramePanel1 to framePanel"]
    N --> O["Add panelFramePanel2 to framePanel"]
    O --> P["return framePanel"]
```

### 3. Parameter Analysis

| Parameter | Type | Direction | Mandatory | Description |
|-----------|------|-----------|-----------|-------------|
| *(none)* | — | — | — | Factory method; returns the assembled panel |

### 4. CRUD Operations / Called Services

| Service / API | Method | Direction | Description |
|---------------|--------|-----------|-------------|
| `JPanel` | `setLayoutManager(BoxLayout.Y_AXIS)` | Configure | Stacks child panels vertically |
| `JPanel` | `setBackground(Color.DARK_GRAY)` | Configure | Sets dark background for visual grouping |
| `textField()` | — | Called | Creates the numeric input text field |
| `comboBox1()` | — | Called | Creates and populates the source currency dropdown |
| `comboBox2()` | — | Called | Creates and populates the target currency dropdown |
| `label()` | — | Called | Creates the result display label |
| `button()` | — | Called | Returns the three action buttons array |

### 5. Dependency Trace

| Caller | Context |
|--------|---------|
| `UI()` (constructor) | Calls `add(panel())` at L60 to populate the frame |

### 6. Per-Branch Detail Blocks

**Block 1 — Input Panel (panelFramePanel1)**

- **Layout**: `FlowLayout(FlowLayout.CENTER)`, centered alignment.
- **Background**: `Color.DARK_GRAY`.
- **Contents**: `textField()` (amount input) and `comboBox1()` (source currency dropdown).
- **Purpose**: Groups all user input controls into a single visual section with a dark background for contrast.

**Block 2 — Output Panel (panelFramePanel2)**

- **Layout**: `FlowLayout(FlowLayout.CENTER)`, centered alignment.
- **Background**: `Color.DARK_GRAY`.
- **Contents**: `label()` (result), `comboBox2()` (target currency dropdown), and all three buttons from `button()[0]`, `button()[1]`, `button()[2]`.
- **Purpose**: Groups the conversion result, target currency selector, and action buttons into a single visual section matching the input panel's styling.

---

## Method: textField()

### 1. Role

Creates and configures the numeric input text field. The text field uses a large bold font (Arial, 24pt), a custom size (300x100), and is restricted to numeric input only via a `DocumentFilter`. The component is stored in the instance field `textField` for later access by action listeners.

### 2. Processing Pattern

```mermaid
flowchart TD
    A["textField()"] --> B["Create JTextField"]
    B --> C["Set Font Arial Bold 24pt"]
    C --> D["Set PreferredSize 300x100"]
    D --> E["Get PlainDocument from textField"]
    E --> F["Attach NumericFilter DocumentFilter"]
    F --> G["return textField"]
```

### 3. Parameter Analysis

| Parameter | Type | Direction | Mandatory | Description |
|-----------|------|-----------|-----------|-------------|
| *(none)* | — | — | — | Factory method; returns the configured text field |

### 4. CRUD Operations / Called Services

| Service / API | Method | Direction | Description |
|---------------|--------|-----------|-------------|
| `PlainDocument` | `setDocumentFilter(DocumentFilter)` | Configure | Attaches input validation to the text field |
| `JTextField` | `setFont(Font)` | Configure | Sets display font |
| `JTextField` | `setPreferredSize(Dimension)` | Configure | Sets component size |

### 5. Dependency Trace

| Caller | Context |
|--------|---------|
| `panel()` | Calls `textField()` at L76 and adds the result to `panelFramePanel1` |

### 6. Per-Branch Detail Blocks

**Block 1 — Text Field Configuration**

- Creates a new `JTextField` instance.
- Sets font to Arial, bold, 24pt for clear numeric readability.
- Sets preferred size to 300x100 pixels to make the amount field prominent.
- Casts the text field's document to `PlainDocument` and attaches a `NumericFilter` instance, which prevents non-numeric characters from being entered (only digits and a single decimal point are allowed).
- Stores the reference in the instance field `textField`.
- Returns the `JTextField` as a `Component`.

---

## Method: label()

### 1. Role

Creates and configures the result display label that shows the converted currency amount. The label is styled with a large bold font (Arial, 24pt), sized to match the text field (300x100), and set to opaque with a white background so it visually occupies a defined space even when empty.

### 2. Processing Pattern

```mermaid
flowchart TD
    A["label()"] --> B["Create JLabel"]
    B --> C["Set Font Arial Bold 24pt"]
    C --> D["Set PreferredSize 300x100"]
    D --> E["Set opaque(true)"]
    E --> F["Set background WHITE"]
    F --> G["return label"]
```

### 3. Parameter Analysis

| Parameter | Type | Direction | Mandatory | Description |
|-----------|------|-----------|-----------|-------------|
| *(none)* | — | — | — | Factory method; returns the configured label |

### 4. CRUD Operations / Called Services

| Service / API | Method | Direction | Description |
|---------------|--------|-----------|-------------|
| `JLabel` | `setFont(Font)` | Configure | Sets display font |
| `JLabel` | `setPreferredSize(Dimension)` | Configure | Sets component size |
| `JLabel` | `setOpaque(boolean)` | Configure | Enables background color rendering |
| `JLabel` | `setBackground(Color)` | Configure | Sets white background |

### 5. Dependency Trace

| Caller | Context |
|--------|---------|
| `panel()` | Calls `label()` at L83 and adds the result to `panelFramePanel2` |

### 6. Per-Branch Detail Blocks

**Block 1 — Label Configuration**

- Creates a new `JLabel` instance.
- Sets font to Arial, bold, 24pt (matching `textField()` for visual consistency).
- Sets preferred size to 300x100 pixels (matching `textField()` dimensions).
- Sets `opaque(true)` so the background color renders behind the label text.
- Sets background to `Color.WHITE` for a clean, readable contrast against the `DARK_GRAY` panel.
- Stores the reference in the instance field `label`.
- Returns the `JLabel` as a `Component`.

---

## Method: comboBox2()

### 1. Role

Creates and configures the target currency dropdown (`JComboBox<String>`), sets its preferred size, and asynchronously populates it with available currency codes from the `CurrencyRateAPI` via a `CurrencyCode` SwingWorker. This allows the user to select the destination currency for conversion.

### 2. Processing Pattern

```mermaid
flowchart TD
    A["comboBox2()"] --> B["Create JComboBox<String>"]
    B --> C["Set PreferredSize 300x30"]
    C --> D["Create CurrencyCode(comboBox2)"]
    D --> E["Execute SwingWorker"]
    E --> F["return comboBox2"]
```

### 3. Parameter Analysis

| Parameter | Type | Direction | Mandatory | Description |
|-----------|------|-----------|-----------|-------------|
| *(none)* | — | — | — | Factory method; returns the configured combo box |

### 4. CRUD Operations / Called Services

| Service / API | Method | Direction | Description |
|---------------|--------|-----------|-------------|
| `JComboBox` | `setPreferredSize(Dimension)` | Configure | Sets component size to 300x30 |
| `CurrencyCode` | `execute()` | Called | Triggers async fetch of currency codes |

### 5. Dependency Trace

| Caller | Context |
|--------|---------|
| `panel()` | Calls `comboBox2()` at L84 and adds the result to `panelFramePanel2` |

### 6. Per-Branch Detail Blocks

**Block 1 — Combo Box Initialization**

- Creates a new `JComboBox<String>` instance.
- Sets preferred size to 300x30 pixels for consistent sizing with `comboBox1`.
- Instantiates a `CurrencyCode` SwingWorker, passing the combo box as the target for population.
- Calls `.execute()` to start the asynchronous HTTP request to `CurrencyRateAPI.getCurrencyCode()`.
- The SwingWorker's `done()` method (executed on the EDT after fetch completes) retrieves the currency code strings, sorts them alphabetically, and adds each item to the combo box.
- Stores the reference in the instance field `comboBox2`.
- Returns the `JComboBox<String>` as a `Component`.

---

## Method: comboBox1()

### 1. Role

Creates and configures the source currency dropdown (`JComboBox<String>`), sets its preferred size, and asynchronously populates it with available currency codes from the `CurrencyRateAPI` via a `CurrencyCode` SwingWorker. This allows the user to select the origin currency for conversion.

### 2. Processing Pattern

```mermaid
flowchart TD
    A["comboBox1()"] --> B["Create JComboBox<String>"]
    B --> C["Set PreferredSize 300x30"]
    C --> D["Create CurrencyCode(comboBox1)"]
    D --> E["Execute SwingWorker"]
    E --> F["return comboBox1"]
```

### 3. Parameter Analysis

| Parameter | Type | Direction | Mandatory | Description |
|-----------|------|-----------|-----------|-------------|
| *(none)* | — | — | — | Factory method; returns the configured combo box |

### 4. CRUD Operations / Called Services

| Service / API | Method | Direction | Description |
|---------------|--------|-----------|-------------|
| `JComboBox` | `setPreferredSize(Dimension)` | Configure | Sets component size to 300x30 |
| `CurrencyCode` | `execute()` | Called | Triggers async fetch of currency codes |

### 5. Dependency Trace

| Caller | Context |
|--------|---------|
| `panel()` | Calls `comboBox1()` at L77 and adds the result to `panelFramePanel1` |

### 6. Per-Branch Detail Blocks

**Block 1 — Combo Box Initialization**

- Creates a new `JComboBox<String>` instance.
- Sets preferred size to 300x30 pixels for consistent sizing with `comboBox2`.
- Instantiates a `CurrencyCode` SwingWorker, passing the combo box as the target for population.
- Calls `.execute()` to start the asynchronous HTTP request to `CurrencyRateAPI.getCurrencyCode()`.
- The SwingWorker's `done()` method (executed on the EDT after fetch completes) retrieves the currency code strings, sorts them alphabetically, and adds each item to the combo box.
- Stores the reference in the instance field `comboBox1`.
- Returns the `JComboBox<String>` as a `Component`.

---

## Nested Class: CurrencyCode

### Role

An inner `SwingWorker<String[], Void>` that asynchronously fetches currency codes from the `CurrencyRateAPI` and populates a `JComboBox`. It enables non-blocking UI initialization by running the HTTP request on a background thread.

### Processing Pattern

```mermaid
flowchart TD
    A["CurrencyCode<br>extends SwingWorker"] --> B["Constructor<br>stores JComboBox reference"]
    B --> C["doInBackground()"]
    C --> D["new CurrencyRateAPI()"]
    D --> E["getCurrencyCode()"]
    E --> F["HTTP GET /latest/USD"]
    F --> G["Parse JSON conversion_rates keys"]
    G --> H["return String[]"]
    C --> I["done()"]
    H --> I
    I --> J["get() result"]
    J --> K["null check"]
    K --> L["Arrays.stream().sorted()"]
    L --> M["forEach<br>comboBox.addItem"]
    I --> N["catch InterruptedException<br>| ExecutionException"]
    N --> O["logger.error(logged message)"]
```

### Dependency Trace

| Caller | Context |
|--------|---------|
| `comboBox1()` | `new CurrencyCode(comboBox1).execute()` at L99 |
| `comboBox2()` | `new CurrencyCode(comboBox2).execute()` at L107 |

### Per-Branch Detail Blocks

**Block 1 — doInBackground()**

- Instantiates `CurrencyRateAPI` and calls `getCurrencyCode()`.
- This performs an HTTP GET to the Exchangerate API endpoint `/latest/USD` and parses the `conversion_rates` JSON object keys into a `String[]` of currency code abbreviations.

**Block 2 — done()**

- Calls `get()` to retrieve the result array from the background thread.
- If the result is not null, streams the array, sorts alphabetically, and adds each currency code as an item in the combo box.
- On `InterruptedException` or `ExecutionException`, logs the error via SLF4J logger with the message "Error occurred while fetching currency codes".

---

## Nested Class: NumericFilter

### Role

An inner `DocumentFilter` that restricts the text field input to valid numeric values only. It intercepts insert and replace operations, validates the resulting text, and only allows it through if the validation passes.

### Processing Pattern

```mermaid
flowchart TD
    A["NumericFilter<br>extends DocumentFilter"] --> B["Input Method"]
    B --> C["insertString()"]
    B --> D["replace()"]
    C --> E["Build candidate<br>StringBuilder"]
    D --> E
    E --> F{"isValid()"}
    F -->|true| G["super.insertString<br>| super.replace"]
    F -->|false| H["Reject input"]
    G --> I["Document updated"]
    H --> I
```

### Per-Branch Detail Blocks

**Block 1 — insertString()**

- Receives the string to be inserted, the offset position, and attribute set.
- Reads the current document text into a `StringBuilder`.
- Inserts the new string at the given offset to build the candidate text.
- Calls `isValid(candidate)` to validate.
- If valid, calls `super.insertString()` to perform the actual insertion; otherwise, the input is silently rejected.

**Block 2 — replace()**

- Receives the replacement string, offset, and the range (length) to be replaced.
- Reads the current document text into a `StringBuilder`.
- Replaces the specified range with the new text to build the candidate.
- Calls `isValid(candidate)` to validate.
- If valid, calls `super.replace()` to perform the actual replacement; otherwise, the input is silently rejected.

**Block 3 — isValid()**

- **Validation Rules**:
  1. **Empty text**: returns `true` (allows the field to be cleared).
  2. **Character scan**: iterates each character in the text.
  3. **Decimal point**: allows at most one `.` — tracks count via `decimalCount`; if a second `.` is found, returns `false`.
  4. **Non-digit**: any character that is not a digit (and not `.`) causes `false`.
  5. **All valid**: returns `true`.
- This allows inputs like `123`, `12.34`, `0.5`, `1000` and rejects inputs like `12.34.56`, `abc`, `12a3`.

---

## Class-Level Mermaid Diagram

```mermaid
flowchart TD
    A["Caller<br>CurrencyConverter"] --> B["UI<br>Constructor"]
    B --> C["panel()"]
    C --> D["textField()"]
    C --> E["comboBox1()"]
    C --> F["comboBox2()"]
    C --> G["label()"]
    C --> H["button()"]
    H --> I["Convert Listener"]
    H --> J["Swap Listener"]
    H --> K["Clear Listener"]
    I --> L["CurrencyRateAPI<br>convert()"]
    E --> M["CurrencyCode<br>SwingWorker"]
    F --> M
    D --> N["NumericFilter<br>DocumentFilter"]
```

---

## Class-Level Mermaid Diagram

```mermaid
classDiagram
    class UI {
        +Logger logger
        -JLabel label
        -JTextField textField
        -JComboBox~String~ comboBox1
        -JComboBox~String~ comboBox2
        +getComboBox1() JComboBox~String~
        +getComboBox2() JComboBox~String~
        +getTextField() JTextField
        +setTextField(String) void
        +setLabel(String) void
        +UI()
        -panel() Component
        -comboBox1() Component
        -comboBox2() Component
        -button() Component[]
        -textField() Component
        -label() Component
    }

    class NumericFilter {
        <<static>>
        <<inner>>
        +insertString(FilterBypass, int, String, AttributeSet)
        +replace(FilterBypass, int, int, String, AttributeSet)
        -isValid(String) boolean
    }

    class CurrencyCode {
        <<static>>
        <<inner>>
        -JComboBox~String~ comboBox
        +CurrencyCode(JComboBox~String~)
        +doInBackground() String[]
        +done() void
    }

    UI o-- NumericFilter : uses
    UI o-- CurrencyCode : creates
    NumericFilter <|-- DocumentFilter
    CurrencyCode <|-- SwingWorker
```

---

## Key Design Observations

1. **Monolithic button listener**: The `button()` method embeds all three action listener implementations inline. The Convert listener's single line of logic (L125-127) performs parsing, API invocation, formatting, and UI update — this is a high-cyclomatic-complexity hot path.

2. **No loading indicator**: The `CurrencyCode` SwingWorker populates the combo boxes asynchronously, but the user sees an empty dropdown until the HTTP response arrives. No progress indicator is shown.

3. **Format inconsistency**: The Convert listener parses the API result as `Double.parseDouble(rate.convert(...))` after `DecimalFormat.format((Number) ...)` — the intermediate cast-and-parse is unnecessary since `rate.convert()` already returns a String.

4. **No input validation for currency selection**: The Convert button does not check whether both combo boxes have a non-null selection before calling `rate.convert()`. The `Objects.requireNonNull()` calls will throw if either selection is null, providing a runtime error instead of a user-friendly message.

5. **DecimalFormat precision**: The format pattern `"#,###.###"` allows up to 3 decimal places without trailing zeros. This may not match all currency precision requirements (e.g., some currencies use 2 decimal places, others use 0).
