diff --git a/NOTICE b/NOTICE index f9d470850..70d46cb44 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 91e8c1831..2c9760c40 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 d2fa2f021..1f71bedcb 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 @@ 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 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); } - } 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 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 59037b720..000000000 --- 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 5146da612..6e1054d89 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.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 { 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 @@ final class DecodeWorker implements Callable { 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 @@ final class DecodeWorker implements Callable { 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 000000000..b938e061d --- /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); + } + +}