Interview preparation: Cheat sheet, and my first multi-threaded application (with timing of how long each version took)

Cheat sheet!

Front:

Back:

My first multi-threaded application: Original single-threaded version

This duplicates a directory structure, where the tab indentation in every text file is replaced with spaces. First, the single-threaded version (this is the same function as in my XBN-Java library, extracted so it has no dependencies on any other part of the library…aside from the timing class).

This took 2,764,557,795 nanoseconds to execute. 2.7 seconds.

package multithreading_tabs_to_spaces;
   import  com.github.xbn.testdev.TimedTest;
   import java.io.BufferedWriter;
   import java.io.Console;
   import java.io.File;
   import java.io.FileWriter;
   import java.io.IOException;
   import java.io.PrintWriter;
   import java.text.DecimalFormat;
   import java.util.Iterator;
   import java.util.Objects;
   import org.apache.commons.io.FileUtils;
   import org.slf4j.Logger;
   import org.slf4j.LoggerFactory;
/**
 * <code>java multithreading_tabs_to_spaces.IndentTabsToSpaces_NonMultiThreaded 0 C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\xbnjava_my_private_sandbox_with_tab_indentation\</code>
 *
 * <code>java -classpath .;C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\jar_dependencies\commons-io-2.4.jar;C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\jar_dependencies\slf4j-api-1.7.12.jar;C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\jar_dependencies\slf4j-simple-1.7.12.jar multithreading_tabs_to_spaces.IndentTabsToSpaces_NonMultiThreaded 5 C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\xbnjava_my_private_sandbox_with_tab_indentation</code>
 */
public class IndentTabsToSpaces_NonMultiThreaded  {
   private static final String FILE_SEP = System.getProperty("file.separator", "\\");
   private static final String PARAM_REQUIREMENTS = "Two required parameters: " +
      "Number of y/n overwrite prompts, and the path of the directory containing " +
      "the files to overwrite (must *not* end with a " + FILE_SEP + ".";
   public static final void main(String[] cmd_lineParams) {
      int areYouSureOverwritePromptCount;
      String sourceDirPreDashSrcDst;
      try {
         areYouSureOverwritePromptCount = Integer.parseInt(cmd_lineParams[0]);
         //itr = FileUtils.iterateFiles(new File(cmd_lineParams[1]), FileFilterUtils.trueFileFilter(), null);
         sourceDirPreDashSrcDst = cmd_lineParams[1];
      } catch(ArrayIndexOutOfBoundsException x) {
         throw new RuntimeException(PARAM_REQUIREMENTS);
      } catch(NumberFormatException x)  {
         throw new RuntimeException("Parameter one must be an integer. " +
            PARAM_REQUIREMENTS);
      }

      String sourceDir = sourceDirPreDashSrcDst + "-source" + FILE_SEP;
      Iterator<File> itr = FileUtils.iterateFiles(new File(sourceDir),
         new String[] {"xml", "java", "txt", "bat", "md", "log", "javax"}, true);

      TimedTest singleThreadedTest = new TimedTest("single-threaded");

      singleThreadedTest.declareStartWithOutput();
      try {
         new TabToSpaceIndenter().replaceAllInDirectory(
            areYouSureOverwritePromptCount, itr,
            sourceDir,
            sourceDirPreDashSrcDst + "-destination" + FILE_SEP);
      } catch(IOException x) {
         throw new RuntimeException(x);
      }
      singleThreadedTest.declareEndWithOutput();
   }
}
class TabToSpaceIndenter {
   //State:
      private final String rplcmntSpaces;
      private final Logger logger = LoggerFactory.getLogger("TabToSpaceIndenter");
   //Constants:
      private static final DecimalFormat DEC_FMT = new DecimalFormat("#.###");
      private static final String LINE_SEP = System.getProperty("line.separator", "\r\n");
      private static final boolean OVERWRITE = false;
      private static final boolean MANUAL_FLUSH = false;
      /**
       * The default value to replace each tab with--equal to three spaces.
       * @see #getReplacementSpaces()
       */
      public static final String SPACES_TO_RPLC_WITH_DEFAULT = "   ";
   /**
    * <p>Create a new instance with the default spaces-replacement.</p>
    *
    * <p>Equal to
    * <br> &nbsp; &nbsp; <code>{@link #TabToSpaceIndenter(String) this}(SPACES_TO_RPLC_WITH_DEFAULT)</code></p>
    */
   public TabToSpaceIndenter() {
      this(SPACES_TO_RPLC_WITH_DEFAULT);
   }
   /**
    * Create a new instance.
    * @param   spaces_toRplcWith  The spaces to replace each tab with. May
    * not be {@code null} or empty. Get with
    * {@link #getReplacementSpaces() getReplacementSpaces}{@code ()}.
    * @see #TabToSpaceIndenter()
    */
   public TabToSpaceIndenter(String spaces_toRplcWith) {
      try {
         if(spaces_toRplcWith.length() == 0) {
            throw new IllegalArgumentException("spaces_toRplcWith has no characters.");
         }
      } catch(NullPointerException x) {
         Objects.requireNonNull(spaces_toRplcWith, "spaces_toRplcWith");
         throw x;
      }
      rplcmntSpaces = spaces_toRplcWith;
   }
   /**
    * The value to replace each tab with.
    * @see #TabToSpaceIndenter(String)
    * @see #SPACES_TO_RPLC_WITH_DEFAULT
    */
   public String getReplacementSpaces() {
      return rplcmntSpaces;
   }
   /**
    * <p>Utility function to replace all indentation tabs for all files in
    * a directory--<i><b>this overwrites all files!</b></i></p>
    *
    * @param areYouSure_overwritePrompts If greater than zero, this is the
    * number of times the user is presented with a prompt to confirm
    * overwriting. If five, for instance, the <i>first</i> five files are
    * not overwritten until the user confirms each (by answering with a
    * <code>'Y'</code> or <code>'y'</code>. Anything else aborts the
    * application.
    * @param file_itr May not be {@code null}. <i>Every</i> returned file
    * in this iterator is expected to be a plain-text file (so filter it as
    * necessary), and both readable and writable.
    * <i>Should</i> be non-{@code null} and non-empty.
    * @param source_baseDir The directory base-path in which files are read
    * from. This is what is replaced by <code>dest_baseDir</code>. May not
    * be {@code null} and must begin the path for every file returned by
    * <code>file_itr</code>.
    * @param dest_baseDir The directory base-path that output is written
    * to. This is what <code>source_baseDir</code> is replaced by. May not
    * be {@code null} or empty.
    * @see #getReplacementSpaces()
    * @see  org.apache.commons.io.FileUtils#iterateFiles(File, IOFileFilter, IOFileFilter) commons.io.FileUtils#iterateFiles
    */
   public void replaceAllInDirectory(int areYouSure_overwritePrompts,
            Iterator<File> file_itr, String source_baseDir, String dest_baseDir)
            throws IOException  {
      logger.info("Source dir:      " + source_baseDir);
      logger.info("Destination dir: " + dest_baseDir);

      Objects.requireNonNull(file_itr, "file_itr");

      int fileCount = 0;        //How many total files were analyzed (and
                                //potentially changed)?
      int totalTabsRplcd = 0;   //How many tabs were replaced in *all*
      int aysPromptsGiven = 0;  //files?

      while(file_itr.hasNext()) {
         File f = file_itr.next();
         fileCount++;

         String sourcePath = f.getAbsolutePath();
         if(!sourcePath.startsWith(source_baseDir))  {
            throw new IllegalArgumentException("sourcePath (" + sourcePath +
               ") does not start with source_baseDir (" + source_baseDir + ").");
         }
         String destPath = dest_baseDir + sourcePath.substring(source_baseDir.length());

         if(++aysPromptsGiven < areYouSure_overwritePrompts)  {
            String promptText = "[" + aysPromptsGiven + "/" +
               areYouSure_overwritePrompts + "] About to overwrite" + LINE_SEP +
               "   " + destPath + LINE_SEP + "Are you sure? 'Y' or 'y' to " +
               "proceed. Anything else to abort: ";

            Console console = System.console();
            Objects.requireNonNull(console, "System.console() (This error is " +
               "expected when you run this in Eclipse. This works when " +
               "executing it directly on the console. If you set the first " +
               "parameter to zero, you can safely run it in Eclipse.)");
            String input = console.readLine(promptText);
            if(!input.toLowerCase().equals("y"))  {
               System.out.println("Aborting.");
               return;
            }
         }

         //Replace the tabs in this file, and get the number of tabs
         //actually replaced.
         totalTabsRplcd += replaceAllInFile(f, destPath);
      }

      //Print summary to console.
      String avgTabsPerFileStr = DEC_FMT.format(totalTabsRplcd /
                                                new Double(fileCount));
      logger.info("Done. {} total tabs replaced in {} total files ({}/file)",
         totalTabsRplcd, fileCount, avgTabsPerFileStr, logger);
   }
   public int replaceAllInFile(File source_file, String dest_path) {
      try {
         logger.trace("Getting input line iterator to {}." + source_file.getAbsolutePath());
      } catch(NullPointerException x) {
         Objects.requireNonNull(source_file, "source_file");
         Objects.requireNonNull(logger, "logger");
         throw x;
      }

      Iterator<String> lineItrInput = null;
      try  {
         lineItrInput = FileUtils.lineIterator(source_file);
      } catch(Exception x) {
         throw new RuntimeException(
            "Attempting to obtain line iterator for \"" + source_file.getAbsolutePath() +
            "\"", x);
      }

      logger.debug("Creating output print writer to dest_path (\"{}\").", dest_path);
      PrintWriter writerOut = null;
      try  {
         writerOut = (new PrintWriter(new BufferedWriter(
            new FileWriter(dest_path, OVERWRITE)), MANUAL_FLUSH));
      } catch(Exception x) {
         throw new RuntimeException("Attempting to create a print writer to \"" +
            dest_path + "\"", x);
      }

      int totalLines = 0;     //How many lines in this file?
      int tabsRplcd = 0;      //How many tabs total in *all* lines?

      try  {
         while(lineItrInput.hasNext()) {
            String line = lineItrInput.next();
            totalLines++;

            if(line.length() == 0) {
               //No text at all, so definitely no tabs.
               writerOut.write(LINE_SEP);
               logger.trace("{} No characters. Writing out LINE_SEP only.", totalLines);
               continue;
            }

            //At least some text.

            int charIdx = 0;

            //Starting at the left-most character, while it's a tab,
            //replace it with spaces.
            while(charIdx < line.length() && line.charAt(charIdx) == '\t') {
               //There *is* another character in the line, and it *is* a
               //tab.
               charIdx++;
               writerOut.write(getReplacementSpaces());
               tabsRplcd++;
               logger.trace("{}. {} tabs replaced.", totalLines, tabsRplcd);
            }

            //No more tabs. Append the rest of the line.

            writerOut.write(line.substring(charIdx));
            writerOut.write(LINE_SEP);
            logger.trace("{}. No more tabs in this file. Appending the rest of the line.", totalLines);
         }
      } catch(Exception x) {
         throw new RuntimeException("source_file=" + source_file.getAbsolutePath() + ", dest_path=" + dest_path + "");

      } finally {
         writerOut.flush();
         writerOut.close();
         logger.trace("{}. Writer flushed and closed.", totalLines);
      }


      String avgTabsPerLineStr = DEC_FMT.format(tabsRplcd / new Double(totalLines));
      logger.debug("This file: {} lines, {} tabs, {} average per line", totalLines, tabsRplcd, avgTabsPerLineStr);

      return tabsRplcd;
   }
}

My first multi-threaded application: The same class, changed to multi-threading

This uses one of a number of executors (and how long it took):

  • Single threaded (2,247,164,905 nanoseconds, 2.2 seconds)
  • Fixed thread pool with 2 (958,872,242), 5 (731,709,280), 10 (699,200,429), and 20 threads (704,502,249),
  • Cached thread pool (765,272,723)

So the ten-to-twenty-thread thread pool is the clear winner (the above, original single-threaded version took 2.7 seconds).

package multithreading_tabs_to_spaces;
   import java.io.BufferedWriter;
   import java.io.Console;
   import java.io.File;
   import java.io.FileWriter;
   import java.io.IOException;
   import java.io.PrintWriter;
   import java.text.DecimalFormat;
   import java.util.ArrayList;
   import java.util.concurrent.Callable;
   import java.util.concurrent.ExecutionException;
   import java.util.concurrent.Executors;
   import java.util.concurrent.ExecutorService;
   import java.util.concurrent.Future;
   import java.util.Iterator;
   import java.util.List;
   import java.util.Objects;
   import org.apache.commons.io.FileUtils;
   import org.slf4j.Logger;
   import org.slf4j.LoggerFactory;
   import com.github.xbn.testdev.TimedTest;
/**
 * java multithreading_tabs_to_spaces.IndentTabsToSpaces_NonMultiThreaded 0 C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\xbnjava_my_private_sandbox_with_tab_indentation\
 *
 * java -classpath C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\bin\;C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\jar_dependencies\commons-io-2.4.jar;C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\jar_dependencies\slf4j-api-1.7.12.jar;C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\jar_dependencies\slf4j-simple-1.7.12.jar multithreading_tabs_to_spaces.IndentTabsToSpaces_MultiThreaded 5 C:\data_jeffy\code\wordpress_posts\java\multithreading_tabs_to_spaces\xbnjava_my_private_sandbox_with_tab_indentation
 */
public class IndentTabsToSpaces_MultiThreaded  {
   private static final String FILE_SEP = System.getProperty("file.separator", "\\");
   private static final String PARAM_REQUIREMENTS = "Two required parameters: " +
      "Number of y/n overwrite prompts, and the path of the directory containing " +
      "the files to overwrite (must *not* end with a " + FILE_SEP + ".";
   public static final void main(String[] cmd_lineParams) {
      int areYouSureOverwritePromptCount;
      String sourceDirPreDashSrcDst;
      try {
         areYouSureOverwritePromptCount = Integer.parseInt(cmd_lineParams[0]);
         //itr = FileUtils.iterateFiles(new File(cmd_lineParams[1]), FileFilterUtils.trueFileFilter(), null);
         sourceDirPreDashSrcDst = cmd_lineParams[1];
      } catch(ArrayIndexOutOfBoundsException x) {
         throw new RuntimeException(PARAM_REQUIREMENTS);
      } catch(NumberFormatException x)  {
         throw new RuntimeException("Parameter one must be an integer. " +
            PARAM_REQUIREMENTS);
      }

      String sourceDir = sourceDirPreDashSrcDst + "-source" + FILE_SEP;
      String destDir = sourceDirPreDashSrcDst + "-destination" + FILE_SEP;
      Iterator<File> itr = FileUtils.iterateFiles(new File(sourceDir),
         new String[] {"xml", "java", "txt", "bat", "md", "log", "javax"}, true);

      TimedTest singleThreadedExecutorTest = new TimedTest("single-threaded executor");

      singleThreadedExecutorTest.declareStartWithOutput();
      replaceAllInDir(itr, sourceDir, destDir, areYouSureOverwritePromptCount,
         Executors.newSingleThreadExecutor());
      singleThreadedExecutorTest.declareEndWithOutput();

      // pauseOneSecond();

      // TimedTest twoThreadFixedPoolTest = new TimedTest("2 thread fixed pool");
      // twoThreadFixedPoolTest.declareStartWithOutput();
      // replaceAllInDir(itr, sourceDir, destDir, areYouSureOverwritePromptCount,
      //    Executors.newFixedThreadPool(2));
      // twoThreadFixedPoolTest.declareEndWithOutput();

      // pauseOneSecond();

      // TimedTest fiveThreadFixedPoolTest = new TimedTest("5 thread fixed pool");
      // fiveThreadFixedPoolTest.declareStartWithOutput();
      // replaceAllInDir(itr, sourceDir, destDir, areYouSureOverwritePromptCount,
      //    Executors.newFixedThreadPool(5));
      // fiveThreadFixedPoolTest.declareEndWithOutput();

      // pauseOneSecond();

      // TimedTest tenThreadFixedPoolTest = new TimedTest("10 thread fixed pool");
      // tenThreadFixedPoolTest.declareStartWithOutput();
      // replaceAllInDir(itr, sourceDir, destDir, areYouSureOverwritePromptCount,
      //    Executors.newFixedThreadPool(10));
      // tenThreadFixedPoolTest.declareEndWithOutput();

      // pauseOneSecond();

      // TimedTest tenThreadFixedPoolTest = new TimedTest("10 thread fixed pool");
      // tenThreadFixedPoolTest.declareStartWithOutput();
      // replaceAllInDir(itr, sourceDir, destDir, areYouSureOverwritePromptCount,
      //    Executors.newFixedThreadPool(20));
      // tenThreadFixedPoolTest.declareEndWithOutput();

      // pauseOneSecond();

      // TimedTest cachedPoolTest = new TimedTest("cached pool");
      // cachedPoolTest.declareStartWithOutput();
      // replaceAllInDir(itr, sourceDir, destDir, areYouSureOverwritePromptCount,
      //    Executors.newCachedThreadPool());
      // cachedPoolTest.declareEndWithOutput();
   }
   private static final void pauseOneSecond()  {
      try {
          Thread.sleep(1000);
      } catch(InterruptedException ex) {
          Thread.currentThread().interrupt();
      }
   }
   private static final void replaceAllInDir(Iterator<File> itr, String source_dir, String dest_dir, int ays_promptCount, ExecutorService exec_svc)  {
      try {
         new TabToSpaceIndenterMT().replaceAllInDirectory(ays_promptCount, itr,
            source_dir, dest_dir,
            exec_svc);
      } catch(IOException | InterruptedException x) {
         throw new RuntimeException(x);
      }
   }
}
class TabToSpaceIndenterMT {
   //State:
      private final String rplcmntSpaces;
      private final Logger logger = LoggerFactory.getLogger("TabToSpaceIndenterMT");
   //Constants:
      private static final String LINE_SEP = System.getProperty("line.separator", "\r\n");
      /**
       * The default value to replace each tab with--equal to three spaces.
       * @see #getReplacementSpaces()
       */
      public static final String SPACES_TO_RPLC_WITH_DEFAULT = "   ";
   /**
    * <p>Create a new instance with the default spaces-replacement.</p>
    *
    * <p>Equal to
    * <br> &nbsp; &nbsp; <code>{@link #TabToSpaceIndenterMT(String) this}(SPACES_TO_RPLC_WITH_DEFAULT)</code></p>
    */
   public TabToSpaceIndenterMT() {
      this(SPACES_TO_RPLC_WITH_DEFAULT);
   }
   /**
    * Create a new instance.
    * @param   spaces_toRplcWith  The spaces to replace each tab with. May
    * not be {@code null} or empty. Get with
    * {@link #getReplacementSpaces() getReplacementSpaces}{@code ()}.
    * @see #TabToSpaceIndenterMT()
    */
   public TabToSpaceIndenterMT(String spaces_toRplcWith) {
      try {
         if(spaces_toRplcWith.length() == 0) {
            throw new IllegalArgumentException("spaces_toRplcWith has no characters.");
         }
      } catch(NullPointerException x) {
         Objects.requireNonNull(spaces_toRplcWith, "spaces_toRplcWith");
         throw x;
      }
      rplcmntSpaces = spaces_toRplcWith;
   }
   /**
    * The value to replace each tab with.
    * @see #TabToSpaceIndenterMT(String)
    * @see #SPACES_TO_RPLC_WITH_DEFAULT
    */
   public String getReplacementSpaces() {
      return rplcmntSpaces;
   }
   /**
    * <p>Utility function to replace all indentation tabs for all files in
    * a directory--<i><b>this overwrites all files!</b></i></p>
    *
    * <p>This function prints both <code>INFO</code> and <code>DEBUG</code>
    * level information, via SLF4J.</p>
    *
    * @param areYouSure_overwritePrompts If greater than zero, this is the
    * number of times the user is presented with a prompt to confirm
    * overwriting. If five, for instance, the <i>first</i> five files are
    * not overwritten until the user confirms each (by answering with a
    * <code>'Y'</code> or <code>'y'</code>. Anything else aborts the
    * application.
    * @param file_itr May not be {@code null}. <i>Every</i> returned file
    * in this iterator is expected to be a plain-text file (so filter it as
    * necessary), and both readable and writable.
    * <i>Should</i> be non-{@code null} and non-empty.
    * @param source_baseDir The directory base-path in which files are read
    * from. This is what is replaced by <code>dest_baseDir</code>. May not
    * be {@code null} and must begin the path for every file returned by
    * <code>file_itr</code>.
    * @param dest_baseDir The directory base-path that output is written
    * to. This is what <code>source_baseDir</code> is replaced by. May not
    * be {@code null} or empty.
    * <br>is {@code true}.
    * @see #getReplacementSpaces()
    * @see  org.apache.commons.io.FileUtils#iterateFiles(File, IOFileFilter, IOFileFilter) commons.io.FileUtils#iterateFiles
    */
   public void replaceAllInDirectory(int areYouSure_overwritePrompts,
            Iterator<File> file_itr, String source_baseDir, String dest_baseDir,
            ExecutorService exec_svc) throws IOException, InterruptedException  {
      logger.info("Source dir:      " + source_baseDir);
      logger.info("Destination dir: " + dest_baseDir);

      Objects.requireNonNull(file_itr, "file_itr");

      int fileCount = 0;        //How many total files were analyzed (and
                                //potentially changed)?
      int totalTabsRplcd = 0;   //How many tabs were replaced in *all*
      int aysPromptsGiven = 0;  //files?
      List<Future<Integer>> futureTabsRplcdList = new ArrayList<>(2000);

      while(file_itr.hasNext()) {
         if(Thread.currentThread().isInterrupted())  {
            break;
         }

         File f = file_itr.next();
         fileCount++;

         String sourcePath = f.getAbsolutePath();
         if(!sourcePath.startsWith(source_baseDir))  {
            throw new IllegalArgumentException("sourcePath (" + sourcePath +
               ") does not start with source_baseDir (" + source_baseDir + ").");
         }
         String destPath = dest_baseDir + sourcePath.substring(source_baseDir.length());

         if(++aysPromptsGiven <= areYouSure_overwritePrompts)  {
            String promptText = "[" + aysPromptsGiven + "/" +
               areYouSure_overwritePrompts + "] About to overwrite" + LINE_SEP +
               "   " + destPath + LINE_SEP + "Are you sure? 'Y' or 'y' to " +
               "proceed. Anything else to abort: ";

            Console console = System.console();
            Objects.requireNonNull(console, "System.console() (This error is " +
               "expected when you run this in Eclipse. This works when " +
               "executing it directly on the console. If you set the first " +
               "parameter to zero, you can safely run it in Eclipse.)");
            String input = console.readLine(promptText);
            if(!input.toLowerCase().equals("y"))  {
               System.out.println("Aborting.");
               return;
            }
         }

         //Replace the tabs in this file, and get the number of tabs
         //actually replaced.
         Callable<Integer> rplcAllInFile = new ReplaceAllTabsInOneFile(f, destPath, getReplacementSpaces(), logger);
         Future<Integer> fint = null;
         try {
            fint = exec_svc.submit(rplcAllInFile);
         } catch(NullPointerException npx) {
            throw new NullPointerException("exec_svc");
         }

         //Can't get the result here! Well, you *can*, but then you block until
         //it's ready, on each iteration.
         futureTabsRplcdList.add(fint);
      }
      try {
         for(Future<Integer> fint: futureTabsRplcdList) {
            totalTabsRplcd += fint.get();
         }
      } catch(InterruptedException | ExecutionException x) {
         throw new RuntimeException("Attempting to get the result from the ReplaceAllTabsInOneFile future: " + x);
      }

      //Print summary to console.
      String avgTabsPerFileStr = new DecimalFormat("#.###").format(totalTabsRplcd /
                                                new Double(fileCount));
      logger.info("Done. {} total tabs replaced in {} total files ({}/file)",
         totalTabsRplcd, fileCount, avgTabsPerFileStr, logger);

      if(Thread.currentThread().isInterrupted())  {
         throw new InterruptedException("Interrupted. INFO-level logged summary " +
            "is only for the " + fileCount + " files processed so far.");
      }
   }
}
class ReplaceAllTabsInOneFile implements Callable<Integer> {
   private static final String LINE_SEP = System.getProperty("line.separator", "\r\n");
   private static final boolean OVERWRITE = false;
   private static final boolean MANUAL_FLUSH = false;
   private static final DecimalFormat DEC_FMT = new DecimalFormat("#.###");
   private final File srcFile;
   private final String destPath;
   private final String spaces;
   private final Logger logger;
   public ReplaceAllTabsInOneFile(File source_file, String dest_path, String spcs_toRplcWith, Logger logger)  {
      srcFile = source_file;
      destPath = dest_path;
      spaces = spcs_toRplcWith;
      this.logger = logger;
   }
   public Integer call() {
      try {
         logger.trace("Getting input line iterator to {}." + srcFile.getAbsolutePath());
      } catch(NullPointerException x) {
         Objects.requireNonNull(srcFile, "source_file");
         Objects.requireNonNull(logger, "logger");
         throw x;
      }

      logger.trace("Getting input line iterator to {}." + srcFile.getAbsolutePath());
      Iterator<String> lineItrInput = null;
      try  {
         lineItrInput = FileUtils.lineIterator(srcFile);
      } catch(Exception x) {
         throw new RuntimeException(
            "Attempting to obtain line iterator for \"" + srcFile.getAbsolutePath() +
            "\"", x);
      }

      logger.debug("Creating output print writer to dest_path (\"{}\").", destPath);
      PrintWriter writerOut = null;
      try  {
         writerOut = (new PrintWriter(new BufferedWriter(
            new FileWriter(destPath, OVERWRITE)), MANUAL_FLUSH));
      } catch(Exception x) {
         throw new RuntimeException("Attempting to create a print writer to \"" +
            destPath + "\"", x);
      }

      int totalLines = 0;     //How many lines in this file?
      int tabsRplcd = 0;      //How many tabs total in *all* lines?

      try  {
         while(lineItrInput.hasNext()) {
            String line = lineItrInput.next();
            totalLines++;

            if(line.length() == 0) {
               //No text at all, so definitely no tabs.
               writerOut.write(LINE_SEP);
               logger.trace("{} No characters. Writing out LINE_SEP only.", totalLines);
               continue;
            }

            //At least some text.

            int charIdx = 0;

            //Starting at the left-most character, while it's a tab,
            //replace it with spaces.
            while(charIdx < line.length() && line.charAt(charIdx) == '\t') {
               //There *is* another character in the line, and it *is* a
               //tab.
               charIdx++;
               writerOut.write(spaces);
               tabsRplcd++;
               logger.trace("{}. {} tabs replaced.", totalLines, tabsRplcd);
            }

            //No more tabs. Append the rest of the line.

            writerOut.write(line.substring(charIdx));
            writerOut.write(LINE_SEP);
            logger.trace("{}. No more tabs in this file. Appending the rest of the line.", totalLines);
         }
      } catch(Exception x) {
         throw new RuntimeException("srcFile=" + srcFile.getAbsolutePath() + ", destPath=" + destPath + "");

      } finally {
         writerOut.flush();
         writerOut.close();
         logger.trace("{}. Writer flushed and closed.", totalLines);
      }

      String avgTabsPerLineStr = DEC_FMT.format(tabsRplcd / new Double(totalLines));
      logger.debug("This file: {} lines, {} tabs, {} average per line", totalLines, tabsRplcd, avgTabsPerLineStr);

      return tabsRplcd;
   }
}
Advertisements

One thought on “Interview preparation: Cheat sheet, and my first multi-threaded application (with timing of how long each version took)

  1. Pingback: Interview preparation: Cheat sheet, and my first multi-threaded application (with timing of how long each version took) | Dinesh Ram Kali.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s