diff --git a/NOTICE b/NOTICE
index f9d4708503..70d46cb44a 100644
--- a/NOTICE
+++ b/NOTICE
@@ -9,3 +9,9 @@ Copyright 2005-2006 Dietmar Bürkle
Portions of this software were contributed under section 5 of the
Apache License. Contributors are listed under:
http://barcode4j.sourceforge.net/contributors.html
+
+--------------------------------------------------------------------------------
+NOTICES FOR JCOMMANDER
+--------------------------------------------------------------------------------
+
+Copyright 2010 Cedric Beust cedric@beust.com
diff --git a/javase/pom.xml b/javase/pom.xml
index 91e8c1831a..2c9760c40e 100644
--- a/javase/pom.xml
+++ b/javase/pom.xml
@@ -26,6 +26,11 @@
com.google.zxing
core
+
+ com.beust
+ jcommander
+ 1.48
+
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 d2fa2f021f..1f71bedcbc 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
@@ -16,25 +16,22 @@
package com.google.zxing.client.j2se;
-import com.google.zxing.BarcodeFormat;
-import com.google.zxing.DecodeHintType;
-
+import com.beust.jcommander.JCommander;
import java.io.IOException;
import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.EnumMap;
-import java.util.Map;
+import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import java.util.regex.Pattern;
/**
* This simple command line utility decodes files, directories of files, or URIs which are passed
@@ -47,188 +44,109 @@
*/
public final class CommandLineRunner {
- private static final Pattern COMMA = Pattern.compile(",");
-
private CommandLineRunner() {
}
public static void main(String[] args) throws Exception {
- if (args.length == 0) {
- printUsage();
+ DecoderConfig config = new DecoderConfig();
+ JCommander jCommander = new JCommander(config, args);
+ jCommander.setProgramName(CommandLineRunner.class.getSimpleName());
+ if (config.help) {
+ jCommander.usage();
return;
}
- Config config = new Config();
- Queue inputs = new ConcurrentLinkedQueue<>();
-
- for (String arg : args) {
- String[] argValue = arg.split("=");
- switch (argValue[0]) {
- case "--try_harder":
- config.setTryHarder(true);
- break;
- case "--pure_barcode":
- config.setPureBarcode(true);
- break;
- case "--products_only":
- config.setProductsOnly(true);
- break;
- case "--dump_results":
- config.setDumpResults(true);
- break;
- case "--dump_black_point":
- config.setDumpBlackPoint(true);
- break;
- case "--multi":
- config.setMulti(true);
- break;
- case "--brief":
- config.setBrief(true);
- break;
- case "--recursive":
- config.setRecursive(true);
- break;
- case "--crop":
- int[] crop = new int[4];
- String[] tokens = COMMA.split(argValue[1]);
- for (int i = 0; i < crop.length; i++) {
- crop[i] = Integer.parseInt(tokens[i]);
- }
- config.setCrop(crop);
- break;
- case "--possibleFormats":
- config.setPossibleFormats(COMMA.split(argValue[1]));
- break;
- default:
- if (arg.startsWith("-")) {
- System.err.println("Unknown command line option " + arg);
- printUsage();
- return;
- }
- URI argURI = URI.create(arg);
- if (argURI.getScheme() == null) {
- argURI = new URI("file", argURI.getSchemeSpecificPart(), argURI.getFragment());
- }
- addArgumentToInputs(argURI, config, inputs);
- break;
- }
- }
+ List inputs = config.inputPaths;
+ do {
+ inputs = retainValid(expand(inputs), config.recursive);
+ } while (config.recursive && isExpandable(inputs));
int numInputs = inputs.size();
if (numInputs == 0) {
- System.err.println("No inputs specified");
- printUsage();
+ jCommander.usage();
return;
}
- config.setHints(buildHints(config));
-
+ Queue syncInputs = new ConcurrentLinkedQueue<>(inputs);
int numThreads = Math.min(numInputs, Runtime.getRuntime().availableProcessors());
int successful = 0;
if (numThreads > 1) {
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
Collection> futures = new ArrayList<>(numThreads);
for (int x = 0; x < numThreads; x++) {
- futures.add(executor.submit(new DecodeWorker(config, inputs)));
+ futures.add(executor.submit(new DecodeWorker(config, syncInputs)));
}
executor.shutdown();
for (Future future : futures) {
successful += future.get();
}
} else {
- successful += new DecodeWorker(config, inputs).call();
+ successful += new DecodeWorker(config, syncInputs).call();
}
- if (numInputs > 1) {
+ if (!config.brief && 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(URI input, Config config, Queue inputs) throws IOException {
- // Special case: a local directory
- if ("file".equals(input.getScheme()) && Files.isDirectory(Paths.get(input))) {
- try (DirectoryStream childPaths = Files.newDirectoryStream(Paths.get(input))) {
- for (Path childPath : childPaths) {
- Path realChildPath = childPath.toRealPath();
- // Skip hidden files and directories (e.g. svn stuff).
- 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());
+ private static List expand(List inputs) throws IOException, URISyntaxException {
+ List expanded = new ArrayList<>();
+ for (URI input : inputs) {
+ if (isFileOrDir(input)) {
+ Path inputPath = Paths.get(input);
+ if (Files.isDirectory(inputPath)) {
+ try (DirectoryStream childPaths = Files.newDirectoryStream(inputPath)) {
+ for (Path childPath : childPaths) {
+ expanded.add(childPath.toUri());
}
}
+ } else {
+ expanded.add(input);
}
+ } else {
+ expanded.add(input);
+ }
+ }
+ for (int i = 0; i < expanded.size(); i++) {
+ URI input = expanded.get(i);
+ if (input.getScheme() == null) {
+ expanded.set(i, new URI("file", input.getSchemeSpecificPart(), input.getFragment()));
}
- } else {
- inputs.add(input);
}
+ return expanded;
}
- private static Map buildHints(Config config) {
- Collection possibleFormats = new ArrayList<>();
- String[] possibleFormatsNames = config.getPossibleFormats();
- if (possibleFormatsNames != null && possibleFormatsNames.length > 0) {
- for (String format : possibleFormatsNames) {
- possibleFormats.add(BarcodeFormat.valueOf(format));
+ private static List retainValid(List inputs, boolean recursive) {
+ List retained = new ArrayList<>();
+ for (URI input : inputs) {
+ boolean retain;
+ if (isFileOrDir(input)) {
+ Path inputPath = Paths.get(input);
+ retain =
+ !inputPath.getFileName().toString().startsWith(".") &&
+ (recursive || !Files.isDirectory(inputPath));
+ } else {
+ retain = true;
}
- } else {
- possibleFormats.add(BarcodeFormat.UPC_A);
- possibleFormats.add(BarcodeFormat.UPC_E);
- possibleFormats.add(BarcodeFormat.EAN_13);
- possibleFormats.add(BarcodeFormat.EAN_8);
- possibleFormats.add(BarcodeFormat.RSS_14);
- possibleFormats.add(BarcodeFormat.RSS_EXPANDED);
- if (!config.isProductsOnly()) {
- possibleFormats.add(BarcodeFormat.CODE_39);
- possibleFormats.add(BarcodeFormat.CODE_93);
- possibleFormats.add(BarcodeFormat.CODE_128);
- possibleFormats.add(BarcodeFormat.ITF);
- possibleFormats.add(BarcodeFormat.QR_CODE);
- possibleFormats.add(BarcodeFormat.DATA_MATRIX);
- possibleFormats.add(BarcodeFormat.AZTEC);
- possibleFormats.add(BarcodeFormat.PDF_417);
- possibleFormats.add(BarcodeFormat.CODABAR);
- possibleFormats.add(BarcodeFormat.MAXICODE);
+ if (retain) {
+ retained.add(input);
}
}
- Map hints = new EnumMap<>(DecodeHintType.class);
- hints.put(DecodeHintType.POSSIBLE_FORMATS, possibleFormats);
- if (config.isTryHarder()) {
- hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
- }
- if (config.isPureBarcode()) {
- hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
- }
- return hints;
+ return retained;
}
- private static void printUsage() {
- System.err.println("Decode barcode images using the ZXing library");
- System.err.println();
- System.err.println("usage: CommandLineRunner { file | dir | url } [ options ]");
- System.err.println(" --try_harder: Use the TRY_HARDER hint, default is normal (mobile) mode");
- System.err.println(" --pure_barcode: Input image is a pure monochrome barcode image, not a photo");
- System.err.println(" --products_only: Only decode the UPC and EAN families of barcodes");
- System.err.println(" --dump_results: Write the decoded contents to input.txt");
- System.err.println(" --dump_black_point: Compare black point algorithms as input.mono.png");
- System.err.println(" --multi: Scans image for multiple barcodes");
- System.err.println(" --brief: Only output one line per file, omitting the contents");
- System.err.println(" --recursive: Descend into subdirectories");
- System.err.println(" --crop=left,top,width,height: Only examine cropped region of input image(s)");
- StringBuilder builder = new StringBuilder();
- builder.append(" --possibleFormats=barcodeFormat[,barcodeFormat2...] where barcodeFormat is any of: ");
- for (BarcodeFormat format : BarcodeFormat.values()) {
- builder.append(format).append(',');
+ private static boolean isExpandable(List inputs) {
+ for (URI input : inputs) {
+ if (isFileOrDir(input) && Files.isDirectory(Paths.get(input))) {
+ return true;
+ }
}
- builder.setLength(builder.length() - 1);
- System.err.println(builder);
+ return false;
+ }
+
+ private static boolean isFileOrDir(URI uri) {
+ return "file".equals(uri.getScheme());
}
}
diff --git a/javase/src/main/java/com/google/zxing/client/j2se/Config.java b/javase/src/main/java/com/google/zxing/client/j2se/Config.java
deleted file mode 100644
index 59037b720d..0000000000
--- a/javase/src/main/java/com/google/zxing/client/j2se/Config.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2011 ZXing authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.zxing.client.j2se;
-
-import com.google.zxing.DecodeHintType;
-
-import java.util.Map;
-
-final class Config {
-
- private Map hints;
- private boolean tryHarder;
- private boolean pureBarcode;
- private boolean productsOnly;
- private boolean dumpResults;
- private boolean dumpBlackPoint;
- private boolean multi;
- private boolean brief;
- private boolean recursive;
- private int[] crop;
- private String[] possibleFormats;
-
- Map getHints() {
- return hints;
- }
-
- void setHints(Map hints) {
- this.hints = hints;
- }
-
- boolean isTryHarder() {
- return tryHarder;
- }
-
- void setTryHarder(boolean tryHarder) {
- this.tryHarder = tryHarder;
- }
-
- boolean isPureBarcode() {
- return pureBarcode;
- }
-
- void setPureBarcode(boolean pureBarcode) {
- this.pureBarcode = pureBarcode;
- }
-
- boolean isProductsOnly() {
- return productsOnly;
- }
-
- void setProductsOnly(boolean productsOnly) {
- this.productsOnly = productsOnly;
- }
-
- boolean isDumpResults() {
- return dumpResults;
- }
-
- void setDumpResults(boolean dumpResults) {
- this.dumpResults = dumpResults;
- }
-
- boolean isDumpBlackPoint() {
- return dumpBlackPoint;
- }
-
- void setDumpBlackPoint(boolean dumpBlackPoint) {
- this.dumpBlackPoint = dumpBlackPoint;
- }
-
- boolean isMulti() {
- return multi;
- }
-
- void setMulti(boolean multi) {
- this.multi = multi;
- }
-
- boolean isBrief() {
- return brief;
- }
-
- void setBrief(boolean brief) {
- this.brief = brief;
- }
-
- boolean isRecursive() {
- return recursive;
- }
-
- void setRecursive(boolean recursive) {
- this.recursive = recursive;
- }
-
- int[] getCrop() {
- return crop;
- }
-
- void setCrop(int[] crop) {
- this.crop = crop;
- }
-
- String[] getPossibleFormats() {
- return possibleFormats;
- }
-
- void setPossibleFormats(String[] possibleFormats) {
- this.possibleFormats = possibleFormats;
- }
-
-}
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 5146da612d..6e1054d892 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,6 +42,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Callable;
@@ -57,22 +58,24 @@ final class DecodeWorker implements Callable {
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
- private final Config config;
+ private final DecoderConfig config;
private final Queue inputs;
+ private final Map hints;
- DecodeWorker(Config config, Queue inputs) {
+ DecodeWorker(DecoderConfig config, Queue inputs) {
this.config = config;
this.inputs = inputs;
+ hints = config.buildHints();
}
@Override
public Integer call() throws IOException {
int successful = 0;
for (URI input; (input = inputs.poll()) != null;) {
- Result[] results = decode(input, config.getHints());
+ Result[] results = decode(input, hints);
if (results != null) {
successful++;
- if (config.isDumpResults()) {
+ if (config.dumpResults) {
dumpResult(input, results);
}
}
@@ -116,22 +119,23 @@ private Result[] decode(URI uri, Map hints) throws IOException
BufferedImage image = ImageReader.readImage(uri);
LuminanceSource source;
- if (config.getCrop() == null) {
+ if (config.crop == null) {
source = new BufferedImageLuminanceSource(image);
} else {
- int[] crop = config.getCrop();
- source = new BufferedImageLuminanceSource(image, crop[0], crop[1], crop[2], crop[3]);
+ List crop = config.crop;
+ source = new BufferedImageLuminanceSource(
+ image, crop.get(0), crop.get(1), crop.get(2), crop.get(3));
}
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
- if (config.isDumpBlackPoint()) {
+ if (config.dumpBlackPoint) {
dumpBlackPoint(uri, image, bitmap);
}
MultiFormatReader multiFormatReader = new MultiFormatReader();
Result[] results;
try {
- if (config.isMulti()) {
+ if (config.multi) {
MultipleBarcodeReader reader = new GenericMultipleBarcodeReader(multiFormatReader);
results = reader.decodeMultiple(bitmap, hints);
} else {
@@ -142,7 +146,7 @@ private Result[] decode(URI uri, Map hints) throws IOException
return null;
}
- if (config.isBrief()) {
+ if (config.brief) {
System.out.println(uri + ": Success");
} else {
for (Result result : results) {
diff --git a/javase/src/main/java/com/google/zxing/client/j2se/DecoderConfig.java b/javase/src/main/java/com/google/zxing/client/j2se/DecoderConfig.java
new file mode 100644
index 0000000000..b938e061da
--- /dev/null
+++ b/javase/src/main/java/com/google/zxing/client/j2se/DecoderConfig.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.j2se;
+
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.validators.PositiveInteger;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+final class DecoderConfig {
+
+ @Parameter(names = "--try_harder",
+ description = "Use the TRY_HARDER hint, default is normal mode")
+ boolean tryHarder;
+
+ @Parameter(names="--pure_barcode",
+ description="Input image is a pure monochrome barcode image, not a photo")
+ boolean pureBarcode;
+
+ @Parameter(names = "--products_only",
+ description = "Only decode the UPC and EAN families of barcodes")
+ boolean productsOnly;
+
+ @Parameter(names = "--dump_results",
+ description = "Write the decoded contents to input.txt")
+ boolean dumpResults;
+
+ @Parameter(names = "--dump_black_point",
+ description = "Compare black point algorithms with dump as input.mono.png")
+ boolean dumpBlackPoint;
+
+ @Parameter(names = "--multi",
+ description = "Scans image for multiple barcodes")
+ boolean multi;
+
+ @Parameter(names = "--brief",
+ description = "Only output one line per file, omitting the contents")
+ boolean brief;
+
+ @Parameter(names = "--recursive",
+ description = "Descend into subdirectories")
+ boolean recursive;
+
+ @Parameter(names = "--crop",
+ description = " Only examine cropped region of input image(s)",
+ arity = 4,
+ validateWith = PositiveInteger.class)
+ List crop;
+
+ @Parameter(names = "--possible_formats",
+ description = "Formats to decode, where format is any value in BarcodeFormat",
+ variableArity = true)
+ List possibleFormats;
+
+ @Parameter(names = "--help",
+ description = "Prints this help message",
+ help = true)
+ boolean help;
+
+ @Parameter(description = "(URIs to decode)", required = true, variableArity = true)
+ List inputPaths;
+
+ Map buildHints() {
+ List finalPossibleFormats = possibleFormats;
+ if (finalPossibleFormats == null || finalPossibleFormats.isEmpty()) {
+ finalPossibleFormats = new ArrayList<>();
+ finalPossibleFormats.addAll(Arrays.asList(
+ BarcodeFormat.UPC_A,
+ BarcodeFormat.UPC_E,
+ BarcodeFormat.EAN_13,
+ BarcodeFormat.EAN_8,
+ BarcodeFormat.RSS_14,
+ BarcodeFormat.RSS_EXPANDED
+ ));
+ if (!productsOnly) {
+ finalPossibleFormats.addAll(Arrays.asList(
+ BarcodeFormat.CODE_39,
+ BarcodeFormat.CODE_93,
+ BarcodeFormat.CODE_128,
+ BarcodeFormat.ITF,
+ BarcodeFormat.QR_CODE,
+ BarcodeFormat.DATA_MATRIX,
+ BarcodeFormat.AZTEC,
+ BarcodeFormat.PDF_417,
+ BarcodeFormat.CODABAR,
+ BarcodeFormat.MAXICODE
+ ));
+ }
+ }
+
+ Map hints = new EnumMap<>(DecodeHintType.class);
+ hints.put(DecodeHintType.POSSIBLE_FORMATS, finalPossibleFormats);
+ if (tryHarder) {
+ hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
+ }
+ if (pureBarcode) {
+ hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
+ }
+ return Collections.unmodifiableMap(hints);
+ }
+
+}