This book genuinely helped me get a job

This is my review of Mark Andersen’s I Got The Job! 8 Steps to Get Hired, as posted on Amazon.


A person I trust, with many years experience in my field (and with a much better view of the “big picture”) urged me to get Mark Andersen’s book. It is not an exaggeration to say that I Got the Job! deserves a significant amount of credit for my receiving an offer last week.

The major things this book taught me are:

– My personal “zone”: Although I didn’t gel with the Don Draper mindset specifically, reading carefully through that chapter made me think of something that did work for me: I’m interviewing the company as much as they’re interviewing me. I came up with a list of questions that I genuinely want to know about the company, that will really affect my day-to-day life, and had them at the ready. (Like: What standards do you follow? What tools are people in my position required to use?)

The interview is not an interrogation. They’re not talking to you because they want to determine all of your flaws–they WANT to hire you. They want to find a good candidate just as much as you want to find a good job! So interview them!

Coming up with my personal zone was the most important thing that helped me feel confident during the interview.

– Chapter 16, regarding the cover letter and resume, was the most impactful for me. The hardest part starting my job search was figuring out how to tell my unique story in a truthful, yet conducive-to-getting-a-job kind of way. I worked for five years after college, was laid off along with many of my peers, couldn’t find work for a few years after that, and ended up being a stay-at-home dad. Over the past three years I’ve re-trained myself in the latest and greatest, and now my youngest is entering kindergarten.

I decided to use the summary statement at the top of my resume as the primary way of telling my story. The problem was that, in its original form, it was spending more time expressing my enthusiasm for returning to the field, instead of “owning my expertise” and listing off all the things that I’ve been doing during my past three years of self-training. What started off as almost apologetic, turned out to be an impressive statement of what I am able to handle, all while taking care of two little boys.

Although I obviously cannot point to recent professional experience, my blog and some downloadable resources that I’ve prepared, now made it easy to point to many specific accomplishments.

– How to research an appropriate salary, by using resources such as glassdoor.com, salary.com, and job boards like dice.com. What is especially important for someone like me, who’s been out of the market for so long, is I need to be careful to not give too low of a salary requirement, to avoid awkwardness when I do indeed get myself back up to speed.

I simply never would’ve thought of these things had I not read I Got the Job!, and I’m reasonably certain that I would not have gotten the job.

A great book, and a steal at five dollars for the Kindle version. Highly recommended.

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;
   }
}