public boolean init(StepMetaInterface smi, StepDataInterface sdi) {
    meta = (TextFileOutputMeta) smi;
    data = (TextFileOutputData) sdi;

    if (super.init(smi, sdi)) {
      data.splitnr = 0;
      // In case user want to create file at first row
      // In that case, DO NOT create file at Init
      if (!meta.isDoNotOpenNewFileInit()) {
        try {
          if (!meta.isFileNameInField()) {
            openNewFile(meta.getFileName());
          }

          data.oneFileOpened = true;
        } catch (Exception e) {
          logError("Couldn't open file " + meta.getFileName(), e);
          setErrors(1L);
          stopAll();
        }
      }

      try {
        initBinaryDataFields();
      } catch (Exception e) {
        logError("Couldn't initialize binary data fields", e);
        setErrors(1L);
        stopAll();
      }

      return true;
    }

    return false;
  }
  private void writeField(ValueMetaInterface v, Object valueData, byte[] nullString)
      throws KettleStepException {
    try {
      byte[] str;

      // First check whether or not we have a null string set
      // These values should be set when a null value passes
      //
      if (nullString != null && v.isNull(valueData)) {
        str = nullString;
      } else {
        if (meta.isFastDump()) {
          if (valueData instanceof byte[]) {
            str = (byte[]) valueData;
          } else {
            str = getBinaryString((valueData == null) ? "" : valueData.toString());
          }
        } else {
          str = formatField(v, valueData);
        }
      }

      if (str != null && str.length > 0) {
        List<Integer> enclosures = null;

        if (v.isString() && meta.isEnclosureForced() && !meta.isPadded()) {
          data.writer.write(data.binaryEnclosure);

          // Also check for the existence of the enclosure character.
          // If needed we double (escape) the enclosure character.
          //
          enclosures = getEnclosurePositions(str);
        }

        if (enclosures == null) {
          data.writer.write(str);
        } else {
          // Skip the enclosures, double them instead...
          int from = 0;
          for (int i = 0; i < enclosures.size(); i++) {
            int position = enclosures.get(i);
            data.writer.write(str, from, position + data.binaryEnclosure.length - from);
            data.writer.write(data.binaryEnclosure); // write enclosure a second time
            from = position + data.binaryEnclosure.length;
          }
          if (from < str.length) {
            data.writer.write(str, from, str.length - from);
          }
        }

        if (v.isString() && meta.isEnclosureForced() && !meta.isPadded()) {
          data.writer.write(data.binaryEnclosure);
        }
      }
    } catch (Exception e) {
      throw new KettleStepException("Error writing field content to file", e);
    }
  }
 public String buildFilename(String filename, boolean ziparchive) {
   return TextFileOutputMeta.buildFilename(
       filename,
       meta.getExtension(),
       this,
       getCopy(),
       getPartitionID(),
       data.splitnr,
       ziparchive,
       meta);
 }
  public Object clone() {
    TextFileOutputMeta retval = (TextFileOutputMeta) super.clone();
    int nrfields = outputFields.length;

    retval.allocate(nrfields);

    for (int i = 0; i < nrfields; i++) {
      retval.outputFields[i] = (TextFileField) outputFields[i].clone();
    }

    return retval;
  }
  private boolean closeFile() {
    boolean retval = false;

    try {
      if (data.writer != null) {
        if (log.isDebug()) logDebug("Closing output stream");
        data.writer.close();
        if (log.isDebug()) logDebug("Closed output stream");
      }
      data.writer = null;
      if (data.cmdProc != null) {
        if (log.isDebug()) logDebug("Ending running external command");
        int procStatus = data.cmdProc.waitFor();
        // close the streams
        // otherwise you get "Too many open files, java.io.IOException" after a lot of iterations
        try {
          data.cmdProc.getErrorStream().close();
          data.cmdProc.getOutputStream().close();
          data.cmdProc.getInputStream().close();
        } catch (IOException e) {
          if (log.isDetailed()) logDetailed("Warning: Error closing streams: " + e.getMessage());
        }
        data.cmdProc = null;
        if (log.isBasic() && procStatus != 0) logBasic("Command exit status: " + procStatus);
      } else {
        if (log.isDebug()) logDebug("Closing normal file ...");
        if (FILE_COMPRESSION_TYPE_ZIP.equals(meta.getFileCompression())) {
          data.zip.closeEntry();
          data.zip.finish();
          data.zip.close();
        } else if (FILE_COMPRESSION_TYPE_GZIP.equals(meta.getFileCompression())) {
          data.gzip.finish();
        }
        if (data.fos != null) {
          data.fos.close();
          data.fos = null;
        }
      }

      retval = true;
    } catch (Exception e) {
      logError("Exception trying to close file: " + e.toString());
      setErrors(1);
      retval = false;
    }

    return retval;
  }
  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;
  }
  /**
   * This method should only be used when you have a filename in the input stream.
   *
   * @param filename the filename to set the data.writer field for
   * @throws KettleException
   */
  private void setDataWriterForFilename(String filename) throws KettleException {
    // First handle the writers themselves.
    // If we didn't have a writer yet, we create one.
    // Basically we open a new file
    //
    data.writer = data.fileWriterMap.get(filename);
    if (data.writer == null) {
      openNewFile(filename);
      data.fileWriterMap.put(filename, data.writer);

      // If it's the first time we open it and we have a header, we write a header...
      //
      if (!meta.isFileAppended() && meta.isHeaderEnabled()) {
        if (writeHeader()) {
          incrementLinesOutput();
        }
      }
    }
  }
 private byte[] getBinaryString(String string) throws KettleStepException {
   try {
     if (data.hasEncoding) {
       return string.getBytes(meta.getEncoding());
     } else {
       return string.getBytes();
     }
   } catch (Exception e) {
     throw new KettleStepException(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);
    }
  }
  private boolean writeEndedLine() {
    boolean retval = false;
    try {
      String sLine = meta.getEndedLine();
      if (sLine != null) {
        if (sLine.trim().length() > 0) {
          data.writer.write(getBinaryString(sLine));
          incrementLinesOutput();
        }
      }
    } catch (Exception e) {
      logError("Error writing ended tag line: " + e.toString());
      logError(Const.getStackTracker(e));
      retval = true;
    }

    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);
    }
  }
  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;
  }
  public void openNewFile(String baseFilename) throws KettleException {
    data.writer = null;

    ResultFile resultFile = null;

    String filename = buildFilename(environmentSubstitute(baseFilename), true);

    try {
      if (meta.isFileAsCommand()) {
        if (log.isDebug()) logDebug("Spawning external process");
        if (data.cmdProc != null) {
          logError("Previous command not correctly terminated");
          setErrors(1);
        }
        String cmdstr = environmentSubstitute(meta.getFileName());
        if (Const.getOS().equals("Windows 95")) {
          cmdstr = "command.com /C " + cmdstr;
        } else {
          if (Const.getOS().startsWith("Windows")) {
            cmdstr = "cmd.exe /C " + cmdstr;
          }
        }
        if (log.isDetailed()) logDetailed("Starting: " + cmdstr);
        Runtime r = Runtime.getRuntime();
        data.cmdProc = r.exec(cmdstr, EnvUtil.getEnvironmentVariablesForRuntimeExec());
        data.writer = data.cmdProc.getOutputStream();
        StreamLogger stdoutLogger = new StreamLogger(data.cmdProc.getInputStream(), "(stdout)");
        StreamLogger stderrLogger = new StreamLogger(data.cmdProc.getErrorStream(), "(stderr)");
        new Thread(stdoutLogger).start();
        new Thread(stderrLogger).start();
      } else {
        // Add this to the result file names...
        resultFile =
            new ResultFile(
                ResultFile.FILE_TYPE_GENERAL,
                KettleVFS.getFileObject(filename),
                getTransMeta().getName(),
                getStepname());
        resultFile.setComment("This file was created with a text file output step");
        addResultFile(resultFile);

        OutputStream outputStream;

        if (!Const.isEmpty(meta.getFileCompression())
            && !meta.getFileCompression().equals(FILE_COMPRESSION_TYPE_NONE)) {
          if (meta.getFileCompression().equals(FILE_COMPRESSION_TYPE_ZIP)) {
            if (log.isDetailed())
              log.logDetailed(toString(), "Opening output stream in zipped mode");

            if (checkPreviouslyOpened(filename)) {
              data.fos = KettleVFS.getOutputStream(filename, true);
            } else {
              data.fos = KettleVFS.getOutputStream(filename, meta.isFileAppended());
            }
            data.zip = new ZipOutputStream(data.fos);
            File entry = new File(filename);
            ZipEntry zipentry = new ZipEntry(entry.getName());
            zipentry.setComment("Compressed by Kettle");
            data.zip.putNextEntry(zipentry);
            outputStream = data.zip;
          } else if (meta.getFileCompression().equals(FILE_COMPRESSION_TYPE_GZIP)) {
            if (log.isDetailed())
              log.logDetailed(toString(), "Opening output stream in gzipped mode");
            if (checkPreviouslyOpened(filename)) {
              data.fos = KettleVFS.getOutputStream(filename, true);
            } else {
              data.fos = KettleVFS.getOutputStream(filename, meta.isFileAppended());
            }
            data.gzip = new GZIPOutputStream(data.fos);
            outputStream = data.gzip;
          } else {
            throw new KettleFileException("No compression method specified!");
          }
        } else {
          if (log.isDetailed())
            log.logDetailed(toString(), "Opening output stream in nocompress mode");
          if (checkPreviouslyOpened(filename)) {
            data.fos = KettleVFS.getOutputStream(filename, true);
          } else {
            data.fos = KettleVFS.getOutputStream(filename, meta.isFileAppended());
          }
          outputStream = data.fos;
        }

        if (!Const.isEmpty(meta.getEncoding())) {
          if (log.isDetailed())
            log.logDetailed(toString(), "Opening output stream in encoding: " + meta.getEncoding());
          data.writer = new BufferedOutputStream(outputStream, 5000);
        } else {
          if (log.isDetailed())
            log.logDetailed(toString(), "Opening output stream in default encoding");
          data.writer = new BufferedOutputStream(outputStream, 5000);
        }

        if (log.isDetailed()) logDetailed("Opened new file with name [" + filename + "]");
      }
    } catch (Exception e) {
      throw new KettleException("Error opening new file : " + e.toString());
    }
    // System.out.println("end of newFile(), splitnr="+splitnr);

    data.splitnr++;

    if (resultFile != null && meta.isAddToResultFiles()) {
      // Add this to the result file names...
      addResultFile(resultFile);
    }
  }
  public String buildFilename(
      String filename,
      String extension,
      VariableSpace space,
      int stepnr,
      String partnr,
      int splitnr,
      boolean ziparchive,
      TextFileOutputMeta meta) {
    SimpleDateFormat daf = new SimpleDateFormat();

    // Replace possible environment variables...
    String retval = space.environmentSubstitute(filename);
    String realextension = space.environmentSubstitute(extension);

    if (meta.isFileAsCommand()) {
      return retval;
    }

    Date now = new Date();

    if (meta.isSpecifyingFormat() && !Const.isEmpty(meta.getDateTimeFormat())) {
      daf.applyPattern(meta.getDateTimeFormat());
      String dt = daf.format(now);
      retval += dt;
    } else {
      if (meta.isDateInFilename()) {
        daf.applyPattern("yyyMMdd");
        String d = daf.format(now);
        retval += "_" + d;
      }
      if (meta.isTimeInFilename()) {
        daf.applyPattern("HHmmss");
        String t = daf.format(now);
        retval += "_" + t;
      }
    }
    if (meta.isStepNrInFilename()) {
      retval += "_" + stepnr;
    }
    if (meta.isPartNrInFilename()) {
      retval += "_" + partnr;
    }
    if (meta.getSplitEvery() > 0) {
      retval += "_" + splitnr;
    }

    if (meta.getFileCompression().equals("Zip")) {
      if (ziparchive) {
        retval += ".zip";
      } else {
        if (realextension != null && realextension.length() != 0) {
          retval += "." + realextension;
        }
      }
    } else {
      if (realextension != null && realextension.length() != 0) {
        retval += "." + realextension;
      }
      if (meta.getFileCompression().equals("GZip")) {
        retval += ".gz";
      }
    }
    return retval;
  }