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 java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Random;
25 import java.util.Set;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28 import org.sw4j.tool.barcode.random.config.EncodingConfig;
29 import org.sw4j.tool.barcode.random.encoder.ByteArrayEncoder;
30
31 /**
32 * <p>
33 * This class contains the random number and the encoded representations for a single ident.
34 * </p>
35 * <p>
36 * This class is immutable.
37 * </p>
38 * @author Uwe Plonus <u.plonus@gmail.com>
39 */
40 public class RandomIdent implements IdentValue {
41
42 /**
43 * <p>
44 * The ident number.
45 * </p>
46 */
47 private final String ident;
48
49 /**
50 * <p>
51 * The generated random number.
52 * </p>
53 */
54 private final byte[] random;
55
56 /**
57 * <p>
58 * A map containing all encoded values for the ident. The key is the encoding and the value the encoded value.
59 * </p>
60 */
61 private final Map<EncodingConfig, String> encoded;
62
63 /**
64 * <p>
65 * Create a new {@code RandomIdent} and the random number for the {@code ident}. The random number has a size of
66 * {@code randomSize} bits (rounded down to the next whole byte). For the generation of the random number the given
67 * {@code rng} will be used. The generated random number will be encoded in all given {@code encodings}.
68 * </p>
69 * <p>
70 * For high quality random numbers use an appropriate random number generator
71 * (e.g. {@link java.security.SecureRandom SecureRandom}).
72 * </p>
73 * @param ident the ident for which the random number will be generated for.
74 * @param randomSize the size in bits (rounded down to the next byte).
75 * @param rng the random number generator to use.
76 * @param encodings the encodings that are used for the encoded representation of the random number.
77 * @throws IllegalArgumentException if no encoder for a given encoding can be found.
78 */
79 public RandomIdent(final String ident, final int randomSize, final Random rng,
80 final Set<EncodingConfig> encodings) {
81 this.ident = ident;
82 random = new byte[randomSize / 8];
83 rng.nextBytes(random);
84 encoded = new HashMap<>();
85 encodings.forEach((encoding) -> {
86 String rawEncoding = encoding.getType();
87 int endIndex = -1;
88 int startIndex = -1;
89 boolean hasMinus = false;
90 if (rawEncoding.matches(".+\\{\\d*-?\\d+\\}")) {
91 Pattern p = Pattern.compile("(.+)\\{((\\d*)-)?(\\d+)\\}");
92 Matcher m = p.matcher(rawEncoding);
93 m.matches();
94 rawEncoding = m.group(1);
95 if (m.group(2) != null && m.group(2).length() > 1) {
96 startIndex = Integer.parseInt(m.group(2).substring(0, m.group(2).length() - 1));
97 }
98 String startIndexGroup = m.group(2);
99 hasMinus = m.group(3) != null;
100 endIndex = Integer.parseInt(m.group(4));
101 }
102 ByteArrayEncoder encoder = ByteArrayEncoder.forEncoding(rawEncoding);
103 if (encoder == null) {
104 throw new IllegalArgumentException(
105 String.format("Cannot find an encoder for encoding %s", rawEncoding));
106 }
107 String encodedValue = encoder.encode(random);
108 if (endIndex > 0) {
109 if (hasMinus) {
110 if (startIndex >= 0) {
111 encodedValue = encodedValue.substring(startIndex, endIndex);
112 } else {
113 encodedValue = encodedValue.substring(encodedValue.length() - endIndex);
114 }
115 } else {
116 encodedValue = encodedValue.substring(0, endIndex);
117 }
118 }
119 if (encoding.getSepCount() != null) {
120 int partsCount = encodedValue.length() / encoding.getSepCount();
121 if (encodedValue.length() % encoding.getSepCount() != 0) {
122 partsCount++;
123 }
124 List<String> parts = new ArrayList<>(partsCount);
125 for (int i = 0; i < partsCount; i++) {
126 int endPos = (i + 1) * encoding.getSepCount() < encodedValue.length() ?
127 (i + 1) * encoding.getSepCount() :
128 encodedValue.length();
129 parts.add(encodedValue.substring(i * encoding.getSepCount(),
130 endPos));
131 }
132 StringBuilder sb = new StringBuilder();
133 for (int i = 0; i < parts.size(); i++) {
134 if (i > 0) {
135 sb.append(encoding.getSepChar());
136 }
137 sb.append(parts.get(i));
138 }
139 encodedValue = sb.toString();
140 }
141 encoded.put(encoding, encodedValue);
142 });
143 }
144
145 /**
146 * <p>
147 * Return the ident number.
148 * </p>
149 * @return the ident number.
150 */
151 @Override
152 public String getIdent() {
153 return ident;
154 }
155
156 /**
157 * <p>
158 * Return the raw random number generated.
159 * </p>
160 * @return the raw random number.
161 */
162 @Override
163 public byte[] getValue() {
164 return Arrays.copyOf(random, random.length);
165 }
166
167 /**
168 * <p>
169 * Return the random number in the given {@code encoding}. Only the encodings that were given during construction
170 * can be returned.
171 * </p>
172 * @param encoding the encoding for which the encoded value should be returned.
173 * @return the encoded value or {@code null} if the encoding is unknown.
174 */
175 @Override
176 public String getEncoded(final EncodingConfig encoding) {
177 return encoded.get(encoding);
178 }
179
180 }