Use JCommander for CommandLineRunner args

This commit is contained in:
Sean Owen 2015-05-03 19:57:48 +01:00
parent 106fd2fc0e
commit 8fe986a9aa
6 changed files with 210 additions and 278 deletions

6
NOTICE
View file

@ -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

View file

@ -26,6 +26,11 @@
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
</dependency>
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.48</version>
</dependency>
</dependencies>
<parent>

View file

@ -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 @@ import java.util.regex.Pattern;
*/
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<URI> 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<URI> 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<URI> syncInputs = new ConcurrentLinkedQueue<>(inputs);
int numThreads = Math.min(numInputs, Runtime.getRuntime().availableProcessors());
int successful = 0;
if (numThreads > 1) {
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
Collection<Future<Integer>> 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<Integer> 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<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 (!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<URI> expand(List<URI> inputs) throws IOException, URISyntaxException {
List<URI> expanded = new ArrayList<>();
for (URI input : inputs) {
if (isFileOrDir(input)) {
Path inputPath = Paths.get(input);
if (Files.isDirectory(inputPath)) {
try (DirectoryStream<Path> childPaths = Files.newDirectoryStream(inputPath)) {
for (Path childPath : childPaths) {
expanded.add(childPath.toUri());
}
}
} else {
expanded.add(input);
}
} else {
expanded.add(input);
}
} else {
inputs.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()));
}
}
return expanded;
}
private static Map<DecodeHintType,?> buildHints(Config config) {
Collection<BarcodeFormat> 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<URI> retainValid(List<URI> inputs, boolean recursive) {
List<URI> 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<DecodeHintType, Object> 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<URI> 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());
}
}

View file

@ -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<DecodeHintType,?> 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<DecodeHintType,?> getHints() {
return hints;
}
void setHints(Map<DecodeHintType,?> 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;
}
}

View file

@ -42,6 +42,7 @@ import java.nio.file.Paths;
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<Integer> {
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
private final Config config;
private final DecoderConfig config;
private final Queue<URI> inputs;
private final Map<DecodeHintType,?> hints;
DecodeWorker(Config config, Queue<URI> inputs) {
DecodeWorker(DecoderConfig config, Queue<URI> 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 @@ final class DecodeWorker implements Callable<Integer> {
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<Integer> 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 @@ final class DecodeWorker implements Callable<Integer> {
return null;
}
if (config.isBrief()) {
if (config.brief) {
System.out.println(uri + ": Success");
} else {
for (Result result : results) {

View file

@ -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<Integer> crop;
@Parameter(names = "--possible_formats",
description = "Formats to decode, where format is any value in BarcodeFormat",
variableArity = true)
List<BarcodeFormat> possibleFormats;
@Parameter(names = "--help",
description = "Prints this help message",
help = true)
boolean help;
@Parameter(description = "(URIs to decode)", required = true, variableArity = true)
List<URI> inputPaths;
Map<DecodeHintType,?> buildHints() {
List<BarcodeFormat> 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<DecodeHintType, Object> 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);
}
}