Loading src/main/java/org/sw4j/tool/barcode/random/generator/BarcodeWriter.java 0 → 100644 +132 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 sw4j.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.sw4j.tool.barcode.random.generator; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.WriterException; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import org.sw4j.tool.barcode.random.codedata.CodeData; import org.sw4j.tool.barcode.random.config.CodeConfig; import org.sw4j.tool.barcode.random.config.CodeType; /** * <p> * This is a {@link java.util.function.Consumer Consumer} for * {@link org.sw4j.tool.barcode.random.generator.RandomIdent RandomIdent} which generates one barcode for a * {@code RandomIdent}. * </p> * <p> * This class is thread safe, as long as no barcodes for the same ident are generated. * </p> * @author Uwe Plonus <u.plonus@gmail.com> */ public class BarcodeWriter implements Consumer<RandomIdent> { /** * <p> * The logger of this class. * </p> */ private final Logger logger = Logger.getLogger(BarcodeWriter.class.getName()); /** * <p> * The configuration for the barcode that should me generated. * </p> */ private final CodeConfig codeConfig; /** * <p> * The factories for the input and output stream. * </p> */ private final CodeData codeData; /** * <p> * Create a new instance with the given barcode config and output factories. * </p> * @param codeConfig the configuration for the barcode to generate. * @param codeData the factories for the output files. */ public BarcodeWriter(final CodeConfig codeConfig, final CodeData codeData) { this.codeConfig = codeConfig; this.codeData = codeData; } /** * <p> * Generate the barcode for the given ident and configured barcode. * </p> * @param randomIdent the ident (and its random numbers) for output. */ @Override public void accept(final RandomIdent randomIdent) { CodeType codeType = codeConfig.getType(); String codeEncoding = codeConfig.getEncoding(); String codeUrl = codeConfig.getUrl(); codeUrl = codeUrl.replace("{code}", randomIdent.getEncoded(codeEncoding)); try { OutputStream os = codeData.getOutputForIdent(codeType, codeEncoding, randomIdent.getIdent(), codeConfig.getFiletype()); MultiFormatWriter codeWriter = new MultiFormatWriter(); Map<EncodeHintType, Object> encodingParameters = new HashMap<>(); encodingParameters.put(EncodeHintType.CHARACTER_SET, "utf-8"); setErrorCorrection(encodingParameters); BitMatrix matrix = codeWriter.encode(codeUrl, codeType.getFormat(), codeConfig.getWidth(), codeConfig.getHeight()); MatrixToImageWriter.writeToStream(matrix, codeConfig.getFiletype(), os); } catch (IOException | WriterException exc) { logger.log(Level.WARNING, String.format("Writing of Code 'type: %s / encoding: %s / ident: %s' failed.", codeType.getType(), codeEncoding, randomIdent.getIdent()), exc); } } /** * <p> * Configure the barcode error correction depending on the bacode type. * </p> * @param encodingParameters the encoding parameters where the error correction should be set in. */ private void setErrorCorrection(final Map<EncodeHintType, Object> encodingParameters) { switch (codeConfig.getType()) { case QRCODE: try { encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.valueOf(codeConfig.getErrorCorrection())); } catch (IllegalArgumentException | NullPointerException exc) { encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); } break; default: logger.warning("This point should never be reached."); } } } src/main/java/org/sw4j/tool/barcode/random/generator/CodeGenerator.java +92 −135 Original line number Diff line number Diff line Loading @@ -20,17 +20,8 @@ import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.databind.SequenceWriter; import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvSchema; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.WriterException; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.security.SecureRandom; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; Loading @@ -40,48 +31,97 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.sw4j.tool.barcode.random.codedata.CodeData; import org.sw4j.tool.barcode.random.config.RandomConfig; import org.sw4j.tool.barcode.random.encoder.ByteArrayEncoder; import org.sw4j.tool.barcode.random.input.Identifier; import org.sw4j.tool.barcode.random.codedata.CodeData; import org.sw4j.tool.barcode.random.config.CodeConfig; import org.sw4j.tool.barcode.random.config.CodeType; /** * * <p> * This class takes the configuration (from the package {@link org.sw4j.tool.barcode.random.config}) and uses this * configuration to generate encoded random numbers and barcodes. * </p> * <p> * This class is not thread safe. * </p> * @author Uwe Plonus <u.plonus@gmail.com> */ public class CodeGenerator { private static final Logger logger = Logger.getLogger(CodeGenerator.class.getName()); /** * <p> * The logger of this class. * </p> */ private final Logger logger = Logger.getLogger(CodeGenerator.class.getName()); /** * <p> * The configuration data for the random number generation and encoding. * </p> */ private final RandomConfig config; /** * <p> * The factories for the input and output stream. * </p> */ private final CodeData codeData; /** * <p> * The random number generator used. * </p> */ private final Random random; public CodeGenerator(RandomConfig config, CodeData codeData) { /** * <p> * The constructor for a new {@code CodeGenerator}. The code generator is initialized with the random configuration * and a factory for the input and output streams. * </p> * <p> * The code generator is initialized with a {@link java.security.SecureRandom SecureRandom} random number generator. * </p> * @param config the random number configuration. * @param codeData the factories for the input and output streams. */ public CodeGenerator(final RandomConfig config, final CodeData codeData) { this(config, codeData, new SecureRandom()); } public CodeGenerator(RandomConfig config, CodeData codeData, Random random) { /** * <p> * The constructor for a new {@code CodeGenerator}. The code generator is initialized with the random configuration * and a factory for the input and output streams. * </p> * <p> * The code generator uses the random number generator supplied during construction. * </p> * <p> * <em>Attention:</em> if you do not know the security issues involved with using your own random number generator * please use the constructor {@link #CodeGenerator(RandomConfig,CodeData)} which creates a secure random number * generator for you. * </p> * @param config the random number configuration. * @param codeData the factories for the input and output streams. * @param random the random number generator to use. */ public CodeGenerator(final RandomConfig config, final CodeData codeData, final Random random) { this.config = config; this.codeData = codeData; this.random = random; } /** * * @throws IOException * @throws FileNotFoundException * @throws IllegalArgumentException * <p> * Create the random numbers (and encoded representations) for the idents and writes the codes to the output csv * file. * </p> * @throws IOException if the reading or writing of the data fails. * @throws IllegalArgumentException if a duplicate ident is found in the input. */ public void createCodes() throws IOException { Set<String> encodings = new HashSet<>(); Loading @@ -103,31 +143,9 @@ public class CodeGenerator { String.format("Duplicated input value '%s' found", ident.getValue())); } } Set<RandomIdent> randomValues = createCodes(inputValues, encodings); randomValues.parallelStream() .forEach(randomIdent -> { config.getCodes().forEach(code -> { CodeType codeType = code.getType(); String codeEncoding = code.getEncoding(); String codeUrl = code.getUrl(); codeUrl = codeUrl.replace("{code}", randomIdent.getEncoded(codeEncoding)); try { OutputStream os = codeData.getOutputForIdent(codeType, codeEncoding, randomIdent.getIdent(), code.getFiletype()); MultiFormatWriter codeWriter = new MultiFormatWriter(); Map<EncodeHintType, Object> encodingParameters = new HashMap<>(); encodingParameters.put(EncodeHintType.CHARACTER_SET, "utf-8"); setErrorCorrection(code, encodingParameters); BitMatrix matrix = codeWriter.encode(codeUrl, codeType.getFormat(), code.getWidth(), code.getHeight()); MatrixToImageWriter.writeToStream(matrix, code.getFiletype(), os); } catch (IOException | WriterException exc) { logger.log(Level.WARNING, String.format("Writing of Code 'type: %s / encoding: %s / ident: %s' failed.", codeType.getType(), codeEncoding, randomIdent.getIdent()), exc); } }); final Set<RandomIdent> randomValues = createCodes(inputValues, encodings); config.getCodes().forEach(codeConfig -> { randomValues.parallelStream().forEach(new BarcodeWriter(codeConfig, codeData)); }); CsvSchema.Builder schemaBuilder = CsvSchema.builder() .addColumn("ident"); Loading @@ -150,22 +168,20 @@ public class CodeGenerator { } } private void setErrorCorrection(CodeConfig codeConfig, Map<EncodeHintType, Object> encodingParameters) { switch (codeConfig.getType()) { case QRCODE: try { encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.valueOf(codeConfig.getErrorCorrection())); } catch (IllegalArgumentException | NullPointerException exc) { encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); } default: // Do nothing } encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); } public Set<RandomIdent> createCodes(Collection<String> inputValues, Set<String> encodings) { /** * <p> * Create random values for all {@code inputValues}. Additionally the encoded representation for the random values * are created. * </p> * <p> * This method is thread safe. * </p> * @param inputValues the inputValues (idents) for which the random numbers should be created. * @param encodings the encodings that should be created for the random numbers. * @return a set of {@link org.sw4j.tool.barcode.random.generator.RandomIdent RandomIdent} instances with the random * numbers and the encoded random numbers. */ public Set<RandomIdent> createCodes(final Collection<String> inputValues, final Set<String> encodings) { Map<String, Set<String>> encodedRandoms = new HashMap<>(); encodings.forEach(encoding -> encodedRandoms.put(encoding, new HashSet<>())); // The following stream may not be a parallel stream, because we check there for duplicates Loading @@ -183,7 +199,16 @@ public class CodeGenerator { .collect(Collectors.toSet()); } private boolean encodedRandomExists(RandomIdent randomIdent, Map<String, Set<String>> encodedRandoms) { /** * <p> * Check is the random number of the given {@code randomIdent} already exists in the encoded randoms. This checks * for each encoding is the representation already exists. * </p> * @param randomIdent the random ident to check. * @param encodedRandoms a map with the encoding as key and the already generated encoded random numbers as values. * @return {@code true} if the given random ident already exists. */ private boolean encodedRandomExists(final RandomIdent randomIdent, final Map<String, Set<String>> encodedRandoms) { boolean valueExists = false; for (Map.Entry<String, Set<String>> entry: encodedRandoms.entrySet()) { valueExists |= entry.getValue().contains(randomIdent.getEncoded(entry.getKey())); Loading @@ -191,72 +216,4 @@ public class CodeGenerator { return valueExists; } public static class RandomIdent { private final String ident; private final byte[] random; private final Map<String, String> encoded; public RandomIdent(String ident, int randomSize, Random rng, Set<String> encodings) { this.ident = ident; random = new byte[randomSize / 8]; rng.nextBytes(random); encoded = new HashMap<>(); encodings.forEach((encoding) -> { String rawEncoding = encoding; int endIndex = -1; int startIndex = -1; boolean hasMinus = false; if (rawEncoding.matches(".+\\{\\d*-?\\d+\\}")) { Pattern p = Pattern.compile("(.+)\\{((\\d*)-)?(\\d+)\\}"); Matcher m = p.matcher(rawEncoding); String startIndexGroup = null; String minusGroup = null; m.matches(); rawEncoding = m.group(1); if (m.group(2) != null && m.group(2).length() > 1) { startIndex = Integer.parseInt(m.group(2).substring(0, m.group(2).length() - 1)); } startIndexGroup = m.group(2); hasMinus = m.group(3) != null; endIndex = Integer.parseInt(m.group(4)); } ByteArrayEncoder encoder = ByteArrayEncoder.forEncoding(rawEncoding); if (encoder == null) { throw new IllegalArgumentException( String.format("Cannot find an encoder for encoding %s", rawEncoding)); } String encodedValue = encoder.encode(random); if (endIndex > 0) { if (hasMinus) { if (startIndex >= 0) { encodedValue = encodedValue.substring(startIndex, endIndex); } else { encodedValue = encodedValue.substring(encodedValue.length() - endIndex); } } else { encodedValue = encodedValue.substring(0, endIndex); } } encoded.put(encoding, encodedValue); }); } public String getIdent() { return ident; } public byte[] getRandom() { return Arrays.copyOf(random, random.length); } public String getEncoded(String encoding) { return encoded.get(encoding); } } } src/main/java/org/sw4j/tool/barcode/random/generator/RandomIdent.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 sw4j.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.sw4j.tool.barcode.random.generator; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.sw4j.tool.barcode.random.encoder.ByteArrayEncoder; /** * <p> * This class contains the random number and the encoded representations for a single ident. * </p> * <p> * This class is immutable. * </p> * @author Uwe Plonus <u.plonus@gmail.com> */ public class RandomIdent { /** * <p> * The ident number. * </p> */ private final String ident; /** * <p> * The generated random number. * </p> */ private final byte[] random; /** * <p> * A map containing all encoded values for the ident. The key is the encoding and the value the encoded value. * </p> */ private final Map<String, String> encoded; /** * <p> * Create a new {@code RandomIdent} and the random number for the {@qcode ident}. The random number has a size of * {@code randomSize} bits (rounded down to the next whole byte). For the generation of the random number the given * {@code rng} will be used. The generated random number will be encoded in all given {@code encodings}. * </p> * <p> * For high quality random numbers use an appropriate random number generator * (e.g. {@link java.security.SecureRandom SecureRandom}). * </p> * @param ident the ident for which the random number will be generated for. * @param randomSize the size in bits (rounded down to the next byte). * @param rng the random number generator to use. * @param encodings the encodings that are used for the encoded representation of the random number. * @throws IllegalArgumentException if no encoder for a given encoding can be found. */ public RandomIdent(final String ident, final int randomSize, final Random rng, final Set<String> encodings) { this.ident = ident; random = new byte[randomSize / 8]; rng.nextBytes(random); encoded = new HashMap<>(); encodings.forEach((encoding) -> { String rawEncoding = encoding; int endIndex = -1; int startIndex = -1; boolean hasMinus = false; if (rawEncoding.matches(".+\\{\\d*-?\\d+\\}")) { Pattern p = Pattern.compile("(.+)\\{((\\d*)-)?(\\d+)\\}"); Matcher m = p.matcher(rawEncoding); m.matches(); rawEncoding = m.group(1); if (m.group(2) != null && m.group(2).length() > 1) { startIndex = Integer.parseInt(m.group(2).substring(0, m.group(2).length() - 1)); } String startIndexGroup = m.group(2); hasMinus = m.group(3) != null; endIndex = Integer.parseInt(m.group(4)); } ByteArrayEncoder encoder = ByteArrayEncoder.forEncoding(rawEncoding); if (encoder == null) { throw new IllegalArgumentException( String.format("Cannot find an encoder for encoding %s", rawEncoding)); } String encodedValue = encoder.encode(random); if (endIndex > 0) { if (hasMinus) { if (startIndex >= 0) { encodedValue = encodedValue.substring(startIndex, endIndex); } else { encodedValue = encodedValue.substring(encodedValue.length() - endIndex); } } else { encodedValue = encodedValue.substring(0, endIndex); } } encoded.put(encoding, encodedValue); }); } /** * <p> * Return the ident number. * </p> * @return the ident number. */ public String getIdent() { return ident; } /** * <p> * Return the raw random number generated. * </p> * @return the raw random number. */ public byte[] getRandom() { return Arrays.copyOf(random, random.length); } /** * <p> * Return the random number in the given {@code encoding}. Only the encodings that were given during construction * can be returned. * </p> * @param encoding the encoding for which the encoded value should be returned. * @return the encoded value or {@code null} if the encoding is unknown. */ public String getEncoded(final String encoding) { return encoded.get(encoding); } } src/main/java/org/sw4j/tool/barcode/random/generator/package-info.java 0 → 100644 +23 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 sw4j.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * <p> * This package contains the generator of the barcodes and encoded random numbers. * </p> */ package org.sw4j.tool.barcode.random.generator; src/test/java/org/sw4j/tool/barcode/random/generator/CodeGeneratorNGTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -33,7 +33,7 @@ import javax.xml.bind.DatatypeConverter; import org.sw4j.tool.barcode.random.codedata.CodeData; import org.sw4j.tool.barcode.random.config.CodeConfig; import org.sw4j.tool.barcode.random.config.RandomConfig; import org.sw4j.tool.barcode.random.generator.CodeGenerator.RandomIdent; import org.sw4j.tool.barcode.random.generator.RandomIdent; import org.sw4j.tool.barcode.random.support.PredictableRandom; import org.sw4j.tool.barcode.random.support.TestCodeData; import static org.testng.Assert.*; Loading Loading
src/main/java/org/sw4j/tool/barcode/random/generator/BarcodeWriter.java 0 → 100644 +132 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 sw4j.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.sw4j.tool.barcode.random.generator; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.WriterException; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import org.sw4j.tool.barcode.random.codedata.CodeData; import org.sw4j.tool.barcode.random.config.CodeConfig; import org.sw4j.tool.barcode.random.config.CodeType; /** * <p> * This is a {@link java.util.function.Consumer Consumer} for * {@link org.sw4j.tool.barcode.random.generator.RandomIdent RandomIdent} which generates one barcode for a * {@code RandomIdent}. * </p> * <p> * This class is thread safe, as long as no barcodes for the same ident are generated. * </p> * @author Uwe Plonus <u.plonus@gmail.com> */ public class BarcodeWriter implements Consumer<RandomIdent> { /** * <p> * The logger of this class. * </p> */ private final Logger logger = Logger.getLogger(BarcodeWriter.class.getName()); /** * <p> * The configuration for the barcode that should me generated. * </p> */ private final CodeConfig codeConfig; /** * <p> * The factories for the input and output stream. * </p> */ private final CodeData codeData; /** * <p> * Create a new instance with the given barcode config and output factories. * </p> * @param codeConfig the configuration for the barcode to generate. * @param codeData the factories for the output files. */ public BarcodeWriter(final CodeConfig codeConfig, final CodeData codeData) { this.codeConfig = codeConfig; this.codeData = codeData; } /** * <p> * Generate the barcode for the given ident and configured barcode. * </p> * @param randomIdent the ident (and its random numbers) for output. */ @Override public void accept(final RandomIdent randomIdent) { CodeType codeType = codeConfig.getType(); String codeEncoding = codeConfig.getEncoding(); String codeUrl = codeConfig.getUrl(); codeUrl = codeUrl.replace("{code}", randomIdent.getEncoded(codeEncoding)); try { OutputStream os = codeData.getOutputForIdent(codeType, codeEncoding, randomIdent.getIdent(), codeConfig.getFiletype()); MultiFormatWriter codeWriter = new MultiFormatWriter(); Map<EncodeHintType, Object> encodingParameters = new HashMap<>(); encodingParameters.put(EncodeHintType.CHARACTER_SET, "utf-8"); setErrorCorrection(encodingParameters); BitMatrix matrix = codeWriter.encode(codeUrl, codeType.getFormat(), codeConfig.getWidth(), codeConfig.getHeight()); MatrixToImageWriter.writeToStream(matrix, codeConfig.getFiletype(), os); } catch (IOException | WriterException exc) { logger.log(Level.WARNING, String.format("Writing of Code 'type: %s / encoding: %s / ident: %s' failed.", codeType.getType(), codeEncoding, randomIdent.getIdent()), exc); } } /** * <p> * Configure the barcode error correction depending on the bacode type. * </p> * @param encodingParameters the encoding parameters where the error correction should be set in. */ private void setErrorCorrection(final Map<EncodeHintType, Object> encodingParameters) { switch (codeConfig.getType()) { case QRCODE: try { encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.valueOf(codeConfig.getErrorCorrection())); } catch (IllegalArgumentException | NullPointerException exc) { encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); } break; default: logger.warning("This point should never be reached."); } } }
src/main/java/org/sw4j/tool/barcode/random/generator/CodeGenerator.java +92 −135 Original line number Diff line number Diff line Loading @@ -20,17 +20,8 @@ import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.databind.SequenceWriter; import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvSchema; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.WriterException; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.security.SecureRandom; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; Loading @@ -40,48 +31,97 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.sw4j.tool.barcode.random.codedata.CodeData; import org.sw4j.tool.barcode.random.config.RandomConfig; import org.sw4j.tool.barcode.random.encoder.ByteArrayEncoder; import org.sw4j.tool.barcode.random.input.Identifier; import org.sw4j.tool.barcode.random.codedata.CodeData; import org.sw4j.tool.barcode.random.config.CodeConfig; import org.sw4j.tool.barcode.random.config.CodeType; /** * * <p> * This class takes the configuration (from the package {@link org.sw4j.tool.barcode.random.config}) and uses this * configuration to generate encoded random numbers and barcodes. * </p> * <p> * This class is not thread safe. * </p> * @author Uwe Plonus <u.plonus@gmail.com> */ public class CodeGenerator { private static final Logger logger = Logger.getLogger(CodeGenerator.class.getName()); /** * <p> * The logger of this class. * </p> */ private final Logger logger = Logger.getLogger(CodeGenerator.class.getName()); /** * <p> * The configuration data for the random number generation and encoding. * </p> */ private final RandomConfig config; /** * <p> * The factories for the input and output stream. * </p> */ private final CodeData codeData; /** * <p> * The random number generator used. * </p> */ private final Random random; public CodeGenerator(RandomConfig config, CodeData codeData) { /** * <p> * The constructor for a new {@code CodeGenerator}. The code generator is initialized with the random configuration * and a factory for the input and output streams. * </p> * <p> * The code generator is initialized with a {@link java.security.SecureRandom SecureRandom} random number generator. * </p> * @param config the random number configuration. * @param codeData the factories for the input and output streams. */ public CodeGenerator(final RandomConfig config, final CodeData codeData) { this(config, codeData, new SecureRandom()); } public CodeGenerator(RandomConfig config, CodeData codeData, Random random) { /** * <p> * The constructor for a new {@code CodeGenerator}. The code generator is initialized with the random configuration * and a factory for the input and output streams. * </p> * <p> * The code generator uses the random number generator supplied during construction. * </p> * <p> * <em>Attention:</em> if you do not know the security issues involved with using your own random number generator * please use the constructor {@link #CodeGenerator(RandomConfig,CodeData)} which creates a secure random number * generator for you. * </p> * @param config the random number configuration. * @param codeData the factories for the input and output streams. * @param random the random number generator to use. */ public CodeGenerator(final RandomConfig config, final CodeData codeData, final Random random) { this.config = config; this.codeData = codeData; this.random = random; } /** * * @throws IOException * @throws FileNotFoundException * @throws IllegalArgumentException * <p> * Create the random numbers (and encoded representations) for the idents and writes the codes to the output csv * file. * </p> * @throws IOException if the reading or writing of the data fails. * @throws IllegalArgumentException if a duplicate ident is found in the input. */ public void createCodes() throws IOException { Set<String> encodings = new HashSet<>(); Loading @@ -103,31 +143,9 @@ public class CodeGenerator { String.format("Duplicated input value '%s' found", ident.getValue())); } } Set<RandomIdent> randomValues = createCodes(inputValues, encodings); randomValues.parallelStream() .forEach(randomIdent -> { config.getCodes().forEach(code -> { CodeType codeType = code.getType(); String codeEncoding = code.getEncoding(); String codeUrl = code.getUrl(); codeUrl = codeUrl.replace("{code}", randomIdent.getEncoded(codeEncoding)); try { OutputStream os = codeData.getOutputForIdent(codeType, codeEncoding, randomIdent.getIdent(), code.getFiletype()); MultiFormatWriter codeWriter = new MultiFormatWriter(); Map<EncodeHintType, Object> encodingParameters = new HashMap<>(); encodingParameters.put(EncodeHintType.CHARACTER_SET, "utf-8"); setErrorCorrection(code, encodingParameters); BitMatrix matrix = codeWriter.encode(codeUrl, codeType.getFormat(), code.getWidth(), code.getHeight()); MatrixToImageWriter.writeToStream(matrix, code.getFiletype(), os); } catch (IOException | WriterException exc) { logger.log(Level.WARNING, String.format("Writing of Code 'type: %s / encoding: %s / ident: %s' failed.", codeType.getType(), codeEncoding, randomIdent.getIdent()), exc); } }); final Set<RandomIdent> randomValues = createCodes(inputValues, encodings); config.getCodes().forEach(codeConfig -> { randomValues.parallelStream().forEach(new BarcodeWriter(codeConfig, codeData)); }); CsvSchema.Builder schemaBuilder = CsvSchema.builder() .addColumn("ident"); Loading @@ -150,22 +168,20 @@ public class CodeGenerator { } } private void setErrorCorrection(CodeConfig codeConfig, Map<EncodeHintType, Object> encodingParameters) { switch (codeConfig.getType()) { case QRCODE: try { encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.valueOf(codeConfig.getErrorCorrection())); } catch (IllegalArgumentException | NullPointerException exc) { encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); } default: // Do nothing } encodingParameters.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); } public Set<RandomIdent> createCodes(Collection<String> inputValues, Set<String> encodings) { /** * <p> * Create random values for all {@code inputValues}. Additionally the encoded representation for the random values * are created. * </p> * <p> * This method is thread safe. * </p> * @param inputValues the inputValues (idents) for which the random numbers should be created. * @param encodings the encodings that should be created for the random numbers. * @return a set of {@link org.sw4j.tool.barcode.random.generator.RandomIdent RandomIdent} instances with the random * numbers and the encoded random numbers. */ public Set<RandomIdent> createCodes(final Collection<String> inputValues, final Set<String> encodings) { Map<String, Set<String>> encodedRandoms = new HashMap<>(); encodings.forEach(encoding -> encodedRandoms.put(encoding, new HashSet<>())); // The following stream may not be a parallel stream, because we check there for duplicates Loading @@ -183,7 +199,16 @@ public class CodeGenerator { .collect(Collectors.toSet()); } private boolean encodedRandomExists(RandomIdent randomIdent, Map<String, Set<String>> encodedRandoms) { /** * <p> * Check is the random number of the given {@code randomIdent} already exists in the encoded randoms. This checks * for each encoding is the representation already exists. * </p> * @param randomIdent the random ident to check. * @param encodedRandoms a map with the encoding as key and the already generated encoded random numbers as values. * @return {@code true} if the given random ident already exists. */ private boolean encodedRandomExists(final RandomIdent randomIdent, final Map<String, Set<String>> encodedRandoms) { boolean valueExists = false; for (Map.Entry<String, Set<String>> entry: encodedRandoms.entrySet()) { valueExists |= entry.getValue().contains(randomIdent.getEncoded(entry.getKey())); Loading @@ -191,72 +216,4 @@ public class CodeGenerator { return valueExists; } public static class RandomIdent { private final String ident; private final byte[] random; private final Map<String, String> encoded; public RandomIdent(String ident, int randomSize, Random rng, Set<String> encodings) { this.ident = ident; random = new byte[randomSize / 8]; rng.nextBytes(random); encoded = new HashMap<>(); encodings.forEach((encoding) -> { String rawEncoding = encoding; int endIndex = -1; int startIndex = -1; boolean hasMinus = false; if (rawEncoding.matches(".+\\{\\d*-?\\d+\\}")) { Pattern p = Pattern.compile("(.+)\\{((\\d*)-)?(\\d+)\\}"); Matcher m = p.matcher(rawEncoding); String startIndexGroup = null; String minusGroup = null; m.matches(); rawEncoding = m.group(1); if (m.group(2) != null && m.group(2).length() > 1) { startIndex = Integer.parseInt(m.group(2).substring(0, m.group(2).length() - 1)); } startIndexGroup = m.group(2); hasMinus = m.group(3) != null; endIndex = Integer.parseInt(m.group(4)); } ByteArrayEncoder encoder = ByteArrayEncoder.forEncoding(rawEncoding); if (encoder == null) { throw new IllegalArgumentException( String.format("Cannot find an encoder for encoding %s", rawEncoding)); } String encodedValue = encoder.encode(random); if (endIndex > 0) { if (hasMinus) { if (startIndex >= 0) { encodedValue = encodedValue.substring(startIndex, endIndex); } else { encodedValue = encodedValue.substring(encodedValue.length() - endIndex); } } else { encodedValue = encodedValue.substring(0, endIndex); } } encoded.put(encoding, encodedValue); }); } public String getIdent() { return ident; } public byte[] getRandom() { return Arrays.copyOf(random, random.length); } public String getEncoded(String encoding) { return encoded.get(encoding); } } }
src/main/java/org/sw4j/tool/barcode/random/generator/RandomIdent.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 sw4j.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.sw4j.tool.barcode.random.generator; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.sw4j.tool.barcode.random.encoder.ByteArrayEncoder; /** * <p> * This class contains the random number and the encoded representations for a single ident. * </p> * <p> * This class is immutable. * </p> * @author Uwe Plonus <u.plonus@gmail.com> */ public class RandomIdent { /** * <p> * The ident number. * </p> */ private final String ident; /** * <p> * The generated random number. * </p> */ private final byte[] random; /** * <p> * A map containing all encoded values for the ident. The key is the encoding and the value the encoded value. * </p> */ private final Map<String, String> encoded; /** * <p> * Create a new {@code RandomIdent} and the random number for the {@qcode ident}. The random number has a size of * {@code randomSize} bits (rounded down to the next whole byte). For the generation of the random number the given * {@code rng} will be used. The generated random number will be encoded in all given {@code encodings}. * </p> * <p> * For high quality random numbers use an appropriate random number generator * (e.g. {@link java.security.SecureRandom SecureRandom}). * </p> * @param ident the ident for which the random number will be generated for. * @param randomSize the size in bits (rounded down to the next byte). * @param rng the random number generator to use. * @param encodings the encodings that are used for the encoded representation of the random number. * @throws IllegalArgumentException if no encoder for a given encoding can be found. */ public RandomIdent(final String ident, final int randomSize, final Random rng, final Set<String> encodings) { this.ident = ident; random = new byte[randomSize / 8]; rng.nextBytes(random); encoded = new HashMap<>(); encodings.forEach((encoding) -> { String rawEncoding = encoding; int endIndex = -1; int startIndex = -1; boolean hasMinus = false; if (rawEncoding.matches(".+\\{\\d*-?\\d+\\}")) { Pattern p = Pattern.compile("(.+)\\{((\\d*)-)?(\\d+)\\}"); Matcher m = p.matcher(rawEncoding); m.matches(); rawEncoding = m.group(1); if (m.group(2) != null && m.group(2).length() > 1) { startIndex = Integer.parseInt(m.group(2).substring(0, m.group(2).length() - 1)); } String startIndexGroup = m.group(2); hasMinus = m.group(3) != null; endIndex = Integer.parseInt(m.group(4)); } ByteArrayEncoder encoder = ByteArrayEncoder.forEncoding(rawEncoding); if (encoder == null) { throw new IllegalArgumentException( String.format("Cannot find an encoder for encoding %s", rawEncoding)); } String encodedValue = encoder.encode(random); if (endIndex > 0) { if (hasMinus) { if (startIndex >= 0) { encodedValue = encodedValue.substring(startIndex, endIndex); } else { encodedValue = encodedValue.substring(encodedValue.length() - endIndex); } } else { encodedValue = encodedValue.substring(0, endIndex); } } encoded.put(encoding, encodedValue); }); } /** * <p> * Return the ident number. * </p> * @return the ident number. */ public String getIdent() { return ident; } /** * <p> * Return the raw random number generated. * </p> * @return the raw random number. */ public byte[] getRandom() { return Arrays.copyOf(random, random.length); } /** * <p> * Return the random number in the given {@code encoding}. Only the encodings that were given during construction * can be returned. * </p> * @param encoding the encoding for which the encoded value should be returned. * @return the encoded value or {@code null} if the encoding is unknown. */ public String getEncoded(final String encoding) { return encoded.get(encoding); } }
src/main/java/org/sw4j/tool/barcode/random/generator/package-info.java 0 → 100644 +23 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 sw4j.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * <p> * This package contains the generator of the barcodes and encoded random numbers. * </p> */ package org.sw4j.tool.barcode.random.generator;
src/test/java/org/sw4j/tool/barcode/random/generator/CodeGeneratorNGTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -33,7 +33,7 @@ import javax.xml.bind.DatatypeConverter; import org.sw4j.tool.barcode.random.codedata.CodeData; import org.sw4j.tool.barcode.random.config.CodeConfig; import org.sw4j.tool.barcode.random.config.RandomConfig; import org.sw4j.tool.barcode.random.generator.CodeGenerator.RandomIdent; import org.sw4j.tool.barcode.random.generator.RandomIdent; import org.sw4j.tool.barcode.random.support.PredictableRandom; import org.sw4j.tool.barcode.random.support.TestCodeData; import static org.testng.Assert.*; Loading