private boolean writeHeader() {
    boolean retval = false;
    RowMetaInterface r = data.outputRowMeta;

    try {
      // If we have fields specified: list them in this order!
      if (meta.getOutputFields() != null && meta.getOutputFields().length > 0) {
        for (int i = 0; i < meta.getOutputFields().length; i++) {
          String fieldName = meta.getOutputFields()[i].getName();
          ValueMetaInterface v = r.searchValueMeta(fieldName);

          if (i > 0 && data.binarySeparator.length > 0) {
            data.writer.write(data.binarySeparator);
          }
          if (meta.isEnclosureForced()
              && data.binaryEnclosure.length > 0
              && v != null
              && v.isString()) {
            data.writer.write(data.binaryEnclosure);
          }
          data.writer.write(getBinaryString(fieldName));
          if (meta.isEnclosureForced()
              && data.binaryEnclosure.length > 0
              && v != null
              && v.isString()) {
            data.writer.write(data.binaryEnclosure);
          }
        }
        data.writer.write(data.binaryNewline);
      } else if (r != null) // Just put all field names in the header/footer
      {
        for (int i = 0; i < r.size(); i++) {
          if (i > 0 && data.binarySeparator.length > 0) {
            data.writer.write(data.binarySeparator);
          }
          ValueMetaInterface v = r.getValueMeta(i);

          if (meta.isEnclosureForced() && data.binaryEnclosure.length > 0 && v.isString()) {
            data.writer.write(data.binaryEnclosure);
          }
          data.writer.write(getBinaryString(v.getName()));
          if (meta.isEnclosureForced() && data.binaryEnclosure.length > 0 && v.isString()) {
            data.writer.write(data.binaryEnclosure);
          }
        }
        data.writer.write(data.binaryNewline);
      } else {
        data.writer.write(getBinaryString("no rows selected" + Const.CR));
      }
    } catch (Exception e) {
      logError("Error writing header line: " + e.toString());
      logError(Const.getStackTracker(e));
      retval = true;
    }
    incrementLinesOutput();
    return retval;
  }
  private void initBinaryDataFields() throws KettleException {
    try {
      data.hasEncoding = !Const.isEmpty(meta.getEncoding());
      data.binarySeparator = new byte[] {};
      data.binaryEnclosure = new byte[] {};
      data.binaryNewline = new byte[] {};

      if (data.hasEncoding) {
        if (!Const.isEmpty(meta.getSeparator()))
          data.binarySeparator =
              environmentSubstitute(meta.getSeparator()).getBytes(meta.getEncoding());
        if (!Const.isEmpty(meta.getEnclosure()))
          data.binaryEnclosure =
              environmentSubstitute(meta.getEnclosure()).getBytes(meta.getEncoding());
        if (!Const.isEmpty(meta.getNewline()))
          data.binaryNewline = meta.getNewline().getBytes(meta.getEncoding());
      } else {
        if (!Const.isEmpty(meta.getSeparator()))
          data.binarySeparator = environmentSubstitute(meta.getSeparator()).getBytes();
        if (!Const.isEmpty(meta.getEnclosure()))
          data.binaryEnclosure = environmentSubstitute(meta.getEnclosure()).getBytes();
        if (!Const.isEmpty(meta.getNewline()))
          data.binaryNewline = environmentSubstitute(meta.getNewline()).getBytes();
      }

      data.binaryNullValue = new byte[meta.getOutputFields().length][];
      for (int i = 0; i < meta.getOutputFields().length; i++) {
        data.binaryNullValue[i] = null;
        String nullString = meta.getOutputFields()[i].getNullString();
        if (!Const.isEmpty(nullString)) {
          if (data.hasEncoding) {
            data.binaryNullValue[i] = nullString.getBytes(meta.getEncoding());
          } else {
            data.binaryNullValue[i] = nullString.getBytes();
          }
        }
      }
    } catch (Exception e) {
      throw new KettleException("Unexpected error while encoding binary fields", e);
    }
  }
  private void writeRowToFile(RowMetaInterface rowMeta, Object[] r) throws KettleStepException {
    try {
      if (meta.getOutputFields() == null || meta.getOutputFields().length == 0) {
        /*
         * Write all values in stream to text file.
         */
        for (int i = 0; i < rowMeta.size(); i++) {
          if (i > 0 && data.binarySeparator.length > 0) {
            data.writer.write(data.binarySeparator);
          }
          ValueMetaInterface v = rowMeta.getValueMeta(i);
          Object valueData = r[i];

          // no special null value default was specified since no fields are specified at all
          // As such, we pass null
          //
          writeField(v, valueData, null);
        }
        data.writer.write(data.binaryNewline);
      } else {
        /*
         * Only write the fields specified!
         */
        for (int i = 0; i < meta.getOutputFields().length; i++) {
          if (i > 0 && data.binarySeparator.length > 0) data.writer.write(data.binarySeparator);

          ValueMetaInterface v = rowMeta.getValueMeta(data.fieldnrs[i]);
          Object valueData = r[data.fieldnrs[i]];
          writeField(v, valueData, data.binaryNullValue[i]);
        }
        data.writer.write(data.binaryNewline);
      }

      incrementLinesOutput();

      // flush every 4k lines
      // if (linesOutput>0 && (linesOutput&0xFFF)==0) data.writer.flush();
    } catch (Exception e) {
      throw new KettleStepException("Error writing line", e);
    }
  }
  public synchronized boolean processRow(StepMetaInterface smi, StepDataInterface sdi)
      throws KettleException {
    meta = (TextFileOutputMeta) smi;
    data = (TextFileOutputData) sdi;

    boolean result = true;
    boolean bEndedLineWrote = false;
    Object[] r = getRow(); // This also waits for a row to be finished.

    if (r != null && first) {
      first = false;
      data.outputRowMeta = getInputRowMeta().clone();
      meta.getFields(data.outputRowMeta, getStepname(), null, null, this);

      // if file name in field is enabled then set field name and open file
      //
      if (meta.isFileNameInField()) {

        // find and set index of file name field in input stream
        //
        data.fileNameFieldIndex = getInputRowMeta().indexOfValue(meta.getFileNameField());

        // set the file name for this row
        //
        if (data.fileNameFieldIndex < 0) {
          throw new KettleStepException(
              Messages.getString(
                  "TextFileOutput.Exception.FileNameFieldNotFound",
                  meta.getFileNameField())); // $NON-NLS-1$
        }

        data.fileNameMeta = getInputRowMeta().getValueMeta(data.fileNameFieldIndex);
        data.fileName = data.fileNameMeta.getString(r[data.fileNameFieldIndex]);
        setDataWriterForFilename(data.fileName);
      } else if (meta.isDoNotOpenNewFileInit() && !meta.isFileNameInField()) {
        // Open a new file here
        //
        openNewFile(meta.getFileName());
        data.oneFileOpened = true;
        initBinaryDataFields();
      }

      if (!meta.isFileAppended()
          && (meta.isHeaderEnabled()
              || meta.isFooterEnabled())) // See if we have to write a header-line)
      {
        if (!meta.isFileNameInField() && meta.isHeaderEnabled() && data.outputRowMeta != null) {
          writeHeader();
        }
      }

      data.fieldnrs = new int[meta.getOutputFields().length];
      for (int i = 0; i < meta.getOutputFields().length; i++) {
        data.fieldnrs[i] = data.outputRowMeta.indexOfValue(meta.getOutputFields()[i].getName());
        if (data.fieldnrs[i] < 0) {
          throw new KettleStepException(
              "Field ["
                  + meta.getOutputFields()[i].getName()
                  + "] couldn't be found in the input stream!");
        }
      }
    }

    if ((r == null && data.outputRowMeta != null && meta.isFooterEnabled())
        || (r != null
            && getLinesOutput() > 0
            && meta.getSplitEvery() > 0
            && ((getLinesOutput() + 1) % meta.getSplitEvery()) == 0)) {
      if (data.outputRowMeta != null) {
        if (meta.isFooterEnabled()) {
          writeHeader();
        }
      }

      if (r == null) {
        // add tag to last line if needed
        writeEndedLine();
        bEndedLineWrote = true;
      }
      // Done with this part or with everything.
      closeFile();

      // Not finished: open another file...
      if (r != null) {
        openNewFile(meta.getFileName());

        if (meta.isHeaderEnabled() && data.outputRowMeta != null)
          if (writeHeader()) incrementLinesOutput();
      }
    }

    if (r == null) // no more input to be expected...
    {
      if (false == bEndedLineWrote) {
        // add tag to last line if needed
        writeEndedLine();
        bEndedLineWrote = true;
      }

      setOutputDone();
      return false;
    }

    // First handle the file name in field
    // Write a header line as well if needed
    //
    if (meta.isFileNameInField()) {
      String baseFilename = data.fileNameMeta.getString(r[data.fileNameFieldIndex]);
      setDataWriterForFilename(baseFilename);
    }
    writeRowToFile(data.outputRowMeta, r);
    putRow(data.outputRowMeta, r); // in case we want it to go further...

    if (checkFeedback(getLinesOutput())) logBasic("linenr " + getLinesOutput());

    return result;
  }