Class EHelper

java.lang.Object
vaadinx.EHelper

public final class EHelper extends Object
Internal plumbing shared by every vaadinx.* emulator — hand-written, so it lives outside the generated vaadinx.awt.Component that would otherwise clobber it on regeneration. A grab-bag by design; the members cluster into seven families: Thread contracts vary per method and are stated there: the hooks and conversions are callable from anywhere; callSwing(Runnable) requires a current UI; the live-UI family must hold the session lock.
  • Field Summary

    Fields
  • Method Summary

    Modifier and Type
    Method
    Description
    static void
    applyChildCss(com.vaadin.flow.dom.Element target, Map<String,String> css)
    Apply per-child layout CSS to a single child's element — the counterpart to applyContainerCss(Element, String, Map) for the per-child leg of a CssEmittingLayoutManager pass (grid-area, justify-self, margin, …).
    static void
    applyContainerCss(com.vaadin.flow.dom.Element target, String layoutManagerName, Map<String,String> css)
    Apply a block of layout CSS to a Container's peer content element per D_layout_css_on_content.
    static void
    applyEmulatorId(com.vaadin.flow.component.Component peer, String name)
    Map a Swing name to the peer's HTML id, with a per-UI suffix counter to keep ids unique when two components share a Swing name (Swing's name has no uniqueness requirement; HTML's id does).
    static <T> T
    awaitBrowserRoundTrip(com.vaadin.flow.component.Component anchor, CompletableFuture<T> future)
    Waits for a browser round-trip — an executeJs result, an ExtendedClientDetails refresh — with Future.get()'s contract.
    static void
    awaitModal(com.vaadin.flow.component.Component anchor, CompletableFuture<?> closed)
    The modal-park seam: parks the calling UI fiber until closed completes, the session lock released so the click that closes the dialog can get in.
    static <T> T
    callOnLiveUISync(com.vaadin.flow.server.VaadinSession session, Supplier<T> body)
    Runs body on the session's single live UI and returns its value to the calling background thread, blocking until it completes.
    static void
    callSwing(Runnable runnable)
    The R_callswing_envelope seam: runs runnable — a peer listener's Swing-side body — on a virtual thread so it can park on a modal dialog without blocking the Vaadin request thread.
    static void
    Reports, once per JVM, that emulator code reached its Vaadin peer from off the Vaadin UI thread.
    static Component
    emulatorFor(com.vaadin.flow.component.Component peer)
    Lenient, walking variant of getEmulator(Component): the emulator owning peer, or the one owning its nearest Vaadin ancestor, or null when no ancestor is a registered peer.
    static List<com.vaadin.flow.component.dialog.Dialog>
    Every Dialog attached to the current UI's component graph — the Vaadin-side view of our Windows.
    static Component
    getEmulator(com.vaadin.flow.component.Component peer)
    The emulator (Component) whose peer is the given Vaadin component.
    static List<Window>
    Every Window this app instance has constructed and not yet let go of, in construction order — the source of truth behind Window.getWindows(), Window.getOwnerlessWindows() and Frame.getFrames().
    static boolean
    isShuttingDown(VaadinSession) for the current session — the form the throw-seams use, since they run inside the dispatch where the current session is set.
    static boolean
    isShuttingDown(com.vaadin.flow.server.VaadinSession session)
    Whether session is in the shutdown dispatch; false for a null session.
    static void
    markShuttingDown(com.vaadin.flow.server.VaadinSession session)
    Marks the session as shutting down — set once at the top of the session-destroy → WINDOW_CLOSING dispatch (D_shutdown_lifecycle), before any user WindowListener runs.
    static void
    onCreated(com.vaadin.flow.component.Component peer, Component emulator)
    Register the 1:1 mapping peer → emulator.
    static void
    onNoop(String className, String method)
    Deliberate no-op hook for Swing API that's redundant in the Vaadin world (layout invalidation, repaint, peer lifecycle).
    static void
    onSessionLiveUI(com.vaadin.flow.server.VaadinSession session, Runnable swingBody)
    Delivers a Swing-side callback (a Timer fire, a SwingWorker done()/process()/PCE) onto the session's single live UI, from a non-UI thread (a scheduler or worker thread that holds no lock).
    static void
    onUnimplemented(String className, String method, Object... args)
    Warn-and-continue stub hook.
    static void
    onUnsupported(String className, String method, Object... args)
    Swing method we *have* implemented, but the underlying Vaadin peer can't honour this particular call — e.g.
    static void
    onUnsupportedPeerShape(String className, String situation)
    ERROR-log hook for situations where emulation is fundamentally unable to honor a well-formed Swing request and the safest response is to do nothing — e.g. setting a LayoutManager on a Container whose peer content element isn't a plain <div> (D_layout_css_on_content).
    static void
    The Window.init() -> addToWindowList() edge, called from Window's peer-injection constructor.
    static void
    The addNotify() -> allWindows.add(this) edge.
    static void
    The removeNotify() -> allWindows.remove(this) edge, and the one place a Window stops being displayable — so also where AutoShutdown learns the app may be over.
    static void
    Relays a surrogate's Swing-model event to the emulator's own listeners: through callSwing(Runnable) when a UI is current, inline on the calling thread otherwise.
    static void
    reportUncaught(com.vaadin.flow.server.VaadinSession session, Throwable t)
    Hands an uncaught throwable to the session's ErrorHandler, acquiring the session lock first if the caller doesn't already hold it — the emulator's EventDispatchThread.processException.
    static void
    resetContainerCss(com.vaadin.flow.dom.Element target)
    Clear a container's layout CSS — the undo for applyContainerCss(Element, String, Map), called from Container.setLayout(LayoutManager) when one LayoutManager replaces another.
    static void
    runInUIThread(boolean callerPlansToBlockAfterwards, Runnable body)
    Runs body on the Vaadin UI thread and returns only once it has run — the seam a Swing→peer write goes through when it may be called from a background thread.
    static com.vaadin.flow.component.UI
    singleLiveUI(com.vaadin.flow.server.VaadinSession session)
    The one UI an app lives in — the delivery target for an emulator Timer fire, a SwingWorker callback, and a parked callSwing(Runnable) continuation resuming after an F5.
    static com.vaadin.flow.component.Component
    toVaadinIconComponent(String className, Icon icon)
    Convert a Icon to a Vaadin Component the peer can install via Button.setIcon(Component).

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

  • Method Details

    • onUnimplemented

      public static void onUnimplemented(String className, String method, Object... args)
      Warn-and-continue stub hook. Never throws — see D_never_fail_on_gaps.
    • checkUIThread

      public static void checkUIThread()
      Reports, once per JVM, that emulator code reached its Vaadin peer from off the Vaadin UI thread. Never throws and never changes behaviour: SB-Emulators tolerates off-thread component work (it is what real Swing apps do, and Vaadin components happen to construct and mutate fine while detached), so this is an observation, not a gate.

      Wired into Component.getPeer() rather than sprinkled over every mutator: the accessor is the one narrow waist between an emulator and its peer, so a new emulator method cannot forget it. Because the report is once per JVM, over-firing (a read, internal plumbing) costs nothing while under-firing is the failure — which is why the chokepoint is the accessor and not a curated list of write paths.

      Deliberately not routed through warnHook: that channel is the stub-WARN inventory the WarnInventoryTest exit gates assert on, and this is thread hygiene in the migrator's own code, not an SB-Emulators gap.

      Defers to SwingUtilities.isEventDispatchThread() for what "the UI thread" means, so there is exactly one answer to that question; a second copy would eventually disagree with the one migrators can call themselves. Silent during shutdown, where a UI-less teardown thread is expected rather than a mistake (R_callswing_envelope).

    • runInUIThread

      public static void runInUIThread(boolean callerPlansToBlockAfterwards, Runnable body)
      Runs body on the Vaadin UI thread and returns only once it has run — the seam a Swing→peer write goes through when it may be called from a background thread. One contract on every thread: inline when a UI is already current, otherwise the session lock is acquired and the body runs before this returns.

      Not callSwing(Runnable)'s inverse and not its sibling: callSwing is the peer→Swing envelope (R_callswing_envelope) and spawns a virtual thread so a modal dialog can park. This is a Swing→peer write, which cannot park, so no VT is spawned and the body simply runs under the lock.

      Synchronous on purpose, and the reasoning is a decision, not a default. The alternative — UI.access() — never runs inline even for a caller holding the lock (flow-server's ensureAccessQueuePurged: "if the lock is held by the current thread, we just release it knowing that the queue gets purged once the lock is ultimately released"), so it would defer every write past the end of the request and reorder background writes against inline ones. An app whose EDT blocks on a worker that needs the EDT deadlocks on real Swing too, so blocking here reproduces a JDK failure mode; deferring would invent an execution order Swing never had, which is what R_no_silent_improvements forbids. The lock is taken directly rather than via callOnLiveUISync(VaadinSession, Supplier)'s queue-and-wait so a stall is attributable — a thread parked on a lock names its owner in a thread dump; one parked on a latch does not.

      Deliberately unbounded. A hand-rolled tryLock + current-instance dance is likelier to be wrong than the deadlock is to happen, so a slow hop is reported (see SLOW_UI_HOP_MILLIS) rather than broken.

      No EmulatorContext on the thread throws (R_match_swing_errors case (7), D_no_context_throws): the migrator started this work on a thread that never carried one, and the fix is one line at its construction — EmulatorContext.wrap(executor), or ctx.run(...) for a hand-rolled Thread. When the task came through a wrapped executor whose submitter had no context, the message says so and names the submit frames instead (D_submit_site). That throw does not depend on callerPlansToBlockAfterwards: a missing context is a configuration error either way.

      Callers may block in a constructor through this — new JLabel(icon) on a background thread waits for the session lock. That is a property no Swing constructor has, and it is the accepted cost of letting the icon arrive at all.

      Not the rejected "is this write cosmetic?" flag, which needed a taxonomy of peer operations nobody can finish. This asks whether the caller is about to block, which the caller always knows, and the set that answers yes is small and closed — the modal park and what funnels into it.

      Parameters:
      callerPlansToBlockAfterwards - a modal show, not an ordinary write — the caller waits for a browser answer once this returns. (Named for the caller because this method always blocks; the flag is about what happens next.) It decides what a reachable session with no live UI means, and only that: false keeps R_decline_effect_only's bargain and drops the effect, since the state is stored and the event fires regardless; true throws, because degrading would be followed by parking on a latch nothing can ever count down. Mandatory rather than defaulted so the question is answered at every site.
      Throws:
      IllegalStateException - if this thread carries no EmulatorContext
      BrowserSessionClosedError - if callerPlansToBlockAfterwards and no live UI is reachable — a closed tab, or a session that went away while this waited
    • callSwing

      public static void callSwing(Runnable runnable)
      The R_callswing_envelope seam: runs runnable — a peer listener's Swing-side body — on a virtual thread so it can park on a modal dialog without blocking the Vaadin request thread. Every peer→Swing listener wraps its body in this:
      button.addClickListener(e -> EHelper.callSwing(() -> {
          fireActionPerformed(...);   // may call JOptionPane.showMessageDialog and park
      }));
      
      100% coverage is the contract, not an optimization: one listener that fires directly instead of through here is one button whose dialog.show() deadlocks the carrier thread. See D_callswing_funnel, D_callswing_loom, hard rule R_callswing_envelope.
      Parameters:
      runnable - the Swing-side callback body (fire events, mutate fields).
      Throws:
      IllegalStateException - if UI.getCurrent() is null and the session is not shutting down — every legitimate live call site has a UI by construction, so a missing one is a programming error that should fail loudly, not silently no-op. The one carve-out is app shutdown (see the shutdown note below). Also when the Vaadin request itself is being handled on a virtual thread, which SB-Emulators cannot run on at all — see assertRequestThreadIsNotVirtual().

      Implementation details

      Re-entrant: when already inside an outer callSwing continuation — e.g. a Swing listener mutated a peer and fired another peer event — the runnable runs inline; the outer VT can already park, and a nested VT would only waste machinery. This backstops the feedback cases R_swing_is_truth's preventPeerEvents flag can't cover (events with no prior value to diff). "Inside a continuation" is UIFibers.isInUIFiber(), not a bare Thread.isVirtual(): the two differ on a virtual request thread, where the bare check would silently take this path.

      Shutdown carve-out (R_callswing_envelope relaxation, D_shutdown_lifecycle). During app termination — a real browser-tab close, where JFrame.dispatchShutdownClosing fires WINDOW_CLOSING and the peer tree detaches — the detach listeners funnel removeNotify back through here on the request-less tab-scope reaper thread, which has no current UI. That is expected teardown, not a programming error: the browser tab is gone, so there is nothing to push to. When isShuttingDown(), the runnable therefore runs inline instead of throwing (and instead of spinning the VT/ UI.access machinery, which would buy nothing with no browser attached). Gated by our own shuttingDown flag, set before the detach cascade, so it never masks a genuine missing-UI bug outside shutdown.

      Exceptions propagate to the session ErrorHandler via UIFibers.runUntilPark(Runnable) — swallowing here would pre-empt user-configured error handling and hide bugs.

    • relayModelEvent

      public static void relayModelEvent(Runnable runnable)
      Relays a surrogate's Swing-model event to the emulator's own listeners: through callSwing(Runnable) when a UI is current, inline on the calling thread otherwise.
      // JList, re-firing the surrogate's forwarded selection event:
      EHelper.relayModelEvent(() -> fireSelectionValueChanged(first, last, adjusting));
      

      Only for bridges a Swing model mutated off the UI thread can reach (SD_background_model_hop); peer listeners keep callSwing and its throw. A browser event always has a UI current, so no UI means an off-thread mutation on a component with no UI to hop to — and the JDK fires those on the mutating thread too.

    • markShuttingDown

      public static void markShuttingDown(com.vaadin.flow.server.VaadinSession session)
      Marks the session as shutting down — set once at the top of the session-destroy → WINDOW_CLOSING dispatch (D_shutdown_lifecycle), before any user WindowListener runs.

      Its one job is to turn the browser-round-trip seams into an actionable throw: once set, a modal-dialog park (Dialog) or a blocking SwingWorker.get() / SwingUtilities.invokeAndWait inside a shutdown listener throws a "shutting down" IllegalStateException instead of hanging on a browser that's already gone (or surfacing a cryptic generic error). Pure server-side cleanup (save, release, log) is unaffected. Not for dedup: session-destroy fires exactly once.

    • isShuttingDown

      public static boolean isShuttingDown(com.vaadin.flow.server.VaadinSession session)
      Whether session is in the shutdown dispatch; false for a null session.
    • isShuttingDown

      public static boolean isShuttingDown()
      isShuttingDown(VaadinSession) for the current session — the form the throw-seams use, since they run inside the dispatch where the current session is set.
    • awaitModal

      public static void awaitModal(com.vaadin.flow.component.Component anchor, CompletableFuture<?> closed)
      The modal-park seam: parks the calling UI fiber until closed completes, the session lock released so the click that closes the dialog can get in.

      The wait belongs to anchor, the dialog's peer: when it stays detached — the session is destroyed, the tab closes — the wait ends rather than parking forever. On resume UI.getCurrent() is the UI the anchor was last attached to, which is the live one after an F5 @PreserveOnRefresh teleport.

      Throws:
      IllegalStateException - outside a UI fiber.
      BrowserSessionClosedError - if the anchor died, or the park was interrupted: nobody will answer, and an Error is what the migrated code's catch (Exception e) can't swallow into a fabricated answer (R_match_swing_errors case (8)). callSwing(Runnable) ends the UI fiber quietly on it.
    • awaitBrowserRoundTrip

      public static <T> T awaitBrowserRoundTrip(com.vaadin.flow.component.Component anchor, CompletableFuture<T> future) throws ExecutionException, InterruptedException
      Waits for a browser round-trip — an executeJs result, an ExtendedClientDetails refresh — with Future.get()'s contract. Inside a UI fiber it parks, the session lock released so the response can get in, and the wait dies with anchor; on any other thread it is a plain future.get().

      future must be a plain CompletableFuture, never Vaadin's DeadlockDetectingCompletableFuture, whose get() refuses any caller that hasLock() — a UI fiber included (D_clipboard_future_bridge).

      Throws:
      BrowserSessionClosedError - if anchor died, or the park was interrupted, as for awaitModal(Component, CompletableFuture).
      ExecutionException
      InterruptedException
    • singleLiveUI

      public static com.vaadin.flow.component.UI singleLiveUI(com.vaadin.flow.server.VaadinSession session)
      The one UI an app lives in — the delivery target for an emulator Timer fire, a SwingWorker callback, and a parked callSwing(Runnable) continuation resuming after an F5. Resolves purely through the AppTab identity MainWindowRoute maintains across an F5 @PreserveOnRefresh teleport (the rebind re-binds the fresh UI to the app tab). Returns null when no live AppTab is recorded.

      Pure, never guesses. There is deliberately no getUIs() fallback: delivering to some arbitrary live UI could run app logic against the wrong (or a second-tab curtain) UI and produce unreproducible state. A null return is the fail-loud signal — onSessionLiveUI(VaadinSession, Runnable) drops the callback, callOnLiveUISync(VaadinSession, Supplier) throws. In production an AppTab is always set before any user callSwing (MainWindowRoute marks it before bootstrap()), so null means a genuine teardown, not a missing identity. A plain (non-parking) callSwing never needs this: it delivers to its invoking UI (see callSwing(Runnable) and UIFibers#runUntilPark), which is why callSwing is inherently UI-local and not part of the orphan bug.

      Never throws here either — a genuine second browser tab is a D_active_ui_pointer curtain that never becomes the AppTab, so the one-app-per-session invariant is enforced at attach, not by guessing among live UIs.

      Must be called while holding the session lock (AppTab.liveUI() reads VaadinSession.getUIs(), which enforces it) — typically from inside onSessionLiveUI(VaadinSession, Runnable)'s session.access block, which is also what guarantees an in-flight F5 has settled (the AppTab already re-bound to the fresh UI) before this reads it (D_session_scoped_pools).

      Returns:
      the app's delivery UI, or null if no live AppTab is recorded (teardown, or an app that never marked one).
    • onSessionLiveUI

      public static void onSessionLiveUI(com.vaadin.flow.server.VaadinSession session, Runnable swingBody)
      Delivers a Swing-side callback (a Timer fire, a SwingWorker done()/process()/PCE) onto the session's single live UI, from a non-UI thread (a scheduler or worker thread that holds no lock).

      The UI is resolved lazily, at delivery time — never captured eagerly at construction. That is what survives an F5 @PreserveOnRefresh teleport: the refresh runs inside one lock-holding request, so by the time this callback's session.access block gets the lock the transition has fully settled and singleLiveUI(VaadinSession) sees exactly the new UI — independent of whether the framework created-then-closed (real Flow) or closed-then-created (Karibu). See UiScopedFeaturesF5Test.

      session.access acquires the lock; inside it we resolve the live UI and hop to it via UI.accessSynchronously(Command) (re-entrant on the already-held lock) so callSwing(Runnable) runs with UI.getCurrent() set — the R_callswing_envelope envelope the loom VT carrier needs. A null live UI (session going down) drops the callback silently.

    • reportUncaught

      public static void reportUncaught(com.vaadin.flow.server.VaadinSession session, Throwable t)
      Hands an uncaught throwable to the session's ErrorHandler, acquiring the session lock first if the caller doesn't already hold it — the emulator's EventDispatchThread.processException.

      The lock is what this method is for: getErrorHandler() is state access behind checkHasLock(), whose default SessionLockCheckStrategy is ASSERT — so an unlocked read blows up under -ea and passes silently in production.

      Deliberately no lookup chain, and in particular no Thread.getDefaultUncaughtExceptionHandler() read — a migrated app's JVM-wide handler is ported to the session instead. Adding the read back is the plausible "improvement" this note exists to stop; UncaughtErrorRoutingTest.jvmWideHandlerIsNotConsulted is what goes red. Reasons: D_uncaught_handler_chain.

      Parameters:
      session - null logs here instead; no handler is reachable without one
    • callOnLiveUISync

      public static <T> T callOnLiveUISync(com.vaadin.flow.server.VaadinSession session, Supplier<T> body)
      Runs body on the session's single live UI and returns its value to the calling background thread, blocking until it completes. The synchronous, result-returning sibling of onSessionLiveUI(VaadinSession, Runnable): used when a background worker (SwingWorker.doInBackground) must read/write browser-backed state (e.g. localStorage preferences) and needs the answer inline.
      // on a SwingWorker VT (no current UI, session held via EmulatorContext):
      String v = EHelper.callOnLiveUISync(ctx.session(),
              () -> Preferences.userRoot().node("com/acme").get("k", "def"));
      
      Mechanism (mirrors SwingUtilities.invokeAndWait's block-until-done, plus onSessionLiveUI(VaadinSession, Runnable)'s UI resolution): queue onto the session via session.access; inside, resolve singleLiveUI(VaadinSession) and run body under accessSynchronously + callSwing(Runnable) so it has a current UI, the lock, and the loom carrier to park on a browser round-trip; latch the result/exception back and await it on the caller. The access queue is drained by @Push in production (the mandatory-Push contract) / MockVaadin.runUIQueue in tests — the same drain invokeAndWait depends on. The caller must be a parkable (virtual) thread; a SwingWorker worker thread is.

      Never hangs, fails loud. Unlike onSessionLiveUI (which silently drops), every terminal state completes the latch: no live UI (tab/session gone), a body exception, or the 30Ls timeout backstop all surface as a thrown exception (per D_prefs_scope_split / D_gap_severity_triage — loud over silently-stale). A thrown exception propagates out of doInBackground as the worker's ExecutionException.

      Throws:
      IllegalStateException - if no single live UI is reachable, the wait times out, or the wait is interrupted; any body runtime exception propagates as-is.
    • onNoop

      public static void onNoop(String className, String method)
      Deliberate no-op hook for Swing API that's redundant in the Vaadin world (layout invalidation, repaint, peer lifecycle). Logs at DEBUG only — these get hit hot. No varargs on purpose: args don't add information at DEBUG for a "we ignored it by design" call.
    • onUnsupported

      public static void onUnsupported(String className, String method, Object... args)
      Swing method we *have* implemented, but the underlying Vaadin peer can't honour this particular call — e.g. setEnabled(false) on a peer that doesn't implement HasEnabled. Distinct from onUnimplemented(String, String, Object...): there, *we* haven't coded the behaviour yet; here, the peer is the blocker. WARN so it shows up in migration logs, but never throws — R_match_swing_errors.
    • onUnsupportedPeerShape

      public static void onUnsupportedPeerShape(String className, String situation)
      ERROR-log hook for situations where emulation is fundamentally unable to honor a well-formed Swing request and the safest response is to do nothing — e.g. setting a LayoutManager on a Container whose peer content element isn't a plain <div> (D_layout_css_on_content). Louder than onUnimplemented(String, String, Object...)'s WARN because the user has stepped into genuinely broken territory, not just unimplemented territory. Returns without throwing; R_match_swing_errors still applies.
    • applyContainerCss

      public static void applyContainerCss(com.vaadin.flow.dom.Element target, String layoutManagerName, Map<String,String> css)
      Apply a block of layout CSS to a Container's peer content element per D_layout_css_on_content. Gates on the element being a plain <div>: non-Div peers get a onUnsupportedPeerShape(String, String) ERROR log and no CSS is written — setting a LayoutManager on a JComponent whose peer isn't a Div (a JButton, a JTextField, ...) leaves children under the peer's own rendering rather than corrupting it.

      Takes the target Element rather than the Container so Container.peerContentElement() stays protected — callers are the built-in LayoutManagers in vaadinx.awt, which already have package access to resolve it.

      null values remove the corresponding style property, mirroring the null → inherit from parent contract used elsewhere in the peer-CSS layer (see Component.setForeground(Color)).

    • applyChildCss

      public static void applyChildCss(com.vaadin.flow.dom.Element target, Map<String,String> css)
      Apply per-child layout CSS to a single child's element — the counterpart to applyContainerCss(Element, String, Map) for the per-child leg of a CssEmittingLayoutManager pass (grid-area, justify-self, margin, …). No <div> gate: a child is any element (a button, a field). null values remove the property, absent keys are left untouched — so a fill-stretched cell can drop a field surrogate's inline width without a non-filled sibling losing its columns size.
    • resetContainerCss

      public static void resetContainerCss(com.vaadin.flow.dom.Element target)
      Clear a container's layout CSS — the undo for applyContainerCss(Element, String, Map), called from Container.setLayout(LayoutManager) when one LayoutManager replaces another. Rationale for why the undo exists (and why per-child CSS is left alone) lives on LayoutCss.resetContainerCss(Element).
    • onCreated

      public static void onCreated(com.vaadin.flow.component.Component peer, Component emulator)
      Register the 1:1 mapping peer → emulator. Invoked from Component's constructor so lookup via getEmulator(com.vaadin.flow.component.Component) never has to synthesize a fresh emulator.
    • applyEmulatorId

      public static void applyEmulatorId(com.vaadin.flow.component.Component peer, String name)
      Map a Swing name to the peer's HTML id, with a per-UI suffix counter to keep ids unique when two components share a Swing name (Swing's name has no uniqueness requirement; HTML's id does). First setName("ok") → id="ok"; second → id="ok-2"; third → id="ok-3". null / blank name removes the id.

      Surrogate-side tagging takes a different shape (data-swing-name attribute through ComponentMixin.setName(String)) because surrogates deliberately leave Vaadin's id alone — see SD_dropin_stance / NameStore. The id-vs-attribute split mirrors the stage-2 vs stage-3 layering: emulators own their peers and can stamp the id; surrogates respect the id semantic boundary.

      Falls back to peer.setId(name) without disambiguation when called outside a UI context — pre-attach construction without a current UI is rare but possible, and an unsuffixed id is the same as the first- claim outcome.

    • toVaadinIconComponent

      public static com.vaadin.flow.component.Component toVaadinIconComponent(String className, Icon icon)
      Convert a Icon to a Vaadin Component the peer can install via Button.setIcon(Component). Two icon-rendering paths funnel through here:
      • Path 1 — ImageIcon: raster encoded as PNG and wrapped in a Vaadin Image via Icons.imageIconToVaadinImage(ImageIcon).
      • Path 2 (external) — VaadinIconAdapter: fresh VaadinIcon-glyph component built per call. Reaches us when migrated code sourced an icon via UIManager.getIcon(Object).
      Other Icon impls log via onUnimplemented(String, String, Object...) and return null (Path 3 — offscreen-paint of arbitrary Icon impls — is permanently deferred per R_match_swing_errors sub-bucket (b)).

      null icon → null (callers interpret as "remove icon"). The className argument feeds the WARN message for non-recognized Icon inputs so migration logs identify which emulator surfaced the dropped icon.

    • getEmulator

      public static Component getEmulator(com.vaadin.flow.component.Component peer)
      The emulator (Component) whose peer is the given Vaadin component. Throws NullPointerException if the peer has no registered emulator — an internal invariant check (see D_peer_emulator_mapping). D_never_fail_on_gaps scopes to emulation incompleteness; this is an internal-plumbing invariant and unrelated.
    • emulatorFor

      public static Component emulatorFor(com.vaadin.flow.component.Component peer)
      Lenient, walking variant of getEmulator(Component): the emulator owning peer, or the one owning its nearest Vaadin ancestor, or null when no ancestor is a registered peer. Never throws.

      Needed wherever the starting point is a Vaadin component we did not choose — com.vaadin.swingbridge.surrogates.FocusTracker's focus owner is the canonical case: the browser focuses the inner field of a composite peer (an SJSpinner's NumberField), which is not itself a registered peer, so the answer sits one or two levels up. Focus landing on a Vaadin component the app added outside vaadinx has no emulator at all, and null is the honest answer rather than getEmulator(Component)'s invariant failure.

    • getDialogs

      public static List<com.vaadin.flow.component.dialog.Dialog> getDialogs()
      Every Dialog attached to the current UI's component graph — the Vaadin-side view of our Windows. A Dialog that hasn't been opened (not in the tree) isn't returned. No retained list: the graph is the source of truth, which avoids the classic registry leak where disposed Windows linger in a static list.

      Returns an empty list when there's no current UI — the typical case for code running off-thread. Callers get "no windows" rather than an exception, matching D_never_fail_on_gaps's never-fail stance for queries.

      We walk Component.getChildren() recursively rather than calling ComponentUtil.findComponents(Element, Consumer), because the latter short-circuits on the first component it finds below a given element — starting from UI it would accept UI and stop without ever reaching the Dialog children.

    • getWindows

      public static List<Window> getWindows()
      Every Window this app instance has constructed and not yet let go of, in construction order — the source of truth behind Window.getWindows(), Window.getOwnerlessWindows() and Frame.getFrames().

      Creation-based, as AWT is. A Window appears here from its constructor and stays while anything references it — never shown, hidden again, even dispose()d. Displayability only decides whether the registry itself is what keeps it alive: while displayable it is pinned strongly, so a frame the app has dropped every reference to is still enumerable exactly as on the desktop. Details, and why the earlier walk-the-UI-graph shape was dropped, live on WindowRegistry.

      Returns an empty list when no app store resolves on this thread, matching getDialogs()'s never-fail stance for queries (D_never_fail_on_gaps).

    • onWindowCreated

      public static void onWindowCreated(Window w)
      The Window.init() -> addToWindowList() edge, called from Window's peer-injection constructor. Silently does nothing off any app thread, per WindowRegistry.current().
    • onWindowDisplayable

      public static void onWindowDisplayable(Window w)
      The addNotify() -> allWindows.add(this) edge.
    • onWindowUndisplayable

      public static void onWindowUndisplayable(Window w)
      The removeNotify() -> allWindows.remove(this) edge, and the one place a Window stops being displayable — so also where AutoShutdown learns the app may be over.