# install4j samples — agent index

Five reference projects. Read the named file when a task matches a pattern below; do not re-derive from the bean catalog.

IDs cited below (e.g. `id="535"`, `id="2297"`) are local to each sample's file. The same conceptual entity (e.g. "Background update downloader") will carry a different `id` in `assets/application-templates.install4j` than in `samples/hello/hello.install4j`. Renumber when copying between files.

## `customCode/` — Java extensions

Custom screen, action, form component, screen style, and L&F handler — wired via `<customCode>` near the top of `customCode.install4j`. `customCode.gradle.kts` depends on `install4j-runtime` with `isTransitive = false` (runtime is provided at install time; do not ship it).

**Build requirement:** the `<customCode>` block points at `./build`, where install4jc looks for the compiled `SampleAction`/`SampleScreen`/etc. classes during validation. Run `./gradlew build` (or the equivalent for your build system) before invoking `install4jc --test` — otherwise it throws `NullPointerException: Cannot invoke "java.beans.BeanInfo.getBeanDescriptor()"` because the bean classes aren't on the classpath. Same rule applies to any project that uses `<customCode>` — compile first, validate second.

**Package requirement:** put custom-code Java sources in a **named package**, never the default package. This sample uses `package com.install4j.sample;`; pick whatever fits your project (e.g. `package com.example.installer;`). Default-package classes are reachable as `<beanClass>` (loaded reflectively) but **NOT from script bodies** which are emitted into named packages.

- **`SampleAction.java` + `SampleActionBeanInfo.java`** — `extends AbstractInstallOrUninstallAction`; override `install`/`uninstall`/`rollback`; drive `context.getProgressInterface()`. BeanInfo uses `EnumerationMapper` to surface an `int` property as a drop-down (the standard way to do fixed-choice properties without a real Java enum) and implements `BeanValidator.checkNotEmpty(...)` for build-time validation.
- **`ManyFeaturesAction.java` + `ManyFeaturesActionBeanInfo.java`** — covers nearly every property type:
  - `ScriptPropertyDescriptor.create(..., returnType, ScriptParameter[])`; invoke at runtime via `context.runScript(script, this, args...)`.
  - `FilePropertyDescriptor` with `setFileContentType(FileContentType.HTML)`, `CONTEXT_EXTERNAL_OR_INTERNAL`, `LocalizedExternalFile` (one-file-per-language).
  - `setParentProperty("parent")` + `setVisibilityDiscriminator((bean, parentValue) -> ...)` — child shown only when lambda returns true.
  - `setAllowTextOverride(true)` — getter MUST then call `replaceWithTextOverride("propertyName", currentValue, ExpectedType.class)` to read the resolved value (see `getTestEnum()`, `getColor()`, `isBooleanValue()` in `ManyFeaturesAction.java`).
  - `PropertyLoggingInterceptor.getLogValueForProperty(name, value)` — return `"****"` to mask secrets in install logs.
  - `ManyFeaturesActionCustomizer.java` — `java.beans.Customizer` (a JPanel implementing the JDK interface) registered via the BeanInfo constructor's last arg + `setCustomizerPlacement(CustomizerPlacement.AFTER_PROPERTIES)`.
  - Predefined contexts: `CONTEXT_LAUNCHER_ID`, `CONTEXT_DATETIME`, `CONTEXT_MULTILINE`, `CONTEXT_STRING_TO_STRING_MAP` — pick the IDE editor without writing a customizer; usage examples in `ManyFeaturesActionBeanInfo.java`.
- **`SampleScreen.java`** — `replaceVariables(buttonTitle)` in the getter is the standard pattern for `${...}` expansion on user-writable string properties.
- **`SampleFormComponent.java`** — `extends AbstractFormComponent`; `createLeftComponent`/`createCenterComponent`; `isFillCenterHorizontal()`; re-apply variable substitution in `initialize()` (called every time the screen is shown).
- **`SunnySkyBackgroundStyle.java`** — `extends WrapperStyle`; expose `nestedStyleId` with `CONTEXT_STYLE_ID`. The `styleComponent` arg of `createComponent(JComponent)` can be `null` if no nested style — line 75 has the fallback.

## `formScreen/jdkSelection.install4j` — XML only

Form-screen authoring with scripts.

- **Lifecycle script slots:** `<validation>` (Next click; return false to block — line 113), `<preActivation>` (before screen shown; pre-populate variables — line 149), `<initScript>` (per-component; e.g. preselect list entry — line 199).
- **`JVMSelector`** — `getJVMLocation(dir, minVer, maxVer, allowJRE)`, `JVMLocation` enum, `setPreferredJVM(jdkHome)`.
- **`context.registerResponseFileVariable("jdkHome")`** (line 147) — persists for unattended replay.
- **`SingleRadiobuttonComponent.coupledComponentIds`** (line 163) — show/hide a component or `<group>` when the radio is selected.
- **`useExternalParametrization="true"`** — exposes the component to a parent installer via the external-parametrization API (also relevant to `mergedProjects/`).
- **`${installer:jvmLocations}`** as a `ListComponent.listEntries` element (line 193) — array installer variable expands into list entries.
- **`ConsoleHandlerFormComponent`** with `<consoleScript>` calling `console.askOkCancel(...)` / `console.askYesNo(...)` — provides the console-mode equivalent of a GUI prompt; runs in `-c` and `-q` modes.

## `hello/hello.install4j` — Java + XML

Multi-launcher project with full auto-update.

- Three `<launcher>`: CLI (`excludeFromMenu="true"`, line 48), Swing GUI (line 65), Windows service (line 95, with `<service>` child) — same JAR, different `<mainClass>`.
- Five installer applications: `installer`, `uninstaller`, `Standalone update downloader` (id `535`), `Background update downloader` (id `2297`), `Configure greeting` (id `594`, post-install maintenance app launched from GUI's File menu).
- Build hooks: `build.xml` (Ant), `pom.xml` (Maven), `build.gradle` + `hello.gradle.kts`.
- `vmoptions/hello.vmoptions` — JVM args bundled with the launcher; `-Dadditional.message=...` read in `cli/Hello.java`.

**Auto-update — all in `src/gui/Hello.java`:**

- `Variables.getCompilerVariable("sys.updatesUrl")` (107) — URL from _Installer → Auto Update Options_.
- `UpdateChecker.getUpdateDescriptor(url, ApplicationDisplayMode.GUI)` (108) inside `SwingWorker.doInBackground()` — non-blocking startup check.
- `UpdateDescriptor.getPossibleUpdateEntry()` (111) → `UpdateDescriptorEntry.isArchive()/isSingleBundle()/isDownloaded()` (119–120).
- `ApplicationLauncher.launchApplication("2297", null, true, callback)` (199) — background downloader, blocking, `Callback.createProgressListener()` returns `ProgressListenerAdapter` receiving `actionStarted("downloadFile")` and `percentCompleted(int)`.
- `ApplicationLauncher.launchApplication("535", null, false, null)` (299) — standalone downloader, separate process, non-blocking.
- `UpdateChecker.isUpdateScheduled()` (128, 242).
- `UpdateChecker.executeScheduledUpdate(Arrays.asList("-q", "-splash", "...", "-alerts"), true, null)` (288) — runs queued installer; second arg `restartLauncher=true`.
- `ApplicationLauncher.launchApplicationInProcess("594", null, callback, WindowMode.DIALOG, parent)` (314) — modal dialog **in the same JVM**. Compare line 317 — separate-process variant.

**Other launcher-API patterns in the same file:**

- `Variables.getInstallerVariable("sys.installationDir")` (150).
- `Variables.getCompilerVariable("sys.version")` (91).
- `Variables.loadFromPreferenceStore(true)` (335) — read variables persisted by the maintenance app.
- `SplashScreen.writeMessage(...)` (365–367); catch `SplashScreen.ConnectionException` for the no-splash case.
- `StartupNotification.registerStartupListener(parameters -> ...)` (378) — Windows: second-launch notification. Requires "Allow second instance" disabled and "Startup notification" enabled.

**Service shape** in `src/service/Hello.java`: `main()` spawns a worker `Thread`, blocks on a `CountDownLatch`; `Runtime.getRuntime().addShutdownHook(...)` interrupts the worker and waits for the latch. **Do NOT loop in `main()`** — the service launcher will treat that as a failed start.

## `properties/properties.install4j` — XML only

Read / edit / write a `.properties` file across the install flow:

1. **`ReadPropertiesFileAction`** (31) on WelcomeScreen — load `${installer:sys.resourceDir}/db.properties` (file shipped with installer; `sys.resourceDir` is the unpacked resource dir) into installer variable `dbProperties` (`Map<String,String>`).
2. **`<group beanClass="ActionGroup">`** (87) on InstallationDirectoryScreen — atomic pair:
   - `ReadPropertiesFileAction` (89) reading `db.properties` from the installation directory (existing prior install), `mergeIntoExistingVariables=true` so old values overlay defaults.
   - `RunScriptAction` (100) extracts map entries to named variables (`host`, `port`, …) so form components can bind.
3. **FormScreen "Database"** (145) — `DropdownListComponent`, `TextfieldComponent`, `IntegerTextfieldComponent`.
4. **InstallationScreen** (190) — `InstallFilesAction` writes default `db.properties`; `RunScriptAction` "Update property map" (198) copies form variables back into the map; `WritePropertiesFileAction` (216) writes the map.

Also: `LoadResponseFileAction` placed early to honor `--response-file`; `RegisterAddRemoveAction` for Windows Add/Remove Programs; `errorMessage="${i18n:FileCorrupted}"` for localized errors.

## `mergedProjects/mergedProjects.install4j` — XML only

`<mergedProjects>` (line 5) declares three child projects with `mergeFiles` / `mergeLaunchers` / `mergeInstallerApplications` toggles. Pair this with `useExternalParametrization` from the `formScreen/` section when a parent must override values inside a merged child's form.

```xml
<project name="JDK selection" id="22" projectFile="../formScreen/jdkSelection.install4j"
         mergeFiles="false" mergeLaunchers="false" mergeInstallerApplications="false" />
```

When all toggles are off, individual elements are pulled in via `<link>`:

```xml
<link id="23" targetId="3854" mergedProjectId="22" />
```

`targetId` = element id (a `<group>`, `<screen>`, …) inside the merged project file; `mergedProjectId` = the parent `<project id>`. Use when a sub-project supplies reusable building blocks the parent positions.
