# Eo / Common / Util

The `eo.common.util` package provides shared string utility methods used across the customer contract domain (顧客契約). It lives in the `koptCommon` library, making these helpers available to any module that depends on the common codebase.

## Overview

This module is a small collection of static helper methods on [`JKKStringUtil`](Source/koptCommon/src/eo/common/util/JKKStringUtil.java). Its responsibilities centre on two common problems in Java applications:

1. **Safe null / empty-string handling** — preventing `NullPointerException` and null-pointer string concatenations by providing idiomatic guards and null-coalescing helpers.
2. **Byte-length-aware truncation** — cutting a string to a fixed byte count while respecting multi-byte character encoding (half-width kana, full-width alphanumeric, etc.), which matters when the system exchanges data with legacy Japanese business applications that use EUC-JP / Shift-JIS byte lengths.

There are no dependencies on external libraries and no relationships with other modules beyond the `java.util.ArrayList` import used by the `isNullEmpty` variant.

## Key Classes and Interfaces

### JKKStringUtil

[`JKKStringUtil`](Source/koptCommon/src/eo/common/util/JKKStringUtil.java:28) is a stateless utility class (all members are `static`) that provides string inspection and transformation helpers.

The class was created by 冨士通 (Fujitsu) on 2012-07-19 as part of the `eo` customer base system (顧客基幹システム) and later migrated to `lot2`.

#### Methods

##### `isNullBlank(String str) -> boolean`

**Purpose:** Determine whether a string is `null` or has zero length.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `str` | `String` | The string to inspect |

**Returns:** `true` if the string is `null` or `""`; `false` otherwise.

**Details:** Uses `"\"".equals(str)` instead of `str.equals("\"")` to avoid `NullPointerException` when `str` is `null`. This is the most frequently used guard in the codebase — check it first if you are unsure whether a string might be blank.

---

##### `isNullSpace(String str) -> boolean`

**Purpose:** Determine whether a string is `null`, empty, or consists solely of whitespace (half-width spaces).

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `str` | `String` | The string to inspect |

**Returns:** `true` if the string is `null`, `""`, or `trim()` yields an empty string; `false` otherwise.

**Details:** Calls `str.trim()` which strips all whitespace characters (`\s`, `\t`, `
`, etc.). Note that Japanese full-width spaces (U+3000) are **not** considered whitespace by `trim()` — a string of only full-width spaces will pass this check as *not* blank.

---

##### `nullToBlank(String str) -> String`

**Purpose:** Safely replace `null` with an empty string.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `str` | `String` | The string to transform |

**Returns:** The original string if non-null, or `""` if `str` was `null`.

**Details:** This is a lightweight null-coalescing helper — equivalent to `str != null ? str : ""`. Useful when building SQL parameters or message templates where a `null` reference would cause a concatenation error.

---

##### `nullToSpace(String str) -> String`

**Purpose:** Safely replace `null` with a single space character.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `str` | `String` | The string to transform |

**Returns:** The original string if non-null, or `" "` (a single half-width space) if `str` was `null`.

**Details:** Handy for display formatting where you want a placeholder space rather than nothing.

---

##### `nullBlankToSpace(String str) -> String`

**Purpose:** Replace `null` or an empty string with a single space.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `str` | `String` | The string to transform |

**Returns:** `" "` if the string is `null` or `""`; otherwise the original string.

**Details:** Chains `isNullBlank` internally — the first two checks (null, empty) are delegated to the dedicated `isNullBlank` method rather than duplicated inline.

---

##### `subStringByte(String str, int len) -> String`

**Purpose:** Truncate a string so its byte length does not exceed `len`.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `str` | `String` | The string to truncate |
| `len` | `int` | Maximum byte length |

**Returns:** A substring of `str` that fits within `len` bytes, or the original string if it already fits.

**Details:** This is the most sophisticated method in the class. It iterates through the string character-by-character and counts bytes according to the following rules:

| Character range | Byte count |
|-----------------|------------|
| `U+0000`–`U+007E` (ASCII) | 1 |
| `U+00A5` (Yen sign) | 1 |
| `U+203E` (Overline) | 1 |
| `U+FF61`–`U+FF9F` (Half-width Kana) | 1 |
| All others (e.g. Kanji, full-width alphanumeric, Hiragana, Katakana) | 2 |

When the running byte total would exceed `len`, it returns `str.substring(0, i)`, cutting at the character boundary *before* the overflow character.

**Why this matters:** Japanese legacy systems frequently encode text in EUC-JP or Shift-JIS, where single-byte and double-byte characters coexist. A fixed "number of characters" cut would produce garbled output at byte boundaries. This method ensures the result is a valid prefix in the source encoding.

---

##### `isNullEmpty(Object target) -> boolean`

**Purpose:** General-purpose null / emptiness check for multiple types.

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `target` | `Object` | The object to inspect (may be `null`, `String`, or `ArrayList`) |

**Returns:** `true` if the target is `null`, an empty `String`, or an empty `ArrayList`; `false` otherwise.

**Details:** Handles three cases:
1. `target == null` → `true`
2. `target instanceof String` && `target.equals("")` → `true`
3. `target instanceof ArrayList` && `((ArrayList) target).isEmpty()` → `true`

The method carries a `@SuppressWarnings("unchecked")` annotation because it performs a raw-type cast on `ArrayList` to call `isEmpty()`. In modern Java code, consider replacing this with `instanceof List<?>` and `List.isEmpty()` for type safety.

**Important:** This method does **not** handle generic `Collection` or `Set` types — only `ArrayList` and `String`. If you need to check a `Set`, you must call a different guard.

## How It Works

### Typical usage pattern

```java
String name = customer.getName();  // might be null

// Guard: skip processing if blank
if (JKKStringUtil.isNullBlank(name)) {
    return;
}

// Safely concatenate with null replacement
String greeting = "Hello, " + JKKStringUtil.nullToBlank(name);

// Or format with a space placeholder
String display = JKKStringUtil.nullToSpace(name) + "-san";
```

### Byte-length truncation flow

For `subStringByte(str, len)`:

1. Start with `dstlen = 0` and iterate `i` from `0` to `str.length() - 1`.
2. For each character, determine its byte cost (1 or 2 bytes based on Unicode range).
3. Add the byte cost to `dstlen`.
4. If `dstlen > len`, cut the string at `i` and return `str.substring(0, i)`.
5. If the loop completes without overflow, return the original string unchanged.

```mermaid
flowchart TD
    A["start subStringByte(str, len)"] --> B["dstlen = 0, i = 0"]
    B --> C{"i < str.length()?"}
    C -->|no| D["return str (fits)"]
    C -->|yes| E["count bytes for char c"]
    E --> F{"1 or 2 bytes?"}
    F -->|ASCII, Yen, Overline, Half Kana| G["dstlen += 1"]
    F -->|Kanji, full-width, etc.| H["dstlen += 2"]
    G --> I{"dstlen > len?"}
    H --> I
    I -->|yes| J["return str.substring(0, i)"]
    I -->|no| K["i++"]
    K --> C
```

## Data Model

This module contains no data models. All methods are pure functions — they take input parameters and return new values without mutating any state or side effects.

## Dependencies and Integration

| Dependency | Type | Notes |
|------------|------|-------|
| `java.util.ArrayList` | Standard library | Used by `isNullEmpty(Object)` via `instanceof` check |
| `eo.common` (parent) | Internal package | `JKKStringUtil` lives in the `eo.common` package tree under `koptCommon` |

There are no cross-module dependencies recorded for this module. The class is designed to be a self-contained utility — any module that imports `eo.common.util.JKKStringUtil` can use it without pulling in additional dependencies.

## Notes for Developers

### When to use which null check

| Method | Checks |
|--------|--------|
| `isNullBlank` | `null` or `""` (empty string) |
| `isNullSpace` | `null`, `""`, or whitespace-only (via `trim()`) |
| `isNullEmpty` | `null`, `""`, or empty `ArrayList` |

### Japanese character encoding gotchas

The `subStringByte` method treats half-width kana (`U+FF61`–`U+FF9F`) as **1-byte** characters. This is correct for Shift-JIS and EUC-JP, but verify the target encoding before using this method. If the data is actually UTF-8, every character is at least 1 byte, and multi-byte characters are typically 3–4 bytes, not 2.

### `isNullEmpty` and raw types

`isNullEmpty(Object target)` uses a raw `ArrayList` cast, which triggers the `@SuppressWarnings("unchecked")` annotation. This is acceptable for a legacy utility class, but in new code prefer:

```java
if (target instanceof List<?> list && list.isEmpty()) {
    return true;
}
```

This avoids the unchecked warning entirely and works with any `List` subtype, not just `ArrayList`.

### Future improvements

The class does not currently handle `String.trim()` for null safety (i.e., there is no `isBlank` that trims before checking). Consider adding a variant that checks `null`, `""`, and whitespace-only in a single call if `isNullSpace` is insufficient for your use case.
