Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / examples / common / QRCode / repo / java / src / main / java / io / nayuki / qrcodegen / QrSegment.java
1 /* 
2  * QR Code generator library (Java)
3  * 
4  * Copyright (c) Project Nayuki. (MIT License)
5  * https://www.nayuki.io/page/qr-code-generator-library
6  * 
7  * Permission is hereby granted, free of charge, to any person obtaining a copy of
8  * this software and associated documentation files (the "Software"), to deal in
9  * the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11  * the Software, and to permit persons to whom the Software is furnished to do so,
12  * subject to the following conditions:
13  * - The above copyright notice and this permission notice shall be included in
14  *   all copies or substantial portions of the Software.
15  * - The Software is provided "as is", without warranty of any kind, express or
16  *   implied, including but not limited to the warranties of merchantability,
17  *   fitness for a particular purpose and noninfringement. In no event shall the
18  *   authors or copyright holders be liable for any claim, damages or other
19  *   liability, whether in an action of contract, tort or otherwise, arising from,
20  *   out of or in connection with the Software or the use or other dealings in the
21  *   Software.
22  */
23
24 package io.nayuki.qrcodegen;
25
26 import java.nio.charset.StandardCharsets;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Objects;
30 import java.util.regex.Pattern;
31
32
33 /**
34  * A segment of character/binary/control data in a QR Code symbol.
35  * Instances of this class are immutable.
36  * <p>The mid-level way to create a segment is to take the payload data and call a
37  * static factory function such as {@link QrSegment#makeNumeric(String)}. The low-level
38  * way to create a segment is to custom-make the bit buffer and call the {@link
39  * QrSegment#QrSegment(Mode,int,BitBuffer) constructor} with appropriate values.</p>
40  * <p>This segment class imposes no length restrictions, but QR Codes have restrictions.
41  * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
42  * Any segment longer than this is meaningless for the purpose of generating QR Codes.
43  * This class can represent kanji mode segments, but provides no help in encoding them
44  * - see {@link QrSegmentAdvanced} for full kanji support.</p>
45  */
46 public final class QrSegment {
47         
48         /*---- Static factory functions (mid level) ----*/
49         
50         /**
51          * Returns a segment representing the specified binary data
52          * encoded in byte mode. All input byte arrays are acceptable.
53          * <p>Any text string can be converted to UTF-8 bytes ({@code
54          * s.getBytes(StandardCharsets.UTF_8)}) and encoded as a byte mode segment.</p>
55          * @param data the binary data (not {@code null})
56          * @return a segment (not {@code null}) containing the data
57          * @throws NullPointerException if the array is {@code null}
58          */
59         public static QrSegment makeBytes(byte[] data) {
60                 Objects.requireNonNull(data);
61                 BitBuffer bb = new BitBuffer();
62                 for (byte b : data)
63                         bb.appendBits(b & 0xFF, 8);
64                 return new QrSegment(Mode.BYTE, data.length, bb);
65         }
66         
67         
68         /**
69          * Returns a segment representing the specified string of decimal digits encoded in numeric mode.
70          * @param digits the text (not {@code null}), with only digits from 0 to 9 allowed
71          * @return a segment (not {@code null}) containing the text
72          * @throws NullPointerException if the string is {@code null}
73          * @throws IllegalArgumentException if the string contains non-digit characters
74          */
75         public static QrSegment makeNumeric(String digits) {
76                 Objects.requireNonNull(digits);
77                 if (!NUMERIC_REGEX.matcher(digits).matches())
78                         throw new IllegalArgumentException("String contains non-numeric characters");
79                 
80                 BitBuffer bb = new BitBuffer();
81                 for (int i = 0; i < digits.length(); ) {  // Consume up to 3 digits per iteration
82                         int n = Math.min(digits.length() - i, 3);
83                         bb.appendBits(Integer.parseInt(digits.substring(i, i + n)), n * 3 + 1);
84                         i += n;
85                 }
86                 return new QrSegment(Mode.NUMERIC, digits.length(), bb);
87         }
88         
89         
90         /**
91          * Returns a segment representing the specified text string encoded in alphanumeric mode.
92          * The characters allowed are: 0 to 9, A to Z (uppercase only), space,
93          * dollar, percent, asterisk, plus, hyphen, period, slash, colon.
94          * @param text the text (not {@code null}), with only certain characters allowed
95          * @return a segment (not {@code null}) containing the text
96          * @throws NullPointerException if the string is {@code null}
97          * @throws IllegalArgumentException if the string contains non-encodable characters
98          */
99         public static QrSegment makeAlphanumeric(String text) {
100                 Objects.requireNonNull(text);
101                 if (!ALPHANUMERIC_REGEX.matcher(text).matches())
102                         throw new IllegalArgumentException("String contains unencodable characters in alphanumeric mode");
103                 
104                 BitBuffer bb = new BitBuffer();
105                 int i;
106                 for (i = 0; i <= text.length() - 2; i += 2) {  // Process groups of 2
107                         int temp = ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
108                         temp += ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
109                         bb.appendBits(temp, 11);
110                 }
111                 if (i < text.length())  // 1 character remaining
112                         bb.appendBits(ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6);
113                 return new QrSegment(Mode.ALPHANUMERIC, text.length(), bb);
114         }
115         
116         
117         /**
118          * Returns a list of zero or more segments to represent the specified Unicode text string.
119          * The result may use various segment modes and switch modes to optimize the length of the bit stream.
120          * @param text the text to be encoded, which can be any Unicode string
121          * @return a new mutable list (not {@code null}) of segments (not {@code null}) containing the text
122          * @throws NullPointerException if the text is {@code null}
123          */
124         public static List<QrSegment> makeSegments(String text) {
125                 Objects.requireNonNull(text);
126                 
127                 // Select the most efficient segment encoding automatically
128                 List<QrSegment> result = new ArrayList<>();
129                 if (text.equals(""));  // Leave result empty
130                 else if (NUMERIC_REGEX.matcher(text).matches())
131                         result.add(makeNumeric(text));
132                 else if (ALPHANUMERIC_REGEX.matcher(text).matches())
133                         result.add(makeAlphanumeric(text));
134                 else
135                         result.add(makeBytes(text.getBytes(StandardCharsets.UTF_8)));
136                 return result;
137         }
138         
139         
140         /**
141          * Returns a segment representing an Extended Channel Interpretation
142          * (ECI) designator with the specified assignment value.
143          * @param assignVal the ECI assignment number (see the AIM ECI specification)
144          * @return a segment (not {@code null}) containing the data
145          * @throws IllegalArgumentException if the value is outside the range [0, 10<sup>6</sup>)
146          */
147         public static QrSegment makeEci(int assignVal) {
148                 BitBuffer bb = new BitBuffer();
149                 if (assignVal < 0)
150                         throw new IllegalArgumentException("ECI assignment value out of range");
151                 else if (assignVal < (1 << 7))
152                         bb.appendBits(assignVal, 8);
153                 else if (assignVal < (1 << 14)) {
154                         bb.appendBits(2, 2);
155                         bb.appendBits(assignVal, 14);
156                 } else if (assignVal < 1_000_000) {
157                         bb.appendBits(6, 3);
158                         bb.appendBits(assignVal, 21);
159                 } else
160                         throw new IllegalArgumentException("ECI assignment value out of range");
161                 return new QrSegment(Mode.ECI, 0, bb);
162         }
163         
164         
165         
166         /*---- Instance fields ----*/
167         
168         /** The mode indicator of this segment. Not {@code null}. */
169         public final Mode mode;
170         
171         /** The length of this segment's unencoded data. Measured in characters for
172          * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
173          * Always zero or positive. Not the same as the data's bit length. */
174         public final int numChars;
175         
176         // The data bits of this segment. Not null. Accessed through getData().
177         final BitBuffer data;
178         
179         
180         /*---- Constructor (low level) ----*/
181         
182         /**
183          * Constructs a QR Code segment with the specified attributes and data.
184          * The character count (numCh) must agree with the mode and the bit buffer length,
185          * but the constraint isn't checked. The specified bit buffer is cloned and stored.
186          * @param md the mode (not {@code null})
187          * @param numCh the data length in characters or bytes, which is non-negative
188          * @param data the data bits (not {@code null})
189          * @throws NullPointerException if the mode or data is {@code null}
190          * @throws IllegalArgumentException if the character count is negative
191          */
192         public QrSegment(Mode md, int numCh, BitBuffer data) {
193                 mode = Objects.requireNonNull(md);
194                 Objects.requireNonNull(data);
195                 if (numCh < 0)
196                         throw new IllegalArgumentException("Invalid value");
197                 numChars = numCh;
198                 this.data = data.clone();  // Make defensive copy
199         }
200         
201         
202         /*---- Methods ----*/
203         
204         /**
205          * Returns the data bits of this segment.
206          * @return a new copy of the data bits (not {@code null})
207          */
208         public BitBuffer getData() {
209                 return data.clone();  // Make defensive copy
210         }
211         
212         
213         // Calculates the number of bits needed to encode the given segments at the given version.
214         // Returns a non-negative number if successful. Otherwise returns -1 if a segment has too
215         // many characters to fit its length field, or the total bits exceeds Integer.MAX_VALUE.
216         static int getTotalBits(List<QrSegment> segs, int version) {
217                 Objects.requireNonNull(segs);
218                 long result = 0;
219                 for (QrSegment seg : segs) {
220                         Objects.requireNonNull(seg);
221                         int ccbits = seg.mode.numCharCountBits(version);
222                         if (seg.numChars >= (1 << ccbits))
223                                 return -1;  // The segment's length doesn't fit the field's bit width
224                         result += 4L + ccbits + seg.data.bitLength();
225                         if (result > Integer.MAX_VALUE)
226                                 return -1;  // The sum will overflow an int type
227                 }
228                 return (int)result;
229         }
230         
231         
232         /*---- Constants ----*/
233         
234         /** Describes precisely all strings that are encodable in numeric mode. To test whether a
235          * string {@code s} is encodable: {@code boolean ok = NUMERIC_REGEX.matcher(s).matches();}.
236          * A string is encodable iff each character is in the range 0 to 9.
237          * @see #makeNumeric(String) */
238         public static final Pattern NUMERIC_REGEX = Pattern.compile("[0-9]*");
239         
240         /** Describes precisely all strings that are encodable in alphanumeric mode. To test whether a
241          * string {@code s} is encodable: {@code boolean ok = ALPHANUMERIC_REGEX.matcher(s).matches();}.
242          * A string is encodable iff each character is in the following set: 0 to 9, A to Z
243          * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
244          * @see #makeAlphanumeric(String) */
245         public static final Pattern ALPHANUMERIC_REGEX = Pattern.compile("[A-Z0-9 $%*+./:-]*");
246         
247         // The set of all legal characters in alphanumeric mode, where
248         // each character value maps to the index in the string.
249         static final String ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
250         
251         
252         
253         /*---- Public helper enumeration ----*/
254         
255         /**
256          * Describes how a segment's data bits are interpreted.
257          */
258         public enum Mode {
259                 
260                 /*-- Constants --*/
261                 
262                 NUMERIC     (0x1, 10, 12, 14),
263                 ALPHANUMERIC(0x2,  9, 11, 13),
264                 BYTE        (0x4,  8, 16, 16),
265                 KANJI       (0x8,  8, 10, 12),
266                 ECI         (0x7,  0,  0,  0);
267                 
268                 
269                 /*-- Fields --*/
270                 
271                 // The mode indicator bits, which is a uint4 value (range 0 to 15).
272                 final int modeBits;
273                 
274                 // Number of character count bits for three different version ranges.
275                 private final int[] numBitsCharCount;
276                 
277                 
278                 /*-- Constructor --*/
279                 
280                 private Mode(int mode, int... ccbits) {
281                         modeBits = mode;
282                         numBitsCharCount = ccbits;
283                 }
284                 
285                 
286                 /*-- Method --*/
287                 
288                 // Returns the bit width of the character count field for a segment in this mode
289                 // in a QR Code at the given version number. The result is in the range [0, 16].
290                 int numCharCountBits(int ver) {
291                         assert QrCode.MIN_VERSION <= ver && ver <= QrCode.MAX_VERSION;
292                         return numBitsCharCount[(ver + 7) / 17];
293                 }
294                 
295         }
296         
297 }