CommandLineRunner can now handle URIs again; refactored some duplicated code
diff --git a/javase/src/main/java/com/google/zxing/client/j2se/CommandLineRunner.java b/javase/src/main/java/com/google/zxing/client/j2se/CommandLineRunner.java
index 75e555d..d2fa2f0 100644
--- a/javase/src/main/java/com/google/zxing/client/j2se/CommandLineRunner.java
+++ b/javase/src/main/java/com/google/zxing/client/j2se/CommandLineRunner.java
@@ -20,6 +20,7 @@
 import com.google.zxing.DecodeHintType;
 
 import java.io.IOException;
+import java.net.URI;
 import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -27,7 +28,6 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.EnumMap;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -43,6 +43,7 @@
  * directories, summary statistics are also displayed.
  *
  * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
  */
 public final class CommandLineRunner {
 
@@ -58,7 +59,7 @@
     }
 
     Config config = new Config();
-    Queue<Path> inputs = new ConcurrentLinkedQueue<>();
+    Queue<URI> inputs = new ConcurrentLinkedQueue<>();
 
     for (String arg : args) {
       String[] argValue = arg.split("=");
@@ -104,13 +105,25 @@
             printUsage();
             return;
           }
-          addArgumentToInputs(Paths.get(arg), config, inputs);
+          URI argURI = URI.create(arg);
+          if (argURI.getScheme() == null) {
+            argURI = new URI("file", argURI.getSchemeSpecificPart(), argURI.getFragment());
+          }
+          addArgumentToInputs(argURI, config, inputs);
           break;
       }
     }
+
+    int numInputs = inputs.size();
+    if (numInputs == 0) {
+      System.err.println("No inputs specified");
+      printUsage();
+      return;
+    }
+
     config.setHints(buildHints(config));
 
-    int numThreads = Math.min(inputs.size(), Runtime.getRuntime().availableProcessors());
+    int numThreads = Math.min(numInputs, Runtime.getRuntime().availableProcessors());
     int successful = 0;    
     if (numThreads > 1) {
       ExecutorService executor = Executors.newFixedThreadPool(numThreads);
@@ -126,44 +139,38 @@
       successful += new DecodeWorker(config, inputs).call();
     }
 
-    int total = inputs.size();
-    if (total > 1) {
-      System.out.println("\nDecoded " + successful + " files out of " + total +
-          " successfully (" + (successful * 100 / total) + "%)\n");
+    if (numInputs > 1) {
+      System.out.println("\nDecoded " + successful + " files out of " + numInputs +
+          " successfully (" + (successful * 100 / numInputs) + "%)\n");
     }
   }
 
-  // Build all the inputs up front into a single flat list, so the threads can atomically pull
-  // paths/URLs off the queue.
-  private static void addArgumentToInputs(Path inputFile, Config config, Queue<Path> inputs) throws IOException {
-    if (Files.isDirectory(inputFile)) {
-      try (DirectoryStream<Path> paths = Files.newDirectoryStream(inputFile)) {
-        for (Path singleFile : paths) {
-          String filename = singleFile.getFileName().toString().toLowerCase(Locale.ENGLISH);
+  /**
+   * Build all the inputs up front into a single flat list, so the threads can atomically pull
+   * paths/URLs off the queue.
+   */
+  private static void addArgumentToInputs(URI input, Config config, Queue<URI> inputs) throws IOException {
+    // Special case: a local directory
+    if ("file".equals(input.getScheme()) && Files.isDirectory(Paths.get(input))) {
+      try (DirectoryStream<Path> childPaths = Files.newDirectoryStream(Paths.get(input))) {
+        for (Path childPath : childPaths) {
+          Path realChildPath = childPath.toRealPath();
           // Skip hidden files and directories (e.g. svn stuff).
-          if (filename.startsWith(".")) {
-            continue;
-          }
-          // Recur on nested directories if requested, otherwise skip them.
-          if (Files.isDirectory(singleFile)) {
-            if (config.isRecursive()) {
-              addArgumentToInputs(singleFile, config, inputs);
+          if (!realChildPath.getFileName().toString().startsWith(".")) {
+            // Recur on nested directories if requested, otherwise skip them.
+            if (config.isRecursive() && Files.isDirectory(realChildPath)) {
+              addArgumentToInputs(realChildPath.toUri(), config, inputs);
+            } else {
+              inputs.add(realChildPath.toUri());
             }
-            continue;
           }
-          // Skip text files and the results of dumping the black point.
-          if (filename.endsWith(".txt") || filename.contains(".mono.png")) {
-            continue;
-          }
-          inputs.add(singleFile);
         }
       }
     } else {
-      inputs.add(inputFile);
+      inputs.add(input);
     }
   }
 
-  // Manually turn on all formats, even those not yet considered production quality.
   private static Map<DecodeHintType,?> buildHints(Config config) {
     Collection<BarcodeFormat> possibleFormats = new ArrayList<>();
     String[] possibleFormatsNames = config.getPossibleFormats();
diff --git a/javase/src/main/java/com/google/zxing/client/j2se/DecodeWorker.java b/javase/src/main/java/com/google/zxing/client/j2se/DecodeWorker.java
index 5275c8b..5146da6 100644
--- a/javase/src/main/java/com/google/zxing/client/j2se/DecodeWorker.java
+++ b/javase/src/main/java/com/google/zxing/client/j2se/DecodeWorker.java
@@ -42,7 +42,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.Callable;
@@ -59,9 +58,9 @@
   private static final int WHITE = 0xFFFFFFFF;
 
   private final Config config;
-  private final Queue<Path> inputs;
+  private final Queue<URI> inputs;
 
-  DecodeWorker(Config config, Queue<Path> inputs) {
+  DecodeWorker(Config config, Queue<URI> inputs) {
     this.config = config;
     this.inputs = inputs;
   }
@@ -69,139 +68,101 @@
   @Override
   public Integer call() throws IOException {
     int successful = 0;
-    Path input;
-    while ((input = inputs.poll()) != null) {
-      if (Files.exists(input)) {
-        if (config.isMulti()) {
-          Result[] results = decodeMulti(input.toUri(), config.getHints());
-          if (results != null) {
-            successful++;
-            if (config.isDumpResults()) {
-              dumpResultMulti(input, results);
-            }
-          }
-        } else {
-          Result result = decode(input.toUri(), config.getHints());
-          if (result != null) {
-            successful++;
-            if (config.isDumpResults()) {
-              dumpResult(input, result);
-            }
-          }
-        }
-      } else {
-        if (decode(input.toUri(), config.getHints()) != null) {
-          successful++;
+    for (URI input; (input = inputs.poll()) != null;) {
+      Result[] results = decode(input, config.getHints());
+      if (results != null) {
+        successful++;
+        if (config.isDumpResults()) {
+          dumpResult(input, results);
         }
       }
     }
     return successful;
   }
 
-  private static void dumpResult(Path input, Result result) throws IOException {
-    String name = input.getFileName().toString();
-    int pos = name.lastIndexOf('.');
-    if (pos > 0) {
-      name = name.substring(0, pos) + ".txt";
+  private static Path buildOutputPath(URI input, String suffix) throws IOException {
+    Path outDir;
+    String inputFileName;
+    if ("file".equals(input.getScheme())) {
+      Path inputPath = Paths.get(input);
+      outDir = inputPath.getParent();
+      inputFileName = inputPath.getFileName().toString();
+    } else {
+      outDir = Paths.get(".").toRealPath();
+      String[] pathElements = input.getPath().split("/");
+      inputFileName = pathElements[pathElements.length - 1];
     }
-    Path dumpFile = input.getParent().resolve(name);
-    Files.write(dumpFile, Collections.singleton(result.getText()), StandardCharsets.UTF_8);
+
+    // Replace/add extension
+    int pos = inputFileName.lastIndexOf('.');
+    if (pos > 0) {
+      inputFileName = inputFileName.substring(0, pos) + suffix;
+    } else {
+      inputFileName += suffix;
+    }
+
+    return outDir.resolve(inputFileName);
   }
 
-  private static void dumpResultMulti(Path input, Result[] results) throws IOException {
-    String name = input.getFileName().toString();
-    int pos = name.lastIndexOf('.');
-    if (pos > 0) {
-      name = name.substring(0, pos) + ".txt";
-    }
-    Path dumpFile = input.getParent().resolve(name);
+  private static void dumpResult(URI input, Result... results) throws IOException {
     Collection<String> resultTexts = new ArrayList<>();
     for (Result result : results) {
       resultTexts.add(result.getText());
     }
-    Files.write(dumpFile, resultTexts, StandardCharsets.UTF_8);
+    Files.write(buildOutputPath(input, ".txt"), resultTexts, StandardCharsets.UTF_8);
   }
 
-  private Result decode(URI uri, Map<DecodeHintType,?> hints) throws IOException {
+  private Result[] decode(URI uri, Map<DecodeHintType,?> hints) throws IOException {
     BufferedImage image = ImageReader.readImage(uri);
-    try {
-      LuminanceSource source;
-      if (config.getCrop() == null) {
-        source = new BufferedImageLuminanceSource(image);
-      } else {
-        int[] crop = config.getCrop();
-        source = new BufferedImageLuminanceSource(image, crop[0], crop[1], crop[2], crop[3]);
-      }
-      BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
-      if (config.isDumpBlackPoint()) {
-        dumpBlackPoint(uri, image, bitmap);
-      }
-      Result result = new MultiFormatReader().decode(bitmap, hints);
-      if (config.isBrief()) {
-        System.out.println(uri + ": Success");
-      } else {
-        ParsedResult parsedResult = ResultParser.parseResult(result);
-        System.out.println(uri + " (format: " + result.getBarcodeFormat() + ", type: " +
-            parsedResult.getType() + "):\nRaw result:\n" + result.getText() + "\nParsed result:\n" +
-            parsedResult.getDisplayResult());
 
+    LuminanceSource source;
+    if (config.getCrop() == null) {
+      source = new BufferedImageLuminanceSource(image);
+    } else {
+      int[] crop = config.getCrop();
+      source = new BufferedImageLuminanceSource(image, crop[0], crop[1], crop[2], crop[3]);
+    }
+
+    BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+    if (config.isDumpBlackPoint()) {
+      dumpBlackPoint(uri, image, bitmap);
+    }
+
+    MultiFormatReader multiFormatReader = new MultiFormatReader();
+    Result[] results;
+    try {
+      if (config.isMulti()) {
+        MultipleBarcodeReader reader = new GenericMultipleBarcodeReader(multiFormatReader);
+        results = reader.decodeMultiple(bitmap, hints);
+      } else {
+        results = new Result[]{multiFormatReader.decode(bitmap, hints)};
+      }
+    } catch (NotFoundException ignored) {
+      System.out.println(uri + ": No barcode found");
+      return null;
+    }
+
+    if (config.isBrief()) {
+      System.out.println(uri + ": Success");
+    } else {
+      for (Result result : results) {
+        ParsedResult parsedResult = ResultParser.parseResult(result);
+        System.out.println(uri +
+            " (format: " + result.getBarcodeFormat() +
+            ", type: " + parsedResult.getType() + "):\n" +
+            "Raw result:\n" +
+            result.getText() + "\n" +
+            "Parsed result:\n" +
+            parsedResult.getDisplayResult());
         System.out.println("Found " + result.getResultPoints().length + " result points.");
         for (int i = 0; i < result.getResultPoints().length; i++) {
           ResultPoint rp = result.getResultPoints()[i];
-          if (rp != null) {
-            System.out.println("  Point " + i + ": (" + rp.getX() + ',' + rp.getY() + ')');
-          }
+          System.out.println("  Point " + i + ": (" + rp.getX() + ',' + rp.getY() + ')');
         }
       }
-
-      return result;
-    } catch (NotFoundException ignored) {
-      System.out.println(uri + ": No barcode found");
-      return null;
     }
-  }
 
-  private Result[] decodeMulti(URI uri, Map<DecodeHintType,?> hints) throws IOException {
-    BufferedImage image = ImageReader.readImage(uri);
-    try {
-      LuminanceSource source;
-      if (config.getCrop() == null) {
-        source = new BufferedImageLuminanceSource(image);
-      } else {
-        int[] crop = config.getCrop();
-        source = new BufferedImageLuminanceSource(image, crop[0], crop[1], crop[2], crop[3]);
-      }
-      BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
-      if (config.isDumpBlackPoint()) {
-        dumpBlackPoint(uri, image, bitmap);
-      }
-
-      MultiFormatReader multiFormatReader = new MultiFormatReader();
-      MultipleBarcodeReader reader = new GenericMultipleBarcodeReader(multiFormatReader);
-      Result[] results = reader.decodeMultiple(bitmap, hints);
-
-      if (config.isBrief()) {
-        System.out.println(uri + ": Success");
-      } else {
-        for (Result result : results) {
-          ParsedResult parsedResult = ResultParser.parseResult(result);
-          System.out.println(uri + " (format: "
-              + result.getBarcodeFormat() + ", type: "
-              + parsedResult.getType() + "):\nRaw result:\n"
-              + result.getText() + "\nParsed result:\n"
-              + parsedResult.getDisplayResult());
-          System.out.println("Found " + result.getResultPoints().length + " result points.");
-          for (int i = 0; i < result.getResultPoints().length; i++) {
-            ResultPoint rp = result.getResultPoints()[i];
-            System.out.println("  Point " + i + ": (" + rp.getX() + ',' + rp.getY() + ')');
-          }
-        }
-      }
-      return results;
-    } catch (NotFoundException ignored) {
-      System.out.println(uri + ": No barcode found");
-      return null;
-    }
+    return results;
   }
 
   /**
@@ -209,11 +170,7 @@
    * to right: the original image, the row sampling monochrome version, and the 2D sampling
    * monochrome version.
    */
-  private static void dumpBlackPoint(URI uri, BufferedImage image, BinaryBitmap bitmap) {
-    if (uri.getPath().contains(".mono.png")) {
-      return;
-    }
-
+  private static void dumpBlackPoint(URI uri, BufferedImage image, BinaryBitmap bitmap) throws IOException {
     int width = bitmap.getWidth();
     int height = bitmap.getHeight();
     int stride = width * 3;
@@ -263,30 +220,17 @@
   private static void writeResultImage(int stride,
                                        int height,
                                        int[] pixels,
-                                       URI uri,
-                                       String suffix) {
+                                       URI input,
+                                       String suffix) throws IOException {
     BufferedImage result = new BufferedImage(stride, height, BufferedImage.TYPE_INT_ARGB);
     result.setRGB(0, 0, stride, height, pixels, 0, stride);
-
-    // Use the current working directory for URLs
-    String resultName = uri.getPath();
-    if ("http".equals(uri.getScheme())) {
-      int pos = resultName.lastIndexOf('/');
-      if (pos > 0) {
-        resultName = '.' + resultName.substring(pos);
-      }
-    }
-    int pos = resultName.lastIndexOf('.');
-    if (pos > 0) {
-      resultName = resultName.substring(0, pos);
-    }
-    resultName += suffix;
+    Path imagePath = buildOutputPath(input, suffix);
     try {
-      if (!ImageIO.write(result, "png", Paths.get(resultName).toFile())) {
-        System.err.println("Could not encode an image to " + resultName);
+      if (!ImageIO.write(result, "png", imagePath.toFile())) {
+        System.err.println("Could not encode an image to " + imagePath);
       }
     } catch (IOException ignored) {
-      System.err.println("Could not write to " + resultName);
+      System.err.println("Could not write to " + imagePath);
     }
   }
 
diff --git a/javase/src/main/java/com/google/zxing/client/j2se/ImageReader.java b/javase/src/main/java/com/google/zxing/client/j2se/ImageReader.java
index 3ee4dbf..45312ba 100644
--- a/javase/src/main/java/com/google/zxing/client/j2se/ImageReader.java
+++ b/javase/src/main/java/com/google/zxing/client/j2se/ImageReader.java
@@ -53,8 +53,8 @@
   }
   
   public static BufferedImage readDataURIImage(URI uri) throws IOException {
-    String uriString = uri.toString();
-    if (!uriString.startsWith("data:image/")) {
+    String uriString = uri.getSchemeSpecificPart();
+    if (!uriString.startsWith("image/")) {
       throw new IOException("Unsupported data URI MIME type");
     }
     int base64Start = uriString.indexOf(BASE64TOKEN);