public static final String[] convertLineToStrings( LogChannelInterface log, String line, TextFileInputMeta inf,
      String delimiter, String enclosure, String escapeCharacters ) throws KettleException {
    String[] strings = new String[inf.inputFields.length];
    int fieldnr;

    String pol; // piece of line

    try {
      if ( line == null ) {
        return null;
      }

      if ( inf.content.fileType.equalsIgnoreCase( "CSV" ) ) {
        // Split string in pieces, only for CSV!

        fieldnr = 0;
        int pos = 0;
        int length = line.length();
        boolean dencl = false;

        int len_encl = ( enclosure == null ? 0 : enclosure.length() );
        int len_esc = ( escapeCharacters == null ? 0 : escapeCharacters.length() );

        while ( pos < length ) {
          int from = pos;
          int next;

          boolean encl_found;
          boolean contains_escaped_enclosures = false;
          boolean contains_escaped_separators = false;
          boolean contains_escaped_escape = false;

          // Is the field beginning with an enclosure?
          // "aa;aa";123;"aaa-aaa";000;...
          if ( len_encl > 0 && line.substring( from, from + len_encl ).equalsIgnoreCase( enclosure ) ) {
            if ( log.isRowLevel() ) {
              log.logRowlevel( BaseMessages.getString( PKG, "TextFileInput.Log.ConvertLineToRowTitle" ), BaseMessages
                  .getString( PKG, "TextFileInput.Log.Encloruse", line.substring( from, from + len_encl ) ) );
            }
            encl_found = true;
            int p = from + len_encl;

            boolean is_enclosure =
                len_encl > 0 && p + len_encl < length && line.substring( p, p + len_encl ).equalsIgnoreCase(
                    enclosure );
            boolean is_escape =
                len_esc > 0 && p + len_esc < length && line.substring( p, p + len_esc ).equalsIgnoreCase(
                    inf.content.escapeCharacter );

            boolean enclosure_after = false;

            // Is it really an enclosure? See if it's not repeated twice or escaped!
            if ( ( is_enclosure || is_escape ) && p < length - 1 ) {
              String strnext = line.substring( p + len_encl, p + 2 * len_encl );
              if ( strnext.equalsIgnoreCase( enclosure ) ) {
                p++;
                enclosure_after = true;
                dencl = true;

                // Remember to replace them later on!
                if ( is_escape ) {
                  contains_escaped_enclosures = true;
                }
              } else if ( strnext.equals( inf.content.escapeCharacter ) ) {
                p++;
                // Remember to replace them later on!
                if ( is_escape ) {
                  contains_escaped_escape = true; // remember
                }
              }
            }

            // Look for a closing enclosure!
            while ( ( !is_enclosure || enclosure_after ) && p < line.length() ) {
              p++;
              enclosure_after = false;
              is_enclosure =
                  len_encl > 0 && p + len_encl < length && line.substring( p, p + len_encl ).equals( enclosure );
              is_escape =
                  len_esc > 0 && p + len_esc < length && line.substring( p, p + len_esc ).equals(
                      inf.content.escapeCharacter );

              // Is it really an enclosure? See if it's not repeated twice or escaped!
              if ( ( is_enclosure || is_escape ) && p < length - 1 ) {

                String strnext = line.substring( p + len_encl, p + 2 * len_encl );
                if ( strnext.equals( enclosure ) ) {
                  p++;
                  enclosure_after = true;
                  dencl = true;

                  // Remember to replace them later on!
                  if ( is_escape ) {
                    contains_escaped_enclosures = true; // remember
                  }
                } else if ( strnext.equals( inf.content.escapeCharacter ) ) {
                  p++;
                  // Remember to replace them later on!
                  if ( is_escape ) {
                    contains_escaped_escape = true; // remember
                  }
                }
              }
            }

            if ( p >= length ) {
              next = p;
            } else {
              next = p + len_encl;
            }

            if ( log.isRowLevel() ) {
              log.logRowlevel( BaseMessages.getString( PKG, "TextFileInput.Log.ConvertLineToRowTitle" ), BaseMessages
                  .getString( PKG, "TextFileInput.Log.EndOfEnclosure", "" + p ) );
            }
          } else {
            encl_found = false;
            boolean found = false;
            int startpoint = from;
            // int tries = 1;
            do {
              next = line.indexOf( delimiter, startpoint );

              // See if this position is preceded by an escape character.
              if ( len_esc > 0 && next > 0 ) {
                String before = line.substring( next - len_esc, next );

                if ( inf.content.escapeCharacter.equals( before ) ) {
                  int previous_escapes = 1;

                  int start = next - len_esc - 1;
                  int end = next - 1;

                  while ( start >= 0 ) {
                    if ( inf.content.escapeCharacter.equals( line.substring( start, end ) ) ) {
                      previous_escapes++;
                      start--;
                      end--;
                    } else {
                      break;
                    }
                  }

                  // If behind the seperator there are a odd number of escaped
                  // The separator is escaped.
                  if ( previous_escapes % 2 != 0 ) {
                    // take the next separator, this one is escaped...
                    startpoint = next + 1;
                    // tries++;
                    contains_escaped_separators = true;
                  } else {
                    found = true;
                  }

                } else {
                  found = true;
                }
              } else {
                found = true;
              }
            } while ( !found && next >= 0 );
          }
          if ( next == -1 ) {
            next = length;
          }

          if ( encl_found && ( ( from + len_encl ) <= ( next - len_encl ) ) ) {
            pol = line.substring( from + len_encl, next - len_encl );
            if ( log.isRowLevel() ) {
              log.logRowlevel( BaseMessages.getString( PKG, "TextFileInput.Log.ConvertLineToRowTitle" ), BaseMessages
                  .getString( PKG, "TextFileInput.Log.EnclosureFieldFound", "" + pol ) );
            }
          } else {
            pol = line.substring( from, next );
            if ( log.isRowLevel() ) {
              log.logRowlevel( BaseMessages.getString( PKG, "TextFileInput.Log.ConvertLineToRowTitle" ), BaseMessages
                  .getString( PKG, "TextFileInput.Log.NormalFieldFound", "" + pol ) );
            }
          }

          if ( dencl && Utils.isEmpty( inf.content.escapeCharacter ) ) {
            StringBuilder sbpol = new StringBuilder( pol );
            int idx = sbpol.indexOf( enclosure + enclosure );
            while ( idx >= 0 ) {
              sbpol.delete( idx, idx + enclosure.length() );
              idx = sbpol.indexOf( enclosure + enclosure );
            }
            pol = sbpol.toString();
          }
          if ( !Utils.isEmpty( inf.content.escapeCharacter ) && ( inf.content.escapeCharacter.equals( enclosure ) ) && ( contains_escaped_escape || contains_escaped_enclosures ) ) {
            // replace the escaped enclosures with enclosures...
            String replace = inf.content.escapeCharacter + enclosure;
            String replaceWith = enclosure;

            pol = Const.replace( pol, replace, replaceWith );
          } else {
            if ( contains_escaped_enclosures ) {
              String replace = inf.content.escapeCharacter + enclosure;
              String replaceWith = enclosure;

              pol = Const.replace( pol, replace, replaceWith );
            }

            contains_escaped_escape = !Utils.isEmpty( inf.content.escapeCharacter ) && pol.contains( inf.content.escapeCharacter + inf.content.escapeCharacter );
            if ( contains_escaped_escape ) {
              String replace = inf.content.escapeCharacter + inf.content.escapeCharacter;
              String replaceWith = inf.content.escapeCharacter;

              pol = Const.replace( pol, replace, replaceWith );
            }
          }

          // replace the escaped separators with separators...
          if ( contains_escaped_separators ) {
            String replace = inf.content.escapeCharacter + delimiter;
            String replaceWith = delimiter;

            pol = Const.replace( pol, replace, replaceWith );
          }

          // Now add pol to the strings found!
          try {
            strings[fieldnr] = pol;
          } catch ( ArrayIndexOutOfBoundsException e ) {
            // In case we didn't allocate enough space.
            // This happens when you have less header values specified than there are actual values in the rows.
            // As this is "the exception" we catch and resize here.
            //
            String[] newStrings = new String[strings.length];
            for ( int x = 0; x < strings.length; x++ ) {
              newStrings[x] = strings[x];
            }
            strings = newStrings;
          }

          pos = next + delimiter.length();
          fieldnr++;
        }
        if ( pos == length ) {
          if ( log.isRowLevel() ) {
            log.logRowlevel( BaseMessages.getString( PKG, "TextFileInput.Log.ConvertLineToRowTitle" ), BaseMessages
                .getString( PKG, "TextFileInput.Log.EndOfEmptyLineFound" ) );
          }
          if ( fieldnr < strings.length ) {
            strings[fieldnr] = Const.EMPTY_STRING;
          }
          fieldnr++;
        }
      } else {
        // Fixed file format: Simply get the strings at the required positions...
        // Note - charBased is the old default behavior. If this is an old transformation, content.length will be null
        // and should be processed as before. If the content.length is equal to "Characters" or there is no specified encoding,
        // it will still use the old behavior. The *only* way to get the new behavior is if content.length = "Bytes" and
        // the encoding is specified.
        boolean charBased = ( inf.content.length == null || inf.content.length.equalsIgnoreCase( "Characters" ) || inf.getEncoding() == null ); // Default to classic behavior
        for ( int i = 0; i < inf.inputFields.length; i++ ) {
          BaseFileField field = inf.inputFields[i];

          int length;
          int fPos = field.getPosition();
          int fLength = field.getLength();
          int fPl = fPos + fLength;
          if ( charBased ) {
            length = line.length();
            if ( fPl <= length ) {
              strings[i] = line.substring( fPos, fPl );
            } else {
              if ( fPos < length ) {
                strings[i] = line.substring( fPos );
              } else {
                strings[i] = "";
              }
            }
          } else {
            byte[] b = null;
            String enc = inf.getEncoding();
            b = line.getBytes( enc );
            length = b.length;
            if ( fPl <= length ) {
              strings[i] = new String( Arrays.copyOfRange( b, fPos, fPl ), enc );
            } else {
              if ( fPos < length ) {
                strings[i] = new String( Arrays.copyOfRange( b, fPos, length - 1 ), enc );
              } else {
                strings[i] = "";
              }
            }
          }
        }
      }
    } catch ( Exception e ) {
      throw new KettleException( BaseMessages.getString( PKG, "TextFileInput.Log.Error.ErrorConvertingLine", e
          .toString() ), e );
    }

    return strings;
  }
@Test
  public void convertCSVLinesToStringsWithEnclosure() throws Exception {
    TextFileInputMeta inputMeta = Mockito.mock( TextFileInputMeta.class );
    inputMeta.content = new TextFileInputMeta.Content();
    inputMeta.content.fileType = "CSV";
    inputMeta.inputFields = new BaseFileField[ 2 ];
    inputMeta.content.escapeCharacter = "\\";
    inputMeta.content.enclosure = "\"";

    String line = "\"A\\\\\",\"B\""; // "A\\","B"

    String[] strings = TextFileInputUtils
      .convertLineToStrings( Mockito.mock( LogChannelInterface.class ), line, inputMeta, ",", "\"", "\\" );
    Assert.assertNotNull( strings );
    Assert.assertEquals( "A\\", strings[ 0 ] );
    Assert.assertEquals( "B", strings[ 1 ] );

    line = "\"\\\\\",\"AB\""; // "\\","AB"

    strings = TextFileInputUtils
      .convertLineToStrings( Mockito.mock( LogChannelInterface.class ), line, inputMeta, ",", "\"", "\\" );
    Assert.assertNotNull( strings );
    Assert.assertEquals( "\\", strings[ 0 ] );
    Assert.assertEquals( "AB", strings[ 1 ] );

    line = "\"A\\B\",\"C\""; // "A\B","C"

    strings = TextFileInputUtils
      .convertLineToStrings( Mockito.mock( LogChannelInterface.class ), line, inputMeta, ",", "\"", "\\" );
    Assert.assertNotNull( strings );
    Assert.assertEquals( "A\\B", strings[ 0 ] );
    Assert.assertEquals( "C", strings[ 1 ] );
  }