Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,145 @@ Tests are organized in `src/test/java/com/senzing/sdk/core/auto/`:
- `checkstyle`: Enables checkstyle validation with suppressions
- `spotbugs`: Enables static analysis including FindSecBugs
- `java-17` / `java-18+`: Handle Javadoc tag compatibility across Java versions

## Java Coding Standards

**IMPORTANT — apply when generating or modifying Java code:** All Java code
(new and existing) in this repository must conform to the formatting rules in
`.java-coding-standards/docs/java-coding-standards.md`. Apply these rules
**from the start** — do not write code first and reformat afterward. When in
doubt about a specific case (parameter alignment, method continuation,
ternary tier, javadoc reflow), read the full standards document or search
the FAQ:
`mcp__sz-sdk-java-auto-faq__search_faqs(query="java formatting")`.

### Quick reference

- **80-character line limit** (enforced by checkstyle via `-Pcheckstyle`).
Lines beyond 80 chars must be wrapped.
- **Allman braces** for class/interface/enum/method/constructor definitions
(opening `{` on its own line, left-aligned with the declaration).
- **Same-line braces** for control flow: `if`/`else`/`for`/`while`/`do`/
`try`/`catch`/`finally`/`switch`/`synchronized`, lambdas, array
initializers, static init blocks.
- **Multi-line conditions**: when an `if`/`catch`/etc. condition wraps to
multiple lines, the opening brace goes on its own line (Allman) to
visually separate condition from body.
- **Method parameters** (priority order): single line if it fits; otherwise
paren-aligned with types/names aligned in columns; otherwise next-line
double-indented.
- **`throws` clauses** go on their own line, single-indented.
- **Continuation indentation**: +4 per wrap level (cumulating to
8 spaces of displacement for the typical double-wrap; see the
full standards doc for the per-level rule).
- **Operators on continuation lines**: break **before** `+`, `&&`, `||`, `?`,
`:`, `.` (the operator starts the continuation line).
- **Short-circuit `if`**: `if (cond) statement;` on one line is preferred
(Tier 1) when it fits; otherwise add braces.
- **Javadoc**: reflow prose and `@param`/`@return`/`@throws` to fill lines
near 80 chars; do not leave 1-3 orphan words on a line.
- **CSOFF/CSON**: only for deliberately aligned multi-line output
(column-formatted diagnostics, ASCII art, SQL DDL with aligned clauses)
— never a general escape hatch.

### Verification

Run checkstyle: `mvn -Pcheckstyle validate` (must report `BUILD SUCCESS`
before opening a PR).

### Bulk formatting

`.java-coding-standards/tooling/scripts/format_file.py` is the
single end-user entry point — a thin wrapper that invokes the
tree-sitter-based AST formatter at `format_java.py` in-process.
Accepts one or more file or directory targets (directories are
recursively scanned for `.java` files):

```bash
# Format a single file in place.
python3 .java-coding-standards/tooling/scripts/format_file.py path/to/File.java

# Format every .java file under src/main/java/ in place.
python3 .java-coding-standards/tooling/scripts/format_file.py src/main/java
```

The same script is wired to the VSCode `Format Java file to
Senzing standards` task, the `emeraldwalk.runonsave` extension
(format-on-save), and the Claude Code `PostToolUse` hook — so
every save runs the canonical formatter. Same input → same
output, regardless of caller. The `building/java-formatting-
standards` FAQ summarizes day-to-day usage.

## FAQ MCP Server

This project ships a local FAQ MCP server registered in `.mcp.json` under
the name `sz-sdk-java-auto-faq`. It serves both:

- **Shared FAQs** from the standards-repo submodule
(`.java-coding-standards/docs/faqs/`) — coding standards, javadoc reflow
rules, system-stubs/ResourceLock test pattern, FAQ-authoring conventions.
- **Project-local FAQs** from `.claude/faqs/<category>/<topic>.md` —
project-specific architecture, conventions, build/release notes,
troubleshooting.

The server merges both into one BM25-ranked search index. Tool surface:

- `mcp__sz-sdk-java-auto-faq__get_faq_categories`
- `mcp__sz-sdk-java-auto-faq__search_faqs(query=...)`
- `mcp__sz-sdk-java-auto-faq__get_faq(title=...)`

**Use it BEFORE making design assumptions or troubleshooting.** Specifically:

- Before changing build, test, or release configuration (`pom.xml`,
surefire, checkstyle, jacoco, spotbugs, release process), call
`search_faqs` for relevant topics.
- Before modifying public APIs, search for any documented invariants or
rationale.
- When a build, test, or dependency issue surfaces, search the
`troubleshooting` category first.
- When unsure what is documented, call `get_faq_categories` to enumerate
what's available.

**After resolving a non-obvious issue**, ask the user whether to capture
the solution as a new FAQ. Project-specific lessons go in
`.claude/faqs/<category>/<topic>.md`. Lessons about the standards
themselves go via PR to the standards repo. Restart the session so the
server re-indexes.

FAQs are pulled on demand, so detail is cheap there. Keep CLAUDE.md lean
and push operational/troubleshooting depth into FAQ files.

## Testing Configuration

Tests use JUnit Jupiter with parallel execution enabled (configured in
`pom.xml` surefire plugin):

- Classes run concurrently.
- Methods within a class run in same thread (default).
- Dynamic parallelism factor.

### System Stubs, ExecutionMode, and ResourceLock

Tests that **stub environment variables** or **capture stdout / stderr**
must follow the project's `system-stubs` + `@Execution(SAME_THREAD)` +
`@ResourceLock` pattern to avoid build-log noise and inter-class capture
races. Before writing such a test, search the FAQ:
`mcp__sz-sdk-java-auto-faq__search_faqs(query="system stubs")`.

Headline rules:

- Use `system-stubs-jupiter` **programmatically at the method level**
(`new EnvironmentVariables(...).execute(...)`, `new SystemOut().execute(...)`,
`new SystemErr().execute(...)`) — never the `@ExtendWith` annotation form.
- Tag the test (or the class) with `@Execution(ExecutionMode.SAME_THREAD)`
— `System.setOut` / `setErr` are JVM-wide, so concurrent redirects race.
- Add `@ResourceLock(Resources.SYSTEM_OUT)` and/or
`@ResourceLock(Resources.SYSTEM_ERR)` for cross-class mutual exclusion.
When both are present, **always declare `SYSTEM_OUT` first, `SYSTEM_ERR`
second** to avoid deadlock.
- If the production code starts a background thread in its constructor,
place the `new ...()` call **inside** the `stub.execute(...)` lambda so
the redirect is active before the thread starts.

Full pattern, examples, and the JVM-warning suppression details are in the
shared `testing/system-stubs-and-output-capture` FAQ.
16 changes: 16 additions & 0 deletions .claude/commands/init-java.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
description: Adopt or refresh the Senzing Java coding standards in this project.
---

Run the adoption playbook from the standards-repo submodule:

@.java-coding-standards/adoption/adopt-standards-prompt.md

This wires up checkstyle, the FAQ MCP server, the formatter, the bulk-format
scripts, the VSCode integration, the Claude Code hooks, and the CLAUDE.md
sections — all sourced from the submodule pin. Re-run any time to refresh
after a submodule bump or to onboard a fresh Java project.

The command is deliberately distinct from Claude Code's built-in `/init`,
which continues to work unchanged for projects that just need a CLAUDE.md
without the standards machinery.
56 changes: 56 additions & 0 deletions .claude/faqs/architecture/sz-sdk-java-auto-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# sz-sdk-java-auto architecture overview

`sz-sdk-java-auto` (Maven artifact `com.senzing:sz-sdk-auto`) extends the
Senzing Core SDK for Java (`com.senzing:sz-sdk`) with automatic handling
features for long-running, multi-threaded, server-side applications.
`SzAutoCoreEnvironment` is a drop-in replacement for `SzCoreEnvironment`.

## The four enhancements

1. **Automatic basic retry** — any Senzing Core SDK method that fails with an
`SzRetryableException` is retried up to a configurable maximum
(`maxBasicRetries`, default `DEFAULT_MAX_BASIC_RETRIES = 2`) with an
increasing delay between attempts.
2. **Automatic configuration refresh** — keeps the active configuration ID in
sync with the current default configuration ID. See the
`conventions/configuration-refresh-modes` FAQ for the DISABLED / REACTIVE /
PROACTIVE modes.
3. **Isolated thread pool** — confines all SDK operations to a fixed worker
pool so Senzing's thread-local native resources are reused across
operations, while preventing the excessive memory growth that comes from
running Senzing work on too many threads. Controlled by `concurrency`
(`null` = disabled / run on calling thread, `0` =
`Runtime.availableProcessors()`, `N` = fixed count).
4. **`ExecutorService`-like interface** — `submitTask(Callable)` /
`submitTask(Runnable)` run user code on the same pool as SDK operations to
avoid unnecessary context switching when several SDK calls happen in one
task.

## Key classes

- **`SzAutoCoreEnvironment`** (`src/main/java/com/senzing/sdk/core/auto/`)
— the main class; extends `SzCoreEnvironment`. Built via the fluent nested
`Builder` / `AbstractBuilder<E, B>` (generic for extensibility). Only **one
active** environment may exist per JVM — call `destroy()` before creating
another.
- **`Reinitializer`** — background thread used only in PROACTIVE mode;
periodically reinitializes when `activeConfigId != defaultConfigId` and shuts
down gracefully on `destroy()`.
- **`RetryHandler`** (inner class) — a dynamic-proxy `InvocationHandler` that
wraps returned SDK interfaces, detects methods annotated with
`@SzConfigRetryable`, sets the thread-local `CONFIG_RETRY_FLAG`, and
recursively proxies SDK-interface return values.
- **`SzAutoEnvironment`** — interface so `SzAutoCoreEnvironment` functionality
can itself be proxied.

## Design patterns

- **Proxy pattern** for intercepting SDK method calls.
- **Thread-local flags** (`CONFIG_RETRY_FLAG`, `RETRIED_FLAG`,
`ENSURING_CONFIG`) to track retry/refresh state across the call stack and
prevent re-entrant recursion during config refresh.
- **`ReentrantReadWriteLock`** to coordinate normal operations against
refresh/destroy.
- **Builder pattern** with type-safe generics for extensibility.

> Garage project: experimental, not production-supported.
50 changes: 50 additions & 0 deletions .claude/faqs/building/sz-sdk-java-auto-build-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# sz-sdk-java-auto build & test commands

Maven project (Java 17). Tests require a working Senzing installation with
native libraries.

## Common commands

```bash
mvn clean install # full clean build
mvn test # run all tests (needs native libs)
mvn test -Dtest=ClassName # single test class
mvn test -Dtest=ClassName#method # single test method
mvn -Pcheckstyle validate # checkstyle (must pass before a PR)
```

## Required environment for tests

Tests load the Senzing native libraries through a generated wrapper script
(`target/java-wrapper/bin/java-wrapper.bat`, produced by
`GenerateTestJVMScript` during the `process-test-classes` phase and used as the
surefire JVM). Two environment variables must be set:

- `SENZING_PATH` — path to the Senzing installation.
- `SENZING_DEV_LIBRARY_PATH` — path to the development libraries.

The `sz-sdk-java` git submodule must be initialized
(`git submodule update --init --recursive`) — tests reuse test utilities from
it, and they are added as test sources via `build-helper-maven-plugin`.

## Profiles

- `jacoco` — coverage report (`mvn -Pjacoco test`, output in
`target/site/jacoco/`).
- `checkstyle` — checkstyle against the shared
`.java-coding-standards/checkstyle/senzing-checkstyle.xml`.
- `spotbugs` — SpotBugs + FindSecBugs static analysis
(`mvn -Pspotbugs validate`).
- `release` — enables GPG signing for Maven Central deployment
(`mvn clean install -Prelease`).

## Coding standards

Java formatting is governed by the `.java-coding-standards` submodule. Reformat
with:

```bash
python3 .java-coding-standards/tooling/scripts/format_file.py src/main/java src/test/java
```

See the `building/java-formatting-standards` shared FAQ for day-to-day usage.
38 changes: 38 additions & 0 deletions .claude/faqs/conventions/configuration-refresh-modes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Configuration refresh modes (DISABLED / REACTIVE / PROACTIVE)

Automatic configuration refresh keeps the
{@code activeConfigId} in sync with the current {@code defaultConfigId}. The
mode is selected by the `configRefreshPeriod` (`Duration`) passed to the
builder:

| Mode | `configRefreshPeriod` | Behavior |
|------|-----------------------|----------|
| **DISABLED** | `null` (`DISABLED_CONFIG_REFRESH`) | No automatic refresh; `@SzConfigRetryable` methods are not retried after config changes. |
| **REACTIVE** | `Duration.ZERO` (`REACTIVE_CONFIG_REFRESH`) | On-demand: when a method annotated `@SzConfigRetryable` fails, the config is refreshed; if it actually changed, the method is retried automatically. |
| **PROACTIVE** | `Duration.ofSeconds(N)` where `N > 0` | A background `Reinitializer` thread refreshes every `N` seconds **and** still performs the reactive refresh on failures. |

## How it works

- REACTIVE/PROACTIVE compare the active vs. default configuration IDs and
reinitialize when they differ.
- Reinitialization is bounded by `MAX_REINITIALIZE_COUNT` (5) to avoid an
infinite loop under racing config changes; on exhaustion a `*** WARNING`
is printed and the caller is allowed to retry anyway.
- The thread-local `ENSURING_CONFIG` flag prevents infinite recursion when
the SDK calls made *during* a refresh (e.g. `getActiveConfigId()`,
`getDefaultConfigId()`, `reinitialize()`) route back through
`execute(...)`.

## Important constraint

You **cannot** specify both an explicit `configId` *and* enable configuration
refresh — the builder throws `IllegalStateException`. A fixed `configId` pins
the environment to one configuration, which is fundamentally incompatible with
keeping it in sync with the default.

## Convenience constants

- `DISABLED_CONFIG_REFRESH` = `null` `Duration`
- `REACTIVE_CONFIG_REFRESH` = `Duration.ofSeconds(0)`
- `DISABLED_CONCURRENCY` = `null` `Integer`
- `RECOMMENDED_CONCURRENCY` = `0` (use `Runtime.availableProcessors()`)
38 changes: 38 additions & 0 deletions .claude/faqs/testing/native-library-and-environment-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Native-library test setup & single-active-environment constraint

Tests in this project exercise the real Senzing Core SDK, which is backed by
native libraries loaded via JNI. There are two things to know before writing or
running tests.

## 1. Native-library JVM wrapper

The build does not run tests on a plain `java` — it generates a wrapper script
that sets up the native library paths first:

- `GenerateTestJVMScript` (from the `sz-sdk-java` submodule, main scope as of
`sz-sdk` 4.3.0) runs in the `process-test-classes` phase and writes
`target/java-wrapper/bin/java-wrapper.bat`.
- `maven-surefire-plugin` is configured to use that wrapper as its `<jvm>`.
- Generation reads `SENZING_PATH` and `SENZING_DEV_LIBRARY_PATH` (see the
`building/sz-sdk-java-auto-build-commands` FAQ). Without them, tests cannot
locate the native libraries.

`AbstractAutoCoreTest` (extends `AbstractCoreTest` from the submodule) is the
base class for the project's tests.

## 2. One active environment per JVM

Only **one active** `SzCoreEnvironment` (including `SzAutoCoreEnvironment`) may
exist per JVM process. Constructing a second while one is active throws
`IllegalStateException`; you must `destroy()` the first one first. Tests must
tear down their environment (and let the background `Reinitializer` shut down)
before the next environment is created.

Surefire runs **classes** concurrently (methods same-thread, dynamic factor) —
so cross-class isolation matters. Tests that stub environment variables or
capture `System.out` / `System.err` must follow the `system-stubs` +
`@Execution(SAME_THREAD)` + `@ResourceLock` pattern; see the shared
`testing/system-stubs-and-output-capture` FAQ. When a production object starts
a background thread in its constructor (e.g. PROACTIVE mode's `Reinitializer`),
construct it **inside** the `stub.execute(...)` lambda so the stream redirect is
active before the thread starts.
35 changes: 34 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
{
"includeCoAuthoredBy": false
"includeCoAuthoredBy": false,
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash -c 'jq -r \".tool_input.file_path\" | { read p; [[ \"$p\" == *.java ]] && python3 \"${CLAUDE_PROJECT_DIR}/.java-coding-standards/tooling/scripts/format_file.py\" \"$p\"; }'"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "cd \"${CLAUDE_PROJECT_DIR}\" && mvn -Pcheckstyle validate -q 2>&1 | grep -E '(ERROR|WARN)' || true"
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'cd \"${CLAUDE_PROJECT_DIR}/.java-coding-standards\" 2>/dev/null || exit 0; head_sha=$(git rev-parse HEAD 2>/dev/null) || exit 0; tags_raw=$(git -c http.lowSpeedLimit=1000 -c http.lowSpeedTime=3 ls-remote --tags origin 2>/dev/null); [ -n \"$tags_raw\" ] || exit 0; latest=$(echo \"$tags_raw\" | awk -F/ \"{print \\$NF}\" | grep -v \"\\^{}$\" | sort -V | tail -1); [ -n \"$latest\" ] || exit 0; case \"$latest\" in *[!A-Za-z0-9._-]*) exit 0 ;; esac; latest_sha=$(echo \"$tags_raw\" | awk \"\\$2==\\\"refs/tags/${latest}^{}\\\" || \\$2==\\\"refs/tags/${latest}\\\" {print \\$1}\" | tail -1); [ \"$head_sha\" = \"$latest_sha\" ] && exit 0; current=$(echo \"$tags_raw\" | awk -v h=\"$head_sha\" \"\\$1==h {n=\\$2; sub(\\\"\\\\\\\\^{}\\\",\\\"\\\",n); sub(\\\"refs/tags/\\\",\\\"\\\",n); print n}\" | sort -V | tail -1); case \"$current\" in *[!A-Za-z0-9._-]*) exit 0 ;; esac; printf \"INSTRUCTION FOR ASSISTANT (from SessionStart hook): At the start of your first response, tell the user verbatim: \\\"Heads up: .java-coding-standards has a newer release available (%s); current pin is %s. Run /init-java to refresh.\\\"\\n\" \"$latest\" \"${current:-untagged}\"'"
}
]
}
]
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ target/**
# Visual Studio code
.vscode/*
!.vscode/cspell.json
!.vscode/settings.json
!.vscode/extensions.json
!.vscode/tasks.json
*.code-workspace
.history

Expand Down
Loading
Loading