You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hop.apache.org by ha...@apache.org on 2022/11/28 09:45:19 UTC

[hop] branch master updated: HOP-4364 Cleanup XML of transform Value Mapper

This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/master by this push:
     new ec6491aa68 HOP-4364 Cleanup XML of transform Value Mapper
     new 524452ccbc Merge pull request #1835 from nadment/HOP-4364
ec6491aa68 is described below

commit ec6491aa68275be67a570d15e7bf7796d0e84f52
Author: Nicolas Adment <39...@users.noreply.github.com>
AuthorDate: Sat Nov 26 16:37:02 2022 +0100

    HOP-4364 Cleanup XML of transform Value Mapper
---
 .../transforms/valuemapper/ValueMapper.java        |  19 ++-
 .../transforms/valuemapper/ValueMapperDialog.java  |  28 ++--
 .../transforms/valuemapper/ValueMapperMeta.java    | 178 +++++----------------
 .../pipeline/transforms/valuemapper/Values.java    |  81 ++++++++++
 .../valuemapper/messages/messages_de_DE.properties |   1 -
 .../valuemapper/messages/messages_es_ES.properties |   1 -
 .../valuemapper/messages/messages_fr_FR.properties |   1 -
 .../valuemapper/messages/messages_it_IT.properties |   1 -
 .../valuemapper/messages/messages_ja_JP.properties |   1 -
 .../valuemapper/messages/messages_nl_NL.properties |   1 -
 .../valuemapper/messages/messages_zh_CN.properties |   1 -
 .../valuemapper/ValueMapperMetaInjectionTest.java  |   4 +-
 .../valuemapper/ValueMapperMetaTest.java           | 168 +++----------------
 .../src/test/resources/value-mapper-transform.xml  |  62 +++++++
 14 files changed, 232 insertions(+), 315 deletions(-)

diff --git a/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapper.java b/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapper.java
index 14656e601a..9f4cfc9dbc 100644
--- a/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapper.java
+++ b/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapper.java
@@ -82,14 +82,13 @@ public class ValueMapper extends BaseTransform<ValueMapperMeta, ValueMapperData>
       // If there is an empty entry: we map null or "" to the target at the index
       // 0 or 1 empty mapping is allowed, not 2 or more.
       //
-      for (int i = 0; i < meta.getSourceValue().length; i++) {
-        if (Utils.isEmpty(meta.getSourceValue()[i])) {
-          if (data.emptyFieldValue==null) {
-            data.emptyFieldValue = resolve(meta.getTargetValue()[i]);
+      for (Values v : meta.getValues()) {
+        if (Utils.isEmpty(v.getSource())) {
+          if (data.emptyFieldValue == null) {
+            data.emptyFieldValue = resolve(v.getTarget());
           } else {
-            throw new HopException(
-                BaseMessages.getString(
-                    PKG, "ValueMapper.RuntimeError.OnlyOneEmptyMappingAllowed.VALUEMAPPER0004"));
+            throw new HopException(BaseMessages.getString(PKG,
+                "ValueMapper.RuntimeError.OnlyOneEmptyMappingAllowed.VALUEMAPPER0004"));
           }
         }
       }
@@ -180,9 +179,9 @@ public class ValueMapper extends BaseTransform<ValueMapperMeta, ValueMapperData>
       }
 
       // Add all source to target mappings in here...
-      for (int i = 0; i < meta.getSourceValue().length; i++) {
-        String src = meta.getSourceValue()[i];
-        String tgt = this.resolve(meta.getTargetValue()[i]);
+      for (Values v : meta.getValues()) {
+        String src = v.getSource();
+        String tgt = this.resolve(v.getTarget());
 
         if (!Utils.isEmpty(src) && !Utils.isEmpty(tgt)) {
           data.mapValues.put(src, tgt);
diff --git a/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperDialog.java b/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperDialog.java
index 23da962e23..6a4a8afbee 100644
--- a/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperDialog.java
+++ b/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperDialog.java
@@ -89,7 +89,7 @@ public class ValueMapperDialog extends BaseTransformDialog implements ITransform
     shell.setText(BaseMessages.getString(PKG, "ValueMapperDialog.DialogTitle"));
 
     int middle = props.getMiddlePct();
-    int margin = props.getMargin();
+    int margin = PropsUi.getMargin();
 
     // Some buttons
     wOk = new Button(shell, SWT.PUSH);
@@ -197,7 +197,7 @@ public class ValueMapperDialog extends BaseTransformDialog implements ITransform
     wlFields.setLayoutData(fdlFields);
 
     final int FieldsCols = 2;
-    final int FieldsRows = input.getSourceValue().length;
+    final int FieldsRows = input.getValues().size();
 
     ColumnInfo[] colinf = new ColumnInfo[FieldsCols];
     colinf[0] =
@@ -275,10 +275,11 @@ public class ValueMapperDialog extends BaseTransformDialog implements ITransform
       wNonMatchDefault.setText(input.getNonMatchDefault());
     }
 
-    for (int i = 0; i < input.getSourceValue().length; i++) {
-      TableItem item = wFields.table.getItem(i);
-      String src = input.getSourceValue()[i];
-      String tgt = input.getTargetValue()[i];
+    int i = 0;
+    for (Values v : input.getValues()) {
+      TableItem item = wFields.table.getItem(i++);
+      String src = v.getSource();
+      String tgt = v.getTarget();
 
       if (src != null) {
         item.setText(1, src);
@@ -312,15 +313,14 @@ public class ValueMapperDialog extends BaseTransformDialog implements ITransform
     input.setTargetField(wTargetFieldName.getText());
     input.setNonMatchDefault(wNonMatchDefault.getText());
 
-    int count = wFields.nrNonEmpty();
-    input.allocate(count);
-
-    // CHECKSTYLE:Indentation:OFF
-    for (int i = 0; i < count; i++) {
-      TableItem item = wFields.getNonEmpty(i);
-      input.getSourceValue()[i] = Utils.isEmpty(item.getText(1)) ? null : item.getText(1);
-      input.getTargetValue()[i] = item.getText(2);
+    input.getValues().clear();
+    for (TableItem item : wFields.getNonEmptyItems()) {
+      Values v = new Values();
+      v.setSource(Utils.isEmpty(item.getText(1)) ? null : item.getText(1));
+      v.setTarget(item.getText(2));
+      input.getValues().add(v);
     }
+    
     dispose();
   }
 }
diff --git a/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMeta.java b/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMeta.java
index 146acf965d..7c7b652dd3 100644
--- a/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMeta.java
+++ b/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMeta.java
@@ -17,34 +17,24 @@
 
 package org.apache.hop.pipeline.transforms.valuemapper;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.hop.core.CheckResult;
-import org.apache.hop.core.Const;
 import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.annotations.Transform;
-import org.apache.hop.core.exception.HopXmlException;
-import org.apache.hop.core.injection.AfterInjection;
-import org.apache.hop.core.injection.Injection;
-import org.apache.hop.core.injection.InjectionSupported;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.row.value.ValueMetaString;
 import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.variables.IVariables;
-import org.apache.hop.core.xml.XmlHandler;
 import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.metadata.api.HopMetadataProperty;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.BaseTransformMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
-import org.w3c.dom.Node;
-
+import java.util.ArrayList;
 import java.util.List;
 
 /** Maps String values of a certain field to new values */
-@InjectionSupported(
-    localizationPrefix = "ValueMapper.Injection.",
-    groups = {"VALUES"})
 @Transform(
     id = "ValueMapper",
     image = "valuemapper.svg",
@@ -55,106 +45,61 @@ import java.util.List;
     documentationUrl = "/pipeline/transforms/valuemapper.html")
 public class ValueMapperMeta extends BaseTransformMeta<ValueMapper, ValueMapperData> {
   private static final Class<?> PKG = ValueMapperMeta.class; // For Translator
-
-  @Injection(name = "FIELDNAME")
+  
+  @HopMetadataProperty(
+      key = "field_to_use",
+      injectionKey = "FIELDNAME",
+      injectionKeyDescription = "ValueMapper.Injection.FIELDNAME")
   private String fieldToUse;
-
-  @Injection(name = "TARGET_FIELDNAME")
+  
+  @HopMetadataProperty(
+      key = "target_field",
+      injectionKey = "TARGET_FIELDNAME",
+      injectionKeyDescription = "ValueMapper.Injection.TARGET_FIELDNAME")
   private String targetField;
 
-  @Injection(name = "NON_MATCH_DEFAULT")
+  @HopMetadataProperty(
+      key = "non_match_default",
+      injectionKey = "NON_MATCH_DEFAULT",
+      injectionKeyDescription = "ValueMapper.Injection.NON_MATCH_DEFAULT")
   private String nonMatchDefault;
 
-  @Injection(name = "SOURCE", group = "VALUES")
-  private String[] sourceValue;
-
-  @Injection(name = "TARGET", group = "VALUES")
-  private String[] targetValue;
+  @HopMetadataProperty(
+      groupKey = "fields",
+      key = "field",
+      injectionGroupKey = "VALUES",
+      injectionGroupDescription = "ValueMapper.Injection.VALUES")
+  private List<Values> values;
 
   public ValueMapperMeta() {
     super(); // allocate BaseTransformMeta
+    this.values = new ArrayList<>();
   }
 
-  /** @return Returns the fieldName. */
-  public String[] getSourceValue() {
-    return sourceValue;
-  }
-
-  /** @param fieldName The fieldName to set. */
-  public void setSourceValue(String[] fieldName) {
-    this.sourceValue = fieldName;
+  public ValueMapperMeta(ValueMapperMeta meta) {
+    this();
+    for (Values v : meta.values) {
+      values.add(new Values(v));
+    }
+    
+    this.fieldToUse = meta.fieldToUse;
+    this.targetField = meta.targetField;
+    this.nonMatchDefault = meta.nonMatchDefault;
   }
-
+  
   /** @return Returns the fieldValue. */
-  public String[] getTargetValue() {
-    return targetValue;
+  public List<Values> getValues() {
+    return values;
   }
 
   /** @param fieldValue The fieldValue to set. */
-  public void setTargetValue(String[] fieldValue) {
-    this.targetValue = fieldValue;
-  }
-
-  @Override
-  public void loadXml(Node transformNode, IHopMetadataProvider metadataProvider)
-      throws HopXmlException {
-    readData(transformNode);
-  }
-
-  public void allocate(int count) {
-    sourceValue = new String[count];
-    targetValue = new String[count];
+  public void setValues(List<Values> values) {
+    this.values = values;
   }
 
   @Override
   public Object clone() {
-    ValueMapperMeta retval = (ValueMapperMeta) super.clone();
-
-    int count = sourceValue.length;
-
-    retval.allocate(count);
-
-    System.arraycopy(sourceValue, 0, retval.sourceValue, 0, count);
-    System.arraycopy(targetValue, 0, retval.targetValue, 0, count);
-
-    return retval;
-  }
-
-  private void readData(Node transformNode) throws HopXmlException {
-    try {
-      fieldToUse = XmlHandler.getTagValue(transformNode, "field_to_use");
-      targetField = XmlHandler.getTagValue(transformNode, "target_field");
-      nonMatchDefault = XmlHandler.getTagValue(transformNode, "non_match_default");
-
-      Node fields = XmlHandler.getSubNode(transformNode, "fields");
-      int count = XmlHandler.countNodes(fields, "field");
-
-      allocate(count);
-
-      for (int i = 0; i < count; i++) {
-        Node fnode = XmlHandler.getSubNodeByNr(fields, "field", i);
-
-        sourceValue[i] = XmlHandler.getTagValue(fnode, "source_value");
-        targetValue[i] = XmlHandler.getTagValue(fnode, "target_value");
-      }
-    } catch (Exception e) {
-      throw new HopXmlException(
-          BaseMessages.getString(
-              PKG, "ValueMapperMeta.RuntimeError.UnableToReadXML.VALUEMAPPER0004"),
-          e);
-    }
-  }
-
-  @Override
-  public void setDefault() {
-    int count = 0;
-
-    allocate(count);
-
-    for (int i = 0; i < count; i++) {
-      sourceValue[i] = "field" + i;
-      targetValue[i] = "";
-    }
+    return new ValueMapperMeta(this);
   }
 
   @Override
@@ -173,12 +118,12 @@ public class ValueMapperMeta extends BaseTransformMeta<ValueMapper, ValueMapperD
       // Take the max length of all the strings...
       //
       int maxlen = -1;
-      for (int i = 0; i < targetValue.length; i++) {
-        if (targetValue[i] != null && targetValue[i].length() > maxlen) {
-          maxlen = targetValue[i].length();
+      for (Values v : this.values) {
+        if (v.getTarget() != null && v.getTarget().length() > maxlen) {
+          maxlen = v.getTarget().length();
         }
       }
-
+      
       // include default value in max length calculation
       //
       if (nonMatchDefault != null && nonMatchDefault.length() > maxlen) {
@@ -200,31 +145,6 @@ public class ValueMapperMeta extends BaseTransformMeta<ValueMapper, ValueMapperD
     }
   }
 
-  @Override
-  public String getXml() {
-    StringBuilder retval = new StringBuilder();
-
-    retval.append("    ").append(XmlHandler.addTagValue("field_to_use", fieldToUse));
-    retval.append("    ").append(XmlHandler.addTagValue("target_field", targetField));
-    retval.append("    ").append(XmlHandler.addTagValue("non_match_default", nonMatchDefault));
-
-    retval.append("    <fields>").append(Const.CR);
-
-    for (int i = 0; i < sourceValue.length; i++) {
-      retval.append("      <field>").append(Const.CR);
-      retval.append("        ").append(XmlHandler.addTagValue("source_value", sourceValue[i]));
-      retval.append("        ").append(XmlHandler.addTagValue("target_value", targetValue[i]));
-      retval.append("      </field>").append(Const.CR);
-    }
-    retval.append("    </fields>").append(Const.CR);
-
-    return retval.toString();
-  }
-
-  private String getNullOrEmpty(String str) {
-    return str == null ? StringUtils.EMPTY : str;
-  }
-
   @Override
   public void check(
       List<ICheckResult> remarks,
@@ -312,18 +232,4 @@ public class ValueMapperMeta extends BaseTransformMeta<ValueMapper, ValueMapperD
   public void setNonMatchDefault(String nonMatchDefault) {
     this.nonMatchDefault = nonMatchDefault;
   }
-
-  /**
-   * If we use injection we can have different arrays lengths. We need synchronize them for
-   * consistency behavior with UI
-   */
-  @AfterInjection
-  public void afterInjectionSynchronization() {
-    int nrFields = (sourceValue == null) ? -1 : sourceValue.length;
-    if (nrFields <= 0) {
-      return;
-    }
-    String[][] rtn = Utils.normalizeArrays(nrFields, targetValue);
-    targetValue = rtn[0];
-  }
 }
diff --git a/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/Values.java b/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/Values.java
new file mode 100644
index 0000000000..83aa8dd3ae
--- /dev/null
+++ b/plugins/transforms/valuemapper/src/main/java/org/apache/hop/pipeline/transforms/valuemapper/Values.java
@@ -0,0 +1,81 @@
+/*
+ * 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.valuemapper;
+
+import org.apache.hop.metadata.api.HopMetadataProperty;
+/**
+ * The values for mapping
+ */
+public class Values {
+
+  @HopMetadataProperty(key = "source_value", injectionKey = "SOURCE", injectionKeyDescription = "ValueMapper.Injection.SOURCE")
+  private String source;
+
+  @HopMetadataProperty(key = "target_value", injectionKey = "TARGET", injectionKeyDescription = "ValueMapper.Injection.TARGET")
+  private String target;
+
+  public Values() {
+  }
+
+  public Values(Values other) {
+    this.source = other.source;
+    this.target = other.target;
+  }
+
+  public Values(String source, String target) {
+    this.source = source;
+    this.target = target;
+  }
+
+  /**
+   * Gets source value
+   *
+   * @return value of source
+   */
+  public String getSource() {
+    return source;
+  }
+
+  /**
+   * Sets source value
+   *
+   * @param value value of source
+   */
+  public void setSource(String value) {
+    this.source = value;
+  }
+  
+  /**
+   * Gets target value
+   *
+   * @return value of source
+   */
+  public String getTarget() {
+    return target;
+  }
+
+  /**
+   * Sets target value
+   *
+   * @param value value of target
+   */
+  public void setTarget(String value) {
+    this.target = value;
+  }
+}
diff --git a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_de_DE.properties b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_de_DE.properties
index 8e8f473cc0..8e8c49547f 100644
--- a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_de_DE.properties
+++ b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_de_DE.properties
@@ -27,7 +27,6 @@ ValueMapperDialog.Fields.Label=Feldwerte:
 ValueMapperDialog.Fields.Column.SourceValue=Quellenwert
 ValueMapperDialog.Fields.Column.TargetValue=Zielwert
 ValueMapper.RuntimeError.FieldToUseNotFound.VALUEMAPPER0001=Nicht möglich Feld [{0}] in den Inputzeilen zu finden.{1}Dies ist die Inputzeile: {2}
-ValueMapperMeta.RuntimeError.UnableToReadXML.VALUEMAPPER0004=Nicht möglich Schrittinformationenvon XML zu lesen
 ValueMapperMeta.CheckResult.NotReceivingFieldsFromPreviousTransforms=Empfängt keine Felder aus vorherigen Schritten\!
 ValueMapperMeta.CheckResult.ReceivingFieldsFromPreviousTransforms=Schritt ist zu einem vorherigen Schritt verbunden, empfängt {0} Felder.
 ValueMapperMeta.CheckResult.NotReceivingInfoFromOtherTransforms=Kein Input von anderen Schritten empfangen\!
diff --git a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_es_ES.properties b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_es_ES.properties
index fc7fa0a1b0..bdb62f28c4 100644
--- a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_es_ES.properties
+++ b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_es_ES.properties
@@ -27,7 +27,6 @@ ValueMapperDialog.Fields.Label=Valores de campo:
 ValueMapperDialog.Fields.Column.SourceValue=Valor origen
 ValueMapperDialog.Fields.Column.TargetValue=Valor destino
 ValueMapper.RuntimeError.FieldToUseNotFound.VALUEMAPPER0001=No se ha podido encontrar el campo [{0}] en la fila de entrada.{1}Esta es la fila de entrada: {2}
-ValueMapperMeta.RuntimeError.UnableToReadXML.VALUEMAPPER0004=No se ha podido cargar información del paso desde XML
 ValueMapperMeta.CheckResult.NotReceivingFieldsFromPreviousTransforms=No se ha recibido campos de otros pasos\!
 ValueMapperMeta.CheckResult.ReceivingFieldsFromPreviousTransforms=Paso esta conectado al anterior, recibiendo {0} campos
 ValueMapperMeta.CheckResult.NotReceivingInfoFromOtherTransforms=No se ha recibido entrada de otros pasos\!
diff --git a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_fr_FR.properties b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_fr_FR.properties
index 1e082ec3f9..e99dec9903 100644
--- a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_fr_FR.properties
+++ b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_fr_FR.properties
@@ -23,7 +23,6 @@ ValueMapperDialog.NonMatchDefault.Label=Non correspondance \:
 ValueMapperDialog.TransformName.Label=Nom de la transformation \:
 ValueMapper.RuntimeError.OnlyOneEmptyMappingAllowed.VALUEMAPPER0004=Uniquement une seule correspondance pour une cha\u00EEne vide ou nulle est autoris\u00E9e.
 ValueMapperDialog.FailedToGetFields.DialogMessage=Impossible de r\u00E9cup\u00E9rer les champs depuis les autres pr\u00E9c\u00E9dentes\!
-ValueMapperMeta.RuntimeError.UnableToReadXML.VALUEMAPPER0004=Impossible de lire depuis XML
 ValueMapperMeta.CheckResult.ReceivingFieldsFromPreviousTransforms=Les transformations pr\u00E9centes renvoient {0} champs.
 ValueMapperDialog.TargetFieldname.Label=Nom champ cible (vide\=\u00E9craser) \:
 ValueMapperMeta.CheckResult.NotReceivingInfoFromOtherTransforms=Les transformations pr\u00E9c\u00E9dentes ne renvoient aucun champ\!
diff --git a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_it_IT.properties b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_it_IT.properties
index 7126e70f5b..e6898281ba 100644
--- a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_it_IT.properties
+++ b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_it_IT.properties
@@ -21,7 +21,6 @@ ValueMapperDialog.NonMatchDefault.Label=Default quando non c''\u00E8 corrisponde
 ValueMapperDialog.TransformName.Label=Nome transform 
 ValueMapper.RuntimeError.OnlyOneEmptyMappingAllowed.VALUEMAPPER0004=Solo una mappatura da stringa vuota o valore nullo \u00E8 ammessa.
 ValueMapperDialog.FailedToGetFields.DialogMessage=Impossibile prelevare il nome dei campi dai transforms precedenti\!
-ValueMapperMeta.RuntimeError.UnableToReadXML.VALUEMAPPER0004=Impossibile leggere le informazioni del transform da XML
 ValueMapperMeta.CheckResult.ReceivingFieldsFromPreviousTransforms=Il transform \u00E8 connesso al precedente, ricezione di {0} campi.
 ValueMapperDialog.TargetFieldname.Label=Nome del campo di destinazione (vuoto\=sovrascrittura) 
 ValueMapperMeta.CheckResult.NotReceivingInfoFromOtherTransforms=Nessun input ricevuto dagli altri transforms\!
diff --git a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_ja_JP.properties b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_ja_JP.properties
index 7ac781bc5e..b0135a589c 100644
--- a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_ja_JP.properties
+++ b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_ja_JP.properties
@@ -22,7 +22,6 @@ ValueMapperDialog.NonMatchDefault.Label=\u5408\u81f4\u3057\u306a\u3044\u5834\u54
 ValueMapperDialog.TransformName.Label=\u30b9\u30c6\u30c3\u30d7\u540d
 ValueMapper.RuntimeError.OnlyOneEmptyMappingAllowed.VALUEMAPPER0004=Only one mapping from an empty string or null value is allowed.
 ValueMapperDialog.FailedToGetFields.DialogMessage=\u5148\u884c\u306e\u30b9\u30c6\u30c3\u30d7\u304b\u3089\u30d5\u30a3\u30fc\u30eb\u30c9\u60c5\u5831\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3002
-ValueMapperMeta.RuntimeError.UnableToReadXML.VALUEMAPPER0004=Unable to read transform information from XML
 ValueMapperMeta.CheckResult.ReceivingFieldsFromPreviousTransforms=Transform is connected to previous one, receiving {0} fields.
 ValueMapperDialog.TargetFieldname.Label=\u51fa\u529b\u30d5\u30a3\u30fc\u30eb\u30c9\u540d\u203b\u7a7a\u306e\u5834\u5408\u306f\u4e0a\u66f8\u304d 
 ValueMapperMeta.CheckResult.NotReceivingInfoFromOtherTransforms=No input received from other transforms\!
diff --git a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_nl_NL.properties b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_nl_NL.properties
index 650f29ca23..958fcee24f 100644
--- a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_nl_NL.properties
+++ b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_nl_NL.properties
@@ -20,7 +20,6 @@
 ValueMapperDialog.NonMatchDefault.Label=De standaard waarde indien er geen overenkomst gevonden word\:
 ValueMapperDialog.TransformName.Label=Stap naam \:
 ValueMapper.RuntimeError.OnlyOneEmptyMappingAllowed.VALUEMAPPER0004=Er is slechts \u00E9\u00E9n mapping toegestaan van een lege tekst of null waarde.
-ValueMapperMeta.RuntimeError.UnableToReadXML.VALUEMAPPER0004=Het was niet mogelijk om de transform informatie te lezen vanuit de XML
 ValueMapperMeta.CheckResult.ReceivingFieldsFromPreviousTransforms=Deze transform is verbonden met voorgaande transforms en ontvangt {0} velden.
 ValueMapperDialog.TargetFieldname.Label=Naam van nieuw veld (leeg\=overschrijven) \: 
 ValueMapperMeta.CheckResult.NotReceivingInfoFromOtherTransforms=Er worden geen informatieve velden ontvangen van andere transforms\!
diff --git a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_zh_CN.properties b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_zh_CN.properties
index 07232452c4..cab724bcc0 100644
--- a/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_zh_CN.properties
+++ b/plugins/transforms/valuemapper/src/main/resources/org/apache/hop/pipeline/transforms/valuemapper/messages/messages_zh_CN.properties
@@ -34,5 +34,4 @@ ValueMapperMeta.CheckResult.NotReceivingFieldsFromPreviousTransforms=Not receivi
 ValueMapperMeta.CheckResult.NotReceivingInfoFromOtherTransforms=No input received from other transforms\!
 ValueMapperMeta.CheckResult.ReceivingFieldsFromPreviousTransforms=Transform is connected to previous one, receiving {0} fields.
 ValueMapperMeta.CheckResult.ReceivingInfoFromOtherTransforms=Transform is receiving info from other transforms.
-ValueMapperMeta.RuntimeError.UnableToReadXML.VALUEMAPPER0004=Unable to read transform information from XML
 ValueMapperMeta.keyword=value,mapper
diff --git a/plugins/transforms/valuemapper/src/test/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMetaInjectionTest.java b/plugins/transforms/valuemapper/src/test/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMetaInjectionTest.java
index 1f8507e05f..6b13b10676 100644
--- a/plugins/transforms/valuemapper/src/test/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMetaInjectionTest.java
+++ b/plugins/transforms/valuemapper/src/test/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMetaInjectionTest.java
@@ -36,7 +36,7 @@ public class ValueMapperMetaInjectionTest extends BaseMetadataInjectionTest<Valu
     check("FIELDNAME", () -> meta.getFieldToUse());
     check("TARGET_FIELDNAME", () -> meta.getTargetField());
     check("NON_MATCH_DEFAULT", () -> meta.getNonMatchDefault());
-    check("SOURCE", () -> meta.getSourceValue()[0]);
-    check("TARGET", () -> meta.getTargetValue()[0]);
+    check("SOURCE", () -> meta.getValues().get(0).getSource());
+    check("TARGET", () -> meta.getValues().get(0).getTarget());
   }
 }
diff --git a/plugins/transforms/valuemapper/src/test/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMetaTest.java b/plugins/transforms/valuemapper/src/test/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMetaTest.java
index d6ef93b717..b94c6a1aa3 100644
--- a/plugins/transforms/valuemapper/src/test/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMetaTest.java
+++ b/plugins/transforms/valuemapper/src/test/java/org/apache/hop/pipeline/transforms/valuemapper/ValueMapperMetaTest.java
@@ -16,163 +16,39 @@
  */
 package org.apache.hop.pipeline.transforms.valuemapper;
 
-import org.apache.commons.lang.StringUtils;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import org.apache.hop.core.HopEnvironment;
-import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.plugins.PluginRegistry;
-import org.apache.hop.junit.rules.RestoreHopEngineEnvironment;
-import org.apache.hop.pipeline.transform.ITransformMeta;
-import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
-import org.apache.hop.pipeline.transforms.loadsave.initializer.IInitializer;
-import org.apache.hop.pipeline.transforms.loadsave.validator.ArrayLoadSaveValidator;
-import org.apache.hop.pipeline.transforms.loadsave.validator.IFieldLoadSaveValidator;
-import org.apache.hop.pipeline.transforms.loadsave.validator.StringLoadSaveValidator;
-import org.junit.Assert;
+import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
 import org.junit.Before;
-import org.junit.ClassRule;
 import org.junit.Test;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class ValueMapperMetaTest implements IInitializer<ITransformMeta> {
-  @ClassRule public static RestoreHopEngineEnvironment env = new RestoreHopEngineEnvironment();
-  LoadSaveTester loadSaveTester;
-  Class<ValueMapperMeta> testMetaClass = ValueMapperMeta.class;
-
+public class ValueMapperMetaTest {
+  
   @Before
   public void setUpLoadSave() throws Exception {
-    IFieldLoadSaveValidator<String[]> stringArrayLoadSaveValidator =
-        new ArrayLoadSaveValidator<>(new StringLoadSaveValidator(), 7);
-
-    init(stringArrayLoadSaveValidator, stringArrayLoadSaveValidator);
-  }
-
-  private void init(
-      IFieldLoadSaveValidator<String[]> sourceStringArrayLoadSaveValidator,
-      IFieldLoadSaveValidator<String[]> targetStringArrayLoadSaveValidator)
-      throws HopException {
-
     HopEnvironment.init();
     PluginRegistry.init();
-    List<String> attributes =
-        Arrays.asList("fieldToUse", "targetField", "nonMatchDefault", "sourceValue", "targetValue");
-
-    Map<String, String> getterMap =
-        new HashMap<String, String>() {
-          {
-            put("fieldToUse", "getFieldToUse");
-            put("targetField", "getTargetField");
-            put("nonMatchDefault", "getNonMatchDefault");
-            put("sourceValue", "getSourceValue");
-            put("targetValue", "getTargetValue");
-          }
-        };
-    Map<String, String> setterMap =
-        new HashMap<String, String>() {
-          {
-            put("fieldToUse", "setFieldToUse");
-            put("targetField", "setTargetField");
-            put("nonMatchDefault", "setNonMatchDefault");
-            put("sourceValue", "setSourceValue");
-            put("targetValue", "setTargetValue");
-          }
-        };
-
-    Map<String, IFieldLoadSaveValidator<?>> attrValidatorMap = new HashMap<>();
-    attrValidatorMap.put("sourceValue", sourceStringArrayLoadSaveValidator);
-    attrValidatorMap.put("targetValue", targetStringArrayLoadSaveValidator);
-
-    Map<String, IFieldLoadSaveValidator<?>> typeValidatorMap = new HashMap<>();
-
-    loadSaveTester =
-        new LoadSaveTester(
-            testMetaClass,
-            attributes,
-            getterMap,
-            setterMap,
-            attrValidatorMap,
-            typeValidatorMap,
-            this);
-  }
-
-  // Call the allocate method on the LoadSaveTester meta class
-  @Override
-  public void modify(ITransformMeta someMeta) {
-    if (someMeta instanceof ValueMapperMeta) {
-      ((ValueMapperMeta) someMeta).allocate(7);
-    }
   }
 
   @Test
-  public void testSerialization() throws HopException {
-    loadSaveTester.testSerialization();
-  }
-
-  @Test
-  public void testSerializationWithNullAttr() throws HopException {
-    String abc = "abc";
-    String stringNull = "null";
-    String[] sourceAttrs = {abc, null, abc, null, stringNull, null, stringNull};
-    String[] targetAttrs = {abc, null, null, abc, null, stringNull, stringNull};
-
-    IFieldLoadSaveValidator<String[]> sourceValidator =
-        new ArrayLoadSaveValidator<>(
-            new CustomStringLoadSaveValidator(sourceAttrs), sourceAttrs.length);
-    IFieldLoadSaveValidator<String[]> targetValidator =
-        new ArrayLoadSaveValidator<>(
-            new CustomStringLoadSaveValidator(targetAttrs), targetAttrs.length);
-
-    init(sourceValidator, targetValidator);
-
-    loadSaveTester.testSerialization();
-  }
-
-  private static class CustomStringLoadSaveValidator extends StringLoadSaveValidator {
-
-    private String[] values;
-    private int index = 0;
-
-    public CustomStringLoadSaveValidator(String... values) {
-      this.values = values;
-    }
-
-    @Override
-    public String getTestObject() {
-      int i = index;
-      index = ++index % values.length;
-      return values[i];
-    }
-
-    @Override
-    public boolean validateTestObject(String test, Object actual) {
-      return test == null ? nullOrEmpty(actual) : test.equals(actual);
-    }
-
-    private boolean nullOrEmpty(Object o) {
-      return o == null || StringUtils.isEmpty(o.toString());
-    }
-  }
-
-  @Test
-  public void testPDI16559() throws Exception {
-    ValueMapperMeta valueMapper = new ValueMapperMeta();
-    valueMapper.setSourceValue(new String[] {"value1", "value2", "value3", "value4"});
-    valueMapper.setTargetValue(new String[] {"targ1", "targ2"});
-
-    try {
-      String badXml = valueMapper.getXml();
-      Assert.fail(
-          "Before calling afterInjectionSynchronization, should have thrown an ArrayIndexOOB");
-    } catch (Exception expected) {
-      // Do Nothing
-    }
-    valueMapper.afterInjectionSynchronization();
-    // run without a exception
-    String ktrXml = valueMapper.getXml();
-
-    Assert.assertEquals(valueMapper.getSourceValue().length, valueMapper.getTargetValue().length);
+  public void testSerialization() throws Exception {
+    ValueMapperMeta meta =
+        TransformSerializationTestUtil.testSerialization(
+            "/value-mapper-transform.xml", ValueMapperMeta.class);
+
+    assertEquals(7, meta.getValues().size());
+    // Test serialization with null source attribute
+    assertNull(meta.getValues().get(0).getSource());
+    assertEquals("[${NOT_DEFINED}]", meta.getValues().get(0).getTarget());
+    
+    assertEquals("BE", meta.getValues().get(1).getSource());
+    assertEquals("Belgium", meta.getValues().get(1).getTarget());
+        
+    assertEquals("Country_Code", meta.getFieldToUse());
+    assertEquals("Country_Name", meta.getTargetField());
+    assertEquals("[${NOT_FOUND}]", meta.getNonMatchDefault());    
   }
 }
+
diff --git a/plugins/transforms/valuemapper/src/test/resources/value-mapper-transform.xml b/plugins/transforms/valuemapper/src/test/resources/value-mapper-transform.xml
new file mode 100644
index 0000000000..5986891911
--- /dev/null
+++ b/plugins/transforms/valuemapper/src/test/resources/value-mapper-transform.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ~ 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. ~ -->
+<transform>
+	<name>Value mapper</name>
+	<type>ValueMapper</type>
+	<description />
+	<distribute>Y</distribute>
+	<custom_distribution />
+	<copies>1</copies>
+	<partitioning>
+		<method>none</method>
+		<schema_name />
+	</partitioning>
+	<field_to_use>Country_Code</field_to_use>
+	<target_field>Country_Name</target_field>
+	<non_match_default>[${NOT_FOUND}]</non_match_default>
+	<fields>
+		<field>
+			<source_value />
+			<target_value>[${NOT_DEFINED}]</target_value>
+		</field>
+		<field>
+			<source_value>BE</source_value>
+			<target_value>Belgium</target_value>
+		</field>
+		<field>
+			<source_value>CA</source_value>
+			<target_value>Canada</target_value>
+		</field>
+		<field>
+			<source_value>FR</source_value>
+			<target_value>France</target_value>
+		</field>
+		<field>
+			<source_value>UK</source_value>
+			<target_value>United Kingdom</target_value>
+		</field>
+		<field>
+			<source_value>US</source_value>
+			<target_value>United States</target_value>
+		</field>
+		<field>
+			<source_value>DE</source_value>
+			<target_value>German</target_value>
+		</field>
+	</fields>
+	<attributes />
+	<GUI>
+		<xloc>240</xloc>
+		<yloc>64</yloc>
+	</GUI>
+</transform>
\ No newline at end of file