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 bf00f703e0 issues #7354, #7343 (#7359)
bf00f703e0 is described below
commit bf00f703e06c1d8cb873500c22902b12b7319e8b
Author: Matt Casters <[email protected]>
AuthorDate: Mon Jun 29 16:05:19 2026 +0200
issues #7354, #7343 (#7359)
* issue #7354 : fixing the project enforce in home checkbox.
* issue #7343 : improve stream consistency checks
---
.../org/apache/hop/projects/project/Project.java | 3 +
.../apache/hop/projects/project/ProjectTest.java | 57 ++++
.../hopgui/file/pipeline/HopGuiPipelineGraph.java | 41 ++-
.../delegates/HopGuiPipelineHopDelegate.java | 290 ++++++++++++++++++++-
.../ui/hopgui/messages/messages_en_US.properties | 24 +-
5 files changed, 406 insertions(+), 9 deletions(-)
diff --git
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/Project.java
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/Project.java
index 82a7729e54..7c76fa8872 100644
---
a/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/Project.java
+++
b/plugins/misc/projects/src/main/java/org/apache/hop/projects/project/Project.java
@@ -81,7 +81,10 @@ public class Project extends ConfigFile implements
IConfigFile {
private String metadataBaseFolder;
private String unitTestsBasePath;
private String dataSetsCsvFolder;
+
+ @JsonInclude(JsonInclude.Include.ALWAYS)
private boolean enforcingExecutionInHome;
+
private String parentProjectName;
@JsonIgnore private MultiMetadataProvider metadataProvider;
@JsonIgnore private List<Path> pipelinePaths;
diff --git
a/plugins/misc/projects/src/test/java/org/apache/hop/projects/project/ProjectTest.java
b/plugins/misc/projects/src/test/java/org/apache/hop/projects/project/ProjectTest.java
new file mode 100644
index 0000000000..4cb65c6110
--- /dev/null
+++
b/plugins/misc/projects/src/test/java/org/apache/hop/projects/project/ProjectTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.projects.project;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.nio.file.Files;
+import org.junit.jupiter.api.Test;
+
+public class ProjectTest {
+
+ @Test
+ public void testEnforcingExecutionInHomeSerialization() throws Exception {
+ File tempFile = Files.createTempFile("project-config-test",
".json").toFile();
+ tempFile.deleteOnExit();
+
+ try {
+ Project project = new Project(tempFile.getAbsolutePath());
+ project.setEnforcingExecutionInHome(false);
+ project.saveToFile();
+
+ // Read back
+ Project readProject = new Project(tempFile.getAbsolutePath());
+ readProject.readFromFile();
+
+ assertFalse(readProject.isEnforcingExecutionInHome());
+
+ // Test true
+ project.setEnforcingExecutionInHome(true);
+ project.saveToFile();
+
+ readProject = new Project(tempFile.getAbsolutePath());
+ readProject.readFromFile();
+
+ assertTrue(readProject.isEnforcingExecutionInHome());
+ } finally {
+ tempFile.delete();
+ }
+ }
+}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
index c20b804a88..42a074d570 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
@@ -83,6 +83,7 @@ import org.apache.hop.core.plugins.IPlugin;
import org.apache.hop.core.plugins.PluginRegistry;
import org.apache.hop.core.plugins.TransformPluginType;
import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.RowBuffer;
import org.apache.hop.core.svg.SvgFile;
import org.apache.hop.core.util.ExecutorUtil;
@@ -345,7 +346,7 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
private PipelineHopMeta lastHopSplit;
- private org.apache.hop.core.gui.Rectangle selectionRegion;
+ private Rectangle selectionRegion;
@Getter @Setter private List<ICheckResult> remarks;
@@ -954,7 +955,7 @@ public class HopGuiPipelineGraph extends HopGuiAbstractGraph
//
canvas.setData("mode", "select");
if (!control && event.button == 1) {
- selectionRegion = new org.apache.hop.core.gui.Rectangle(real.x,
real.y, 0, 0);
+ selectionRegion = new Rectangle(real.x, real.y, 0, 0);
// Change cursor when selecting a region
setCursor(getDisplay().getSystemCursor(SWT.CURSOR_CROSS));
}
@@ -2406,7 +2407,7 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
toolBarWidgets = new GuiToolbarWidgets();
toolBarWidgets.registerGuiPluginObject(this);
toolBarWidgets.createToolbarWidgets(toolBarContainer,
GUI_PLUGIN_TOOLBAR_PARENT_ID);
- if
(org.apache.hop.ui.hopgui.PaletteEngineFilter.shouldShowPipelineComboFilter()) {
+ if (PaletteEngineFilter.shouldShowPipelineComboFilter()) {
setDesignEngineComboFromConfig();
} else {
disposeDesignEngineToolbarItem();
@@ -2613,6 +2614,40 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
HelpUtils.openHelp(getShell(), plugin);
}
+ @GuiContextAction(
+ id = "pipeline-graph-transform-11000-layout-check",
+ parentId = HopGuiPipelineTransformContext.CONTEXT_ID,
+ type = GuiActionType.Info,
+ name = "Layout Check",
+ tooltip = "Check that the row layouts of all previous transforms are
identical",
+ image = "ui/images/check.svg",
+ category =
"i18n::HopGuiPipelineGraph.ContextualAction.Category.Routing.Text",
+ categoryOrder = "1")
+ public void layoutCheck(HopGuiPipelineTransformContext context) {
+ pipelineHopDelegate.checkLayoutOfTransformInput(
+ context.getTransformMeta(), context.getPipelineMeta());
+ }
+
+ public boolean isLayoutIdentical(IRowMeta rm1, IRowMeta rm2) {
+ if (rm1 == null || rm2 == null) {
+ return rm1 == rm2;
+ }
+ if (rm1.size() != rm2.size()) {
+ return false;
+ }
+ for (int i = 0; i < rm1.size(); i++) {
+ IValueMeta vm1 = rm1.getValueMeta(i);
+ IValueMeta vm2 = rm2.getValueMeta(i);
+ if (!vm1.getName().equalsIgnoreCase(vm2.getName())) {
+ return false;
+ }
+ if (vm1.getType() != vm2.getType()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@GuiContextAction(
id = "pipeline-graph-transform-10100-transform-detach",
parentId = HopGuiPipelineTransformContext.CONTEXT_ID,
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineHopDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineHopDelegate.java
index ee6accf014..1a59bac640 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineHopDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineHopDelegate.java
@@ -19,16 +19,25 @@ package org.apache.hop.ui.hopgui.file.pipeline.delegates;
import java.util.ArrayList;
import java.util.List;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.exception.HopRowException;
+import org.apache.hop.core.plugins.IPlugin;
import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.plugins.TransformPluginType;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.variables.IVariables;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.pipeline.PipelineHopMeta;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.pipeline.transform.IRowDistribution;
+import org.apache.hop.pipeline.transform.ITransformMeta;
import org.apache.hop.pipeline.transform.RowDistributionPluginType;
import org.apache.hop.pipeline.transform.TransformErrorMeta;
import org.apache.hop.pipeline.transform.TransformMeta;
import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.dialog.EnterTextDialog;
import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.core.dialog.MessageBox;
import org.apache.hop.ui.core.dialog.MessageDialogWithToggle;
@@ -36,6 +45,7 @@ import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
import org.apache.hop.ui.pipeline.dialog.PipelineHopDialog;
import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Shell;
public class HopGuiPipelineHopDelegate {
@@ -128,11 +138,7 @@ public class HopGuiPipelineHopDelegate {
}
} catch (HopRowException re) {
// Show warning about mixing rows with conflicting layouts...
- new ErrorDialog(
- hopGui.getActiveShell(),
- BaseMessages.getString(PKG,
"PipelineGraph.Dialog.HopCausesRowMixing.Title"),
- BaseMessages.getString(PKG,
"PipelineGraph.Dialog.HopCausesRowMixing.Message"),
- re);
+ checkLayoutOfTransformInput(newHop.getToTransform(), pipelineMeta);
}
// Verify copy distribution only if the new hop are not for error
handling
@@ -316,4 +322,278 @@ public class HopGuiPipelineHopDelegate {
}
pipelineGraph.updateGui();
}
+
+ public void checkLayoutOfTransformInput(
+ TransformMeta currentTransformMeta, PipelineMeta pipelineMeta) {
+ Shell shell = hopGui.getShell();
+ IVariables variables = hopGui.getVariables();
+ List<TransformMeta> prevTransforms =
pipelineMeta.findPreviousTransforms(currentTransformMeta);
+
+ if (prevTransforms.isEmpty()) {
+ MessageBox box = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
+ box.setText(BaseMessages.getString(PKG, "HopGui.LayoutCheck.Title"));
+ box.setMessage(BaseMessages.getString(PKG,
"HopGui.LayoutCheck.NoInputs"));
+ box.open();
+ return;
+ } else if (prevTransforms.size() == 1) {
+ MessageBox box = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
+ box.setText(BaseMessages.getString(PKG, "HopGui.LayoutCheck.Title"));
+ box.setMessage(
+ BaseMessages.getString(
+ PKG, "HopGui.LayoutCheck.OneInput",
prevTransforms.get(0).getName()));
+ box.open();
+ return;
+ }
+
+ List<IRowMeta> rowMetas = new ArrayList<>();
+ boolean errorOccurred = false;
+ for (TransformMeta prev : prevTransforms) {
+ try {
+ IRowMeta rowMeta = pipelineMeta.getTransformFields(variables, prev);
+ rowMetas.add(rowMeta);
+ } catch (HopException e) {
+ new ErrorDialog(
+ shell,
+ BaseMessages.getString(PKG, "HopGui.LayoutCheck.Error"),
+ BaseMessages.getString(PKG,
"HopGui.LayoutCheck.ErrorGettingRowLayout", prev.getName()),
+ e);
+ errorOccurred = true;
+ break;
+ }
+ }
+ if (errorOccurred) {
+ return;
+ }
+
+ boolean allIdentical = true;
+ IRowMeta firstMeta = rowMetas.get(0);
+ for (int i = 1; i < rowMetas.size(); i++) {
+ if (!pipelineGraph.isLayoutIdentical(firstMeta, rowMetas.get(i))) {
+ allIdentical = false;
+ break;
+ }
+ }
+
+ if (allIdentical) {
+ MessageBox box = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
+ box.setText(BaseMessages.getString(PKG, "HopGui.LayoutCheck.Title"));
+ box.setMessage(BaseMessages.getString(PKG,
"HopGui.LayoutCheck.Identical"));
+ box.open();
+ } else {
+ StringBuilder diffText = new StringBuilder();
+ diffText
+ .append(BaseMessages.getString(PKG,
"HopGui.LayoutCheck.DifferentInputs"))
+ .append(Const.CR)
+ .append(Const.CR);
+
+ for (int i = 0; i < prevTransforms.size(); i++) {
+ TransformMeta prev = prevTransforms.get(i);
+ IRowMeta rowMeta = rowMetas.get(i);
+ diffText
+ .append(
+ BaseMessages.getString(
+ PKG, "HopGui.LayoutCheck.InputFromTransform",
prev.getName()))
+ .append(Const.CR);
+ if (rowMeta == null || rowMeta.isEmpty()) {
+ diffText
+ .append(BaseMessages.getString(PKG,
"HopGui.LayoutCheck.NoFields"))
+ .append(Const.CR);
+ } else {
+ for (int j = 0; j < rowMeta.size(); j++) {
+ IValueMeta vm = rowMeta.getValueMeta(j);
+ diffText
+ .append(
+ BaseMessages.getString(
+ PKG,
+ "HopGui.LayoutCheck.FieldLine",
+ String.format("%3d", j + 1),
+ vm.getName(),
+ vm.getTypeDesc()))
+ .append(Const.CR);
+ }
+ }
+ diffText.append("\n");
+ }
+
+ diffText
+ .append(BaseMessages.getString(PKG,
"HopGui.LayoutCheck.DifferencesList"))
+ .append(Const.CR);
+ String firstTransformName = prevTransforms.get(0).getName();
+ for (int i = 1; i < rowMetas.size(); i++) {
+ IRowMeta currentMeta = rowMetas.get(i);
+ String currentTransformName = prevTransforms.get(i).getName();
+
+ if (firstMeta == null || currentMeta == null) {
+ diffText
+ .append(
+ BaseMessages.getString(
+ PKG,
+ "HopGui.LayoutCheck.LayoutIsNull",
+ firstTransformName,
+ currentTransformName))
+ .append(Const.CR);
+ continue;
+ }
+
+ if (firstMeta.size() != currentMeta.size()) {
+ diffText
+ .append(
+ BaseMessages.getString(
+ PKG,
+ "HopGui.LayoutCheck.FieldCountMismatch",
+ firstTransformName,
+ Integer.toString(firstMeta.size()),
+ currentTransformName,
+ Integer.toString(currentMeta.size())))
+ .append(Const.CR);
+ }
+
+ int minSize = Math.min(firstMeta.size(), currentMeta.size());
+ for (int j = 0; j < minSize; j++) {
+ IValueMeta vm1 = firstMeta.getValueMeta(j);
+ IValueMeta vm2 = currentMeta.getValueMeta(j);
+ if (!vm1.getName().equalsIgnoreCase(vm2.getName())) {
+ diffText
+ .append(
+ BaseMessages.getString(
+ PKG,
+ "HopGui.LayoutCheck.NameMismatch",
+ Integer.toString(j + 1),
+ firstTransformName,
+ vm1.getName(),
+ currentTransformName,
+ vm2.getName()))
+ .append(Const.CR);
+ } else if (vm1.getType() != vm2.getType()) {
+ diffText
+ .append(
+ BaseMessages.getString(
+ PKG,
+ "HopGui.LayoutCheck.TypeMismatch",
+ vm1.getName(),
+ firstTransformName,
+ vm1.getTypeDesc(),
+ currentTransformName,
+ vm2.getTypeDesc()))
+ .append(Const.CR);
+ }
+ }
+
+ if (firstMeta.size() > currentMeta.size()) {
+ for (int j = minSize; j < firstMeta.size(); j++) {
+ diffText
+ .append(
+ BaseMessages.getString(
+ PKG,
+ "HopGui.LayoutCheck.FieldMissing",
+ firstMeta.getValueMeta(j).getName(),
+ firstTransformName,
+ currentTransformName))
+ .append(Const.CR);
+ }
+ } else if (currentMeta.size() > firstMeta.size()) {
+ for (int j = minSize; j < currentMeta.size(); j++) {
+ diffText
+ .append(
+ BaseMessages.getString(
+ PKG,
+ "HopGui.LayoutCheck.FieldMissing",
+ currentMeta.getValueMeta(j).getName(),
+ currentTransformName,
+ firstTransformName))
+ .append(Const.CR);
+ }
+ }
+ }
+
+ EnterTextDialog dialog =
+ new EnterTextDialog(
+ shell,
+ BaseMessages.getString(PKG,
"HopGui.LayoutCheck.Dialog.MismatchTitle"),
+ BaseMessages.getString(PKG,
"HopGui.LayoutCheck.Dialog.MismatchMessage"),
+ diffText.toString(),
+ true);
+ dialog.setReadOnly();
+ dialog.open();
+ }
+
+ if ("Dummy".equals(currentTransformMeta.getPluginId())) {
+ MessageBox box = new MessageBox(shell, SWT.ICON_QUESTION | SWT.YES |
SWT.NO);
+ box.setText(BaseMessages.getString(PKG,
"HopGui.LayoutCheck.Dialog.ReplaceDummyTitle"));
+ box.setMessage(BaseMessages.getString(PKG,
"HopGui.LayoutCheck.Dialog.ReplaceDummyMessage"));
+ if (box.open() == SWT.YES) {
+ try {
+ IPlugin plugin =
+ PluginRegistry.getInstance()
+ .findPluginWithId(TransformPluginType.class, "StreamSchema");
+ if (plugin != null) {
+ ITransformMeta streamSchemaMeta =
+ (ITransformMeta)
PluginRegistry.getInstance().loadClass(plugin);
+
+ configureSchemaMergeTransform(streamSchemaMeta, prevTransforms);
+
streamSchemaMeta.searchInfoAndTargetTransforms(pipelineMeta.getTransforms());
+
+ TransformMeta before = (TransformMeta)
currentTransformMeta.clone();
+
+ currentTransformMeta.setTransform(streamSchemaMeta);
+ currentTransformMeta.setTransformPluginId("StreamSchema");
+ currentTransformMeta.setChanged();
+
+ TransformMeta after = (TransformMeta) currentTransformMeta.clone();
+
+ hopGui.undoDelegate.addUndoChange(
+ pipelineMeta,
+ new TransformMeta[] {before},
+ new TransformMeta[] {after},
+ new int[]
{pipelineMeta.indexOfTransform(currentTransformMeta)});
+
+ pipelineMeta.setChanged();
+ pipelineGraph.redraw();
+ } else {
+ new ErrorDialog(
+ shell,
+ BaseMessages.getString(PKG, "HopGui.LayoutCheck.Error"),
+ BaseMessages.getString(PKG,
"HopGui.LayoutCheck.Dialog.StreamSchemaPluginNotFound"),
+ new HopException("Plugin not found"));
+ }
+ } catch (Exception e) {
+ new ErrorDialog(
+ shell,
+ BaseMessages.getString(PKG, "HopGui.LayoutCheck.Error"),
+ BaseMessages.getString(PKG,
"HopGui.LayoutCheck.Dialog.ReplaceDummyError"),
+ e);
+ }
+ }
+ }
+ }
+
+ private void configureSchemaMergeTransform(
+ ITransformMeta streamSchemaMeta, List<TransformMeta> prevTransforms) {
+ // Configure the Stream Schema Merge transform with the names of the
previous transforms
+ try {
+ Class<?> metaClass = streamSchemaMeta.getClass();
+ ClassLoader pluginClassLoader = metaClass.getClassLoader();
+ Class<?> transformToMergeClass =
+ Class.forName(
+
"org.apache.hop.pipeline.transforms.streamschemamerge.StreamSchemaMeta$TransformToMerge",
+ true,
+ pluginClassLoader);
+ java.lang.reflect.Constructor<?> constructor =
transformToMergeClass.getConstructor();
+ java.lang.reflect.Method setNameMethod =
+ transformToMergeClass.getMethod("setName", String.class);
+
+ List<Object> list = new ArrayList<>();
+ for (TransformMeta prev : prevTransforms) {
+ Object transformToMerge = constructor.newInstance();
+ setNameMethod.invoke(transformToMerge, prev.getName());
+ list.add(transformToMerge);
+ }
+
+ java.lang.reflect.Method setTransformsToMergeMethod =
+ metaClass.getMethod("setTransformsToMerge", List.class);
+ setTransformsToMergeMethod.invoke(streamSchemaMeta, list);
+ } catch (Exception ex) {
+ hopGui.getLog().logError("Error setting transforms to merge via
reflection", ex);
+ }
+ }
}
diff --git
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
index 8931fa0124..e1a614bfed 100644
---
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
+++
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
@@ -304,4 +304,26 @@ WorkflowLog.Dialog.SaveJobBeforeRunning.Title=Save workflow
WorkflowLog.System.ERROR=ERROR
WorkflowLog.System.EXCEPTION=EXCEPTION
PipelineLog.Dialog.Pause.Tooltip=Pause the logs
-PipelineLog.Dialog.Resume.Tooltip=Resume the logs
\ No newline at end of file
+PipelineLog.Dialog.Resume.Tooltip=Resume the logs
+HopGui.LayoutCheck.Title=Layout Check
+HopGui.LayoutCheck.NoInputs=This transform has no input transforms. Layouts
are identical.
+HopGui.LayoutCheck.OneInput=This transform has only one input transform ({0}).
Layouts are identical.
+HopGui.LayoutCheck.Error=Error
+HopGui.LayoutCheck.ErrorGettingRowLayout=Error getting row layout for
transform ''{0}''
+HopGui.LayoutCheck.Identical=The row layouts of all previous transforms are
identical.
+HopGui.LayoutCheck.DifferentInputs=Input row layouts are different!
+HopGui.LayoutCheck.InputFromTransform=Input from transform ''{0}'':
+HopGui.LayoutCheck.NoFields=\ (No fields)
+HopGui.LayoutCheck.FieldLine=\ {0}. {1} ({2})
+HopGui.LayoutCheck.DifferencesList=Differences list:
+HopGui.LayoutCheck.LayoutIsNull=- Layout for ''{0}'' or ''{1}'' is null.
+HopGui.LayoutCheck.FieldCountMismatch=- Field count mismatch: ''{0}'' has {1}
fields, but ''{2}'' has {3} fields.
+HopGui.LayoutCheck.NameMismatch=- Position {0}: name mismatch (''{1}'' field
''{2}'' vs ''{3}'' field ''{4}'')
+HopGui.LayoutCheck.TypeMismatch=- Field ''{0}'' type mismatch: ''{1}'' is {2},
but ''{3}'' is {4}
+HopGui.LayoutCheck.FieldMissing=- Field ''{0}'' from ''{1}'' is missing in
''{2}''.
+HopGui.LayoutCheck.Dialog.MismatchTitle=Layout Check - Mismatches
+HopGui.LayoutCheck.Dialog.MismatchMessage=Details of row layout mismatches:
+HopGui.LayoutCheck.Dialog.ReplaceDummyTitle=Replace Transform
+HopGui.LayoutCheck.Dialog.ReplaceDummyMessage=Do you want to replace this
Dummy transform with a ''Stream Schema Merge'' transform?
+HopGui.LayoutCheck.Dialog.StreamSchemaPluginNotFound=Stream Schema Merge
plugin not found.
+HopGui.LayoutCheck.Dialog.ReplaceDummyError=Error replacing Dummy transform
with Stream Schema Merge
\ No newline at end of file