Class MigrationGuardrails

java.lang.Object
com.vaadin.swingbridge.migration.guardrails.MigrationGuardrails

public final class MigrationGuardrails extends Object
The three build-time gates a Swing-to-web migration has to pass, as one test:
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 static field is final and of an immutable type, or carries IntentionallyStatic with a reason. On a server a mutable static is shared by every user, and the failure is silent.
  • No component in a static field — a component belongs to a UI, not to the JVM. Catches static MainFrame FRAME (where MainFrame extends JFrame) by resolving the type hierarchy, which a grep 'static .*JFrame' misses.
  • No JVM exit — System.exit / Runtime.exit / Runtime.halt kill 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 Details

    • MigrationGuardrails

      public MigrationGuardrails(String rootPackage, String... moreRootPackages)
      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 a com.vaadin.swingbridge.fixture package) sweeps somebody else's classes in.
  • Method Details

    • allowSystemExitInMainMethods

      public MigrationGuardrails allowSystemExitInMainMethods()
      Permits System.exit in the body of a public static void main(String[]) and nowhere else.

      A lambda inside main compiles to a synthetic method and is not covered — deliberately, since invokeLater(() -> … System.exit(0)) is exactly the UI-reachable exit the gate exists to catch.

    • allowSystemExitIn

      public MigrationGuardrails allowSystemExitIn(String... fullyQualifiedClassNames)
      Permits JVM exit anywhere in the named classes.
      Parameters:
      fullyQualifiedClassNames - matched exactly — "com.myapp.Main" does not cover com.myapp.Main$1, so an anonymous class stays gated
    • run

      public void run()
      Runs all three checks, collects every violation, and throws one AssertionError with 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 a static field is neither provably immutable nor allowlisted
    • checkStaticComponents

      public void checkStaticComponents()
      Throws:
      AssertionError - if a static field's type is a component
    • checkJvmExit

      public void checkJvmExit()
      Throws:
      AssertionError - if any code unit terminates the JVM outside the permitted places