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 003677bce8 hop web CTabFolder escape and unit testing #6227 #6230
(#6237)
003677bce8 is described below
commit 003677bce86dc703e26763687df0da84bf917cdb
Author: Bart Maertens <[email protected]>
AuthorDate: Sat Dec 20 07:54:37 2025 +0000
hop web CTabFolder escape and unit testing #6227 #6230 (#6237)
* #6227 Fix unit testing context menu options in hop-web
The filterTestingActions() method uses getCurrentUnitTest() which iterates
through HopGui.getExplorerPerspective().getItems() to find the pipeline's
state map. This doesn't work in RAP/web mode.
Fixed by adding getUnitTestFromContext() helper that directly accesses
context.getPipelineGraph().getStateMap() instead.
* Fix hop-web startup NoClassDefFoundError for CTabFolderRenderer
The SafeCTabFolderRenderer inner class in PropsUi caused a
NoClassDefFoundError
when PropsUi was loaded in RAP/web mode, because Java resolves parent
classes
(CTabFolderRenderer) at class load time, not at instantiation time.
Fixed by:
1. Moving SafeCTabFolderRenderer to a separate file so it's only loaded when
actually instantiated
2. Using reflection in ensureSafeRenderer() to avoid any compile-time
references
to SafeCTabFolderRenderer or CTabFolderRenderer, preventing them from
being
loaded when PropsUi class is loaded
3. Adding isWeb() check at the start of ensureSafeRenderer() to
short-circuit
in web mode
This fixes a regression that broke the apache/hop-web:Development container.
* excluded SafeCTabFolderRenderer from hop web image build. fixes #6230
* fixes/changes to allow unit tests to show up and work in hop web. fixes
#6227
* Simplify SafeCTabFolderRenderer reflection code in PropsUi
Replaces method iteration with direct cast for cleaner code while
maintaining the same reflection approach to avoid class resolution at load time.
---
docker/unified.Dockerfile | 16 +++-
.../apache/hop/testing/gui/TestingGuiPlugin.java | 103 ++++++++++-----------
...DrawGoldenDataSetOnTransformExtensionPoint.java | 8 +-
.../DrawInputDataSetOnTransformExtensionPoint.java | 7 +-
...opGuiFlagPipelineForUnitTestExtensionPoint.java | 20 ++--
.../xp/HopGuiFlagUnitTestExtensionPoint.java | 17 ++--
.../main/java/org/apache/hop/ui/core/PropsUi.java | 79 ++++------------
.../apache/hop/ui/core/SafeCTabFolderRenderer.java | 85 +++++++++++++++++
8 files changed, 198 insertions(+), 137 deletions(-)
diff --git a/docker/unified.Dockerfile b/docker/unified.Dockerfile
index d828fae106..64c48df365 100644
--- a/docker/unified.Dockerfile
+++ b/docker/unified.Dockerfile
@@ -138,7 +138,7 @@ FROM builder-${BUILDER_TYPE} AS builder-selected
FROM alpine:latest AS builder
# Install tools needed for preparation
-RUN apk add --no-cache unzip bash openjdk17-jre
+RUN apk add --no-cache unzip zip bash openjdk17-jre
WORKDIR /build
@@ -189,6 +189,20 @@ RUN mkdir -p /build/hop-web-prepared/webapps/ROOT && \
cp -r /build/assemblies/client/target/hop/lib/beam/*
/build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/ && \
cp -r /build/assemblies/client/target/hop/lib/core/*
/build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/ && \
rm /build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/hop-ui-rcp* && \
+ # Remove SafeCTabFolderRenderer.class from hop-ui JAR - it extends
CTabFolderRenderer
+ # which doesn't exist in RAP/web mode, causing NoClassDefFoundError
+ for jar in /build/hop-web-prepared/webapps/ROOT/WEB-INF/lib/hop-ui-*.jar;
do \
+ if [ -f "$jar" ]; then \
+ cd /tmp && \
+ unzip -q "$jar" -d hop-ui-temp && \
+ rm -f
hop-ui-temp/org/apache/hop/ui/core/SafeCTabFolderRenderer.class && \
+ rm -f "$jar" && \
+ cd hop-ui-temp && \
+ zip -q -r "$jar" . && \
+ cd /build && \
+ rm -rf /tmp/hop-ui-temp; \
+ fi; \
+ done && \
mkdir -p /build/hop-web-prepared/bin && \
cp -r /build/docker/resources/run-web.sh
/build/hop-web-prepared/bin/run-web.sh
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
index 69d6cd2a8a..958714e8e9 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
@@ -162,10 +162,10 @@ public class TestingGuiPlugin {
PipelineMeta pipelineMeta = context.getPipelineMeta();
TransformMeta transformMeta = context.getTransformMeta();
- if (checkTestPresent(hopGui, pipelineMeta)) {
+ if (checkTestPresent(hopGui, context)) {
return;
}
- PipelineUnitTest unitTest = getCurrentUnitTest(pipelineMeta);
+ PipelineUnitTest unitTest = getUnitTestFromContext(context);
try {
@@ -311,12 +311,12 @@ public class TestingGuiPlugin {
TransformMeta transformMeta = context.getTransformMeta();
IVariables variables = context.getPipelineGraph().getVariables();
- if (checkTestPresent(hopGui, pipelineMeta)) {
+ if (checkTestPresent(hopGui, context)) {
return;
}
try {
- PipelineUnitTest currentUnitTest = getCurrentUnitTest(pipelineMeta);
+ PipelineUnitTest currentUnitTest = getUnitTestFromContext(context);
PipelineUnitTestSetLocation inputLocation =
currentUnitTest.findInputLocation(transformMeta.getName());
@@ -338,9 +338,9 @@ public class TestingGuiPlugin {
}
}
- private boolean checkTestPresent(HopGui hopGui, PipelineMeta pipelineMeta) {
-
- PipelineUnitTest activeTest = getCurrentUnitTest(pipelineMeta);
+ private boolean checkTestPresent(HopGui hopGui,
HopGuiPipelineTransformContext context) {
+ // Get the unit test directly from the pipeline graph context (works in
web/RAP mode)
+ PipelineUnitTest activeTest = getUnitTestFromContext(context);
if (activeTest != null) {
return false;
}
@@ -376,10 +376,10 @@ public class TestingGuiPlugin {
HopGui hopGui = HopGui.getInstance();
IHopMetadataProvider metadataProvider = hopGui.getMetadataProvider();
- if (checkTestPresent(hopGui, sourcePipelineMeta)) {
+ if (checkTestPresent(hopGui, context)) {
return;
}
- PipelineUnitTest unitTest = getCurrentUnitTest(sourcePipelineMeta);
+ PipelineUnitTest unitTest = getUnitTestFromContext(context);
try {
// Create a copy and modify the pipeline
@@ -528,12 +528,12 @@ public class TestingGuiPlugin {
TransformMeta transformMeta = context.getTransformMeta();
IVariables variables = context.getPipelineGraph().getVariables();
- if (checkTestPresent(hopGui, pipelineMeta)) {
+ if (checkTestPresent(hopGui, context)) {
return;
}
try {
- PipelineUnitTest currentUnitTest = getCurrentUnitTest(pipelineMeta);
+ PipelineUnitTest currentUnitTest = getUnitTestFromContext(context);
PipelineUnitTestSetLocation goldenLocation =
currentUnitTest.findGoldenLocation(transformMeta.getName());
@@ -558,7 +558,10 @@ public class TestingGuiPlugin {
@GuiContextActionFilter(parentId = HopGuiPipelineTransformContext.CONTEXT_ID)
public boolean filterTestingActions(
String contextActionId, HopGuiPipelineTransformContext context) {
- PipelineUnitTest currentTest =
getCurrentUnitTest(context.getPipelineMeta());
+ // Get the unit test directly from the pipeline graph context
+ // Using getCurrentUnitTest(pipelineMeta) doesn't work in web/RAP mode
because
+ // it looks for the pipeline in HopGui.getExplorerPerspective().getItems()
+ PipelineUnitTest currentTest = getUnitTestFromContext(context);
// Input & golden data set handling
//
@@ -605,6 +608,22 @@ public class TestingGuiPlugin {
return true;
}
+ /**
+ * Get the active unit test directly from the pipeline graph context. This
method is used by
+ * filterTestingActions as an alternative to
getCurrentUnitTest(pipelineMeta) which doesn't work
+ * in web/RAP mode because it relies on
HopGui.getExplorerPerspective().getItems().
+ *
+ * @param context The pipeline transform context
+ * @return The active PipelineUnitTest or null if none is active
+ */
+ private PipelineUnitTest
getUnitTestFromContext(HopGuiPipelineTransformContext context) {
+ Map<String, Object> stateMap = context.getPipelineGraph().getStateMap();
+ if (stateMap == null) {
+ return null;
+ }
+ return (PipelineUnitTest)
stateMap.get(DataSetConst.STATE_KEY_ACTIVE_UNIT_TEST);
+ }
+
/** Create a new data set with the output from */
@GuiContextAction(
id = "pipeline-graph-transform-20400-create-data-set-from-transform",
@@ -649,7 +668,7 @@ public class TestingGuiPlugin {
hopGui.getShell());
if (manager.newMetadata(dataSet) != null) {
- PipelineUnitTest unitTest = getCurrentUnitTest(pipelineMeta);
+ PipelineUnitTest unitTest = getUnitTestFromContext(context);
if (unitTest == null) {
return;
}
@@ -1205,9 +1224,7 @@ public class TestingGuiPlugin {
category = "i18n::TestingGuiPlugin.Category",
categoryOrder = "8")
public void
enableTweakRemoveTransformInUnitTest(HopGuiPipelineTransformContext context) {
- IVariables variables = context.getPipelineGraph().getVariables();
- tweakRemoveTransformInUnitTest(
- variables, context.getPipelineMeta(), context.getTransformMeta(),
true);
+ tweakRemoveTransformInUnitTest(context, true);
}
@GuiContextAction(
@@ -1220,20 +1237,12 @@ public class TestingGuiPlugin {
category = "i18n::TestingGuiPlugin.Category",
categoryOrder = "8")
public void
disableTweakRemoveTransformInUnitTest(HopGuiPipelineTransformContext context) {
- tweakRemoveTransformInUnitTest(
- context.getPipelineGraph().getVariables(),
- context.getPipelineMeta(),
- context.getTransformMeta(),
- false);
+ tweakRemoveTransformInUnitTest(context, false);
}
- public void tweakRemoveTransformInUnitTest(
- IVariables variables,
- PipelineMeta pipelineMeta,
- TransformMeta transformMeta,
- boolean enable) {
- tweakUnitTestTransform(
- variables, pipelineMeta, transformMeta,
PipelineTweak.REMOVE_TRANSFORM, enable);
+ private void tweakRemoveTransformInUnitTest(
+ HopGuiPipelineTransformContext context, boolean enable) {
+ tweakUnitTestTransform(context, PipelineTweak.REMOVE_TRANSFORM, enable);
}
@GuiContextAction(
@@ -1246,11 +1255,7 @@ public class TestingGuiPlugin {
category = "i18n::TestingGuiPlugin.Category",
categoryOrder = "8")
public void
enableTweakBypassTransformInUnitTest(HopGuiPipelineTransformContext context) {
- tweakBypassTransformInUnitTest(
- context.getPipelineGraph().getVariables(),
- context.getPipelineMeta(),
- context.getTransformMeta(),
- true);
+ tweakBypassTransformInUnitTest(context, true);
}
@GuiContextAction(
@@ -1263,39 +1268,31 @@ public class TestingGuiPlugin {
category = "i18n::TestingGuiPlugin.Category",
categoryOrder = "8")
public void
disableTweakBypassTransformInUnitTest(HopGuiPipelineTransformContext context) {
- tweakBypassTransformInUnitTest(
- context.getPipelineGraph().getVariables(),
- context.getPipelineMeta(),
- context.getTransformMeta(),
- false);
+ tweakBypassTransformInUnitTest(context, false);
}
- public void tweakBypassTransformInUnitTest(
- IVariables variables,
- PipelineMeta pipelineMeta,
- TransformMeta transformMeta,
- boolean enable) {
- tweakUnitTestTransform(
- variables, pipelineMeta, transformMeta,
PipelineTweak.BYPASS_TRANSFORM, enable);
+ private void tweakBypassTransformInUnitTest(
+ HopGuiPipelineTransformContext context, boolean enable) {
+ tweakUnitTestTransform(context, PipelineTweak.BYPASS_TRANSFORM, enable);
}
private void tweakUnitTestTransform(
- IVariables variables,
- PipelineMeta pipelineMeta,
- TransformMeta transformMeta,
- PipelineTweak tweak,
- boolean enable) {
+ HopGuiPipelineTransformContext context, PipelineTweak tweak, boolean
enable) {
HopGui hopGui = HopGui.getInstance();
IHopMetadataProvider metadataProvider = hopGui.getMetadataProvider();
+ PipelineMeta pipelineMeta = context.getPipelineMeta();
+ TransformMeta transformMeta = context.getTransformMeta();
+ IVariables variables = context.getPipelineGraph().getVariables();
+
if (transformMeta == null || pipelineMeta == null) {
return;
}
- if (checkTestPresent(hopGui, pipelineMeta)) {
+ if (checkTestPresent(hopGui, context)) {
return;
}
try {
- PipelineUnitTest unitTest = getCurrentUnitTest(pipelineMeta);
+ PipelineUnitTest unitTest = getUnitTestFromContext(context);
PipelineUnitTestTweak unitTestTweak =
unitTest.findTweak(transformMeta.getName());
if (unitTestTweak != null) {
unitTest.getTweaks().remove(unitTestTweak);
@@ -1308,7 +1305,7 @@ public class TestingGuiPlugin {
selectUnitTest(pipelineMeta, unitTest);
- hopGui.getActiveFileTypeHandler().updateGui();
+ context.getPipelineGraph().updateGui();
} catch (Exception exception) {
new ErrorDialog(
hopGui.getShell(),
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/DrawGoldenDataSetOnTransformExtensionPoint.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/DrawGoldenDataSetOnTransformExtensionPoint.java
index e2cdae000b..71b12cf6e4 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/DrawGoldenDataSetOnTransformExtensionPoint.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/DrawGoldenDataSetOnTransformExtensionPoint.java
@@ -34,7 +34,6 @@ import org.apache.hop.pipeline.PipelinePainterExtension;
import org.apache.hop.pipeline.transform.TransformMeta;
import org.apache.hop.testing.PipelineUnitTest;
import org.apache.hop.testing.PipelineUnitTestSetLocation;
-import org.apache.hop.testing.gui.TestingGuiPlugin;
import org.apache.hop.testing.util.DataSetConst;
import org.apache.hop.ui.core.ConstUi;
@@ -51,7 +50,12 @@ public class DrawGoldenDataSetOnTransformExtensionPoint
ILogChannel log, IVariables variables, PipelinePainterExtension ext)
throws HopException {
TransformMeta transformMeta = ext.transformMeta;
PipelineMeta pipelineMeta = ext.pipelineMeta;
- PipelineUnitTest unitTest =
TestingGuiPlugin.getCurrentUnitTest(pipelineMeta);
+
+ // Get unit test directly from stateMap (works in both desktop and web/RAP
mode)
+ PipelineUnitTest unitTest = null;
+ if (ext.stateMap != null) {
+ unitTest = (PipelineUnitTest)
ext.stateMap.get(DataSetConst.STATE_KEY_ACTIVE_UNIT_TEST);
+ }
if (unitTest != null) {
drawGoldenSetMarker(ext, transformMeta, unitTest, ext.areaOwners);
}
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/DrawInputDataSetOnTransformExtensionPoint.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/DrawInputDataSetOnTransformExtensionPoint.java
index d103e449e3..1fe19cec55 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/DrawInputDataSetOnTransformExtensionPoint.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/DrawInputDataSetOnTransformExtensionPoint.java
@@ -32,7 +32,6 @@ import org.apache.hop.pipeline.PipelinePainterExtension;
import org.apache.hop.pipeline.transform.TransformMeta;
import org.apache.hop.testing.PipelineUnitTest;
import org.apache.hop.testing.PipelineUnitTestSetLocation;
-import org.apache.hop.testing.gui.TestingGuiPlugin;
import org.apache.hop.testing.util.DataSetConst;
@ExtensionPoint(
@@ -48,7 +47,11 @@ public class DrawInputDataSetOnTransformExtensionPoint
TransformMeta transformMeta = ext.transformMeta;
PipelineMeta pipelineMeta = ext.pipelineMeta;
- PipelineUnitTest unitTest =
TestingGuiPlugin.getCurrentUnitTest(pipelineMeta);
+ // Get unit test directly from stateMap (works in both desktop and web/RAP
mode)
+ PipelineUnitTest unitTest = null;
+ if (ext.stateMap != null) {
+ unitTest = (PipelineUnitTest)
ext.stateMap.get(DataSetConst.STATE_KEY_ACTIVE_UNIT_TEST);
+ }
if (unitTest != null) {
drawInputDataSetMarker(ext, transformMeta, unitTest, ext.areaOwners);
}
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiFlagPipelineForUnitTestExtensionPoint.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiFlagPipelineForUnitTestExtensionPoint.java
index a217d34211..9283de23d0 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiFlagPipelineForUnitTestExtensionPoint.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiFlagPipelineForUnitTestExtensionPoint.java
@@ -26,8 +26,6 @@ import org.apache.hop.core.variables.IVariables;
import org.apache.hop.pipeline.config.IPipelineEngineRunConfiguration;
import org.apache.hop.pipeline.engine.IPipelineEngine;
import org.apache.hop.pipeline.engines.local.LocalPipelineRunConfiguration;
-import org.apache.hop.testing.PipelineUnitTest;
-import org.apache.hop.testing.gui.TestingGuiPlugin;
import org.apache.hop.testing.util.DataSetConst;
@ExtensionPoint(
@@ -54,21 +52,17 @@ public class HopGuiFlagPipelineForUnitTestExtensionPoint
return;
}
- PipelineUnitTest unitTest =
TestingGuiPlugin.getCurrentUnitTest(pipeline.getPipelineMeta());
- if (unitTest == null) {
- return;
- }
-
- String unitTestName = unitTest.getName();
+ // The unit test variables should already be set by
HopGuiFlagUnitTestExtensionPoint
+ // Just pass them through to the pipeline engine
+ String runUnitTest = variables.getVariable(DataSetConst.VAR_RUN_UNIT_TEST);
+ String unitTestName =
variables.getVariable(DataSetConst.VAR_UNIT_TEST_NAME);
- if (!StringUtil.isEmpty(unitTestName)) {
+ if ("Y".equalsIgnoreCase(runUnitTest) &&
!StringUtil.isEmpty(unitTestName)) {
// We found the variables in the GUI and pass them to the pipeline right
before (prepare)
// execution
//
- pipeline.setVariable(
- DataSetConst.VAR_RUN_UNIT_TEST,
variables.getVariable(DataSetConst.VAR_RUN_UNIT_TEST));
- pipeline.setVariable(
- DataSetConst.VAR_UNIT_TEST_NAME,
variables.getVariable(DataSetConst.VAR_UNIT_TEST_NAME));
+ pipeline.setVariable(DataSetConst.VAR_RUN_UNIT_TEST, runUnitTest);
+ pipeline.setVariable(DataSetConst.VAR_UNIT_TEST_NAME, unitTestName);
}
}
}
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiFlagUnitTestExtensionPoint.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiFlagUnitTestExtensionPoint.java
index 2c91d5cdec..dacf392b67 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiFlagUnitTestExtensionPoint.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiFlagUnitTestExtensionPoint.java
@@ -25,7 +25,6 @@ import org.apache.hop.core.util.StringUtil;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.testing.PipelineUnitTest;
-import org.apache.hop.testing.gui.TestingGuiPlugin;
import org.apache.hop.testing.util.DataSetConst;
import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
@@ -46,11 +45,6 @@ public class HopGuiFlagUnitTestExtensionPoint implements
IExtensionPoint<Pipelin
public void callExtensionPoint(ILogChannel log, IVariables variables,
PipelineMeta pipelineMeta)
throws HopException {
- PipelineUnitTest unitTest =
TestingGuiPlugin.getCurrentUnitTest(pipelineMeta);
- if (unitTest == null) {
- return;
- }
-
// Look up the variables of the current active pipeline graph...
//
HopGuiPipelineGraph activePipelineGraph = HopGui.getActivePipelineGraph();
@@ -58,6 +52,17 @@ public class HopGuiFlagUnitTestExtensionPoint implements
IExtensionPoint<Pipelin
return;
}
+ // Get unit test directly from the pipeline graph's state map (works in
both desktop and web/RAP
+ // mode)
+ PipelineUnitTest unitTest = null;
+ java.util.Map<String, Object> stateMap = activePipelineGraph.getStateMap();
+ if (stateMap != null) {
+ unitTest = (PipelineUnitTest)
stateMap.get(DataSetConst.STATE_KEY_ACTIVE_UNIT_TEST);
+ }
+ if (unitTest == null) {
+ return;
+ }
+
String unitTestName = unitTest.getName();
if (!StringUtil.isEmpty(unitTestName)) {
diff --git a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
index 00cf036b94..9fa1db70ac 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
@@ -37,15 +37,10 @@ import org.apache.hop.ui.hopgui.TextSizeUtilFacade;
import org.apache.hop.ui.util.EnvironmentUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
-import org.eclipse.swt.custom.CTabFolderRenderer;
-import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
-import org.eclipse.swt.graphics.GC;
-import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
-import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
@@ -530,64 +525,28 @@ public class PropsUi extends Props {
* @param tabFolder the CTabFolder to protect
*/
private static void ensureSafeRenderer(CTabFolder tabFolder) {
- // Only apply safe renderer on Linux where the issue occurs
- if (Const.isLinux() && !(tabFolder.getRenderer() instanceof
SafeCTabFolderRenderer)) {
- tabFolder.setRenderer(new SafeCTabFolderRenderer(tabFolder));
- }
- }
-
- /**
- * A safe CTabFolderRenderer that catches IllegalArgumentException when
drawing tab images. This
- * prevents crashes on some Linux desktop environments where images may be
invalid or disposed
- * during rendering. When an image drawing error occurs, the image is
skipped but the text is
- * still rendered.
- */
- private static class SafeCTabFolderRenderer extends CTabFolderRenderer {
- private final CTabFolder parentFolder;
-
- SafeCTabFolderRenderer(CTabFolder parent) {
- super(parent);
- this.parentFolder = parent;
+ // CTabFolderRenderer and SafeCTabFolderRenderer are not available in RAP
(web mode),
+ // so skip entirely. The class is also removed from the hop-ui JAR during
Docker build.
+ // Use reflection to avoid compile-time class resolution that would cause
NoClassDefFoundError
+ // in web mode when the class doesn't exist in the JAR.
+ if (EnvironmentUtils.getInstance().isWeb()) {
+ return;
}
-
- @Override
- protected void draw(int part, int state, Rectangle bounds, GC gc) {
- if (bounds != null && (bounds.width <= 0 || bounds.height <= 0)) {
- return;
- }
+ // Only apply safe renderer on Linux where the issue occurs
+ if (Const.isLinux()) {
try {
- super.draw(part, state, bounds, gc);
- } catch (IllegalArgumentException e) {
- // If image drawing fails, temporarily remove images from tab items
and redraw
- // This allows text to be rendered even when images are invalid
- if (parentFolder != null && !parentFolder.isDisposed()) {
- CTabItem[] items = parentFolder.getItems();
- Image[] savedImages = new Image[items.length];
- boolean hadImages = false;
-
- // Save and temporarily clear images
- for (int i = 0; i < items.length; i++) {
- savedImages[i] = items[i].getImage();
- if (savedImages[i] != null) {
- items[i].setImage(null);
- hadImages = true;
- }
- }
-
- if (hadImages) {
- try {
- // Redraw without images - this should succeed and render the
text
- super.draw(part, state, bounds, gc);
- } finally {
- // Restore images (even if they're invalid, we'll catch the
exception next time)
- for (int i = 0; i < items.length; i++) {
- if (savedImages[i] != null) {
- items[i].setImage(savedImages[i]);
- }
- }
- }
- }
+ // Use reflection to avoid compile-time dependency on
SafeCTabFolderRenderer
+ // This prevents Java's bytecode verifier from trying to resolve the
class at load time
+ Class<?> rendererClass =
Class.forName("org.apache.hop.ui.core.SafeCTabFolderRenderer");
+ Object currentRenderer = tabFolder.getRenderer();
+ if (currentRenderer == null ||
!rendererClass.isInstance(currentRenderer)) {
+ Object safeRenderer =
+
rendererClass.getConstructor(CTabFolder.class).newInstance(tabFolder);
+ tabFolder.setRenderer((org.eclipse.swt.custom.CTabFolderRenderer)
safeRenderer);
}
+ } catch (Exception e) {
+ // If SafeCTabFolderRenderer can't be loaded, just continue without
the safe renderer
+ LogChannel.GENERAL.logDetailed("Could not apply
SafeCTabFolderRenderer: " + e.getMessage());
}
}
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/SafeCTabFolderRenderer.java
b/ui/src/main/java/org/apache/hop/ui/core/SafeCTabFolderRenderer.java
new file mode 100644
index 0000000000..7d5a3830cc
--- /dev/null
+++ b/ui/src/main/java/org/apache/hop/ui/core/SafeCTabFolderRenderer.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.core;
+
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabFolderRenderer;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+
+/**
+ * A safe CTabFolderRenderer that catches IllegalArgumentException when
drawing tab images. This
+ * prevents crashes on some Linux desktop environments where images may be
invalid or disposed
+ * during rendering. When an image drawing error occurs, the image is skipped
but the text is still
+ * rendered.
+ *
+ * <p>This class is in a separate file to avoid loading CTabFolderRenderer
(which doesn't exist in
+ * RAP/web mode) when PropsUi is loaded. The class is only instantiated on
Linux desktop
+ * environments where it's needed.
+ */
+public class SafeCTabFolderRenderer extends CTabFolderRenderer {
+ private final CTabFolder parentFolder;
+
+ public SafeCTabFolderRenderer(CTabFolder parent) {
+ super(parent);
+ this.parentFolder = parent;
+ }
+
+ @Override
+ protected void draw(int part, int state, Rectangle bounds, GC gc) {
+ if (bounds != null && (bounds.width <= 0 || bounds.height <= 0)) {
+ return;
+ }
+ try {
+ super.draw(part, state, bounds, gc);
+ } catch (IllegalArgumentException e) {
+ // If image drawing fails, temporarily remove images from tab items and
redraw
+ // This allows text to be rendered even when images are invalid
+ if (parentFolder != null && !parentFolder.isDisposed()) {
+ CTabItem[] items = parentFolder.getItems();
+ Image[] savedImages = new Image[items.length];
+ boolean hadImages = false;
+
+ // Save and temporarily clear images
+ for (int i = 0; i < items.length; i++) {
+ savedImages[i] = items[i].getImage();
+ if (savedImages[i] != null) {
+ items[i].setImage(null);
+ hadImages = true;
+ }
+ }
+
+ if (hadImages) {
+ try {
+ // Redraw without images - this should succeed and render the text
+ super.draw(part, state, bounds, gc);
+ } finally {
+ // Restore images (even if they're invalid, we'll catch the
exception next time)
+ for (int i = 0; i < items.length; i++) {
+ if (savedImages[i] != null) {
+ items[i].setImage(savedImages[i]);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}