Quoter.java

/*
** Module   : Quoter.java
** Abstract : Java implementation of the Quoter utility
**
** Copyright (c) 2012-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------Description-----------------
** 001 CS  20121214 Created initial version.
** 002 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 003 RFB 20230214 Changes related to ref. #7024:
**                  - The actual INPUT_NOT_AVAILABLE_ERR message from 4GL does not have a
**                    colon in it. The INCOMPATIBLE_OPTIONS_ERR string had an extra space.
**                  - The delimited tokens need to take into account they could contain delimiters.
**                    These will be surrounded by double-quotes (not single) regardless of what the
**                    4GL documentation indicates. The quoter-utility.p testcases bear this out.
**                  - Must take into account quoted columns which may have multiple or unbalanced
**                    quotation marks in a given column, as well as continue past the end of line.
**     RFB 20230301 Post-review updates:
**                  - Re-add protection against NPE in quoted()
**                  - Simplification of the quoteCountWorker
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/
package com.goldencode.p2j.util;

import java.io.*;
import java.util.*;

/**
 * A class implementation of the Progress QUOTER utility.
 * <p>
 * This class is used to parse the quoter utility parameters and execute the
 * equivalent logic of the Progress utility.
 * <p>
 * The quoter utility will put the elements identified and extracted from 
 * input between double quotes (in the case where a double quote is found 
 * inside it will be doubled) the result being sent to output which may be a 
 * stream or a file.
 * <p>
 * The quoter utility can be called with the following parameters:
 * <ul>
 *   <li> Output is always written to System.out.
 *   <li>An input file can be specified as argument with full or relative
 *       path. 
 *       <p> If specified, the  input will be taken from the file, and in case 
 *       the file doesn't exist or the file does not have read permission,
 *       an error will be sent to output announcing the source cannot be read.
 *       <p> If the file parameter is not specified the input will be taken 
 *       from System.in.
 *   <li> Delimiter option (optional): <b>-D</b> followed by the 
 *        delimiter character.
 *        <p> When this option is used the input is read line by line and each
 *        line will be split using the specified delimiter before being sent 
 *        to output.
 *        <p>Ex: <b>OS-COMMAND QUOTER /home/table.csv -D , &gt;/home/table.q</b>
 *        <p>Delimiter and column options can never be used together! 
 *   <li> Column range option (optional) : <b>-C</b> followed by the column 
 *        ranges.
 *        <p> The column ranges must be separated in the form <b>start-end</b>
 *        and separated by: <b>,</b> . No spaces must be present!
 *        <p> This option will read input line by line and if presented with
 *        valid column ranges will split the line into columns from the 
 *        starting position of the column range to the end position of the 
 *        column range (Ex of column range: <b>1-4,5-15,...</b> the first 
 *        column will start from the first character up to the 4'th character 
 *        included). 
 *        <p>Usage Ex: <b>
 *        OS-COMMAND QUOTER /home/table.csv -C 1-2,4-20,22-26 &gt;/home/table.q
 *        </b>
 *        <p>Delimiter and column options can never be used together!
 *   <li> When there is no delimiter or column option the quoter will  read 
 *        the input line by line , quote it and output it.
 * </ul>
 * <p><b>Error Handling.</b>
 * <p>When errors are encountered quoter will send the error message to the 
 * output, then quit.
 * <p>The following errors messages will be output:
 * <ul>
 *   <li> <b>Unknown option:</b> + option : 
 *        <p>This error is sent to output when quoter is called with an 
 *        option different from -C or -D.
 *   <li> <b>Quoter only accepts one input file</b>
 *        <p>This error is sent to output when there are more than one 
 *        arguments that are not options or options attributes.
 *   <li> <b>The -c option should be followed by a space and then a list of 
 *        column ranges, separated by commas</b>
 *        <p>This error is sent to output when -c option doesn't have a
 *        value after the option.
 *   <li> <b>The -d option should be followed by a space and then a single 
 *        character</b>
 *        <p>This error is sent to output when -d option doesn't have a value 
 *        after the option or that value is not a single character.
 *   <li> <b>Invalid range: "</b> + invalid range.
 *        <p>This error is sent when an invalid range is detected within the 
 *        column ranges: This can happen because of the starting index being 
 *        greater than the end index or, one or both of the indexes are not 
 *        a number.
 *   <li> <b>Unable to read: </b> + input file.
 *        <p>This error is sent when the input file cannot be read (doesn't 
 *        exist or the user doesn't have read permission).
 *   <li> <b>Incompatible options: -c and -d</b>
 *        <p>This error is sent when quoter is used with both column and 
 *        delimiter options.
 * </ul>
 */
public class Quoter
{
   /** Error for invalid column range. */
   private static final String INVALID_RANGE_ERR = "Invalid range: ";

   /** Error message for unknown option. */
   private static final String UNKNOWN_OPTION_ERR = "Unknown option: ";
   
   /** Error message for multiple inputs. */
   private static final String INPUT_ERR = 
      "Quoter only accepts one input file";
   
   /** Error message for missing column range */
   private static final String COLL_ERR = 
      "The -c option should be followed by a space and then a list of " +
      "column ranges, separated by commas";
   
   /** Error message for missing delimiter. */
   private static final String DELIM_ERR = 
      "The -d option should be followed by a space and then a single " +
      "character";
   
   /** Error message for when input is not available for read. */
   private static final String INPUT_NOT_AVAILABLE_ERR = "Unable to read ";
   
   /** 
    * Error message for when both -C and -D options are used at the same time. 
     */
   private static final String INCOMPATIBLE_OPTIONS_ERR = 
     "Incompatible options: -c and -d";
   
   /** quoter detection variable. */
   private static final String QUOTER = "QUOTER";
   
   /** quoter.exe detection variable*/
   private static final String QUOTER_EXE = "QUOTER.EXE";
   
   /** Columns option indicator. */
   private static final String COLUMNS_OPTION = "-C";
   
   /** Delimiter option indicator. */
   private static final String DELIMITER_OPTION = "-D";
   
   /** The quote character used to delimit output strings. */
   private static final String QUOTE = "\"";

   /** 
    * The escaped quote character (by doubling it) used inside output strings.
    */
   private static final String QUOTE_2X = QUOTE + QUOTE;
   
   /**
    * Private constructor to stop class instantiation
    */
   private Quoter()
   {
      
   }
   
   /**
    * The entry point method of the Quoter class. 
    * <p>This will check for valid parameters and option usage of quoter 
    * before calling the mainm quoter logic.
    * 
    * @param    args
    *           The parameters given to the quoter utility
    */
   public static void main(String args[])
   {
      String inFile = null;
      String delimiter = null;
      String colRanges = null;
      String errorMessage = null;
      
      // interpret args (no args is still a valid case)
      if (args != null && args.length > 0)
      {
         for (int i = 0; i < args.length; i++)
         {
            if (isOption(args[i]))
            {
               if (args[i].toUpperCase().equals(DELIMITER_OPTION))
               {
                  // check to see if there is a value for the option
                  if (i + 1 < args.length)
                  {
                     delimiter = args[i + 1];
                     // skip this arg for checking
                     i++;
                  }
                  else
                  {
                     errorMessage = DELIM_ERR;
                     // get out of the "for" processing loop when the first
                     // error is encountered
                     break;
                  }
               }
               else if (args[i].toUpperCase().equals(COLUMNS_OPTION))
               {
                  // check to see if there is a value for the option
                  if (i + 1 < args.length)
                  {
                     colRanges = args[i + 1];
                     // skip this arg for checking
                     i++;
                  }
                  // no column option value
                  else
                  {
                     errorMessage = COLL_ERR;
                     // get out of the "for" processing loop when the first
                     // error is encountered
                     break;
                  }
               }
               else
               {
                  errorMessage = UNKNOWN_OPTION_ERR + args[i];
                  // get out of the "for" processing loop when the first
                  // error is encountered
                  break;
               }
            }
            else
            {
               if (inFile == null)
               {
                  inFile = args[i];
               }
               else
               {
                  errorMessage = INPUT_ERR;
                  // get out of the "for" processing loop when 
                  // error is encountered
                  break;
               }
            }
         }
      }
      
      // check for delimiter error and output it if another error hasn't been
      // detected before
      if (delimiter != null && delimiter.length() > 1 && errorMessage == null)
      {
         errorMessage = DELIM_ERR;
      }
      BufferedReader inputReader = null;
      BufferedWriter outputWriter =
        new BufferedWriter(new OutputStreamWriter(System.out));
      
      // check to see if any errors have been discovered
      if (errorMessage != null)
      {
         try
         {
            outputWriter.write(errorMessage);
            outputWriter.close();
         }
         catch (IOException e)
         {
            // nothing to be done but return
         }
         return;
      }
      
      if (inFile == null)
      {
         inputReader = new BufferedReader(new InputStreamReader(System.in));
      }
      else
      {
         try
         {
            inputReader = new BufferedReader(new FileReader(inFile));
         }
         catch (FileNotFoundException e)
         {
            try
            {
               outputWriter.write(INPUT_NOT_AVAILABLE_ERR + inFile);
               outputWriter.close();
            }
            catch (IOException ioe)
            {
               return;
            }
            return;
         }
      }
      
      try
      {
         process(inputReader, outputWriter, delimiter, colRanges);
      }
      catch (Exception e)
      {
         // nothing to do
      }

      finally
      {
         if (inputReader != null)
         {
            tryClose(inputReader);
         }

         tryClose(outputWriter);
      }
   }

   /**
    * Utility method that checks if a string argument is a call to the quoter
    * utility ot not.
    * 
    * @param    command
    *           The <code>String</code> argument which is to be tested for 
    *           identifying it as a quoter argument or not.

    * @return   Returns <code>true</code> if the parameter is identified as 
    *           a call to quoter utility or <code>false</code> otherwise.
    */
   public static boolean isQuoterCommand(String command)
   {
      // case insensitive matching
      command = command.toUpperCase();

      // the only possible matches must always end with one of these two
      // values
      if (command.endsWith(QUOTER) || command.endsWith(QUOTER_EXE))
      {
         int start = command.lastIndexOf(QUOTER);

         if (start == 0)
         {
            // no pathing or trash in front is a match
            return true;
         }
         else
         {
            // something is in front, check if it is a path
            char pathSep = command.charAt(start - 1);

            if (pathSep == '/' || pathSep == '\\')
            {
               return true;
            }

            // at this point there must be trash in front, this isn't valid
         }
      }

      // all the other case will return false
      return false;
   }

   /**
    * Utility method that checks for option's values and process them before 
    * calling the main quoter logic.
    * 
    * @param    inputReader
    *           The input file name
    * @param    outputWriter
    *           The output file name
    * @param    delimiter
    *           The delimiter if any
    * @param    colRange
    *           The column ranges to be parsed
    *           
    * @throws   Exception
    *           In case something goes wrong.This signals abort. 
    */
   private static void process(BufferedReader inputReader,
                               BufferedWriter outputWriter,
                               String         delimiter,
                               String         colRange) 
   throws Exception
   {
      String separator = null;
      List<ColumnRange> columnRanges = null;
     
      if (delimiter != null && colRange != null)
      {
         // We cannot have both column and delimiter options
         outputWriter.write(INCOMPATIBLE_OPTIONS_ERR);
         throw new Exception();
      }
      
      if (delimiter != null)
      {
         // Progress behavior if separator is not a character it will
         // take only the first character of the String.
         separator = delimiter.substring(0, 1);
         delModeWorker(inputReader, outputWriter, separator);
      }
      else
      {
         // get column ranges if any.
         columnRanges = getColumnRange(colRange, outputWriter);
         if ((columnRanges != null) && (columnRanges.size() > 0))
         {
            colModeWorker(inputReader, outputWriter, columnRanges);
         }
         else
         {
            simModeWorker(inputReader, outputWriter);
         }
      }
   }

   /**
    * Utility method that writes into the destination file the line of quoted
    * tokens (row of columns parsed from the input file).
    *
    * @param   tokens 
    *          The <code>array</code> of <code>String</code> values 
    *          representing a row of columns parsed from the input file
    * @param   writer  
    *          The <code>BufferedWriter</code> used to output the quoted
    *          characters.
    *                
    * @throws  IOException
    *          In case something went wrong in the writing process.
    *          <p>This indicates that the main quoter method should close 
    *          opened resources and terminate.
    */
   private static void writeDelimitedLine(String[]       tokens, 
                                          BufferedWriter writer)
   throws IOException
   {
      for (int i = 0; i < tokens.length; i++)
      {
         if (i != 0)
         {
            writer.write(" ");
         }

         // empty tokens are replaced with hyphens ('-') in the output, unless it ends the line
         writer.write(tokens[i].length() > 0 ? quote(tokens[i]) 
                                             : (i+1) < tokens.length ? "-" 
                                                                     : "");
      }
   }
   
   /**
    * Utility method that quotes a <code>String</code> using double-quote and
    * doubles any double quote it finds inside inside the <code>String</code>
    * variable
    * 
    * @param  quoted
    *         The <code>String</code> to be quoted.
    *         
    * @return The quoted <code>String</code> value.
    */
   private static String quote(String quoted)
   {
      if (quoted != null)
      {
         // Handle an edge case where a single quote is left to append
         String qString = (quoted.length() == 1 && quoted.charAt(0) == '\"') ? "" 
                                                                             : quoted.replaceAll(QUOTE, QUOTE_2X);
         return '"' + qString + '"';
      }
      else
      {
         return null;
      }
   }
   
   /**
    * Utility method to see if a string starts with - and can be a command
    * line option.
    * 
    * @param   string
    *          The <code>String</code> to be checked
    *          
    * @return  <code>true</code> if string starts with - , <code>false</code> 
    *          otherwise
    */
   private static boolean isOption(String string)
   {
      return string.startsWith("-");
   }

   /**
    * Utility method that identifies the columns from an input 
    * <code>String</code> line extracts and quotes them then outputs them to 
    * the destination file
    * 
    * @param   line 
    *          The <code>String</code> line representing a row from the input
    *          file.
    * @param   columnRanges 
    *          The <code>List</code> of <code>ColumnRange</code> used to 
    *          identify the column from the input line.
    * @param   writer
    *          The <code>BufferedWriter</code> used to output the quoted
    *          characters.
    * 
    * @throws  IOException
    *          In case something went wrong in the writing process.
    *          <p>This indicates that the main quoter method should close 
    *          opened resources and terminate.
    */
   private static void writeColumnsLine(String            line,
                                        List<ColumnRange> columnRanges,
                                        BufferedWriter    writer)
   throws IOException
   {
      boolean first = true;
      for (ColumnRange column : columnRanges)
      {
         String columnText = null;
         if (line.length() >= column.getEndColumn())
         {
            columnText = line.substring(column.getStartColumn(),
                                        column.getEndColumn());
         }
         else
         {
            if (column.getStartColumn() > line.length())
            {
               columnText = null;
            }
            else
            {
               columnText = line.substring(column.getStartColumn());
            }
         }

         if (first)
         {
            first = false;
         }
         else
         {
            writer.write(" ");
         }

         if (columnText != null)
         {
            writer.write(quote(columnText));
         }
         else
         {
            // when column text is null Progres puts a "-" mark
            writer.write("-");
         }
      }
   }

   /**
    * Utility method that parses a <code>String</code> containing column range
    * and returns the <code>List</code> of <code>ColumnRange</code> that 
    * contains the character range from a line where the column can be found.
    *
    * @param   colRange
    *          The <code>String</code> representation of the column ranges.
    * @param   destFile
    *          The <code>BufferedWriter</code> used to output the quoted
    *          characters or any error messages in this case
    * 
    * @return  The parsed <code>List</code> of <code>ColumnRange</code>.
    * 
    * @throws  Exception
    *          In case there are invalid column ranges. 
    *          <p>This indicates that the   main quoter method should close
    *          opened resources and terminate.
    */
   private static List<ColumnRange> getColumnRange(String         colRange, 
                                                   BufferedWriter destFile) 
   throws Exception
   {
      List<ColumnRange> columnRanges = null;
      if (colRange != null)
      {
         columnRanges = new ArrayList<ColumnRange>();

         String[] ranges = colRange.split(",");
         for (int i = 0; i < ranges.length; i++)
         {
            String[] columns = ranges[i].split("-");
            if (columns.length == 2)
            {
               int startCol = -1;
               int endCol = -1;
               try
               {
                  startCol = Integer.parseInt(columns[0]);
                  endCol = Integer.parseInt(columns[1]);
               }
               catch (NumberFormatException e)
               {
                  // column range not a number
                  // output error to destination file
                  destFile.write(INVALID_RANGE_ERR + ranges[i]);
                  // Invalid range - Throw exception to mark end
                  throw new Exception();
               }

               // check for invalid column ranges
               if (startCol > endCol)
               {
                  if (destFile != null)
                  {
                     // output error to destination file
                     destFile.write(INVALID_RANGE_ERR + ranges[i]);
                     // Invalid range - Throw exception to mark end
                     throw new Exception();
                  }
               }
               else
               {
                  columnRanges.add(new ColumnRange(startCol, endCol));
               }
            }
         }
      }

      return columnRanges;
   }


   /**
    * Utility method that determines number of consecutive quotation marks
    * 
    * @param   line
    *          The <code>String</code> input line being checked.
    * @param   index
    *          The <code>int</code> location to start checking from.
    *          
    * @return  <code>integer</code> count of consecutive quotations marks.
    */
   private static int quoteCountWorker(String line, int index)
   {
      int  quoteCount = 0;

      while (index < line.length() && line.charAt(index) == '"')
      {
         quoteCount++;
         index++;
      }
      
      return quoteCount;
   }

   /**
    * Utility method that breaks up the tokens in a line by the delimiter
    * 
    * @param   readFile
    *          The input <code>BufferedReader</code> from which the info
    *          will be read.
    * @param   line
    *          The <code>String</code> input line to be broken up.
    * @param   delimiter
    *          The <code>String</code> character that is used to split the
    *          information from a line into columns.
    *          
    * @return  <code>String</code> array of the delimited columns.
    */
   private static String[] delimitWorker(BufferedReader readFile,
                                         String         line,
                                         String         delimiter)
   {
      // Double quotes are used when the delimiter character is within the quoted string
      List<String> result = new LinkedList<>();
      boolean inColumn = false;
      StringBuilder sb = new StringBuilder();
      char delimiterCharacter = delimiter.charAt(0);
      int end = line.length();
      boolean more_quotes = false;   // Indicates we have multiple quotes within a column
      int quotes;               // We use the quoteCountWorker to count the number of consecutive quotes

      try
      {
         do // Outer loop handles columns split across lines
         {
            for (int i = 0; i < end; i++)
            {
               char current = line.charAt(i);
               char next = (i < (end-1)) ? line.charAt(i+1) : '\0';
               boolean newColumn = (sb.length() == 0);
               boolean next_is_quote = (next == '\"');
               
               if (current == '\"')
               {
                  // Quotations marks need to be handled to determine if
                  // we are in a column containing delimiter characters
                  quotes = quoteCountWorker(line, i);
                  if (newColumn)
                  {
                     if (quotes % 2 == 1)
                     {
                        // An odd number of quotes starts inQuote mode
                        inColumn = !inColumn;
                     }
                     else
                     {
                        // We have extra quotations marks to deal with in pairs
                        more_quotes = true;
                        sb.append(current);
                        i++;
                     }
                  }
                  else
                  {
                     // Handle remaining portion of a column being processed
                     if (inColumn)
                     {
                        // Determine if the next character is a quote or not
                        if (next_is_quote)
                        {
                           sb.append(current);
                           i++;
                        }
                        else
                        {
                           // Next character is not a quote, so it we exit the quote section, 
                           // but include conditionally if it is not the delimiter
                           inColumn = !inColumn;
                           if (next != delimiterCharacter)
                           {
                              sb.append(current);
                           }
                        }
                     }
                     else
                     {
                        // We aren't in a quote, include the current quote, but check for extras
                        sb.append(current);
                        if (more_quotes)
                        {
                           i++;
                           more_quotes = false;
                        }
                     }
                  }
               }
               else if (!inColumn && (current == delimiterCharacter))
               {
                  // Snap off the column as we are not longer in a quoted portion of the line
                  result.add(sb.toString());
                  sb = new StringBuilder();
                  inColumn = false;
                  more_quotes = false;
               }
               else
               {
                  // not an escaped char or special case
                  sb.append(current);
               }
               
            }
            
            if (inColumn)
            {
               sb.append(System.getProperty("line.separator"));
               if ((line = readFile.readLine()) == null)
               {
                  inColumn = false;
               }
               else
               {
                  end = line.length();
               }
            }
            
         } while (inColumn);
         
         result.add(sb.toString());
      }
      catch (IOException e)
      {
         // Best effort
      }
      
      return result.toArray(new String[0]);
   }
   
   /**
    * Utility method that handles the logic of quoter utility for column mode.
    * 
    * @param   readFile
    *          The input <code>BufferedReader</code> from which the info
    *          will be read.
    * @param   destFile
    *          The destination <code>BufferedWriter</code> file to which the 
    *          quoted lines will be output.
    * @param   columnRanges
    *          The <code>List</code> of <code>ColumnRange</code> used to 
    *          identify the column from the input line.
    *  
    * @return  <code>true</code> if operation was successful or 
    *          <code>false</code> if the operation failed at some point.
    */
   private static boolean colModeWorker(BufferedReader    readFile,
                                        BufferedWriter    destFile,
                                        List<ColumnRange> columnRanges)
   {
      boolean first   = true;
      
      try
      { 
         String line = null;
         while ((line = readFile.readLine()) != null)
         {
            if (!first)
            {
               destFile.newLine();
            }
            else
            {
               first = false;
            }

            writeColumnsLine(line, columnRanges, destFile);
            destFile.flush();
         }
         destFile.newLine();
         destFile.flush();
      }
      catch (IOException e)
      {
         return false;
      }

      return true;
   }

   /**
    * Utility method that handles the logic of quoter utility for delimiters.
    * 
    * @param   readFile
    *          The input <code>BufferedReader</code> from which the info
    *          will be read.
    * @param   destFile
    *          The destination <code>BufferedWriter</code> file to which the 
    *          quoted lines will be output.
    * @param   delimiter
    *          The <code>String</code> character that is used to split the 
    *          information from a line into columns.
    *  
    * @return  <code>true</code> if operation was successful or 
    *          <code>false</code> if the operation failed at some point.
    */
   private static boolean delModeWorker(BufferedReader    readFile,
                                        BufferedWriter    destFile,
                                        String            delimiter)
   {
      boolean first   = true; 
      try
      { 
         String line = null;
         while ((line = readFile.readLine()) != null)
         {
            if (first)
            {
               first = false;
            }
            else
            {
               destFile.newLine();
            }

            String[] tokens = delimitWorker(readFile, line, delimiter);
            writeDelimitedLine(tokens, destFile);
            destFile.flush();
         }
         destFile.newLine();
         destFile.flush();
      }
      catch (IOException e)
      {
         return false;
      }

      return true;
   }
   
   /**
    * Utility method that handles the logic of quoter utility for neither delimiter nor column mode.
    * 
    * @param   readFile
    *          The input <code>BufferedReader</code> from which the info
    *          will be read.
    * @param   destFile
    *          The destination <code>BufferedWriter</code> file to which the 
    *          quoted lines will be output.
    *  
    * @return  <code>true</code> if operation was successful or 
    *          <code>false</code> if the operation failed at some point.
    */
   private static boolean simModeWorker(BufferedReader    readFile,
                                        BufferedWriter    destFile)
   {
      boolean first   = true; 
      try
      { 
         String line = null;
         while ((line = readFile.readLine()) != null)
         {
            if (!first)
            {
               destFile.newLine();
            }
            else
            {
               first = false;
            }

            destFile.write(quote(line));
            destFile.flush();
         }
         destFile.newLine();
         destFile.flush();
      }
      catch (IOException e)
      {
         return false;
      }

      return true;
   }

   /**
    * Utility method that closes the given stream.
    * 
    * @param stream
    *        The stream to be closed.
    */
   private static void tryClose(Closeable stream)
   {
      try
      {
         stream.close();
      }

      catch (IOException ioe)
      {
         // ignore, this is a best efforts close
      }
   }
   
   /**
    * Utility class to handle column ranges
    */
   private static class ColumnRange
   {
      /**
       * This represents the starting point from where a column will  be
       * subtracted from a <code> String </code> line.
       */
      private int startColumn;
      
      /**
       * This represents the end point to where a column will be subtracted
       * from a <code> String </code> line.
       */
      private int endColumn;

      /**
       * Constructor with 2 parameters. Which will set the start and end index
       * defining a column. 
       * 
       * @param   startColumn
       *          The index point from where the column starts(position 
       *          starts from 1).
       * @param   endColumn
       *          The end index to where the column ends (inclusive).
       */
      public ColumnRange(int startColumn, int endColumn)
      {
         // Java starts from 0 not from 1 
         this.startColumn = startColumn - 1;
         // in Progress endColumn is inclusive so no need to subtract 
         this.endColumn = endColumn;
      }
      
      /**
       * Returns the starting index of the column.
       * 
       * @return   The Java equivalent starting index of the column
       */
      public int getStartColumn()
      {
         return startColumn;
      }

      /**
       * Returns the end index of the column.
       * @return   The Java equivalent end index of the column.
       */
      public int getEndColumn()
      {
         return endColumn;
      }
   }
}