OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
181 ilm 1
/*
2
 * Copyright 2014-2015 Robin Stuart, Daniel Gredler
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
 * in compliance with the License. You may obtain a copy of the License at
6
 *
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 *
9
 * Unless required by applicable law or agreed to in writing, software distributed under the License
10
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
 * or implied. See the License for the specific language governing permissions and limitations under
12
 * the License.
13
 */
14
 
15
package uk.org.okapibarcode.backend;
16
 
17
import static uk.org.okapibarcode.util.Arrays.contains;
18
import static uk.org.okapibarcode.util.Arrays.insertArray;
19
 
20
import java.awt.geom.Ellipse2D;
21
import java.util.Arrays;
22
 
23
/**
24
 * <p>
25
 * Implements MaxiCode according to ISO 16023:2000.
26
 *
27
 * <p>
28
 * MaxiCode employs a pattern of hexagons around a central 'bulls-eye' finder pattern. Encoding in
29
 * several modes is supported, but encoding in Mode 2 and 3 require primary messages to be set.
30
 * Input characters can be any from the ISO 8859-1 (Latin-1) character set.
31
 *
32
 * <p>
33
 * TODO: Add ECI functionality.
34
 *
35
 * @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
36
 * @author Daniel Gredler
37
 */
38
public class MaxiCode extends Symbol {
39
 
40
    /** MaxiCode module sequence, from ISO/IEC 16023 Figure 5 (30 x 33 data grid). */
41
    private static final int[] MAXICODE_GRID = { 122, 121, 128, 127, 134, 133, 140, 139, 146, 145, 152, 151, 158, 157, 164, 163, 170, 169, 176, 175, 182, 181, 188, 187, 194, 193, 200, 199, 0, 0, 124,
42
            123, 130, 129, 136, 135, 142, 141, 148, 147, 154, 153, 160, 159, 166, 165, 172, 171, 178, 177, 184, 183, 190, 189, 196, 195, 202, 201, 817, 0, 126, 125, 132, 131, 138, 137, 144, 143, 150,
43
            149, 156, 155, 162, 161, 168, 167, 174, 173, 180, 179, 186, 185, 192, 191, 198, 197, 204, 203, 819, 818, 284, 283, 278, 277, 272, 271, 266, 265, 260, 259, 254, 253, 248, 247, 242, 241,
44
            236, 235, 230, 229, 224, 223, 218, 217, 212, 211, 206, 205, 820, 0, 286, 285, 280, 279, 274, 273, 268, 267, 262, 261, 256, 255, 250, 249, 244, 243, 238, 237, 232, 231, 226, 225, 220, 219,
45
            214, 213, 208, 207, 822, 821, 288, 287, 282, 281, 276, 275, 270, 269, 264, 263, 258, 257, 252, 251, 246, 245, 240, 239, 234, 233, 228, 227, 222, 221, 216, 215, 210, 209, 823, 0, 290, 289,
46
            296, 295, 302, 301, 308, 307, 314, 313, 320, 319, 326, 325, 332, 331, 338, 337, 344, 343, 350, 349, 356, 355, 362, 361, 368, 367, 825, 824, 292, 291, 298, 297, 304, 303, 310, 309, 316,
47
            315, 322, 321, 328, 327, 334, 333, 340, 339, 346, 345, 352, 351, 358, 357, 364, 363, 370, 369, 826, 0, 294, 293, 300, 299, 306, 305, 312, 311, 318, 317, 324, 323, 330, 329, 336, 335, 342,
48
            341, 348, 347, 354, 353, 360, 359, 366, 365, 372, 371, 828, 827, 410, 409, 404, 403, 398, 397, 392, 391, 80, 79, 0, 0, 14, 13, 38, 37, 3, 0, 45, 44, 110, 109, 386, 385, 380, 379, 374, 373,
49
            829, 0, 412, 411, 406, 405, 400, 399, 394, 393, 82, 81, 41, 0, 16, 15, 40, 39, 4, 0, 0, 46, 112, 111, 388, 387, 382, 381, 376, 375, 831, 830, 414, 413, 408, 407, 402, 401, 396, 395, 84,
50
            83, 42, 0, 0, 0, 0, 0, 6, 5, 48, 47, 114, 113, 390, 389, 384, 383, 378, 377, 832, 0, 416, 415, 422, 421, 428, 427, 104, 103, 56, 55, 17, 0, 0, 0, 0, 0, 0, 0, 21, 20, 86, 85, 434, 433, 440,
51
            439, 446, 445, 834, 833, 418, 417, 424, 423, 430, 429, 106, 105, 58, 57, 0, 0, 0, 0, 0, 0, 0, 0, 23, 22, 88, 87, 436, 435, 442, 441, 448, 447, 835, 0, 420, 419, 426, 425, 432, 431, 108,
52
            107, 60, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 90, 89, 438, 437, 444, 443, 450, 449, 837, 836, 482, 481, 476, 475, 470, 469, 49, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 54, 53, 464, 463, 458,
53
            457, 452, 451, 838, 0, 484, 483, 478, 477, 472, 471, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 466, 465, 460, 459, 454, 453, 840, 839, 486, 485, 480, 479, 474, 473, 52, 51, 32, 0,
54
            0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 43, 468, 467, 462, 461, 456, 455, 841, 0, 488, 487, 494, 493, 500, 499, 98, 97, 62, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 92, 91, 506, 505, 512, 511, 518,
55
            517, 843, 842, 490, 489, 496, 495, 502, 501, 100, 99, 64, 63, 0, 0, 0, 0, 0, 0, 0, 0, 29, 28, 94, 93, 508, 507, 514, 513, 520, 519, 844, 0, 492, 491, 498, 497, 504, 503, 102, 101, 66, 65,
56
            18, 0, 0, 0, 0, 0, 0, 0, 19, 30, 96, 95, 510, 509, 516, 515, 522, 521, 846, 845, 560, 559, 554, 553, 548, 547, 542, 541, 74, 73, 33, 0, 0, 0, 0, 0, 0, 11, 68, 67, 116, 115, 536, 535, 530,
57
            529, 524, 523, 847, 0, 562, 561, 556, 555, 550, 549, 544, 543, 76, 75, 0, 0, 8, 7, 36, 35, 12, 0, 70, 69, 118, 117, 538, 537, 532, 531, 526, 525, 849, 848, 564, 563, 558, 557, 552, 551,
58
            546, 545, 78, 77, 0, 34, 10, 9, 26, 25, 0, 0, 72, 71, 120, 119, 540, 539, 534, 533, 528, 527, 850, 0, 566, 565, 572, 571, 578, 577, 584, 583, 590, 589, 596, 595, 602, 601, 608, 607, 614,
59
            613, 620, 619, 626, 625, 632, 631, 638, 637, 644, 643, 852, 851, 568, 567, 574, 573, 580, 579, 586, 585, 592, 591, 598, 597, 604, 603, 610, 609, 616, 615, 622, 621, 628, 627, 634, 633,
60
            640, 639, 646, 645, 853, 0, 570, 569, 576, 575, 582, 581, 588, 587, 594, 593, 600, 599, 606, 605, 612, 611, 618, 617, 624, 623, 630, 629, 636, 635, 642, 641, 648, 647, 855, 854, 728, 727,
61
            722, 721, 716, 715, 710, 709, 704, 703, 698, 697, 692, 691, 686, 685, 680, 679, 674, 673, 668, 667, 662, 661, 656, 655, 650, 649, 856, 0, 730, 729, 724, 723, 718, 717, 712, 711, 706, 705,
62
            700, 699, 694, 693, 688, 687, 682, 681, 676, 675, 670, 669, 664, 663, 658, 657, 652, 651, 858, 857, 732, 731, 726, 725, 720, 719, 714, 713, 708, 707, 702, 701, 696, 695, 690, 689, 684,
63
            683, 678, 677, 672, 671, 666, 665, 660, 659, 654, 653, 859, 0, 734, 733, 740, 739, 746, 745, 752, 751, 758, 757, 764, 763, 770, 769, 776, 775, 782, 781, 788, 787, 794, 793, 800, 799, 806,
64
            805, 812, 811, 861, 860, 736, 735, 742, 741, 748, 747, 754, 753, 760, 759, 766, 765, 772, 771, 778, 777, 784, 783, 790, 789, 796, 795, 802, 801, 808, 807, 814, 813, 862, 0, 738, 737, 744,
65
            743, 750, 749, 756, 755, 762, 761, 768, 767, 774, 773, 780, 779, 786, 785, 792, 791, 798, 797, 804, 803, 810, 809, 816, 815, 864, 863 };
66
 
67
    /**
68
     * ASCII character to Code Set mapping, from ISO/IEC 16023 Appendix A. 1 = Set A, 2 = Set B, 3 =
69
     * Set C, 4 = Set D, 5 = Set E. 0 refers to special characters that fit into more than one set
70
     * (e.g. GS).
71
     */
72
    private static final int[] MAXICODE_SET = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1,
73
            1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
74
            2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 4, 5, 3, 4, 3, 5, 5, 4, 4,
75
            3, 3, 3, 4, 3, 5, 4, 4, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
76
            4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 };
77
 
78
    /** ASCII character to symbol value, from ISO/IEC 16023 Appendix A. */
79
    private static final int[] MAXICODE_SYMBOL_CHAR = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 30, 28, 29, 30, 35, 32, 53, 34, 35, 36, 37,
80
            38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 37, 38, 39, 40, 41, 52, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
81
            23, 24, 25, 26, 42, 43, 44, 45, 46, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32, 54, 34, 35, 36, 48, 49, 50, 51, 52, 53, 54, 55,
82
            56, 57, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 36, 37, 37, 38, 39, 40, 41, 42, 43, 38, 44, 37, 39, 38, 45, 46, 40, 41, 39, 40, 41, 42, 42, 47,
83
            43, 44, 43, 44, 45, 45, 46, 47, 46, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32, 33, 34, 35, 36, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
84
            11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 32, 33, 34, 35, 36 };
85
 
86
    private int mode;
87
    private int structuredAppendPosition = 1;
88
    private int structuredAppendTotal = 1;
89
    private String primaryData = "";
90
    private int[] codewords;
91
    private final int[] set = new int[144];
92
    private final int[] character = new int[144];
93
    private final boolean[][] grid = new boolean[33][30];
94
 
95
    /**
96
     * Sets the MaxiCode mode to use. Only modes 2 to 6 are supported.
97
     *
98
     * @param mode the MaxiCode mode to use
99
     */
100
    public void setMode(final int mode) {
101
        if (mode < 2 || mode > 6) {
102
            throw new IllegalArgumentException("Invalid MaxiCode mode: " + mode);
103
        }
104
        this.mode = mode;
105
    }
106
 
107
    /**
108
     * Returns the MaxiCode mode being used. Only modes 2 to 6 are supported.
109
     *
110
     * @return the MaxiCode mode being used
111
     */
112
    public int getMode() {
113
        return this.mode;
114
    }
115
 
116
    /**
117
     * If this MaxiCode symbol is part of a series of MaxiCode symbols appended in a structured
118
     * format, this method sets the position of this symbol in the series. Valid values are 1
119
     * through 8 inclusive.
120
     *
121
     * @param position the position of this MaxiCode symbol in the structured append series
122
     */
123
    public void setStructuredAppendPosition(final int position) {
124
        if (position < 1 || position > 8) {
125
            throw new IllegalArgumentException("Invalid MaxiCode structured append position: " + position);
126
        }
127
        this.structuredAppendPosition = position;
128
    }
129
 
130
    /**
131
     * Returns the position of this MaxiCode symbol in a series of symbols using structured append.
132
     * If this symbol is not part of such a series, this method will return <code>1</code>.
133
     *
134
     * @return the position of this MaxiCode symbol in a series of symbols using structured append
135
     */
136
    public int getStructuredAppendPosition() {
137
        return this.structuredAppendPosition;
138
    }
139
 
140
    /**
141
     * If this MaxiCode symbol is part of a series of MaxiCode symbols appended in a structured
142
     * format, this method sets the total number of symbols in the series. Valid values are 1
143
     * through 8 inclusive. A value of 1 indicates that this symbol is not part of a structured
144
     * append series.
145
     *
146
     * @param total the total number of MaxiCode symbols in the structured append series
147
     */
148
    public void setStructuredAppendTotal(final int total) {
149
        if (total < 1 || total > 8) {
150
            throw new IllegalArgumentException("Invalid MaxiCode structured append total: " + total);
151
        }
152
        this.structuredAppendTotal = total;
153
    }
154
 
155
    /**
156
     * Returns the size of the series of MaxiCode symbols using structured append that this symbol
157
     * is part of. If this symbol is not part of a structured append series, this method will return
158
     * <code>1</code>.
159
     *
160
     * @return size of the series that this symbol is part of
161
     */
162
    public int getStructuredAppendTotal() {
163
        return this.structuredAppendTotal;
164
    }
165
 
166
    /**
167
     * Sets the primary data. Should only be used for modes 2 and 3. Must conform to the following
168
     * structure:
169
     *
170
     * <table summary="Expected primary data structure.">
171
     * <tr>
172
     * <th>Characters</th>
173
     * <th>Meaning</th>
174
     * </tr>
175
     * <tr>
176
     * <td>1-9</td>
177
     * <td>Postal code data which can consist of up to 9 digits (for mode 2) or up to 6 alphanumeric
178
     * characters (for mode 3). Remaining unused characters should be filled with the SPACE
179
     * character (ASCII 32).</td>
180
     * </tr>
181
     * <tr>
182
     * <td>10-12</td>
183
     * <td>Three-digit country code according to ISO-3166.</td>
184
     * </tr>
185
     * <tr>
186
     * <td>13-15</td>
187
     * <td>Three digit service code. This depends on your parcel courier.</td>
188
     * </tr>
189
     * </table>
190
     *
191
     * @param primary the primary data
192
     */
193
    public void setPrimary(final String primary) {
194
        this.primaryData = primary;
195
    }
196
 
197
    /**
198
     * Returns the primary data for this MaxiCode symbol. Should only be used for modes 2 and 3.
199
     *
200
     * @return the primary data for this MaxiCode symbol
201
     */
202
    public String getPrimary() {
203
        return this.primaryData;
204
    }
205
 
206
    /** {@inheritDoc} */
207
    @Override
208
    protected void encode() {
209
 
210
        eciProcess();
211
 
212
        // mode 2 -> mode 3 if postal code isn't strictly numeric
213
        if (this.mode == 2) {
214
            for (int i = 0; i < 10 && i < this.primaryData.length(); i++) {
215
                if (this.primaryData.charAt(i) < '0' || this.primaryData.charAt(i) > '9') {
216
                    this.mode = 3;
217
                    break;
218
                }
219
            }
220
        }
221
 
222
        // initialize the set and character arrays
223
        processText();
224
 
225
        // start building the codeword array, starting with a copy of the character data
226
        // insert primary message if this is a structured carrier message; insert mode otherwise
227
        this.codewords = Arrays.copyOf(this.character, this.character.length);
228
        if (this.mode == 2 || this.mode == 3) {
229
            final int[] primary = getPrimaryCodewords();
230
            this.codewords = insertArray(this.codewords, 0, primary);
231
        } else {
232
            this.codewords = insertArray(this.codewords, 0, new int[] { this.mode });
233
        }
234
 
235
        // insert structured append flag if necessary
236
        if (this.structuredAppendTotal > 1) {
237
 
238
            final int[] flag = new int[2];
239
            flag[0] = 33; // padding
240
            flag[1] = this.structuredAppendPosition - 1 << 3 | this.structuredAppendTotal - 1; // position
241
                                                                                               // +
242
                                                                                               // total
243
 
244
            int index;
245
            if (this.mode == 2 || this.mode == 3) {
246
                index = 10; // first two data symbols in the secondary message
247
            } else {
248
                index = 1; // first two data symbols in the primary message (first symbol at index 0
249
                           // isn't a data symbol)
250
            }
251
 
252
            this.codewords = insertArray(this.codewords, index, flag);
253
        }
254
 
255
        int secondaryMax, secondaryECMax;
256
        if (this.mode == 5) {
257
            // 68 data codewords, 56 error corrections in secondary message
258
            secondaryMax = 68;
259
            secondaryECMax = 56;
260
        } else {
261
            // 84 data codewords, 40 error corrections in secondary message
262
            secondaryMax = 84;
263
            secondaryECMax = 40;
264
        }
265
 
266
        // truncate data codewords to maximum data space available
267
        final int totalMax = secondaryMax + 10;
268
        if (this.codewords.length > totalMax) {
269
            this.codewords = Arrays.copyOfRange(this.codewords, 0, totalMax);
270
        }
271
 
272
        // insert primary error correction between primary message and secondary message (always
273
        // EEC)
274
        final int[] primary = Arrays.copyOfRange(this.codewords, 0, 10);
275
        final int[] primaryCheck = getErrorCorrection(primary, 10);
276
        this.codewords = insertArray(this.codewords, 10, primaryCheck);
277
 
278
        // calculate secondary error correction
279
        final int[] secondary = Arrays.copyOfRange(this.codewords, 20, this.codewords.length);
280
        final int[] secondaryOdd = new int[secondary.length / 2];
281
        final int[] secondaryEven = new int[secondary.length / 2];
282
        for (int i = 0; i < secondary.length; i++) {
283
            if ((i & 1) != 0) { // odd
284
                secondaryOdd[(i - 1) / 2] = secondary[i];
285
            } else { // even
286
                secondaryEven[i / 2] = secondary[i];
287
            }
288
        }
289
        final int[] secondaryECOdd = getErrorCorrection(secondaryOdd, secondaryECMax / 2);
290
        final int[] secondaryECEven = getErrorCorrection(secondaryEven, secondaryECMax / 2);
291
 
292
        // add secondary error correction after secondary message
293
        this.codewords = Arrays.copyOf(this.codewords, this.codewords.length + secondaryECOdd.length + secondaryECEven.length);
294
        for (int i = 0; i < secondaryECOdd.length; i++) {
295
            this.codewords[20 + secondaryMax + 2 * i + 1] = secondaryECOdd[i];
296
        }
297
        for (int i = 0; i < secondaryECEven.length; i++) {
298
            this.codewords[20 + secondaryMax + 2 * i] = secondaryECEven[i];
299
        }
300
 
301
        infoLine("Mode: " + this.mode);
302
        infoLine("ECC Codewords: " + secondaryECMax);
303
        info("Codewords: ");
304
        for (int i = 0; i < this.codewords.length; i++) {
305
            infoSpace(this.codewords[i]);
306
        }
307
        infoLine();
308
 
309
        // copy data into symbol grid
310
        final int[] bit_pattern = new int[7];
311
        for (int i = 0; i < 33; i++) {
312
            for (int j = 0; j < 30; j++) {
313
 
314
                final int block = (MAXICODE_GRID[i * 30 + j] + 5) / 6;
315
                final int bit = (MAXICODE_GRID[i * 30 + j] + 5) % 6;
316
 
317
                if (block != 0) {
318
 
319
                    bit_pattern[0] = (this.codewords[block - 1] & 0x20) >> 5;
320
                    bit_pattern[1] = (this.codewords[block - 1] & 0x10) >> 4;
321
                    bit_pattern[2] = (this.codewords[block - 1] & 0x8) >> 3;
322
                    bit_pattern[3] = (this.codewords[block - 1] & 0x4) >> 2;
323
                    bit_pattern[4] = (this.codewords[block - 1] & 0x2) >> 1;
324
                    bit_pattern[5] = this.codewords[block - 1] & 0x1;
325
 
326
                    if (bit_pattern[bit] != 0) {
327
                        this.grid[i][j] = true;
328
                    } else {
329
                        this.grid[i][j] = false;
330
                    }
331
                }
332
            }
333
        }
334
 
335
        // add orientation markings
336
        this.grid[0][28] = true; // top right filler
337
        this.grid[0][29] = true;
338
        this.grid[9][10] = true; // top left marker
339
        this.grid[9][11] = true;
340
        this.grid[10][11] = true;
341
        this.grid[15][7] = true; // left hand marker
342
        this.grid[16][8] = true;
343
        this.grid[16][20] = true; // right hand marker
344
        this.grid[17][20] = true;
345
        this.grid[22][10] = true; // bottom left marker
346
        this.grid[23][10] = true;
347
        this.grid[22][17] = true; // bottom right marker
348
        this.grid[23][17] = true;
349
 
350
        // the following is provided for compatibility, but the results are not useful
351
        this.row_count = 33;
352
        this.readable = "";
353
        this.pattern = new String[33];
354
        this.row_height = new int[33];
355
        for (int i = 0; i < 33; i++) {
356
            final StringBuilder bin = new StringBuilder(30);
357
            for (int j = 0; j < 30; j++) {
358
                if (this.grid[i][j]) {
359
                    bin.append("1");
360
                } else {
361
                    bin.append("0");
362
                }
363
            }
364
            this.pattern[i] = bin2pat(bin);
365
            this.row_height[i] = 1;
366
        }
367
        this.symbol_height = 72;
368
        this.symbol_width = 74;
369
    }
370
 
371
    /**
372
     * Extracts the postal code, country code and service code from the primary data and returns the
373
     * corresponding primary message codewords.
374
     *
375
     * @return the primary message codewords
376
     */
377
    private int[] getPrimaryCodewords() {
378
 
379
        assert this.mode == 2 || this.mode == 3;
380
 
381
        if (this.primaryData.length() != 15) {
382
            throw new OkapiException("Invalid Primary String");
383
        }
384
 
385
        for (int i = 9; i < 15; i++) { /* check that country code and service are numeric */
386
            if (this.primaryData.charAt(i) < '0' || this.primaryData.charAt(i) > '9') {
387
                throw new OkapiException("Invalid Primary String");
388
            }
389
        }
390
 
391
        String postcode;
392
        if (this.mode == 2) {
393
            postcode = this.primaryData.substring(0, 9);
394
            final int index = postcode.indexOf(' ');
395
            if (index != -1) {
396
                postcode = postcode.substring(0, index);
397
            }
398
        } else {
399
            assert this.mode == 3;
400
            postcode = this.primaryData.substring(0, 6);
401
        }
402
 
403
        final int country = Integer.parseInt(this.primaryData.substring(9, 12));
404
        final int service = Integer.parseInt(this.primaryData.substring(12, 15));
405
 
406
        infoLine("Postal Code: " + postcode);
407
        infoLine("Country Code: " + country);
408
        infoLine("Service: " + service);
409
 
410
        if (this.mode == 2) {
411
            return getMode2PrimaryCodewords(postcode, country, service);
412
        } else {
413
            assert this.mode == 3;
414
            return getMode3PrimaryCodewords(postcode, country, service);
415
        }
416
    }
417
 
418
    /**
419
     * Returns the primary message codewords for mode 2.
420
     *
421
     * @param postcode the postal code
422
     * @param country the country code
423
     * @param service the service code
424
     * @return the primary message, as codewords
425
     */
426
    private static int[] getMode2PrimaryCodewords(String postcode, final int country, final int service) {
427
 
428
        for (int i = 0; i < postcode.length(); i++) {
429
            if (postcode.charAt(i) < '0' || postcode.charAt(i) > '9') {
430
                postcode = postcode.substring(0, i);
431
                break;
432
            }
433
        }
434
 
435
        final int postcodeNum = Integer.parseInt(postcode);
436
 
437
        final int[] primary = new int[10];
438
        primary[0] = (postcodeNum & 0x03) << 4 | 2;
439
        primary[1] = (postcodeNum & 0xfc) >> 2;
440
        primary[2] = (postcodeNum & 0x3f00) >> 8;
441
        primary[3] = (postcodeNum & 0xfc000) >> 14;
442
        primary[4] = (postcodeNum & 0x3f00000) >> 20;
443
        primary[5] = (postcodeNum & 0x3c000000) >> 26 | (postcode.length() & 0x3) << 4;
444
        primary[6] = (postcode.length() & 0x3c) >> 2 | (country & 0x3) << 4;
445
        primary[7] = (country & 0xfc) >> 2;
446
        primary[8] = (country & 0x300) >> 8 | (service & 0xf) << 2;
447
        primary[9] = (service & 0x3f0) >> 4;
448
 
449
        return primary;
450
    }
451
 
452
    /**
453
     * Returns the primary message codewords for mode 3.
454
     *
455
     * @param postcode the postal code
456
     * @param country the country code
457
     * @param service the service code
458
     * @return the primary message, as codewords
459
     */
460
    private static int[] getMode3PrimaryCodewords(String postcode, final int country, final int service) {
461
 
462
        final int[] postcodeNums = new int[postcode.length()];
463
 
464
        postcode = postcode.toUpperCase();
465
        for (int i = 0; i < postcodeNums.length; i++) {
466
            postcodeNums[i] = postcode.charAt(i);
467
            if (postcode.charAt(i) >= 'A' && postcode.charAt(i) <= 'Z') {
468
                // (Capital) letters shifted to Code Set A values
469
                postcodeNums[i] -= 64;
470
            }
471
            if (postcodeNums[i] == 27 || postcodeNums[i] == 31 || postcodeNums[i] == 33 || postcodeNums[i] >= 59) {
472
                // Not a valid postal code character, use space instead
473
                postcodeNums[i] = 32;
474
            }
475
            // Input characters lower than 27 (NUL - SUB) in postal code are interpreted as capital
476
            // letters in Code Set A (e.g. LF becomes 'J')
477
        }
478
 
479
        final int[] primary = new int[10];
480
        primary[0] = (postcodeNums[5] & 0x03) << 4 | 3;
481
        primary[1] = (postcodeNums[4] & 0x03) << 4 | (postcodeNums[5] & 0x3c) >> 2;
482
        primary[2] = (postcodeNums[3] & 0x03) << 4 | (postcodeNums[4] & 0x3c) >> 2;
483
        primary[3] = (postcodeNums[2] & 0x03) << 4 | (postcodeNums[3] & 0x3c) >> 2;
484
        primary[4] = (postcodeNums[1] & 0x03) << 4 | (postcodeNums[2] & 0x3c) >> 2;
485
        primary[5] = (postcodeNums[0] & 0x03) << 4 | (postcodeNums[1] & 0x3c) >> 2;
486
        primary[6] = (postcodeNums[0] & 0x3c) >> 2 | (country & 0x3) << 4;
487
        primary[7] = (country & 0xfc) >> 2;
488
        primary[8] = (country & 0x300) >> 8 | (service & 0xf) << 2;
489
        primary[9] = (service & 0x3f0) >> 4;
490
 
491
        return primary;
492
    }
493
 
494
    /**
495
     * Formats text according to Appendix A, populating the {@link #set} and {@link #character}
496
     * arrays.
497
     *
498
     * @return true if the content fits in this symbol and was formatted; false otherwise
499
     */
500
    private void processText() {
501
 
502
        int length = this.content.length();
503
        int i, j, count, current_set;
504
 
505
        if (length > 138) {
506
            throw new OkapiException("Input data too long");
507
        }
508
 
509
        for (i = 0; i < 144; i++) {
510
            this.set[i] = -1;
511
            this.character[i] = 0;
512
        }
513
 
514
        for (i = 0; i < length; i++) {
515
            /*
516
             * Look up characters in table from Appendix A - this gives value and code set for most
517
             * characters
518
             */
519
            this.set[i] = MAXICODE_SET[this.inputData[i]];
520
            this.character[i] = MAXICODE_SYMBOL_CHAR[this.inputData[i]];
521
        }
522
 
523
        // If a character can be represented in more than one code set, pick which version to use.
524
        if (this.set[0] == 0) {
525
            if (this.character[0] == 13) {
526
                this.character[0] = 0;
527
            }
528
            this.set[0] = 1;
529
        }
530
 
531
        for (i = 1; i < length; i++) {
532
            if (this.set[i] == 0) {
533
                /* Special character that can be represented in more than one code set. */
534
                if (this.character[i] == 13) {
535
                    /* Carriage Return */
536
                    this.set[i] = bestSurroundingSet(i, length, 1, 5);
537
                    if (this.set[i] == 5) {
538
                        this.character[i] = 13;
539
                    } else {
540
                        this.character[i] = 0;
541
                    }
542
                } else if (this.character[i] == 28) {
543
                    /* FS */
544
                    this.set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5);
545
                    if (this.set[i] == 5) {
546
                        this.character[i] = 32;
547
                    }
548
                } else if (this.character[i] == 29) {
549
                    /* GS */
550
                    this.set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5);
551
                    if (this.set[i] == 5) {
552
                        this.character[i] = 33;
553
                    }
554
                } else if (this.character[i] == 30) {
555
                    /* RS */
556
                    this.set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5);
557
                    if (this.set[i] == 5) {
558
                        this.character[i] = 34;
559
                    }
560
                } else if (this.character[i] == 32) {
561
                    /* Space */
562
                    this.set[i] = bestSurroundingSet(i, length, 1, 2, 3, 4, 5);
563
                    if (this.set[i] == 1) {
564
                        this.character[i] = 32;
565
                    } else if (this.set[i] == 2) {
566
                        this.character[i] = 47;
567
                    } else {
568
                        this.character[i] = 59;
569
                    }
570
                } else if (this.character[i] == 44) {
571
                    /* Comma */
572
                    this.set[i] = bestSurroundingSet(i, length, 1, 2);
573
                    if (this.set[i] == 2) {
574
                        this.character[i] = 48;
575
                    }
576
                } else if (this.character[i] == 46) {
577
                    /* Full Stop */
578
                    this.set[i] = bestSurroundingSet(i, length, 1, 2);
579
                    if (this.set[i] == 2) {
580
                        this.character[i] = 49;
581
                    }
582
                } else if (this.character[i] == 47) {
583
                    /* Slash */
584
                    this.set[i] = bestSurroundingSet(i, length, 1, 2);
585
                    if (this.set[i] == 2) {
586
                        this.character[i] = 50;
587
                    }
588
                } else if (this.character[i] == 58) {
589
                    /* Colon */
590
                    this.set[i] = bestSurroundingSet(i, length, 1, 2);
591
                    if (this.set[i] == 2) {
592
                        this.character[i] = 51;
593
                    }
594
                }
595
            }
596
        }
597
 
598
        for (i = length; i < this.set.length; i++) {
599
            /* Add the padding */
600
            if (this.set[length - 1] == 2) {
601
                this.set[i] = 2;
602
            } else {
603
                this.set[i] = 1;
604
            }
605
            this.character[i] = 33;
606
        }
607
 
608
        /*
609
         * Find candidates for number compression (not allowed in primary message in modes 2 and 3).
610
         */
611
        if (this.mode == 2 || this.mode == 3) {
612
            j = 9;
613
        } else {
614
            j = 0;
615
        }
616
        count = 0;
617
        for (i = j; i < 143; i++) {
618
            if (this.set[i] == 1 && this.character[i] >= 48 && this.character[i] <= 57) {
619
                /* Character is a number */
620
                count++;
621
            } else {
622
                count = 0;
623
            }
624
            if (count == 9) {
625
                /* Nine digits in a row can be compressed */
626
                this.set[i] = 6;
627
                this.set[i - 1] = 6;
628
                this.set[i - 2] = 6;
629
                this.set[i - 3] = 6;
630
                this.set[i - 4] = 6;
631
                this.set[i - 5] = 6;
632
                this.set[i - 6] = 6;
633
                this.set[i - 7] = 6;
634
                this.set[i - 8] = 6;
635
                count = 0;
636
            }
637
        }
638
 
639
        /* Add shift and latch characters */
640
        current_set = 1;
641
        i = 0;
642
        do {
643
            if (this.set[i] != current_set && this.set[i] != 6) {
644
                switch (this.set[i]) {
645
                case 1:
646
                    if (i + 1 < this.set.length && this.set[i + 1] == 1) {
647
                        if (i + 2 < this.set.length && this.set[i + 2] == 1) {
648
                            if (i + 3 < this.set.length && this.set[i + 3] == 1) {
649
                                /* Latch A */
650
                                insert(i, 63);
651
                                current_set = 1;
652
                                length++;
653
                                i += 3;
654
                            } else {
655
                                /* 3 Shift A */
656
                                insert(i, 57);
657
                                length++;
658
                                i += 2;
659
                            }
660
                        } else {
661
                            /* 2 Shift A */
662
                            insert(i, 56);
663
                            length++;
664
                            i++;
665
                        }
666
                    } else {
667
                        /* Shift A */
668
                        insert(i, 59);
669
                        length++;
670
                    }
671
                    break;
672
                case 2:
673
                    if (i + 1 < this.set.length && this.set[i + 1] == 2) {
674
                        /* Latch B */
675
                        insert(i, 63);
676
                        current_set = 2;
677
                        length++;
678
                        i++;
679
                    } else {
680
                        /* Shift B */
681
                        insert(i, 59);
682
                        length++;
683
                    }
684
                    break;
685
                case 3:
686
                    if (i + 3 < this.set.length && this.set[i + 1] == 3 && this.set[i + 2] == 3 && this.set[i + 3] == 3) {
687
                        /* Lock In C */
688
                        insert(i, 60);
689
                        insert(i, 60);
690
                        current_set = 3;
691
                        length++;
692
                        i += 3;
693
                    } else {
694
                        /* Shift C */
695
                        insert(i, 60);
696
                        length++;
697
                    }
698
                    break;
699
                case 4:
700
                    if (i + 3 < this.set.length && this.set[i + 1] == 4 && this.set[i + 2] == 4 && this.set[i + 3] == 4) {
701
                        /* Lock In D */
702
                        insert(i, 61);
703
                        insert(i, 61);
704
                        current_set = 4;
705
                        length++;
706
                        i += 3;
707
                    } else {
708
                        /* Shift D */
709
                        insert(i, 61);
710
                        length++;
711
                    }
712
                    break;
713
                case 5:
714
                    if (i + 3 < this.set.length && this.set[i + 1] == 5 && this.set[i + 2] == 5 && this.set[i + 3] == 5) {
715
                        /* Lock In E */
716
                        insert(i, 62);
717
                        insert(i, 62);
718
                        current_set = 5;
719
                        length++;
720
                        i += 3;
721
                    } else {
722
                        /* Shift E */
723
                        insert(i, 62);
724
                        length++;
725
                    }
726
                    break;
727
                default:
728
                    throw new OkapiException("Unexpected set " + this.set[i] + " at index " + i + ".");
729
                }
730
                i++;
731
            }
732
            i++;
733
        } while (i < this.set.length);
734
 
735
        /* Number compression has not been forgotten! It's handled below. */
736
        i = 0;
737
        do {
738
            if (this.set[i] == 6) {
739
                /* Number compression */
740
                int value = 0;
741
                for (j = 0; j < 9; j++) {
742
                    value *= 10;
743
                    value += this.character[i + j] - '0';
744
                }
745
                this.character[i] = 31; /* NS */
746
                this.character[i + 1] = (value & 0x3f000000) >> 24;
747
                this.character[i + 2] = (value & 0xfc0000) >> 18;
748
                this.character[i + 3] = (value & 0x3f000) >> 12;
749
                this.character[i + 4] = (value & 0xfc0) >> 6;
750
                this.character[i + 5] = value & 0x3f;
751
                i += 6;
752
                for (j = i; j < 140; j++) {
753
                    this.set[j] = this.set[j + 3];
754
                    this.character[j] = this.character[j + 3];
755
                }
756
                length -= 3;
757
            } else {
758
                i++;
759
            }
760
        } while (i < this.set.length);
761
 
762
        /* Inject ECI codes to beginning of data, according to Table 3 */
763
        if (this.eciMode != 3) {
764
            insert(0, 27); // ECI
765
 
766
            if (this.eciMode >= 0 && this.eciMode <= 31) {
767
                insert(1, this.eciMode & 0x1F);
768
                length += 2;
769
            }
770
 
771
            if (this.eciMode >= 32 && this.eciMode <= 1023) {
772
                insert(1, 0x20 + (this.eciMode >> 6));
773
                insert(2, this.eciMode & 0x3F);
774
                length += 3;
775
            }
776
 
777
            if (this.eciMode >= 1024 && this.eciMode <= 32767) {
778
                insert(1, 0x30 + (this.eciMode >> 12));
779
                insert(2, this.eciMode >> 6 & 0x3F);
780
                insert(3, this.eciMode & 0x3F);
781
                length += 4;
782
            }
783
 
784
            if (this.eciMode >= 32768 && this.eciMode <= 999999) {
785
                insert(1, 0x38 + (this.eciMode >> 18));
786
                insert(2, this.eciMode >> 12 & 0x3F);
787
                insert(3, this.eciMode >> 6 & 0x3F);
788
                insert(4, this.eciMode & 0x3F);
789
                length += 5;
790
            }
791
        }
792
 
793
        /* Make sure we haven't exceeded the maximum data length. */
794
        int maxLength;
795
        if (this.mode == 2 || this.mode == 3) {
796
            maxLength = 84;
797
        } else if (this.mode == 4 || this.mode == 6) {
798
            maxLength = 93;
799
        } else if (this.mode == 5) {
800
            maxLength = 77;
801
        } else {
802
            maxLength = 0; // impossible
803
        }
804
        if (length > maxLength) {
805
            throw new OkapiException("Input data too long");
806
        }
807
    }
808
 
809
    /**
810
     * Guesses the best set to use at the specified index by looking at the surrounding sets. In
811
     * general, characters in lower-numbered sets are more common, so we choose them if we can. If
812
     * no good surrounding sets can be found, the default value returned is the first value from the
813
     * valid set.
814
     *
815
     * @param index the current index
816
     * @param length the maximum length to look at
817
     * @param valid the valid sets for this index
818
     * @return the best set to use at the specified index
819
     */
820
    private int bestSurroundingSet(final int index, final int length, final int... valid) {
821
        final int option1 = this.set[index - 1];
822
        if (index + 1 < length) {
823
            // we have two options to check
824
            final int option2 = this.set[index + 1];
825
            if (contains(valid, option1) && contains(valid, option2)) {
826
                return Math.min(option1, option2);
827
            } else if (contains(valid, option1)) {
828
                return option1;
829
            } else if (contains(valid, option2)) {
830
                return option2;
831
            } else {
832
                return valid[0];
833
            }
834
        } else {
835
            // we only have one option to check
836
            if (contains(valid, option1)) {
837
                return option1;
838
            } else {
839
                return valid[0];
840
            }
841
        }
842
    }
843
 
844
    /**
845
     * Moves everything up so that the specified shift or latch character can be inserted.
846
     *
847
     * @param position the position beyond which everything needs to be shifted
848
     * @param c the latch or shift character to insert at the specified position, after everything
849
     *        has been shifted
850
     */
851
    private void insert(final int position, final int c) {
852
        for (int i = 143; i > position; i--) {
853
            this.set[i] = this.set[i - 1];
854
            this.character[i] = this.character[i - 1];
855
        }
856
        this.character[position] = c;
857
    }
858
 
859
    /**
860
     * Returns the error correction codewords for the specified data codewords.
861
     *
862
     * @param codewords the codewords that we need error correction codewords for
863
     * @param ecclen the number of error correction codewords needed
864
     * @return the error correction codewords for the specified data codewords
865
     */
866
    private static int[] getErrorCorrection(final int[] codewords, final int ecclen) {
867
 
868
        final ReedSolomon rs = new ReedSolomon();
869
        rs.init_gf(0x43);
870
        rs.init_code(ecclen, 1);
871
        rs.encode(codewords.length, codewords);
872
 
873
        final int[] results = new int[ecclen];
874
        for (int i = 0; i < ecclen; i++) {
875
            results[i] = rs.getResult(results.length - 1 - i);
876
        }
877
 
878
        return results;
879
    }
880
 
881
    /** {@inheritDoc} */
882
    @Override
883
    protected void plotSymbol() {
884
 
885
        // hexagons
886
        for (int row = 0; row < 33; row++) {
887
            for (int col = 0; col < 30; col++) {
888
                if (this.grid[row][col]) {
889
                    double x = 2.46 * col + 1.23;
890
                    if ((row & 1) != 0) {
891
                        x += 1.23;
892
                    }
893
                    final double y = 2.135 * row + 1.43;
894
                    this.hexagons.add(new Hexagon(x, y));
895
                }
896
            }
897
        }
898
 
899
        // circles
900
        final double[] radii = { 10.85, 8.97, 7.10, 5.22, 3.31, 1.43 };
901
        for (int i = 0; i < radii.length; i++) {
902
            final Ellipse2D.Double circle = new Ellipse2D.Double();
903
            circle.setFrameFromCenter(35.76, 35.60, 35.76 + radii[i], 35.60 + radii[i]);
904
            this.target.add(circle);
905
        }
906
    }
907
 
908
    /** {@inheritDoc} */
909
    @Override
910
    protected int[] getCodewords() {
911
        return this.codewords;
912
    }
913
}