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 0d151e827f Enhance Concat Fields to Support Skipping Null Values 
(#6455)
0d151e827f is described below

commit 0d151e827fa9604718cc14d7940479c5fec62dcf
Author: lance <[email protected]>
AuthorDate: Mon Feb 2 20:42:20 2026 +0800

    Enhance Concat Fields to Support Skipping Null Values (#6455)
    
    Signed-off-by: lance <[email protected]>
---
 .../transforms/concatfields/ConcatFields.java      | 116 +---------
 .../concatfields/ConcatFieldsDialog.java           | 235 ++++++++++----------
 .../transforms/concatfields/ConcatFieldsMeta.java  |   7 +
 .../concatfields/helper/ConcatFieldHelper.java     | 171 +++++++++++++++
 .../messages/messages_en_US.properties             |   3 +-
 .../helper/ConcatFieldHelperTests.java             | 239 +++++++++++++++++++++
 6 files changed, 549 insertions(+), 222 deletions(-)

diff --git 
a/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFields.java
 
b/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFields.java
index 580b061dbf..7ac956e954 100644
--- 
a/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFields.java
+++ 
b/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFields.java
@@ -18,19 +18,15 @@
 package org.apache.hop.pipeline.transforms.concatfields;
 
 import java.util.ArrayList;
-import org.apache.hop.core.Const;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.exception.HopTransformException;
-import org.apache.hop.core.exception.HopValueException;
-import org.apache.hop.core.row.IValueMeta;
-import org.apache.hop.core.row.RowDataUtil;
-import org.apache.hop.core.row.value.ValueMetaBase;
 import org.apache.hop.core.util.StringUtil;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.pipeline.Pipeline;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.BaseTransform;
 import org.apache.hop.pipeline.transform.TransformMeta;
+import 
org.apache.hop.pipeline.transforms.concatfields.helper.ConcatFieldHelper;
 
 public class ConcatFields extends BaseTransform<ConcatFieldsMeta, 
ConcatFieldsData> {
 
@@ -43,13 +39,14 @@ public class ConcatFields extends 
BaseTransform<ConcatFieldsMeta, ConcatFieldsDa
       int copyNr,
       PipelineMeta pipelineMeta,
       Pipeline pipeline) {
-    super(transformMeta, meta, data, copyNr, pipelineMeta, pipeline); // 
allocate TextFileOutput
+    // allocate TextFileOutput
+    super(transformMeta, meta, data, copyNr, pipelineMeta, pipeline);
   }
 
   @Override
   public synchronized boolean processRow() throws HopException {
-
-    Object[] row = getRow(); // This also waits for a row to be finished.
+    // This also waits for a row to be finished.
+    Object[] row = getRow();
     if (row == null) {
       setOutputDone();
       return false;
@@ -115,7 +112,7 @@ public class ConcatFields extends 
BaseTransform<ConcatFieldsMeta, ConcatFieldsDa
       }
     }
 
-    Object[] outputRowData = concatFields(row);
+    Object[] outputRowData = ConcatFieldHelper.concat(row, data, meta, 
getInputRowMeta());
     putRow(data.outputRowMeta, outputRowData);
 
     if (isRowLevel()) {
@@ -132,102 +129,6 @@ public class ConcatFields extends 
BaseTransform<ConcatFieldsMeta, ConcatFieldsDa
     return true;
   }
 
-  /** Concat the field values and call putRow() */
-  private Object[] concatFields(Object[] inputRowData) throws HopException {
-    Object[] outputRowData = createOutputRow(inputRowData);
-
-    StringBuilder targetString = new StringBuilder(data.targetFieldLength); // 
use a good capacity
-
-    for (int i = 0; i < data.inputFieldIndexes.size(); i++) {
-      if (i > 0) {
-        targetString.append(data.stringSeparator);
-      }
-
-      int inputRowIndex = data.inputFieldIndexes.get(i);
-      IValueMeta valueMeta = getInputRowMeta().getValueMeta(inputRowIndex);
-      Object valueData = inputRowData[inputRowIndex];
-
-      String nullString;
-      String trimType;
-      if (meta.getOutputFields().isEmpty()) {
-        // No specific null value defined
-        nullString = "";
-        trimType = ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE);
-      } else {
-        ConcatField field = meta.getOutputFields().get(i);
-        nullString = Const.NVL(field.getNullString(), "");
-        trimType = data.trimType[i];
-        // Manage missing trim type. Leave the incoming value as it is
-        if (trimType == null) {
-          trimType = ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE);
-        }
-      }
-
-      concatField(targetString, valueMeta, valueData, nullString, trimType);
-    }
-
-    outputRowData[data.outputRowMeta.size() - 1] = targetString.toString();
-
-    return outputRowData;
-  }
-
-  private void concatField(
-      StringBuilder targetField,
-      IValueMeta valueMeta,
-      Object valueData,
-      String nullString,
-      String trimType)
-      throws HopValueException {
-
-    if (meta.isForceEnclosure()) {
-      targetField.append(meta.getEnclosure());
-    }
-    if (valueMeta.isNull(valueData)) {
-      targetField.append(nullString);
-    } else {
-      if (trimType != null) {
-        // Get the raw string value without applying the incoming field's trim 
type
-        // by temporarily setting trim type to NONE
-        int originalTrimType = valueMeta.getTrimType();
-        valueMeta.setTrimType(IValueMeta.TRIM_TYPE_NONE);
-        String stringValue = valueMeta.getString(valueData);
-        // Restore the original trim type
-        valueMeta.setTrimType(originalTrimType);
-
-        // Apply only the configured trim type from the transform
-        targetField.append(
-            Const.trimToType(stringValue, 
ValueMetaBase.getTrimTypeByCode(trimType)));
-      }
-    }
-    if (meta.isForceEnclosure()) {
-      targetField.append(meta.getEnclosure());
-    }
-  }
-
-  // reserve room for the target field and eventually re-map the fields
-  private Object[] createOutputRow(Object[] inputRowData) {
-    // Allocate a new empty row
-    //
-    Object[] outputRowData;
-
-    if (meta.getExtraFields().isRemoveSelectedFields()) {
-      outputRowData = RowDataUtil.allocateRowData(data.outputRowMeta.size());
-      int outputRowIndex = 0;
-
-      // Copy over only the fields we want from the input.
-      //
-      for (int inputRowIndex : data.outputFieldIndexes) {
-        outputRowData[outputRowIndex++] = inputRowData[inputRowIndex];
-      }
-    } else {
-      // Keep the current row and add a field
-      //
-      outputRowData = RowDataUtil.createResizedCopy(inputRowData, 
data.outputRowMeta.size());
-    }
-
-    return outputRowData;
-  }
-
   @Override
   public boolean init() {
 
@@ -262,9 +163,4 @@ public class ConcatFields extends 
BaseTransform<ConcatFieldsMeta, ConcatFieldsDa
       }
     }
   }
-
-  @Override
-  public void dispose() {
-    super.dispose();
-  }
 }
diff --git 
a/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFieldsDialog.java
 
b/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFieldsDialog.java
index 08a31f46f3..8c83f7d8b4 100644
--- 
a/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFieldsDialog.java
+++ 
b/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFieldsDialog.java
@@ -31,6 +31,7 @@ import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
 import org.apache.hop.ui.core.ConstUi;
+import org.apache.hop.ui.core.FormDataBuilder;
 import org.apache.hop.ui.core.PropsUi;
 import org.apache.hop.ui.core.dialog.BaseDialog;
 import org.apache.hop.ui.core.dialog.ErrorDialog;
@@ -43,9 +44,9 @@ import 
org.apache.hop.ui.pipeline.transform.ITableItemInsertListener;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.CTabFolder;
 import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.layout.FormLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
@@ -72,8 +73,14 @@ public class ConcatFieldsDialog extends BaseTransformDialog {
 
   private Button wRemove;
 
+  /** enclosure label / text */
+  private Label wlEnclosure;
+
   private Button wForceEnclosure;
 
+  /** skip empty value(empty/null) */
+  private Button skipEmptyBtn;
+
   private TableView wFields;
 
   private final ConcatFieldsMeta input;
@@ -124,18 +131,16 @@ public class ConcatFieldsDialog extends 
BaseTransformDialog {
     wlTransformName.setText(BaseMessages.getString(PKG, 
"System.TransformName.Label"));
     wlTransformName.setToolTipText(BaseMessages.getString(PKG, 
"System.TransformName.Tooltip"));
     PropsUi.setLook(wlTransformName);
-    fdlTransformName = new FormData();
-    fdlTransformName.left = new FormAttachment(0, 0);
-    fdlTransformName.top = new FormAttachment(0, margin);
-    fdlTransformName.right = new FormAttachment(middle, -margin);
-    wlTransformName.setLayoutData(fdlTransformName);
+    wlTransformName.setLayoutData(
+        FormDataBuilder.builder().top(0, margin).left().right(middle, 
-margin).build());
     wTransformName = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
     PropsUi.setLook(wTransformName);
-    fdTransformName = new FormData();
-    fdTransformName.left = new FormAttachment(middle, 0);
-    fdTransformName.top = new FormAttachment(wlTransformName, 0, SWT.CENTER);
-    fdTransformName.right = new FormAttachment(100, 0);
-    wTransformName.setLayoutData(fdTransformName);
+    wTransformName.setLayoutData(
+        FormDataBuilder.builder()
+            .top(wlTransformName, 0, SWT.CENTER)
+            .left(middle, 0)
+            .right(100, 0)
+            .build());
     Control lastControl = wTransformName;
 
     // TargetFieldName line
@@ -145,19 +150,16 @@ public class ConcatFieldsDialog extends 
BaseTransformDialog {
     wlTargetFieldName.setToolTipText(
         BaseMessages.getString(PKG, 
"ConcatFieldsDialog.TargetFieldName.Tooltip"));
     PropsUi.setLook(wlTargetFieldName);
-    FormData fdlTargetFieldName = new FormData();
-    fdlTargetFieldName.left = new FormAttachment(0, 0);
-    fdlTargetFieldName.top = new FormAttachment(lastControl, margin);
-    fdlTargetFieldName.right = new FormAttachment(middle, -margin);
-    wlTargetFieldName.setLayoutData(fdlTargetFieldName);
+    wlTargetFieldName.setLayoutData(
+        FormDataBuilder.builder().top(lastControl, 
margin).left().right(middle, -margin).build());
     wTargetFieldName = new TextVar(variables, shell, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
     PropsUi.setLook(wTargetFieldName);
-    FormData fdTargetFieldName = new FormData();
-    fdTargetFieldName.left = new FormAttachment(middle, 0);
-    fdTargetFieldName.top = new FormAttachment(wlTargetFieldName, 0, 
SWT.CENTER);
-    fdTargetFieldName.right = new FormAttachment(100, 0);
-    wTargetFieldName.setLayoutData(fdTargetFieldName);
-    lastControl = wTargetFieldName;
+    wTargetFieldName.setLayoutData(
+        FormDataBuilder.builder()
+            .top(wlTargetFieldName, 0, SWT.CENTER)
+            .left(middle, 0)
+            .right(100, 0)
+            .build());
 
     // TargetFieldLength line
     Label wlTargetFieldLength = new Label(shell, SWT.RIGHT);
@@ -166,101 +168,116 @@ public class ConcatFieldsDialog extends 
BaseTransformDialog {
     wlTargetFieldLength.setToolTipText(
         BaseMessages.getString(PKG, 
"ConcatFieldsDialog.TargetFieldLength.Tooltip"));
     PropsUi.setLook(wlTargetFieldLength);
-    FormData fdlTargetFieldLength = new FormData();
-    fdlTargetFieldLength.left = new FormAttachment(0, 0);
-    fdlTargetFieldLength.top = new FormAttachment(wTargetFieldName, margin);
-    fdlTargetFieldLength.right = new FormAttachment(middle, -margin);
-    wlTargetFieldLength.setLayoutData(fdlTargetFieldLength);
+    wlTargetFieldLength.setLayoutData(
+        FormDataBuilder.builder()
+            .top(wTargetFieldName, margin)
+            .left()
+            .right(middle, -margin)
+            .build());
     wTargetFieldLength = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
     PropsUi.setLook(wTargetFieldLength);
-    FormData fdTargetFieldLength = new FormData();
-    fdTargetFieldLength.left = new FormAttachment(middle, 0);
-    fdTargetFieldLength.top = new FormAttachment(wlTargetFieldLength, 0, 
SWT.CENTER);
-    fdTargetFieldLength.right = new FormAttachment(100, 0);
-    wTargetFieldLength.setLayoutData(fdTargetFieldLength);
+    wTargetFieldLength.setLayoutData(
+        FormDataBuilder.builder()
+            .top(wlTargetFieldLength, 0, SWT.CENTER)
+            .left(middle, 0)
+            .right(100, 0)
+            .build());
     lastControl = wTargetFieldLength;
 
     // Separator
     Label wlSeparator = new Label(shell, SWT.RIGHT);
     wlSeparator.setText(BaseMessages.getString(PKG, 
"ConcatFieldsDialog.Separator.Label"));
     PropsUi.setLook(wlSeparator);
-    FormData fdlSeparator = new FormData();
-    fdlSeparator.left = new FormAttachment(0, 0);
-    fdlSeparator.top = new FormAttachment(lastControl, margin);
-    fdlSeparator.right = new FormAttachment(middle, -margin);
-    wlSeparator.setLayoutData(fdlSeparator);
+    wlSeparator.setLayoutData(
+        FormDataBuilder.builder().top(lastControl, 
margin).left().right(middle, -margin).build());
 
     Button wbSeparator = new Button(shell, SWT.PUSH | SWT.CENTER);
     PropsUi.setLook(wbSeparator);
     wbSeparator.setText(BaseMessages.getString(PKG, 
"ConcatFieldsDialog.Separator.Button"));
-    FormData fdbSeparator = new FormData();
-    fdbSeparator.right = new FormAttachment(100, 0);
-    fdbSeparator.top = new FormAttachment(wlSeparator, 0, SWT.CENTER);
-    wbSeparator.setLayoutData(fdbSeparator);
+    wbSeparator.setLayoutData(
+        FormDataBuilder.builder().top(wlSeparator, 0, SWT.CENTER).right(100, 
0).build());
     wbSeparator.addListener(SWT.Selection, se -> 
wSeparator.getTextWidget().insert("\t"));
 
     wSeparator = new TextVar(variables, shell, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
     PropsUi.setLook(wSeparator);
-    FormData fdSeparator = new FormData();
-    fdSeparator.left = new FormAttachment(middle, 0);
-    fdSeparator.top = new FormAttachment(wlSeparator, 0, SWT.CENTER);
-    fdSeparator.right = new FormAttachment(wbSeparator, -margin);
-    wSeparator.setLayoutData(fdSeparator);
+    wSeparator.setLayoutData(
+        FormDataBuilder.builder()
+            .top(wlSeparator, 0, SWT.CENTER)
+            .left(middle, 0)
+            .right(wbSeparator, -margin)
+            .build());
     lastControl = wbSeparator;
 
-    // Enclosure line...
-    Label wlEnclosure = new Label(shell, SWT.RIGHT);
-    wlEnclosure.setText(BaseMessages.getString(PKG, 
"ConcatFieldsDialog.Enclosure.Label"));
-    PropsUi.setLook(wlEnclosure);
-    FormData fdlEnclosure = new FormData();
-    fdlEnclosure.left = new FormAttachment(0, 0);
-    fdlEnclosure.top = new FormAttachment(lastControl, margin);
-    fdlEnclosure.right = new FormAttachment(middle, -margin);
-    wlEnclosure.setLayoutData(fdlEnclosure);
-    wEnclosure = new TextVar(variables, shell, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
-    PropsUi.setLook(wEnclosure);
-    FormData fdEnclosure = new FormData();
-    fdEnclosure.left = new FormAttachment(middle, 0);
-    fdEnclosure.top = new FormAttachment(wlEnclosure, 0, SWT.CENTER);
-    fdEnclosure.right = new FormAttachment(100, 0);
-    wEnclosure.setLayoutData(fdEnclosure);
-    lastControl = wEnclosure;
-
     // Force enclosure
     Label wlForceEnclosure = new Label(shell, SWT.RIGHT);
     wlForceEnclosure.setText(
         BaseMessages.getString(PKG, 
"ConcatFieldsDialog.ForceEnclosure.Label"));
     PropsUi.setLook(wlForceEnclosure);
-    FormData fdlForceEnclosure = new FormData();
-    fdlForceEnclosure.left = new FormAttachment(0, 0);
-    fdlForceEnclosure.top = new FormAttachment(lastControl, margin);
-    fdlForceEnclosure.right = new FormAttachment(middle, -margin);
-    wlForceEnclosure.setLayoutData(fdlForceEnclosure);
+    wlForceEnclosure.setLayoutData(
+        FormDataBuilder.builder().left().top(lastControl, 
margin).right(middle, -margin).build());
     wForceEnclosure = new Button(shell, SWT.CHECK | SWT.LEFT);
     PropsUi.setLook(wForceEnclosure);
-    FormData fdForceEnclosure = new FormData();
-    fdForceEnclosure.left = new FormAttachment(middle, 0);
-    fdForceEnclosure.top = new FormAttachment(wlForceEnclosure, 0, SWT.CENTER);
-    fdForceEnclosure.right = new FormAttachment(100, 0);
-    wForceEnclosure.setLayoutData(fdForceEnclosure);
+    wForceEnclosure.setLayoutData(
+        FormDataBuilder.builder().left(middle, 0).top(wlForceEnclosure, 0, 
SWT.CENTER).build());
     lastControl = wForceEnclosure;
+    wForceEnclosure.addSelectionListener(
+        new SelectionAdapter() {
+          @Override
+          public void widgetSelected(SelectionEvent e) {
+            input.setChanged();
+            activateEnclosure();
+          }
+        });
+
+    // Enclosure line...
+    wlEnclosure = new Label(shell, SWT.RIGHT);
+    wlEnclosure.setText(BaseMessages.getString(PKG, 
"ConcatFieldsDialog.Enclosure.Label"));
+    PropsUi.setLook(wlEnclosure);
+    wlEnclosure.setLayoutData(
+        FormDataBuilder.builder()
+            .left(wForceEnclosure, margin)
+            .top(wSeparator, margin)
+            .width(180)
+            .build());
+    wEnclosure = new TextVar(variables, shell, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wEnclosure);
+    wEnclosure.setLayoutData(
+        FormDataBuilder.builder()
+            .left(wlEnclosure, margin)
+            .top(wSeparator, margin)
+            .right(100, 0)
+            .build());
+
+    // skip null/empty
+    Label skipEmptyLabel = new Label(shell, SWT.RIGHT);
+    skipEmptyLabel.setText(BaseMessages.getString(PKG, 
"ConcatFieldsDialog.SkipEmpty.Label"));
+    PropsUi.setLook(skipEmptyLabel);
+    skipEmptyLabel.setLayoutData(
+        FormDataBuilder.builder().left().top(lastControl, 
margin).right(middle, -margin).build());
+    skipEmptyBtn = new Button(shell, SWT.CHECK | SWT.LEFT);
+    PropsUi.setLook(skipEmptyBtn);
+    skipEmptyBtn.setLayoutData(
+        FormDataBuilder.builder()
+            .left(middle, 0)
+            .top(skipEmptyLabel, 0, SWT.CENTER)
+            .right(100, 0)
+            .build());
+    lastControl = skipEmptyLabel;
 
     // Remove concatenated fields from input...
     Label wlRemove = new Label(shell, SWT.RIGHT);
     wlRemove.setText(BaseMessages.getString(PKG, 
"ConcatFieldsDialog.Remove.Label"));
     PropsUi.setLook(wlRemove);
-    FormData fdlRemove = new FormData();
-    fdlRemove.left = new FormAttachment(0, 0);
-    fdlRemove.top = new FormAttachment(lastControl, margin);
-    fdlRemove.right = new FormAttachment(middle, -margin);
-    wlRemove.setLayoutData(fdlRemove);
+    wlRemove.setLayoutData(
+        FormDataBuilder.builder().top(lastControl, 
margin).left().right(middle, -margin).build());
     wRemove = new Button(shell, SWT.CHECK | SWT.LEFT);
     PropsUi.setLook(wRemove);
-    FormData fdRemove = new FormData();
-    fdRemove.left = new FormAttachment(middle, 0);
-    fdRemove.top = new FormAttachment(wlRemove, 0, SWT.CENTER);
-    fdRemove.right = new FormAttachment(100, 0);
-    wRemove.setLayoutData(fdRemove);
+    wRemove.setLayoutData(
+        FormDataBuilder.builder()
+            .top(wlRemove, 0, SWT.CENTER)
+            .left(middle, 0)
+            .right(100, 0)
+            .build());
     lastControl = wlRemove;
 
     // ////////////////////////
@@ -305,7 +322,7 @@ public class ConcatFieldsDialog extends BaseTransformDialog 
{
     int totalSize = dats.length + nums.length;
     String[] formats = new String[totalSize];
     System.arraycopy(dats, 0, formats, 0, dats.length);
-    System.arraycopy(nums, 0, formats, dats.length + 0, nums.length);
+    System.arraycopy(nums, 0, formats, dats.length, nums.length);
 
     fieldColumns = new ColumnInfo[FieldsCols];
     fieldColumns[0] =
@@ -370,13 +387,8 @@ public class ConcatFieldsDialog extends 
BaseTransformDialog {
             FieldsRows,
             null,
             props);
-
-    FormData fdFields = new FormData();
-    fdFields.left = new FormAttachment(0, 0);
-    fdFields.top = new FormAttachment(0, 0);
-    fdFields.right = new FormAttachment(100, 0);
-    fdFields.bottom = new FormAttachment(wGet, -margin);
-    wFields.setLayoutData(fdFields);
+    wFields.setLayoutData(
+        FormDataBuilder.builder().top().left().right(100, 0).bottom(wGet, 
-margin).build());
 
     //
     // Search the fields in the background
@@ -399,23 +411,18 @@ public class ConcatFieldsDialog extends 
BaseTransformDialog {
           }
         };
     new Thread(runnable).start();
-
-    FormData fdFieldsComp = new FormData();
-    fdFieldsComp.left = new FormAttachment(0, 0);
-    fdFieldsComp.top = new FormAttachment(0, 0);
-    fdFieldsComp.right = new FormAttachment(100, 0);
-    fdFieldsComp.bottom = new FormAttachment(100, 0);
-    wFieldsComp.setLayoutData(fdFieldsComp);
+    wFieldsComp.setLayoutData(
+        FormDataBuilder.builder().top().left().right(100, 0).bottom(100, 
0).build());
 
     wFieldsComp.layout();
     wFieldsTab.setControl(wFieldsComp);
-
-    FormData fdTabFolder = new FormData();
-    fdTabFolder.left = new FormAttachment(0, 0);
-    fdTabFolder.top = new FormAttachment(lastControl, 2 * margin);
-    fdTabFolder.right = new FormAttachment(100, 0);
-    fdTabFolder.bottom = new FormAttachment(wOk, -2 * margin);
-    wTabFolder.setLayoutData(fdTabFolder);
+    wTabFolder.setLayoutData(
+        FormDataBuilder.builder()
+            .top(lastControl, 2 * margin)
+            .left()
+            .right(100, 0)
+            .bottom(wOk, -2 * margin)
+            .build());
 
     // Whenever something changes, set the tooltip to the expanded version:
     wTargetFieldName.addModifyListener(
@@ -434,8 +441,8 @@ public class ConcatFieldsDialog extends BaseTransformDialog 
{
 
     getData();
 
+    activateEnclosure();
     BaseDialog.defaultShellHandling(shell, c -> ok(), c -> cancel());
-
     return transformName;
   }
 
@@ -456,6 +463,7 @@ public class ConcatFieldsDialog extends BaseTransformDialog 
{
     wEnclosure.setText(Const.NVL(input.getEnclosure(), ""));
     wForceEnclosure.setSelection(input.isForceEnclosure());
     wRemove.setSelection(input.getExtraFields().isRemoveSelectedFields());
+    skipEmptyBtn.setSelection(input.isSkipValueEmpty());
 
     logDebug("getting fields info...");
 
@@ -497,6 +505,7 @@ public class ConcatFieldsDialog extends BaseTransformDialog 
{
     meta.setSeparator(wSeparator.getText());
     meta.setEnclosure(wEnclosure.getText());
     meta.setForceEnclosure(wForceEnclosure.getSelection());
+    meta.setSkipValueEmpty(skipEmptyBtn.getSelection());
     meta.getExtraFields().setRemoveSelectedFields(wRemove.getSelection());
 
     input.getOutputFields().clear();
@@ -522,12 +531,11 @@ public class ConcatFieldsDialog extends 
BaseTransformDialog {
     if (StringUtil.isEmpty(wTransformName.getText())) {
       return;
     }
-
-    transformName = wTransformName.getText(); // return value
+    // return value
+    transformName = wTransformName.getText();
 
     getInfo(input);
     input.setChanged();
-
     dispose();
   }
 
@@ -597,4 +605,9 @@ public class ConcatFieldsDialog extends BaseTransformDialog 
{
 
     wFields.optWidth(true);
   }
+
+  private void activateEnclosure() {
+    wlEnclosure.setEnabled(wForceEnclosure.getSelection());
+    wEnclosure.setEnabled(wForceEnclosure.getSelection());
+  }
 }
diff --git 
a/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFieldsMeta.java
 
b/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFieldsMeta.java
index 1676c6a4e9..3a75945044 100644
--- 
a/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFieldsMeta.java
+++ 
b/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/ConcatFieldsMeta.java
@@ -74,6 +74,10 @@ public class ConcatFieldsMeta extends 
BaseTransformMeta<ConcatFields, ConcatFiel
   @HopMetadataProperty(key = "ConcatFields")
   private ExtraFields extraFields;
 
+  /** ignore empty value */
+  @HopMetadataProperty(key = "skip_value_empty", injectionKey = 
"SKIP_VALUE_EMPTY")
+  private boolean skipValueEmpty;
+
   public ConcatFieldsMeta() {
     super();
     outputFields = new ArrayList<>();
@@ -100,6 +104,9 @@ public class ConcatFieldsMeta extends 
BaseTransformMeta<ConcatFields, ConcatFiel
     extraFields.setTargetFieldName("");
     extraFields.setTargetFieldLength(0);
     extraFields.setRemoveSelectedFields(false);
+
+    // default disable null/empty value
+    skipValueEmpty = false;
   }
 
   @Override
diff --git 
a/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/helper/ConcatFieldHelper.java
 
b/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/helper/ConcatFieldHelper.java
new file mode 100644
index 0000000000..e7403ad7e9
--- /dev/null
+++ 
b/plugins/transforms/concatfields/src/main/java/org/apache/hop/pipeline/transforms/concatfields/helper/ConcatFieldHelper.java
@@ -0,0 +1,171 @@
+/*
+ * 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.pipeline.transforms.concatfields.helper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import lombok.experimental.UtilityClass;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowDataUtil;
+import org.apache.hop.core.row.value.ValueMetaBase;
+import org.apache.hop.core.util.Utils;
+import org.apache.hop.pipeline.transforms.concatfields.ConcatField;
+import org.apache.hop.pipeline.transforms.concatfields.ConcatFieldsData;
+import org.apache.hop.pipeline.transforms.concatfields.ConcatFieldsMeta;
+
+/**
+ * Utility class used by the Concat Fields transform to concatenate multiple 
input field values into
+ * a single string field.
+ *
+ * <p>The concatenation behavior supports:
+ *
+ * <ul>
+ *   <li>Custom field separators
+ *   <li>Null value replacement
+ *   <li>Configurable trim types per field
+ *   <li>Optional value enclosure
+ *   <li>Skipping null and/or empty values
+ * </ul>
+ *
+ * <p>This helper is stateless and thread-safe.
+ */
+@UtilityClass
+public class ConcatFieldHelper {
+
+  /**
+   * Concatenates the values of the selected input fields into a single string 
field.
+   *
+   * @param inputRowData the input row data
+   * @param data the runtime data for the Concat Fields transform
+   * @param meta the metadata configuration for the Concat Fields transform
+   * @param rowMeta the metadata describing the input row structure
+   * @return the output row data with the concatenated field appended
+   * @throws HopException if a conversion error occurs while processing field 
values
+   */
+  public static Object[] concat(
+      Object[] inputRowData, ConcatFieldsData data, ConcatFieldsMeta meta, 
IRowMeta rowMeta)
+      throws HopException {
+    Object[] outputRowData = createOutputRow(inputRowData, data, meta);
+    List<String> fields = new ArrayList<>(Math.max(data.targetFieldLength, 8));
+
+    for (int i = 0; i < data.inputFieldIndexes.size(); i++) {
+      int inputRowIndex = data.inputFieldIndexes.get(i);
+      IValueMeta valueMeta = rowMeta.getValueMeta(inputRowIndex);
+      Object valueData = inputRowData[inputRowIndex];
+
+      String nullString;
+      String trimType;
+      if (meta.getOutputFields().isEmpty()) {
+        // No specific null value defined
+        nullString = "";
+        trimType = ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE);
+      } else {
+        ConcatField field = meta.getOutputFields().get(i);
+        nullString = Const.NVL(field.getNullString(), "");
+        trimType = data.trimType[i];
+        // Manage missing trim type. Leave the incoming value as it is
+        if (trimType == null) {
+          trimType = ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE);
+        }
+      }
+
+      handlerField(fields, valueMeta, valueData, nullString, trimType);
+    }
+
+    // list -> string(support separator)
+    String separator = Const.NVL(data.stringSeparator, "");
+    String collect =
+        fields.stream()
+            .filter(c -> !(meta.isSkipValueEmpty() && Utils.isEmpty(c)))
+            .map(c -> meta.isForceEnclosure() ? meta.getEnclosure() + c + 
meta.getEnclosure() : c)
+            .collect(Collectors.joining(separator));
+
+    outputRowData[data.outputRowMeta.size() - 1] = collect;
+    return outputRowData;
+  }
+
+  /**
+   * Converts a single field value to its string representation and adds it to 
the provided list of
+   * field values.
+   *
+   * @param fields the list collecting converted field values
+   * @param valueMeta the value metadata describing the field type
+   * @param valueData the actual field value
+   * @param nullString the replacement value used when the field value is null
+   * @param trimType the trim type code to apply
+   * @throws HopValueException if the value cannot be converted to a string
+   */
+  private static void handlerField(
+      List<String> fields,
+      IValueMeta valueMeta,
+      Object valueData,
+      String nullString,
+      String trimType)
+      throws HopValueException {
+    if (valueMeta.isNull(valueData)) {
+      fields.add(nullString);
+      return;
+    }
+
+    if (trimType != null) {
+      // Get the raw string value without applying the incoming field's trim 
type
+      // by temporarily setting trim type to NONE
+      int originalTrimType = valueMeta.getTrimType();
+      valueMeta.setTrimType(IValueMeta.TRIM_TYPE_NONE);
+      String stringValue = valueMeta.getString(valueData);
+      // Restore the original trim type
+      valueMeta.setTrimType(originalTrimType);
+
+      // Apply only the configured trim type from the transform
+      fields.add(Const.trimToType(stringValue, 
ValueMetaBase.getTrimTypeByCode(trimType)));
+    }
+  }
+
+  /**
+   * Creates the output row based on the transform configuration.
+   *
+   * @param inputRowData the input row data
+   * @param data the runtime data for the transform
+   * @param meta the transform metadata
+   * @return a new output row array with the appropriate size
+   */
+  private static Object[] createOutputRow(
+      Object[] inputRowData, ConcatFieldsData data, ConcatFieldsMeta meta) {
+    // Allocate a new empty row
+    Object[] outputRowData;
+
+    if (meta.getExtraFields().isRemoveSelectedFields()) {
+      outputRowData = RowDataUtil.allocateRowData(data.outputRowMeta.size());
+      int outputRowIndex = 0;
+
+      // Copy over only the fields we want from the input.
+      for (int inputRowIndex : data.outputFieldIndexes) {
+        outputRowData[outputRowIndex++] = inputRowData[inputRowIndex];
+      }
+    } else {
+      // Keep the current row and add a field
+      outputRowData = RowDataUtil.createResizedCopy(inputRowData, 
data.outputRowMeta.size());
+    }
+    return outputRowData;
+  }
+}
diff --git 
a/plugins/transforms/concatfields/src/main/resources/org/apache/hop/pipeline/transforms/concatfields/messages/messages_en_US.properties
 
b/plugins/transforms/concatfields/src/main/resources/org/apache/hop/pipeline/transforms/concatfields/messages/messages_en_US.properties
index 8f6fcc0b4e..8f74a4bb03 100644
--- 
a/plugins/transforms/concatfields/src/main/resources/org/apache/hop/pipeline/transforms/concatfields/messages/messages_en_US.properties
+++ 
b/plugins/transforms/concatfields/src/main/resources/org/apache/hop/pipeline/transforms/concatfields/messages/messages_en_US.properties
@@ -51,4 +51,5 @@ ConcatFieldsMeta.CheckResult.FieldsReceived=Transform is 
connected to previous o
 ConcatFieldsMeta.CheckResult.TargetFieldLengthMissingFastDataDump=It is 
recommended to specify the Length of Target Field when the Fast Data Dump 
option is enabled.
 ConcatFieldsMeta.CheckResult.TargetFieldNameMissing=The Target Field Name is 
not specified.
 ConcatFieldsMeta.keyword=concat,field
-ConcatFieldsDialog.ForceEnclosure.Label=Force enclosure?
\ No newline at end of file
+ConcatFieldsDialog.ForceEnclosure.Label=Force enclosure?
+ConcatFieldsDialog.SkipEmpty.Label=Skip empty
\ No newline at end of file
diff --git 
a/plugins/transforms/concatfields/src/test/java/org/apache/hop/pipeline/transforms/concatfields/helper/ConcatFieldHelperTests.java
 
b/plugins/transforms/concatfields/src/test/java/org/apache/hop/pipeline/transforms/concatfields/helper/ConcatFieldHelperTests.java
new file mode 100644
index 0000000000..54d9a8ba15
--- /dev/null
+++ 
b/plugins/transforms/concatfields/src/test/java/org/apache/hop/pipeline/transforms/concatfields/helper/ConcatFieldHelperTests.java
@@ -0,0 +1,239 @@
+/*
+ * 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.pipeline.transforms.concatfields.helper;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.stream.Stream;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaBase;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.pipeline.transforms.concatfields.ConcatField;
+import org.apache.hop.pipeline.transforms.concatfields.ConcatFieldsData;
+import org.apache.hop.pipeline.transforms.concatfields.ConcatFieldsMeta;
+import org.apache.hop.pipeline.transforms.concatfields.ExtraFields;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/** Unit test for {@link ConcatFieldHelper} */
+class ConcatFieldHelperTests {
+
+  @Test
+  void testConcatFieldsBasic() throws Exception {
+    // rowMeta
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("f1"));
+    rowMeta.addValueMeta(new ValueMetaString("f2"));
+    // input row
+    Object[] inputRow = new Object[] {"hello", "world"};
+
+    // data
+    ConcatFieldsData data = new ConcatFieldsData();
+    data.inputFieldIndexes = List.of(0, 1);
+    data.outputFieldIndexes = List.of(0, 1);
+    data.stringSeparator = ",";
+    data.targetFieldLength = 50;
+    data.trimType =
+        new String[] {
+          ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE),
+          ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)
+        };
+
+    RowMeta outputRowMeta = new RowMeta();
+    outputRowMeta.addValueMeta(new ValueMetaString("f1"));
+    outputRowMeta.addValueMeta(new ValueMetaString("f2"));
+    outputRowMeta.addValueMeta(new ValueMetaString("concat"));
+
+    data.outputRowMeta = outputRowMeta;
+
+    // meta
+    ConcatFieldsMeta meta = mock(ConcatFieldsMeta.class);
+    when(meta.isForceEnclosure()).thenReturn(false);
+    when(meta.getOutputFields()).thenReturn(List.of());
+    when(meta.getExtraFields()).thenReturn(new ExtraFields());
+
+    // result
+    Object[] result = ConcatFieldHelper.concat(inputRow, data, meta, rowMeta);
+
+    assertEquals("hello,world", result[2]);
+  }
+
+  @Test
+  void testConcatFieldsWithNullAndTrim() throws Exception {
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("f1"));
+    rowMeta.addValueMeta(new ValueMetaString("f2"));
+
+    Object[] inputRow = new Object[] {" hello ", null};
+
+    ConcatFieldsData data = new ConcatFieldsData();
+    data.inputFieldIndexes = List.of(0, 1);
+    data.outputFieldIndexes = List.of(0, 1);
+    data.stringSeparator = "|";
+    data.targetFieldLength = 50;
+    data.trimType =
+        new String[] {
+          ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_BOTH),
+          ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)
+        };
+
+    RowMeta outputRowMeta = new RowMeta();
+    outputRowMeta.addValueMeta(new ValueMetaString("f1"));
+    outputRowMeta.addValueMeta(new ValueMetaString("f2"));
+    outputRowMeta.addValueMeta(new ValueMetaString("concat"));
+    data.outputRowMeta = outputRowMeta;
+
+    ConcatField field1 = new ConcatField();
+    field1.setNullString("");
+
+    ConcatField field2 = new ConcatField();
+    field2.setNullString("NULL");
+
+    ConcatFieldsMeta meta = mock(ConcatFieldsMeta.class);
+    when(meta.isForceEnclosure()).thenReturn(false);
+    when(meta.getOutputFields()).thenReturn(List.of(field1, field2));
+    when(meta.getExtraFields()).thenReturn(new ExtraFields());
+
+    Object[] result = ConcatFieldHelper.concat(inputRow, data, meta, rowMeta);
+
+    assertEquals("hello|NULL", result[2]);
+  }
+
+  @Test
+  void testConcatFieldsWithEnclosure() throws Exception {
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("f1"));
+
+    Object[] inputRow = new Object[] {"abc"};
+
+    ConcatFieldsData data = new ConcatFieldsData();
+    data.inputFieldIndexes = List.of(0);
+    data.outputFieldIndexes = List.of(0, 1);
+    data.stringSeparator = "";
+    data.targetFieldLength = 10;
+    data.trimType = new String[] 
{ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)};
+
+    RowMeta outputRowMeta = new RowMeta();
+    outputRowMeta.addValueMeta(new ValueMetaString("f1"));
+    outputRowMeta.addValueMeta(new ValueMetaString("concat"));
+    data.outputRowMeta = outputRowMeta;
+
+    ConcatFieldsMeta meta = mock(ConcatFieldsMeta.class);
+    when(meta.isForceEnclosure()).thenReturn(true);
+    when(meta.getEnclosure()).thenReturn("\"");
+    when(meta.getOutputFields()).thenReturn(List.of());
+    when(meta.getExtraFields()).thenReturn(new ExtraFields());
+
+    Object[] result = ConcatFieldHelper.concat(inputRow, data, meta, rowMeta);
+
+    assertEquals("\"abc\"", result[1]);
+  }
+
+  @ParameterizedTest
+  @MethodSource("inputRowProvider")
+  void testConcatFieldsBasic(
+      Object[] inputRow, boolean isForceEnclosure, boolean skipEmpty, String 
expected)
+      throws Exception {
+    // rowMeta
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("col1"));
+    rowMeta.addValueMeta(new ValueMetaString("col2"));
+    rowMeta.addValueMeta(new ValueMetaString("col3"));
+
+    // data
+    ConcatFieldsData data = new ConcatFieldsData();
+    data.inputFieldIndexes = List.of(0, 1, 2);
+    data.outputFieldIndexes = List.of(0, 1, 2);
+    data.stringSeparator = ";";
+    data.targetFieldLength = 50;
+    data.trimType =
+        new String[] {
+          ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE),
+          ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE),
+          ValueMetaBase.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)
+        };
+
+    RowMeta outputRowMeta = new RowMeta();
+    outputRowMeta.addValueMeta(new ValueMetaString("col1"));
+    outputRowMeta.addValueMeta(new ValueMetaString("col2"));
+    outputRowMeta.addValueMeta(new ValueMetaString("col3"));
+    outputRowMeta.addValueMeta(new ValueMetaString("concat"));
+
+    data.outputRowMeta = outputRowMeta;
+
+    // meta
+    ConcatFieldsMeta meta = mock(ConcatFieldsMeta.class);
+    when(meta.isForceEnclosure()).thenReturn(isForceEnclosure);
+    when(meta.getEnclosure()).thenReturn("\"");
+    when(meta.getOutputFields()).thenReturn(List.of());
+    when(meta.getExtraFields()).thenReturn(new ExtraFields());
+    when(meta.isSkipValueEmpty()).thenReturn(skipEmpty);
+
+    // result
+    Object[] result = ConcatFieldHelper.concat(inputRow, data, meta, rowMeta);
+    assertEquals(expected, result[3]);
+  }
+
+  static Stream<Arguments> inputRowProvider() {
+    return Stream.of(
+        Arguments.of(new Object[] {"a", "b", "c"}, false, false, "a;b;c"),
+        Arguments.of(new Object[] {"a", "b", null}, false, false, "a;b;"),
+        Arguments.of(new Object[] {"a", null, null}, false, false, "a;;"),
+        Arguments.of(new Object[] {null, "b", "c"}, false, false, ";b;c"),
+        Arguments.of(new Object[] {null, "b", null}, false, false, ";b;"),
+        Arguments.of(new Object[] {null, null, "c"}, false, false, ";;c"),
+        Arguments.of(new Object[] {null, null, null}, false, false, ";;"),
+        Arguments.of(new Object[] {"", "", ""}, false, false, ";;"),
+
+        // enable force enclosure.
+        Arguments.of(new Object[] {"a", "b", "c"}, true, false, 
"\"a\";\"b\";\"c\""),
+        Arguments.of(new Object[] {"a", "b", null}, true, false, 
"\"a\";\"b\";\"\""),
+        Arguments.of(new Object[] {"a", null, null}, true, false, 
"\"a\";\"\";\"\""),
+        Arguments.of(new Object[] {null, "b", "c"}, true, false, 
"\"\";\"b\";\"c\""),
+        Arguments.of(new Object[] {null, "b", null}, true, false, 
"\"\";\"b\";\"\""),
+        Arguments.of(new Object[] {null, null, "c"}, true, false, 
"\"\";\"\";\"c\""),
+        Arguments.of(new Object[] {null, null, null}, true, false, 
"\"\";\"\";\"\""),
+        Arguments.of(new Object[] {"", "", ""}, true, false, "\"\";\"\";\"\""),
+
+        // skip empty(null/"")
+        Arguments.of(new Object[] {"a", "b", "c"}, false, true, "a;b;c"),
+        Arguments.of(new Object[] {"a", "b", null}, false, true, "a;b"),
+        Arguments.of(new Object[] {"a", null, null}, false, true, "a"),
+        Arguments.of(new Object[] {null, "b", "c"}, false, true, "b;c"),
+        Arguments.of(new Object[] {null, "b", null}, false, true, "b"),
+        Arguments.of(new Object[] {null, null, "c"}, false, true, "c"),
+        Arguments.of(new Object[] {null, null, null}, false, true, ""),
+        Arguments.of(new Object[] {"", "", ""}, false, true, ""),
+
+        // enable force enclosure, skip empty(null/"")
+        Arguments.of(new Object[] {"a", "b", "c"}, true, true, 
"\"a\";\"b\";\"c\""),
+        Arguments.of(new Object[] {"a", "b", null}, true, true, "\"a\";\"b\""),
+        Arguments.of(new Object[] {"a", null, null}, true, true, "\"a\""),
+        Arguments.of(new Object[] {null, "b", "c"}, true, true, "\"b\";\"c\""),
+        Arguments.of(new Object[] {null, "b", null}, true, true, "\"b\""),
+        Arguments.of(new Object[] {null, null, "c"}, true, true, "\"c\""),
+        Arguments.of(new Object[] {null, null, null}, true, true, ""),
+        Arguments.of(new Object[] {"", "", ""}, true, true, ""));
+  }
+}


Reply via email to