This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 723435d231 make the lint plugin session aware in Hop Web, fixes #8232
(#8236)
723435d231 is described below
commit 723435d231ddf8b2dc144d8b2f08923793e2c1ed
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Wed Sep 2 14:11:08 2026 +0200
make the lint plugin session aware in Hop Web, fixes #8232 (#8236)
---
.../org/apache/hop/lint/BackgroundLintService.java | 74 ++--
.../org/apache/hop/lint/ExplorerLintGuiPlugin.java | 386 +++++++++++----------
.../apache/hop/lint/LintCanvasOverlayRefresh.java | 24 +-
.../org/apache/hop/lint/LintNavigationHelper.java | 2 +-
.../apache/hop/lint/LintProblemsBarManager.java | 51 ++-
.../org/apache/hop/lint/LintProblemsBarPlugin.java | 2 +-
.../org/apache/hop/lint/LintResultsManager.java | 27 +-
.../java/org/apache/hop/lint/LintResultsPanel.java | 5 +-
.../java/org/apache/hop/lint/LintResultsUi.java | 2 +-
.../org/apache/hop/lint/LintStatusFilePainter.java | 163 ++++-----
.../org/apache/hop/lint/LinterConfigPlugin.java | 2 +-
.../java/org/apache/hop/lint/LinterGuiPlugin.java | 146 ++++----
.../org/apache/hop/lint/LinterProgressDialog.java | 6 +-
.../apache/hop/lint/PreCommitLintExtension.java | 2 +-
.../hop/lint/ProjectLoadedLintExtension.java | 2 +-
.../apache/hop/lint/WorkflowVerifyLintService.java | 2 +-
16 files changed, 487 insertions(+), 409 deletions(-)
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/BackgroundLintService.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/BackgroundLintService.java
index 3437461888..63753adb0c 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/BackgroundLintService.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/BackgroundLintService.java
@@ -33,6 +33,7 @@ import org.apache.hop.core.util.Utils;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.ui.hopgui.BackgroundThreadFacade;
import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
import org.apache.hop.ui.hopgui.file.shared.HopGuiAbstractGraph;
@@ -46,29 +47,47 @@ public class BackgroundLintService {
private static final ILogChannel log = LogChannel.GENERAL;
private static final int DEFERRED_CHECK_DELAY_MS = 500;
- private static BackgroundLintService instance;
+ /**
+ * Shared by every session: this is CPU bound file parsing, and a pool per
Hop Web session would
+ * multiply the threads by the number of people logged in.
+ */
+ private static final ExecutorService executor =
+ Executors.newFixedThreadPool(
+ Math.max(2, Runtime.getRuntime().availableProcessors() / 2),
+ r -> {
+ Thread t = new Thread(r, "HopLinter-Background");
+ t.setDaemon(true);
+ return t;
+ });
+
+ /** Used when there is no GUI to own this: the command line, and unit tests.
*/
+ private static BackgroundLintService fallback;
- private final ExecutorService executor;
private final LintCheckTracker tracker;
private final Map<String, AtomicInteger> deferredGenerations = new
ConcurrentHashMap<>();
private BackgroundLintService() {
- this.executor =
- Executors.newFixedThreadPool(
- Math.max(2, Runtime.getRuntime().availableProcessors() / 2),
- r -> {
- Thread t = new Thread(r, "HopLinter-Background");
- t.setDaemon(true);
- return t;
- });
this.tracker = new LintCheckTracker();
}
- public static synchronized BackgroundLintService getInstance() {
- if (instance == null) {
- instance = new BackgroundLintService();
+ /**
+ * The service belonging to the GUI that asks for it.
+ *
+ * <p>Hop Web serves many people from one JVM, each with their own editors
and their own findings,
+ * so what has already been linted and which editors are waiting for an
answer is per session. The
+ * desktop has one HopGui and therefore one of these.
+ */
+ public static BackgroundLintService getInstance() {
+ HopGui hopGui = HopGui.peekInstance();
+ if (hopGui != null) {
+ return hopGui.getSessionSingleton(BackgroundLintService.class,
BackgroundLintService::new);
+ }
+ synchronized (BackgroundLintService.class) {
+ if (fallback == null) {
+ fallback = new BackgroundLintService();
+ }
+ return fallback;
}
- return instance;
}
public LintCheckTracker getTracker() {
@@ -95,7 +114,7 @@ public class BackgroundLintService {
LintProblemsBarManager.getInstance().updateProblemsBar(filePath);
return;
}
- executor.submit(() -> lintFileInternal(filePath, graphId));
+ submit(() -> lintFileInternal(filePath, graphId));
}
public void scheduleGraphLint(HopGuiAbstractGraph graph, boolean force) {
@@ -125,7 +144,7 @@ public class BackgroundLintService {
if (!isEnabled() || Utils.isEmpty(projectPath)) {
return;
}
- executor.submit(
+ submit(
() -> {
try {
HopLinter linter = new HopLinter();
@@ -184,14 +203,14 @@ public class BackgroundLintService {
return;
}
- HopGui hopGuiForSnapshot = HopGui.getInstance();
+ HopGui hopGuiForSnapshot = HopGui.peekInstance();
EditorSnapshot snapshot =
snapshotOf(graph, hopGuiForSnapshot != null ?
hopGuiForSnapshot.getVariables() : null);
- executor.submit(
+ submit(
() -> {
try {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
IHopMetadataProvider metadataProvider =
hopGui != null ? hopGui.getMetadataProvider() : null;
IVariables variables = hopGui != null ? hopGui.getVariables() :
null;
@@ -296,7 +315,7 @@ public class BackgroundLintService {
private void lintFileInternal(String filePath, String graphId) {
try {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
IHopMetadataProvider metadataProvider = hopGui != null ?
hopGui.getMetadataProvider() : null;
IVariables variables = hopGui != null ? hopGui.getVariables() : null;
@@ -331,8 +350,19 @@ public class BackgroundLintService {
return linter.lintFile(filePath, metadataProvider, variables);
}
- public void shutdown() {
- executor.shutdown();
+ /**
+ * Hand work to the pool without losing the session it belongs to.
+ *
+ * <p>Everything the work then reaches for - the HopGui of the person who
asked, its metadata
+ * provider, the editors to report into - belongs to a RAP {@code UISession}
in Hop Web, and a
+ * pooled thread has none of its own: the lookup fails with "Invalid thread
access" and the lint
+ * dies before it starts. {@link BackgroundThreadFacade} carries the session
of the thread that
+ * schedules the work over to the thread that runs it, and is a no-op on the
desktop.
+ *
+ * <p>Call this from the UI thread: that is where the session is read.
+ */
+ private static void submit(Runnable work) {
+ executor.submit(BackgroundThreadFacade.bind(work));
}
private boolean includeMetadataInGuiLint() {
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/ExplorerLintGuiPlugin.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/ExplorerLintGuiPlugin.java
index c66c9b0cd3..d86bac1c7f 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/ExplorerLintGuiPlugin.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/ExplorerLintGuiPlugin.java
@@ -32,6 +32,7 @@ import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.core.dialog.MessageBox;
+import org.apache.hop.ui.hopgui.BackgroundThreadFacade;
import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
@@ -49,33 +50,64 @@ import org.eclipse.swt.widgets.Display;
public class ExplorerLintGuiPlugin {
private static final ILogChannel log = LogChannel.GENERAL;
- private static ExplorerLintGuiPlugin instance;
- private static LintStatusFilePainter filePainter;
+ /** Used when there is no GUI to own this: unit tests. */
+ private static ExplorerLintGuiPlugin fallback;
+
+ private LintStatusFilePainter filePainter;
+
+ /**
+ * The plugin state of the GUI that asks for it. Hop Web serves many people
from one JVM, each
+ * with an Explorer of their own; the desktop has one HopGui and therefore
one of these.
+ */
public static ExplorerLintGuiPlugin getInstance() {
- if (instance == null) {
- instance = new ExplorerLintGuiPlugin();
+ HopGui hopGui = HopGui.peekInstance();
+ if (hopGui != null) {
+ return hopGui.getSessionSingleton(ExplorerLintGuiPlugin.class,
ExplorerLintGuiPlugin::new);
+ }
+ synchronized (ExplorerLintGuiPlugin.class) {
+ if (fallback == null) {
+ fallback = new ExplorerLintGuiPlugin();
+ }
+ return fallback;
}
- return instance;
}
public ExplorerLintGuiPlugin() {
- instance = this;
+ // The GUI plugin registry builds one of these for its callbacks, and
getInstance() builds one
+ // per session. Both go through the static helpers below, so this holds
nothing itself.
}
@GuiCallback(callbackId =
ExplorerPerspective.GUI_TOOLBAR_CREATED_CALLBACK_ID)
public void registerExplorerPaintListener() {
- if (filePainter == null) {
- filePainter = new LintStatusFilePainter();
- }
+ LintStatusFilePainter painter = getFilePainter();
ExplorerPerspective perspective = ExplorerPerspective.getInstance();
- if (!perspective.getFilePaintListeners().contains(filePainter)) {
- perspective.getFilePaintListeners().add(filePainter);
+ if (!perspective.getFilePaintListeners().contains(painter)) {
+ perspective.getFilePaintListeners().add(painter);
}
}
- /** Get the file painter instance */
+ /**
+ * The painter of the GUI that asks for it.
+ *
+ * <p>It caches the icons it composites, and an image belongs to the display
that created it: in
+ * Hop Web a shared painter hands one session images another session's
display disposed, which SWT
+ * reports as "Argument not valid" (issue #3508). It also remembers the
Explorer tree it last
+ * painted, and that tree is one session's widget.
+ */
public static LintStatusFilePainter getFilePainter() {
+ return getInstance().filePainter();
+ }
+
+ /**
+ * Built here rather than by the session singleton map itself: the painter
subscribes to that
+ * session's lint results, and asking for one singleton while another is
being built is a
+ * recursive update of the map they both live in.
+ */
+ private synchronized LintStatusFilePainter filePainter() {
+ if (filePainter == null) {
+ filePainter = new LintStatusFilePainter();
+ }
return filePainter;
}
@@ -85,12 +117,13 @@ public class ExplorerLintGuiPlugin {
* the painter has not yet been attached.
*/
public static void refreshExplorerIcons() {
+ LintStatusFilePainter painter = getFilePainter();
Display.getDefault()
.asyncExec(
() -> {
try {
- if (filePainter != null) {
- filePainter.repaintExplorerIcons();
+ if (painter != null) {
+ painter.repaintExplorerIcons();
return;
}
ExplorerPerspective perspective =
HopGui.getExplorerPerspective();
@@ -223,7 +256,7 @@ public class ExplorerLintGuiPlugin {
}
private static void runLintOnFile(String filePath) {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui == null) {
showMessage("Hop GUI Not Available", "Could not access Hop GUI
instance.", SWT.ICON_ERROR);
return;
@@ -274,38 +307,36 @@ public class ExplorerLintGuiPlugin {
final HopGuiAbstractGraph graph = (HopGuiAbstractGraph) handler;
final IHopFileTypeHandler openHandler = handler;
- Thread linterThread =
- new Thread(
- () -> {
- try {
- List<LintResult> results =
- lintFileResults(normalizedPath, openHandler,
metadataProvider, variables);
-
-
LintResultsManager.getInstance().updateResultsForFile(normalizedPath, results);
-
- Display.getDefault()
- .asyncExec(
- () -> {
- // Populates the editor Problems tab + toolbar badge
(UI thread).
-
LintProblemsBarManager.getInstance().updateProblemsBar(normalizedPath);
- refreshExplorerIcons();
- LintResultsUi.logSummary(results, new
File(normalizedPath).getName());
- // Bring the Problems tab to the front once it has
been populated.
- Display.getDefault().asyncExec(() ->
bringProblemsTabToFront(graph));
- });
- } catch (Exception e) {
- log.logError("Error during file linting: " + e.getMessage(),
e);
- Display.getDefault()
- .asyncExec(
- () ->
- showErrorDialog(
- "Linting Error",
- "An error occurred during linting: " +
e.getMessage(),
- e));
- }
- },
- "HopLinter-File");
- linterThread.start();
+ BackgroundThreadFacade.start(
+ () -> {
+ try {
+ List<LintResult> results =
+ lintFileResults(normalizedPath, openHandler, metadataProvider,
variables);
+
+
LintResultsManager.getInstance().updateResultsForFile(normalizedPath, results);
+
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+ // Populates the editor Problems tab + toolbar badge (UI
thread).
+
LintProblemsBarManager.getInstance().updateProblemsBar(normalizedPath);
+ refreshExplorerIcons();
+ LintResultsUi.logSummary(results, new
File(normalizedPath).getName());
+ // Bring the Problems tab to the front once it has been
populated.
+ Display.getDefault().asyncExec(() ->
bringProblemsTabToFront(graph));
+ });
+ } catch (Exception e) {
+ log.logError("Error during file linting: " + e.getMessage(), e);
+ Display.getDefault()
+ .asyncExec(
+ () ->
+ showErrorDialog(
+ "Linting Error",
+ "An error occurred during linting: " +
e.getMessage(),
+ e));
+ }
+ },
+ "HopLinter-File");
}
/** Bring the editor's check/Problems tab to the front, creating it if
needed. */
@@ -353,36 +384,34 @@ public class ExplorerLintGuiPlugin {
final IVariables variables = hopGui.getVariables();
final IHopMetadataProvider metadataProvider = hopGui.getMetadataProvider();
- Thread linterThread =
- new Thread(
- () -> {
- try {
- List<LintResult> results =
- lintFileResults(normalizedPath, openHandler,
metadataProvider, variables);
-
-
LintResultsManager.getInstance().updateResultsForFile(normalizedPath, results);
-
- Display.getDefault()
- .asyncExec(
- () -> {
-
LintProblemsBarManager.getInstance().updateProblemsBar(normalizedPath);
- refreshExplorerIcons();
- LintResultsUi.logSummary(results, new
File(normalizedPath).getName());
- LintResultsUi.showResultsForFile(normalizedPath);
- });
- } catch (Exception e) {
- log.logError("Error during file linting: " + e.getMessage(),
e);
- Display.getDefault()
- .asyncExec(
- () ->
- showErrorDialog(
- "Linting Error",
- "An error occurred during linting: " +
e.getMessage(),
- e));
- }
- },
- "HopLinter-File");
- linterThread.start();
+ BackgroundThreadFacade.start(
+ () -> {
+ try {
+ List<LintResult> results =
+ lintFileResults(normalizedPath, openHandler, metadataProvider,
variables);
+
+
LintResultsManager.getInstance().updateResultsForFile(normalizedPath, results);
+
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+
LintProblemsBarManager.getInstance().updateProblemsBar(normalizedPath);
+ refreshExplorerIcons();
+ LintResultsUi.logSummary(results, new
File(normalizedPath).getName());
+ LintResultsUi.showResultsForFile(normalizedPath);
+ });
+ } catch (Exception e) {
+ log.logError("Error during file linting: " + e.getMessage(), e);
+ Display.getDefault()
+ .asyncExec(
+ () ->
+ showErrorDialog(
+ "Linting Error",
+ "An error occurred during linting: " +
e.getMessage(),
+ e));
+ }
+ },
+ "HopLinter-File");
}
private static List<LintResult> lintFileResults(
@@ -456,7 +485,7 @@ public class ExplorerLintGuiPlugin {
}
private static void runLintOnFolder(String folderPath) {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui == null) {
showMessage("Hop GUI Not Available", "Could not access Hop GUI
instance.", SWT.ICON_ERROR);
return;
@@ -467,116 +496,113 @@ public class ExplorerLintGuiPlugin {
final LinterProgressDialog progressDialog = new
LinterProgressDialog(hopGui.getShell());
- Thread linterThread =
- new Thread(
- () -> {
- try {
- HopLinter linter = new HopLinter();
- linter.loadConfigurationForContext(new File(folderPath));
-
- List<String> hopFilePaths =
linter.findLintableFiles(folderPath, true);
-
- if (hopFilePaths.isEmpty()) {
- Display.getDefault()
- .asyncExec(
- () -> {
- progressDialog.close();
- showMessage(
- "No Hop Files",
- "No .hpl or .hwf files found in the selected
folder.",
- SWT.ICON_INFORMATION);
- });
- return;
- }
+ BackgroundThreadFacade.start(
+ () -> {
+ try {
+ HopLinter linter = new HopLinter();
+ linter.loadConfigurationForContext(new File(folderPath));
+
+ List<String> hopFilePaths = linter.findLintableFiles(folderPath,
true);
+
+ if (hopFilePaths.isEmpty()) {
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+ progressDialog.close();
+ showMessage(
+ "No Hop Files",
+ "No .hpl or .hwf files found in the selected
folder.",
+ SWT.ICON_INFORMATION);
+ });
+ return;
+ }
+
+ progressDialog.updateProgress(
+ "Found " + hopFilePaths.size() + " files to analyze", 0,
hopFilePaths.size());
+
+ Display.getDefault().asyncExec(progressDialog::show);
+
+ List<LintResult> results = new java.util.ArrayList<>();
+ int processedFilesCount = 0;
+
+ for (String filePath : hopFilePaths) {
+ File file = new File(filePath);
+ if (progressDialog.isCancelled()) {
+ log.logDetailed("Folder linting cancelled by user");
+ return;
+ }
+ try {
progressDialog.updateProgress(
- "Found " + hopFilePaths.size() + " files to analyze", 0,
hopFilePaths.size());
-
- Display.getDefault().asyncExec(progressDialog::show);
-
- List<LintResult> results = new java.util.ArrayList<>();
- int processedFilesCount = 0;
-
- for (String filePath : hopFilePaths) {
- File file = new File(filePath);
- if (progressDialog.isCancelled()) {
- log.logDetailed("Folder linting cancelled by user");
- return;
- }
-
- try {
- progressDialog.updateProgress(
- "Processing: " + file.getName(), processedFilesCount,
hopFilePaths.size());
- String normalizedPath =
LintPathUtils.normalizePath(file.getAbsolutePath());
- List<LintResult> fileResults;
- if (normalizedPath.toLowerCase().endsWith(".hpl")) {
- PipelineMeta pipelineMeta =
- new PipelineMeta(file.getAbsolutePath(),
metadataProvider, variables);
- fileResults =
- PipelineLintResultsBuilder.build(
- pipelineMeta, normalizedPath, metadataProvider,
variables);
- } else if
(HopMetadataFileLoader.isMetadataJsonFile(normalizedPath)) {
- fileResults = linter.processFile(file, metadataProvider,
variables);
- } else {
- fileResults = linter.processFile(file, metadataProvider,
variables);
- }
- results.addAll(fileResults);
- LintResultsManager.getInstance()
- .updateResultsForFile(normalizedPath, fileResults);
- processedFilesCount++;
- } catch (Exception e) {
- log.logError("Error processing file: " +
file.getAbsolutePath(), e);
- LintResult errorResult =
- new LintResult(
- "SYSTEM-001",
- "File Processing Error",
- "ERROR",
- "Failed to process file: " + e.getMessage(),
-
LintPathUtils.normalizePath(file.getAbsolutePath()));
- results.add(errorResult);
- LintResultsManager.getInstance()
- .updateResultsForFile(
-
LintPathUtils.normalizePath(file.getAbsolutePath()),
- List.of(errorResult));
- processedFilesCount++;
- }
+ "Processing: " + file.getName(), processedFilesCount,
hopFilePaths.size());
+ String normalizedPath =
LintPathUtils.normalizePath(file.getAbsolutePath());
+ List<LintResult> fileResults;
+ if (normalizedPath.toLowerCase().endsWith(".hpl")) {
+ PipelineMeta pipelineMeta =
+ new PipelineMeta(file.getAbsolutePath(),
metadataProvider, variables);
+ fileResults =
+ PipelineLintResultsBuilder.build(
+ pipelineMeta, normalizedPath, metadataProvider,
variables);
+ } else if
(HopMetadataFileLoader.isMetadataJsonFile(normalizedPath)) {
+ fileResults = linter.processFile(file, metadataProvider,
variables);
+ } else {
+ fileResults = linter.processFile(file, metadataProvider,
variables);
}
-
- progressDialog.setComplete("Completed. Found " +
results.size() + " issues");
-
- LintProblemsBarManager.getInstance().refreshAllOpenEditors();
-
- Display.getDefault()
- .asyncExec(
- () -> {
- progressDialog.close();
- refreshExplorerIcons();
- LintResultsUi.logSummary(results, new
File(folderPath).getName());
- // A folder has no editor to put findings in, so
this is one of the cases
- // the results window exists for. Without this the
run finished with
- // nothing to show for it but a line in the log.
- LintResultsUi.showResultsForFolder(folderPath);
- });
-
+ results.addAll(fileResults);
+
LintResultsManager.getInstance().updateResultsForFile(normalizedPath,
fileResults);
+ processedFilesCount++;
} catch (Exception e) {
- log.logError("Error during folder linting: " + e.getMessage(),
e);
- Display.getDefault()
- .asyncExec(
- () -> {
- progressDialog.close();
- showErrorDialog(
- "Linting Error",
- "An error occurred during linting: " +
e.getMessage(),
- e);
- });
+ log.logError("Error processing file: " +
file.getAbsolutePath(), e);
+ LintResult errorResult =
+ new LintResult(
+ "SYSTEM-001",
+ "File Processing Error",
+ "ERROR",
+ "Failed to process file: " + e.getMessage(),
+ LintPathUtils.normalizePath(file.getAbsolutePath()));
+ results.add(errorResult);
+ LintResultsManager.getInstance()
+ .updateResultsForFile(
+ LintPathUtils.normalizePath(file.getAbsolutePath()),
List.of(errorResult));
+ processedFilesCount++;
}
- });
- linterThread.start();
+ }
+
+ progressDialog.setComplete("Completed. Found " + results.size() +
" issues");
+
+ LintProblemsBarManager.getInstance().refreshAllOpenEditors();
+
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+ progressDialog.close();
+ refreshExplorerIcons();
+ LintResultsUi.logSummary(results, new
File(folderPath).getName());
+ // A folder has no editor to put findings in, so this is
one of the cases
+ // the results window exists for. Without this the run
finished with
+ // nothing to show for it but a line in the log.
+ LintResultsUi.showResultsForFolder(folderPath);
+ });
+
+ } catch (Exception e) {
+ log.logError("Error during folder linting: " + e.getMessage(), e);
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+ progressDialog.close();
+ showErrorDialog(
+ "Linting Error",
+ "An error occurred during linting: " +
e.getMessage(),
+ e);
+ });
+ }
+ },
+ "HopLinter-Folder");
}
private static void showMessage(String title, String message, int style) {
log.logBasic(title + ": " + message);
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui != null && hopGui.getShell() != null) {
MessageBox box = new MessageBox(hopGui.getShell(), style | SWT.OK);
box.setText(title);
@@ -586,7 +612,7 @@ public class ExplorerLintGuiPlugin {
}
private static void showErrorDialog(String title, String message, Exception
e) {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui != null && hopGui.getShell() != null) {
new ErrorDialog(hopGui.getShell(), title, message, e);
}
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintCanvasOverlayRefresh.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintCanvasOverlayRefresh.java
index bec9662df3..d54b1e7439 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintCanvasOverlayRefresh.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintCanvasOverlayRefresh.java
@@ -16,6 +16,9 @@
*/
package org.apache.hop.lint;
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
import org.apache.hop.ui.hopgui.file.shared.HopGuiAbstractGraph;
@@ -28,20 +31,25 @@ import org.eclipse.swt.widgets.Display;
*/
public final class LintCanvasOverlayRefresh {
- private static volatile boolean registered;
+ /**
+ * The results this listens to, one entry per set of findings we have
subscribed to.
+ *
+ * <p>A single flag registered with the first session's results and left
every later session
+ * without canvas overlays: Hop Web gives each of them findings of their
own. Weakly held so a
+ * session that goes away takes its entry with it.
+ */
+ private static final Set<LintResultsManager> registered =
+ Collections.newSetFromMap(Collections.synchronizedMap(new
WeakHashMap<>()));
private LintCanvasOverlayRefresh() {}
public static void ensureRegistered() {
- if (registered) {
- return;
- }
+ LintResultsManager results = LintResultsManager.getInstance();
synchronized (LintCanvasOverlayRefresh.class) {
- if (registered) {
+ if (!registered.add(results)) {
return;
}
-
LintResultsManager.getInstance().addListener(LintCanvasOverlayRefresh::onResultsUpdated);
- registered = true;
+ results.addListener(LintCanvasOverlayRefresh::onResultsUpdated);
}
}
@@ -50,7 +58,7 @@ public final class LintCanvasOverlayRefresh {
}
public static void redrawOpenGraphs() {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui == null) {
return;
}
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintNavigationHelper.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintNavigationHelper.java
index 5c5359cf2b..332fbb370d 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintNavigationHelper.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintNavigationHelper.java
@@ -35,7 +35,7 @@ public final class LintNavigationHelper {
return;
}
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui == null) {
return;
}
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintProblemsBarManager.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintProblemsBarManager.java
index 42752f63b8..640b4012be 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintProblemsBarManager.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintProblemsBarManager.java
@@ -21,6 +21,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hop.core.logging.ILogChannel;
import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.file.shared.HopGuiAbstractGraph;
import org.eclipse.swt.widgets.Display;
@@ -33,18 +34,33 @@ public class LintProblemsBarManager {
private static final int EDITOR_WAIT_ATTEMPTS = 12;
private static final int EDITOR_WAIT_INTERVAL_MS = 250;
- private static LintProblemsBarManager instance;
+
+ /** Used when there is no GUI to own the editors: the command line, and unit
tests. */
+ private static LintProblemsBarManager fallback;
private final Map<String, HopGuiAbstractGraph> graphsById = new
ConcurrentHashMap<>();
private final Map<String, String> filePathByGraphId = new
ConcurrentHashMap<>();
private LintProblemsBarManager() {}
- public static synchronized LintProblemsBarManager getInstance() {
- if (instance == null) {
- instance = new LintProblemsBarManager();
+ /**
+ * The editors of the GUI that asks for them.
+ *
+ * <p>This holds widgets, and in Hop Web the widgets of one session may not
be touched from
+ * another: a shared map had {@link #refreshAllOpenEditors()} reaching into
every session that
+ * happened to be logged in. The desktop has one HopGui and therefore one of
these.
+ */
+ public static LintProblemsBarManager getInstance() {
+ HopGui hopGui = HopGui.peekInstance();
+ if (hopGui != null) {
+ return hopGui.getSessionSingleton(LintProblemsBarManager.class,
LintProblemsBarManager::new);
+ }
+ synchronized (LintProblemsBarManager.class) {
+ if (fallback == null) {
+ fallback = new LintProblemsBarManager();
+ }
+ return fallback;
}
- return instance;
}
public void attachToGraph(HopGuiAbstractGraph graph) {
@@ -123,8 +139,12 @@ public class LintProblemsBarManager {
// This touches SWT widgets, so make sure it runs on the UI thread
regardless of which
// thread the caller is on (background lint threads call this too).
if (Display.getCurrent() == null) {
- Display display = Display.getDefault();
+ Display display = sessionDisplay();
if (display == null || display.isDisposed()) {
+ log.logDetailed(
+ "Not syncing the Problems tab for "
+ + filePath
+ + ": this thread has no display to do it on");
return;
}
display.asyncExec(() -> updateProblemsBar(filePath));
@@ -161,6 +181,25 @@ public class LintProblemsBarManager {
display.timerExec(EDITOR_WAIT_INTERVAL_MS, () ->
updateProblemsBar(filePath, attempt + 1));
}
+ /**
+ * The display to get onto the UI thread with.
+ *
+ * <p>An editor we already know about answers first: {@code
Display.getDefault()} only knows the
+ * session bound to the calling thread, which is nothing at all on a thread
that was started
+ * without one - and in Hop Web the wrong session's display would be worse
than none.
+ */
+ private Display sessionDisplay() {
+ for (HopGuiAbstractGraph graph : graphsById.values()) {
+ if (!graph.isDisposed()) {
+ Display display = graph.getDisplay();
+ if (display != null && !display.isDisposed()) {
+ return display;
+ }
+ }
+ }
+ return Display.getDefault();
+ }
+
public void refreshAllOpenEditors() {
for (Map.Entry<String, String> entry : filePathByGraphId.entrySet()) {
updateProblemsBar(entry.getValue());
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintProblemsBarPlugin.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintProblemsBarPlugin.java
index 51a94e260e..21447f928a 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintProblemsBarPlugin.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintProblemsBarPlugin.java
@@ -29,7 +29,7 @@ public class LintProblemsBarPlugin {
public static void showLintResults() {
try {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui != null && hopGui.getShell() != null) {
String filename = LintEditorGraphHelper.getActiveEditorFilename();
if (filename != null) {
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsManager.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsManager.java
index ce5060734a..d101da665a 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsManager.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsManager.java
@@ -24,12 +24,15 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hop.core.logging.ILogChannel;
import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.ui.hopgui.HopGui;
/** Manages lint results and provides access for GUI components */
public class LintResultsManager {
private static final ILogChannel log = LogChannel.GENERAL;
- private static LintResultsManager instance;
+
+ /** Used when there is no GUI to own the findings: the command line, and
unit tests. */
+ private static LintResultsManager fallback;
// Store results by file path for quick lookup
private final Map<String, List<LintResult>> resultsByFile = new
ConcurrentHashMap<>();
@@ -58,11 +61,25 @@ public class LintResultsManager {
// Singleton
}
- public static synchronized LintResultsManager getInstance() {
- if (instance == null) {
- instance = new LintResultsManager();
+ /**
+ * The findings of the GUI that asks for them.
+ *
+ * <p>Findings are what one person's editors show, and Hop Web serves many
of them from one JVM: a
+ * single set of results would let one user's project decide what another
sees, and would send
+ * every session's Problems tab a refresh whenever anybody linted anything.
The desktop has one
+ * HopGui, so there it is still one set.
+ */
+ public static LintResultsManager getInstance() {
+ HopGui hopGui = HopGui.peekInstance();
+ if (hopGui != null) {
+ return hopGui.getSessionSingleton(LintResultsManager.class,
LintResultsManager::new);
+ }
+ synchronized (LintResultsManager.class) {
+ if (fallback == null) {
+ fallback = new LintResultsManager();
+ }
+ return fallback;
}
- return instance;
}
/**
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsPanel.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsPanel.java
index ec4f900bb9..c5c4fec09c 100644
--- a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsPanel.java
+++ b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsPanel.java
@@ -30,7 +30,6 @@ import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
@@ -174,7 +173,9 @@ public class LintResultsPanel extends Composite implements
LintResultsManager.Li
}
private void refreshResults() {
- Display.getDefault()
+ // The panel's own display, not Display.getDefault(): results arrive on
lint threads, and in
+ // Hop Web a thread that was started without a session has no default
display to find.
+ getDisplay()
.asyncExec(
() -> {
if (isDisposed()) {
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsUi.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsUi.java
index 40ae32f85d..43b281e68b 100644
--- a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsUi.java
+++ b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintResultsUi.java
@@ -66,7 +66,7 @@ public final class LintResultsUi {
}
public static void showResultsForFile(String filePath) {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui == null || hopGui.getShell() == null) {
return;
}
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintStatusFilePainter.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintStatusFilePainter.java
index 062a1b4a02..60043a9839 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintStatusFilePainter.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LintStatusFilePainter.java
@@ -19,12 +19,11 @@ package org.apache.hop.lint;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.hop.core.logging.ILogChannel;
import org.apache.hop.core.logging.LogChannel;
import org.apache.hop.core.util.Utils;
+import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
import
org.apache.hop.ui.hopgui.perspective.explorer.IExplorerFilePaintListener;
@@ -48,23 +47,23 @@ public class LintStatusFilePainter implements
IExplorerFilePaintListener {
private static final String BASE_ICON_KEY = "lintBaseIcon";
private static final String APPLIED_STATUS_KEY = "lintAppliedStatus";
+ /** Size of the status badge, matching the small icons the Explorer tree
draws. */
+ private static final int BADGE_SIZE = 12;
+
private final Map<String, LintStatus> fileStatusCache = new
ConcurrentHashMap<>();
private final Map<String, Long> cacheTimestamps = new ConcurrentHashMap<>();
// Bounded cache of composited (base icon + badge) images, keyed by
base/badge identity.
// Reused across paints and tree items so we never allocate a new Image per
paint.
private final Map<String, Image> compositeIconCache = new
ConcurrentHashMap<>();
- private final ScheduledExecutorService cleanupExecutor =
- Executors.newSingleThreadScheduledExecutor(
- r -> {
- Thread t = new Thread(r, "LintStatusFilePainter-Cleanup");
- t.setDaemon(true);
- return t;
- });
-
- private Image errorIcon;
- private Image warningIcon;
- private Image cleanIcon;
+
+ /**
+ * The display of the GUI this painter belongs to, read when it is built on
the UI thread.
+ *
+ * <p>{@code Display.getDefault()} answers for the session bound to the
calling thread, and the
+ * results this repaints for arrive on lint threads that may have none.
+ */
+ private final Display display;
// Last Explorer tree we painted into; lets us repaint icons on result
changes without a full
// perspective.refresh() (which rebuilds the tree and loses
selection/expansion state).
@@ -78,66 +77,45 @@ public class LintStatusFilePainter implements
IExplorerFilePaintListener {
}
public LintStatusFilePainter() {
- initializeIcons();
+ this.display = Display.getCurrent() != null ? Display.getCurrent() :
Display.getDefault();
updateFileStatusCache();
LintResultsManager.getInstance()
.addListener(
() -> {
updateFileStatusCache();
- Display display = Display.getDefault();
if (display != null && !display.isDisposed()) {
display.asyncExec(this::repaintExplorerIcons);
}
});
- // Start periodic cache cleanup
- startCacheCleanupScheduler();
-
log.logBasic("Lint Status File Painter initialized");
}
- private void initializeIcons() {
- Display display = Display.getDefault();
- if (display != null && !display.isDisposed()) {
- try {
- errorIcon = new Image(display, 12, 12);
- org.eclipse.swt.graphics.GC gc = new
org.eclipse.swt.graphics.GC(errorIcon);
- gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
- gc.fillOval(0, 0, 11, 11);
- gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
- org.eclipse.swt.graphics.Font smallFont =
- new org.eclipse.swt.graphics.Font(display, "Arial", 8, SWT.BOLD);
- gc.setFont(smallFont);
- gc.drawString("!", 4, -1, true);
- smallFont.dispose();
- gc.dispose();
-
- warningIcon = new Image(display, 12, 12);
- gc = new org.eclipse.swt.graphics.GC(warningIcon);
- gc.setBackground(display.getSystemColor(SWT.COLOR_YELLOW));
- int[] triangle = {6, 0, 0, 11, 11, 11};
- gc.fillPolygon(triangle);
- gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
- smallFont = new org.eclipse.swt.graphics.Font(display, "Arial", 8,
SWT.BOLD);
- gc.setFont(smallFont);
- gc.drawString("!", 4, 2, true);
- smallFont.dispose();
- gc.dispose();
-
- cleanIcon = new Image(display, 12, 12);
- gc = new org.eclipse.swt.graphics.GC(cleanIcon);
- gc.setBackground(display.getSystemColor(SWT.COLOR_GREEN));
- gc.fillOval(0, 0, 11, 11);
- gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
- smallFont = new org.eclipse.swt.graphics.Font(display, "Arial", 8,
SWT.BOLD);
- gc.setFont(smallFont);
- gc.drawString("✓", 3, -1, true);
- smallFont.dispose();
- gc.dispose();
- } catch (Exception e) {
- log.logError("Error creating lint status icons: " + e.getMessage(), e);
- }
+ /**
+ * The badge for a status, or null when there is none to draw.
+ *
+ * <p>These used to be drawn here into off-screen images with a {@code GC},
which Hop Web cannot
+ * do: RWT only draws on a control, so the very first call failed and the
Explorer showed no lint
+ * status at all. Hop ships the three icons as SVG, and {@link GuiResource}
loads and caches them
+ * per session, which also settles who disposes them - not us.
+ */
+ private Image badgeIcon(LintStatus status) {
+ String location =
+ switch (status) {
+ case ERROR -> "ui/images/error.svg";
+ case WARNING -> "ui/images/warning.svg";
+ case CLEAN -> "ui/images/success.svg";
+ case UNKNOWN -> null;
+ };
+ if (location == null) {
+ return null;
+ }
+ try {
+ return GuiResource.getInstance().getImage(location, BADGE_SIZE,
BADGE_SIZE);
+ } catch (Exception e) {
+ log.logDetailed("No lint status icon available for " + status + ": " +
e.getMessage());
+ return null;
}
}
@@ -203,24 +181,22 @@ public class LintStatusFilePainter implements
IExplorerFilePaintListener {
}
}
+ Image badge = badgeIcon(status);
+ if (badge == null || badge.isDisposed()) {
+ return;
+ }
switch (status) {
case ERROR:
- if (errorIcon != null && !errorIcon.isDisposed()) {
- addOverlayIcon(treeItem, errorIcon, status);
- addLintTooltip(treeItem, name, "Linter errors");
- }
+ addOverlayIcon(treeItem, badge, status);
+ addLintTooltip(treeItem, name, "Linter errors");
break;
case WARNING:
- if (warningIcon != null && !warningIcon.isDisposed()) {
- addOverlayIcon(treeItem, warningIcon, status);
- addLintTooltip(treeItem, name, "Linter warnings");
- }
+ addOverlayIcon(treeItem, badge, status);
+ addLintTooltip(treeItem, name, "Linter warnings");
break;
case CLEAN:
- if (cleanIcon != null && !cleanIcon.isDisposed()) {
- addOverlayIcon(treeItem, cleanIcon, status);
- addLintTooltip(treeItem, name, "No linter issues");
- }
+ addOverlayIcon(treeItem, badge, status);
+ addLintTooltip(treeItem, name, "No linter issues");
break;
default:
break;
@@ -319,8 +295,13 @@ public class LintStatusFilePainter implements
IExplorerFilePaintListener {
return;
}
+ // No composite (Hop Web cannot draw one): leave the file's own icon
alone. The item's
+ // colour already says what the status is, and replacing the icon with a
bare badge would
+ // cost more than it tells.
Image compositeIcon = getOrCreateComposite(base, lintIcon);
- treeItem.setImage(compositeIcon != null ? compositeIcon : lintIcon);
+ if (compositeIcon != null) {
+ treeItem.setImage(compositeIcon);
+ }
treeItem.setData(APPLIED_STATUS_KEY, status);
} catch (Exception e) {
log.logError("Error creating overlay icon: " + e.getMessage(), e);
@@ -344,7 +325,6 @@ public class LintStatusFilePainter implements
IExplorerFilePaintListener {
private Image createCompositeIcon(Image originalIcon, Image lintIcon) {
try {
- Display display = Display.getDefault();
if (display == null || display.isDisposed()) {
return null;
}
@@ -409,17 +389,13 @@ public class LintStatusFilePainter implements
IExplorerFilePaintListener {
return LintEditorGraphHelper.isLintableFilename(filePath);
}
- /** Start the cache cleanup scheduler */
- private void startCacheCleanupScheduler() {
- // Clean up cache every 30 minutes
- cleanupExecutor.scheduleAtFixedRate(
- this::cleanupExpiredCacheEntries,
- CACHE_EXPIRATION_MINUTES,
- CACHE_EXPIRATION_MINUTES,
- TimeUnit.MINUTES);
- }
-
- /** Clean up expired and excess cache entries */
+ /**
+ * Clean up expired and excess cache entries.
+ *
+ * <p>Done when the cache is rebuilt rather than on a timer of its own: a
thread per painter is a
+ * thread per Hop Web session, and one holding a reference to the painter
keeps that session's
+ * images alive long after the session is gone.
+ */
private void cleanupExpiredCacheEntries() {
try {
long currentTime = System.currentTimeMillis();
@@ -484,21 +460,14 @@ public class LintStatusFilePainter implements
IExplorerFilePaintListener {
fileStatusCache.put(normalizedPath, status);
cacheTimestamps.put(normalizedPath, System.currentTimeMillis());
}
+ cleanupExpiredCacheEntries();
} catch (Exception e) {
log.logError("Error updating file status cache: " + e.getMessage(), e);
}
}
public void dispose() {
- if (errorIcon != null && !errorIcon.isDisposed()) {
- errorIcon.dispose();
- }
- if (warningIcon != null && !warningIcon.isDisposed()) {
- warningIcon.dispose();
- }
- if (cleanIcon != null && !cleanIcon.isDisposed()) {
- cleanIcon.dispose();
- }
+ // The badge icons belong to GuiResource, which disposes them with the
session.
// Dispose cached composite images
for (Image composite : compositeIconCache.values()) {
@@ -511,13 +480,5 @@ public class LintStatusFilePainter implements
IExplorerFilePaintListener {
// Clean up cache
fileStatusCache.clear();
cacheTimestamps.clear();
-
- // Shutdown cleanup executor
- cleanupExecutor.shutdown();
- try {
- cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- log.logError("Interrupted while shutting down cache cleanup executor",
e);
- }
}
}
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterConfigPlugin.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterConfigPlugin.java
index 9d1850891e..d4250fbbef 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterConfigPlugin.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterConfigPlugin.java
@@ -436,7 +436,7 @@ public class LinterConfigPlugin implements IConfigOptions,
IGuiPluginCompositeWi
// Try to get project path from Hop variables
try {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui != null && hopGui.getVariables() != null) {
String projectPath = hopGui.getVariables().getVariable("PROJECT_HOME");
if (Utils.isEmpty(projectPath)) {
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterGuiPlugin.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterGuiPlugin.java
index 2a7dbf9fc6..487aa78b91 100644
--- a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterGuiPlugin.java
+++ b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterGuiPlugin.java
@@ -33,6 +33,7 @@ import org.apache.hop.lint.registry.RuleRegistry;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.core.dialog.MessageBox;
+import org.apache.hop.ui.hopgui.BackgroundThreadFacade;
import org.apache.hop.ui.hopgui.HopGui;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
@@ -81,7 +82,7 @@ public class LinterGuiPlugin {
public static void lintProject() {
// Add debug output at the very start
LogChannel.GENERAL.logBasic("Lint Project menu item selected");
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
try {
// Null-safety check for HopGui
@@ -124,79 +125,72 @@ public class LinterGuiPlugin {
final String finalProjectName = projectName != null ? projectName :
"Unknown Project";
// Run linter in background thread
- Thread linterThread =
- new Thread(
- () -> {
- try {
- long lintStartTime = System.currentTimeMillis();
- HopLinter linter = new HopLinter();
- IHopMetadataProvider metadataProvider =
finalHopGui.getMetadataProvider();
-
- List<LintResult> results =
- linter.run(
- finalProjectPath, metadataProvider, finalVariables,
progressDialog);
- long lintEndTime = System.currentTimeMillis();
- long totalLintTime = lintEndTime - lintStartTime;
-
- // Check if cancelled
- if (progressDialog.isCancelled()) {
- LogChannel.GENERAL.logBasic("Linting cancelled by user");
- return;
- }
-
- // Generate rule summary
- Map<String, HopLinter.RuleSummary> ruleSummary =
- linter.generateRuleSummary(results);
-
- // Process and display results on UI thread
- Display.getDefault()
- .asyncExec(
- () -> {
- try {
- // Update results manager for GUI integration
-
LintResultsManager.getInstance().updateResults(results);
-
LintProblemsBarManager.getInstance().refreshAllOpenEditors();
-
- LinterGuiPlugin plugin = new LinterGuiPlugin();
- plugin.displayResults(
- finalHopGui,
- results,
- ruleSummary,
- totalLintTime,
- finalProjectName);
- } catch (Exception e) {
- LogChannel.GENERAL.logError(
- "Error displaying results: " +
e.getMessage(), e);
- }
- });
-
- } catch (Exception e) {
- LogChannel.GENERAL.logError("Error in linter thread: " +
e.getMessage(), e);
- Display.getDefault()
- .asyncExec(
- () -> {
- if (finalHopGui != null && finalHopGui.getShell()
!= null) {
- new ErrorDialog(
- finalHopGui.getShell(),
- "Linting Error",
- "An error occurred while running the linter:
" + e.getMessage(),
- e);
- }
- });
- } finally {
- // Ensure progress dialog closes
- Display.getDefault()
- .asyncExec(
- () -> {
- if (!progressDialog.isComplete()) {
- progressDialog.close();
- }
- });
- }
- });
-
- // Start the thread and show progress dialog
- linterThread.start();
+ BackgroundThreadFacade.start(
+ () -> {
+ try {
+ long lintStartTime = System.currentTimeMillis();
+ HopLinter linter = new HopLinter();
+ IHopMetadataProvider metadataProvider =
finalHopGui.getMetadataProvider();
+
+ List<LintResult> results =
+ linter.run(finalProjectPath, metadataProvider,
finalVariables, progressDialog);
+ long lintEndTime = System.currentTimeMillis();
+ long totalLintTime = lintEndTime - lintStartTime;
+
+ // Check if cancelled
+ if (progressDialog.isCancelled()) {
+ LogChannel.GENERAL.logBasic("Linting cancelled by user");
+ return;
+ }
+
+ // Generate rule summary
+ Map<String, HopLinter.RuleSummary> ruleSummary =
linter.generateRuleSummary(results);
+
+ // Process and display results on UI thread
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+ try {
+ // Update results manager for GUI integration
+
LintResultsManager.getInstance().updateResults(results);
+
LintProblemsBarManager.getInstance().refreshAllOpenEditors();
+
+ LinterGuiPlugin plugin = new LinterGuiPlugin();
+ plugin.displayResults(
+ finalHopGui, results, ruleSummary,
totalLintTime, finalProjectName);
+ } catch (Exception e) {
+ LogChannel.GENERAL.logError(
+ "Error displaying results: " + e.getMessage(),
e);
+ }
+ });
+
+ } catch (Exception e) {
+ LogChannel.GENERAL.logError("Error in linter thread: " +
e.getMessage(), e);
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+ if (finalHopGui != null && finalHopGui.getShell() !=
null) {
+ new ErrorDialog(
+ finalHopGui.getShell(),
+ "Linting Error",
+ "An error occurred while running the linter: " +
e.getMessage(),
+ e);
+ }
+ });
+ } finally {
+ // Ensure progress dialog closes
+ Display.getDefault()
+ .asyncExec(
+ () -> {
+ if (!progressDialog.isComplete()) {
+ progressDialog.close();
+ }
+ });
+ }
+ },
+ "HopLinter-Project");
+
+ // Show the progress dialog while the linter runs
progressDialog.open();
} catch (Exception e) {
@@ -278,7 +272,7 @@ public class LinterGuiPlugin {
String projectName) {
try {
// Get project path from HopGui variables
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
IVariables variables = hopGui.getVariables();
String projectPath = variables.getVariable("PROJECT_HOME");
@@ -434,7 +428,7 @@ public class LinterGuiPlugin {
image = "lint-check.svg")
public static void manageCustomRules() {
try {
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui == null) {
LogChannel.GENERAL.logError("HopGui instance not available");
return;
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterProgressDialog.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterProgressDialog.java
index bf700e799a..2dc4a97c46 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterProgressDialog.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/LinterProgressDialog.java
@@ -144,7 +144,8 @@ public class LinterProgressDialog implements
ProgressCallback {
@Override
public void updateProgress(String message, int completed, int total) {
if (!dialog.isDisposed()) {
- Display.getDefault()
+ dialog
+ .getDisplay()
.asyncExec(
() -> {
if (!dialog.isDisposed()) {
@@ -167,7 +168,8 @@ public class LinterProgressDialog implements
ProgressCallback {
public void setComplete(String message) {
complete = true;
if (!dialog.isDisposed()) {
- Display.getDefault()
+ dialog
+ .getDisplay()
.asyncExec(
() -> {
if (!dialog.isDisposed()) {
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/PreCommitLintExtension.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/PreCommitLintExtension.java
index 2c5b496e37..b81d28db31 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/PreCommitLintExtension.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/PreCommitLintExtension.java
@@ -54,7 +54,7 @@ public class PreCommitLintExtension implements
IExtensionPoint<HopGuiFileBeforeC
return;
}
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
if (hopGui == null) {
log.logError("HopGui instance not available for pre-commit linting");
return;
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/ProjectLoadedLintExtension.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/ProjectLoadedLintExtension.java
index d1aa761106..bdb1b3b9ab 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/ProjectLoadedLintExtension.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/ProjectLoadedLintExtension.java
@@ -37,7 +37,7 @@ public class ProjectLoadedLintExtension implements
IExtensionPoint<Object> {
if (Utils.isEmpty(projectPath)) {
return;
}
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
BackgroundLintService.getInstance()
.lintProjectAsync(
projectPath, hopGui != null ? hopGui.getMetadataProvider() : null,
variables);
diff --git
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/WorkflowVerifyLintService.java
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/WorkflowVerifyLintService.java
index 56e54ed9a9..efde4a1453 100644
---
a/plugins/misc/lint/src/main/java/org/apache/hop/lint/WorkflowVerifyLintService.java
+++
b/plugins/misc/lint/src/main/java/org/apache/hop/lint/WorkflowVerifyLintService.java
@@ -46,7 +46,7 @@ public final class WorkflowVerifyLintService {
delegate.addWorkflowCheck();
graph.extraViewTabFolder.setSelection(delegate.getWorkflowCheckTab());
- HopGui hopGui = HopGui.getInstance();
+ HopGui hopGui = HopGui.peekInstance();
WorkflowMeta workflowMeta = graph.getWorkflowMeta();
String fileName =
LintPathUtils.normalizePath(workflowMeta.getFilename());