View Javadoc
1   /*
2    * Copyright (C) 2019 sw4j.org
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16   */
17  package org.sw4j.tool.barcode.random.generator;
18  
19  import com.fasterxml.jackson.databind.MappingIterator;
20  import com.fasterxml.jackson.databind.SequenceWriter;
21  import com.fasterxml.jackson.dataformat.csv.CsvMapper;
22  import com.fasterxml.jackson.dataformat.csv.CsvSchema;
23  import java.io.IOException;
24  import java.security.SecureRandom;
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.LinkedHashMap;
29  import java.util.LinkedHashSet;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Random;
34  import java.util.Set;
35  import java.util.logging.Logger;
36  import java.util.stream.Collectors;
37  import org.sw4j.tool.barcode.random.codedata.CodeData;
38  import org.sw4j.tool.barcode.random.config.EncodingConfig;
39  import org.sw4j.tool.barcode.random.config.GenerationConfig;
40  import org.sw4j.tool.barcode.random.input.Identifier;
41  import org.sw4j.tool.barcode.random.input.Predefined;
42  
43  /**
44   * <p>
45   * This class takes the configuration (from the package {@link org.sw4j.tool.barcode.random.config}) and uses this
46   * configuration to generate encoded random numbers and barcodes.
47   * </p>
48   * <p>
49   * This class is not thread safe.
50   * </p>
51   * @author Uwe Plonus &lt;u.plonus@gmail.com&gt;
52   */
53  public class CodeGenerator {
54  
55      /**
56       * <p>
57       * The logger of this class.
58       * </p>
59       */
60      private final Logger logger = Logger.getLogger(CodeGenerator.class.getName());
61  
62      /**
63       * <p>
64       * The configuration data for the random number generation and encoding.
65       * </p>
66       */
67      private final GenerationConfig config;
68  
69      /**
70       * <p>
71       * The factories for the input and output stream.
72       * </p>
73       */
74      private final CodeData codeData;
75  
76      /**
77       * <p>
78       * The random number generator used.
79       * </p>
80       */
81      private final Random random;
82  
83      /**
84       * <p>
85       * The constructor for a new {@code CodeGenerator}. The code generator is initialized with the random configuration
86       * and a factory for the input and output streams.
87       * </p>
88       * <p>
89       * The code generator is initialized with a {@link java.security.SecureRandom SecureRandom} random number generator.
90       * </p>
91       * @param config the random number configuration.
92       * @param codeData the factories for the input and output streams.
93       */
94      public CodeGenerator(final GenerationConfig config, final CodeData codeData) {
95          this(config, codeData, new SecureRandom());
96      }
97  
98      /**
99       * <p>
100      * The constructor for a new {@code CodeGenerator}. The code generator is initialized with the random configuration
101      * and a factory for the input and output streams.
102      * </p>
103      * <p>
104      * The code generator uses the random number generator supplied during construction.
105      * </p>
106      * <p>
107      * <em>Attention:</em> if you do not know the security issues involved with using your own random number generator
108      * please use the constructor {@link #CodeGenerator(RandomConfig,CodeData)} which creates a secure random number
109      * generator for you.
110      * </p>
111      * @param config the random number configuration.
112      * @param codeData the factories for the input and output streams.
113      * @param random the random number generator to use.
114      */
115     public CodeGenerator(final GenerationConfig config, final CodeData codeData, final Random random) {
116         this.config = config;
117         this.codeData = codeData;
118         this.random = random;
119     }
120 
121     /**
122      * <p>
123      * Create the random numbers (and encoded representations) for the idents and writes the codes to the output csv
124      * file.
125      * </p>
126      * @throws IOException if the reading or writing of the data fails.
127      * @throws IllegalArgumentException if a duplicate ident is found in the input.
128      */
129     public void createCodes() throws IOException {
130         Set<EncodingConfig> encodings = new HashSet<>();
131         config.getCodes().forEach((codeConfig) -> {
132             encodings.add(codeConfig.getEncoding());
133         });
134         config.getEncodings().forEach((encoding) -> {
135             encodings.add(encoding);
136         });
137 
138         CsvMapper csvMapper = new CsvMapper();
139         Set<IdentValue> values;
140 
141         if (config.getEncoding() != null) {
142             Map<String, String> inputValues = new LinkedHashMap<>();
143             MappingIterator<Predefined> mappingIterator = csvMapper.readerWithSchemaFor(Predefined.class)
144                     .readValues(codeData.getInput());
145             while (mappingIterator.hasNext()) {
146                 Predefined ident = mappingIterator.next();
147                 inputValues.put(ident.getIdent(), ident.getCode());
148             }
149             values = convertCodes(inputValues, config.getEncoding(), encodings);
150         } else {
151             Set<String> inputValues = new LinkedHashSet<>();
152             MappingIterator<Identifier> mappingIterator = csvMapper.readerWithSchemaFor(Identifier.class)
153                     .readValues(codeData.getInput());
154             while (mappingIterator.hasNext()) {
155                 Identifier ident = mappingIterator.next();
156                 if (!inputValues.add(ident.getIdent())) {
157                     throw new IllegalArgumentException(
158                             String.format("Duplicated input value '%s' found", ident.getIdent()));
159                 }
160             }
161             values = createCodes(inputValues, encodings);
162         }
163 
164         config.getCodes().forEach(codeConfig -> {
165             values.parallelStream().forEach(new BarcodeWriter(codeConfig, codeData));
166         });
167         CsvSchema.Builder schemaBuilder = CsvSchema.builder().addColumn("ident");
168         // TODO Check column header
169         encodings.forEach(encoding -> schemaBuilder.addColumn(encoding.toString()));
170         CsvSchema schema = schemaBuilder.build().withHeader();
171         CsvMapper mapper = new CsvMapper();
172         List<Map<String, String>> outData = new LinkedList<>();
173         values.stream().map((value) -> {
174             Map<String, String> dataValues = new HashMap<>();
175             dataValues.put("ident", value.getIdent());
176             encodings.forEach((encoding) -> {
177                 dataValues.put(encoding.toString(), value.getEncoded(encoding));
178             });
179             return dataValues;
180         }).forEachOrdered((dataValues) -> {
181             outData.add(dataValues);
182         });
183         try (SequenceWriter writer = mapper.writer(schema).writeValues(codeData.getOutput())) {
184             writer.writeAll(outData);
185         }
186     }
187 
188     /**
189      * <p>
190      * Create the encoded representation for all {@code inputValues}.
191      * </p>
192      * <p>
193      * This method is thread safe.
194      * </p>
195      * @param inputValues the inputValues (idents) for which the random numbers should be created.
196      * @param encoding the encoding of the source value.
197      * @param encodings the encodings that should be created for the random numbers.
198      * @return a set of {@link org.sw4j.tool.barcode.random.generator.RandomIdent RandomIdent} instances with the random
199      *   numbers and the encoded random numbers.
200      */
201     public Set<IdentValue> convertCodes(final Map<String, String> inputValues, final EncodingConfig encoding,
202             final Set<EncodingConfig> encodings) {
203         Map<EncodingConfig, Set<String>> encoded = new HashMap<>();
204         encodings.forEach(enc -> encoded.put(enc, new HashSet<>()));
205         return inputValues.keySet().stream()
206                 .map(ident -> {
207                     PredefinedIdentm/generator/PredefinedIdent.html#PredefinedIdent">PredefinedIdent pi = new PredefinedIdent(ident, inputValues.get(ident), encoding, encodings);
208                     for (EncodingConfig enc: encodings) {
209                         encoded.get(enc).add(pi.getEncoded(enc));
210                     }
211                     return pi;
212                 })
213                 .collect(Collectors.toSet());
214     }
215 
216     /**
217      * <p>
218      * Create random values for all {@code inputValues}. Additionally the encoded representation for the random values
219      * are created.
220      * </p>
221      * <p>
222      * This method is thread safe.
223      * </p>
224      * @param inputValues the inputValues (idents) for which the random numbers should be created.
225      * @param encodings the encodings that should be created for the random numbers.
226      * @return a set of {@link org.sw4j.tool.barcode.random.generator.RandomIdent RandomIdent} instances with the random
227      *   numbers and the encoded random numbers.
228      */
229     public Set<IdentValue> createCodes(final Collection<String> inputValues, final Set<EncodingConfig> encodings) {
230         Map<EncodingConfig, Set<String>> encodedRandoms = new HashMap<>();
231         encodings.forEach(encoding -> encodedRandoms.put(encoding, new HashSet<>()));
232         // The following stream may not be a parallel stream, because we check there for duplicates
233         return inputValues.stream()
234                 .map(ident -> {
235                     RandomIdentandom/generator/RandomIdent.html#RandomIdent">RandomIdent ri = new RandomIdent(ident, config.getSize(), random, encodings);
236                     while (encodedRandomExists(ri, encodedRandoms)) {
237                         ri = new RandomIdent(ident, config.getSize(), random, encodings);
238                     }
239                     for (EncodingConfig encoding: encodings) {
240                         encodedRandoms.get(encoding).add(ri.getEncoded(encoding));
241                     }
242                     return ri;
243                 })
244                 .collect(Collectors.toSet());
245     }
246 
247     /**
248      * <p>
249      * Check is the random number of the given {@code randomIdent} already exists in the encoded randoms. This checks
250      * for each encoding is the representation already exists.
251      * </p>
252      * @param randomIdent the random ident to check.
253      * @param encodedRandoms a map with the encoding as key and the already generated encoded random numbers as values.
254      * @return {@code true} if the given random ident already exists.
255      */
256     private boolean encodedRandomExists(final RandomIdent randomIdent,
257             final Map<EncodingConfig, Set<String>> encodedRandoms) {
258         boolean valueExists = false;
259         for (Map.Entry<EncodingConfig, Set<String>> entry: encodedRandoms.entrySet()) {
260             valueExists |= entry.getValue().contains(randomIdent.getEncoded(entry.getKey()));
261         }
262         return valueExists;
263     }
264 
265 }