public Object clone() {
    FieldSplitterMeta retval = (FieldSplitterMeta) super.clone();

    final int nrfields = fieldName.length;

    retval.allocate(nrfields);

    for (int i = 0; i < nrfields; i++) {
      retval.fieldName[i] = fieldName[i];
      retval.fieldID[i] = fieldID[i];
      retval.fieldRemoveID[i] = fieldRemoveID[i];
      retval.fieldType[i] = fieldType[i];
      retval.fieldLength[i] = fieldLength[i];
      retval.fieldPrecision[i] = fieldPrecision[i];
      retval.fieldFormat[i] = fieldFormat[i];
      retval.fieldGroup[i] = fieldGroup[i];
      retval.fieldDecimal[i] = fieldDecimal[i];
      retval.fieldCurrency[i] = fieldCurrency[i];
      retval.fieldNullIf[i] = fieldNullIf[i];
      retval.fieldIfNull[i] = fieldIfNull[i];
      retval.fieldTrimType[i] = fieldTrimType[i];
    }

    return retval;
  }
  @Before
  public void setUp() throws Exception {

    when(meta.getDelimiter()).thenReturn(",");
    when(meta.getEnclosure()).thenReturn("\"");
    when(meta.getSplitField()).thenReturn("splitField");
    when(meta.getFieldName()).thenReturn(outputFields);

    analyzer = spy(new SplitFieldsStepAnalyzer());
  }
  private Object[] splitField(Object[] r) throws KettleException {
    if (first) {
      first = false;
      // get the RowMeta
      data.previousMeta = getInputRowMeta().clone();

      // search field
      data.fieldnr = data.previousMeta.indexOfValue(meta.getSplitField());
      if (data.fieldnr < 0) {
        throw new KettleValueException(
            BaseMessages.getString(
                PKG, "FieldSplitter.Log.CouldNotFindFieldToSplit", meta.getSplitField()));
      }

      // only String type allowed
      if (!data.previousMeta.getValueMeta(data.fieldnr).isString()) {
        throw new KettleValueException(
            (BaseMessages.getString(
                PKG, "FieldSplitter.Log.SplitFieldNotValid", meta.getSplitField())));
      }

      // prepare the outputMeta
      //
      data.outputMeta = getInputRowMeta().clone();
      meta.getFields(data.outputMeta, getStepname(), null, null, this, repository, metaStore);

      // Now create objects to do string to data type conversion...
      //
      data.conversionMeta = data.outputMeta.cloneToType(ValueMetaInterface.TYPE_STRING);

      data.delimiter = environmentSubstitute(meta.getDelimiter());
      data.enclosure = environmentSubstitute(meta.getEnclosure());
    }

    // reserve room
    Object[] outputRow = RowDataUtil.allocateRowData(data.outputMeta.size());

    int nrExtraFields = meta.getFieldID().length - 1;

    System.arraycopy(r, 0, outputRow, 0, data.fieldnr);
    System.arraycopy(
        r,
        data.fieldnr + 1,
        outputRow,
        data.fieldnr + 1 + nrExtraFields,
        data.previousMeta.size() - (data.fieldnr + 1));

    // OK, now we have room in the middle to place the fields...
    //

    // Named values info.id[0] not filled in!
    final boolean selectFieldById =
        (meta.getFieldID().length > 0)
            && (meta.getFieldID()[0] != null)
            && (meta.getFieldID()[0].length() > 0);

    if (log.isDebug()) {
      if (selectFieldById) {
        logDebug(BaseMessages.getString(PKG, "FieldSplitter.Log.UsingIds"));
      } else {
        logDebug(BaseMessages.getString(PKG, "FieldSplitter.Log.UsingPositionOfValue"));
      }
    }

    String valueToSplit = data.previousMeta.getString(r, data.fieldnr);
    String[] valueParts = Const.splitString(valueToSplit, data.delimiter, data.enclosure);
    int prev = 0;
    for (int i = 0; i < meta.getFieldName().length; i++) {
      String rawValue = null;
      if (selectFieldById) {
        for (String part : valueParts) {
          if (part.startsWith(meta.getFieldID()[i])) {
            // Optionally remove the id
            if (meta.getFieldRemoveID()[i]) {
              rawValue = part.substring(meta.getFieldID()[i].length());
            } else {
              rawValue = part;
            }

            break;
          }
        }

        if (log.isDebug()) {
          logDebug(BaseMessages.getString(PKG, "FieldSplitter.Log.SplitInfo") + rawValue);
        }
      } else {
        rawValue = (valueParts == null || i >= valueParts.length) ? null : valueParts[i];
        prev += (rawValue == null ? 0 : rawValue.length()) + data.delimiter.length();

        if (log.isDebug()) {
          logDebug(
              BaseMessages.getString(
                  PKG, "FieldSplitter.Log.SplitFieldsInfo", rawValue, String.valueOf(prev)));
        }
      }

      Object value;
      try {
        ValueMetaInterface valueMeta = data.outputMeta.getValueMeta(data.fieldnr + i);
        ValueMetaInterface conversionValueMeta = data.conversionMeta.getValueMeta(data.fieldnr + i);

        if (rawValue != null && valueMeta.isNull(rawValue)) {
          rawValue = null;
        }
        value =
            valueMeta.convertDataFromString(
                rawValue,
                conversionValueMeta,
                meta.getFieldNullIf()[i],
                meta.getFieldIfNull()[i],
                meta.getFieldTrimType()[i]);
      } catch (Exception e) {
        throw new KettleValueException(
            BaseMessages.getString(
                PKG,
                "FieldSplitter.Log.ErrorConvertingSplitValue",
                rawValue,
                meta.getSplitField() + "]!"),
            e);
      }
      outputRow[data.fieldnr + i] = value;
    }

    return outputRow;
  }