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