Class MigrationGuardrails
class MigrationGuardrailsTest {
@Test
void guardrails() {
new MigrationGuardrails("com.myapp")
.allowSystemExitInMainMethods()
.run();
}
}
run() checks all three and reports every violation at once:
- No unvetted static state — every
staticfield isfinaland of an immutable type, or carriesIntentionallyStaticwith a reason. On a server a mutable static is shared by every user, and the failure is silent. - No component in a
staticfield — a component belongs to a UI, not to the JVM. Catchesstatic MainFrame FRAME(whereMainFrame extends JFrame) by resolving the type hierarchy, which agrep 'static .*JFrame'misses. - No JVM exit —
System.exit/Runtime.exit/Runtime.haltkill every user's session, not just the caller's window.
Relaxations name their target, so none of them is an off-switch: allowSystemExitInMainMethods() covers the process-level main and nothing reachable from
the UI, allowSystemExitIn(String...) names exact classes. The static gate has no
relaxation at all — IntentionallyStatic is already its per-field hatch.
Add the module in test scope; ArchUnit is the engine and never reaches your runtime. Nothing here is Swing-specific enough to retire once the emulators are gone: "no component in a static field" and "no JVM exit" stay true of the pure-Vaadin app the migration ends at.
IntentionallyStatic's javadoc is the canonical definition of the static gate —
which types count as immutable, what passes unannotated, and which of the four reasons to
claim. This class implements that definition; it does not restate it.
A facade over GuardrailEngine in :migration-tool, which owns the
rules and the ArchUnit dependency so that StaticSweep's worklist and this gate cannot
answer the same question two ways (M1D_static_sweep_tool § Q_one_rule_engine). The
facade is what the migrator sees, and it is kept rather than folded away because it is what
makes this artifact's promises true: one SB-Emulators type imported, no com.tngtech.*, test
scope by consumption. The division of labour is the terminal — the engine returns the
violations and judges nothing, run() throws on them, and the tool's `main` prints
them and exits 0.
Root packages are required and never defaulted: absent one the importer scans the whole
classpath and reports mutable statics inside Vaadin and the JDK, and a gate that noisy gets
deleted. For the same reason an import resolving no classes fails loudly rather than
passing vacuously — the three rules all allow an empty should() (a fully swept app can
have zero statics), so a mistyped root package would otherwise pass forever.
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionallowSystemExitIn(String... fullyQualifiedClassNames) Permits JVM exit anywhere in the named classes.PermitsSystem.exitin the body of apublic static void main(String[])and nowhere else.voidvoidvoidvoidrun()Runs all three checks, collects every violation, and throws oneAssertionErrorwith the combined report.
-
Constructor Details
-
MigrationGuardrails
- Parameters:
rootPackage- the app's own root package, e.g."com.myapp"moreRootPackages- further roots — an in-tree library, a fixture package. Leaving one out makes the rules pass by not looking at it; naming one wider than you own (a bare"SB-Emulators"over acom.vaadin.swingbridge.fixturepackage) sweeps somebody else's classes in.
-
-
Method Details
-
allowSystemExitInMainMethods
PermitsSystem.exitin the body of apublic static void main(String[])and nowhere else.A lambda inside
maincompiles to a synthetic method and is not covered — deliberately, sinceinvokeLater(() -> … System.exit(0))is exactly the UI-reachable exit the gate exists to catch. -
allowSystemExitIn
Permits JVM exit anywhere in the named classes.- Parameters:
fullyQualifiedClassNames- matched exactly —"com.myapp.Main"does not covercom.myapp.Main$1, so an anonymous class stays gated
-
run
public void run()Runs all three checks, collects every violation, and throws oneAssertionErrorwith the combined report.Never short-circuits: a first-failure throw would serialise the worklist into fix-statics, re-run, now discover the
System.exit.- Throws:
AssertionError- if any check found a violation, or the roots resolved no classes
-
checkStaticState
public void checkStaticState()- Throws:
AssertionError- if astaticfield is neither provably immutable nor allowlisted
-
checkStaticComponents
public void checkStaticComponents()- Throws:
AssertionError- if astaticfield's type is a component
-
checkJvmExit
public void checkJvmExit()- Throws:
AssertionError- if any code unit terminates the JVM outside the permitted places
-