1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.codec.language;
19
20 import org.apache.commons.codec.EncoderException;
21 import org.apache.commons.codec.StringEncoder;
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 public class DoubleMetaphone implements StringEncoder {
38
39
40
41
42 private static final String VOWELS = "AEIOUY";
43
44
45
46
47 private static final String[] SILENT_START =
48 { "GN", "KN", "PN", "WR", "PS" };
49 private static final String[] L_R_N_M_B_H_F_V_W_SPACE =
50 { "L", "R", "N", "M", "B", "H", "F", "V", "W", " " };
51 private static final String[] ES_EP_EB_EL_EY_IB_IL_IN_IE_EI_ER =
52 { "ES", "EP", "EB", "EL", "EY", "IB", "IL", "IN", "IE", "EI", "ER" };
53 private static final String[] L_T_K_S_N_M_B_Z =
54 { "L", "T", "K", "S", "N", "M", "B", "Z" };
55
56
57
58
59 private int maxCodeLen = 4;
60
61
62
63
64 public DoubleMetaphone() {
65 super();
66 }
67
68
69
70
71
72
73
74 public String doubleMetaphone(final String value) {
75 return doubleMetaphone(value, false);
76 }
77
78
79
80
81
82
83
84
85 public String doubleMetaphone(String value, final boolean alternate) {
86 value = cleanInput(value);
87 if (value == null) {
88 return null;
89 }
90
91 final boolean slavoGermanic = isSlavoGermanic(value);
92 int index = isSilentStart(value) ? 1 : 0;
93
94 final DoubleMetaphoneResult result = new DoubleMetaphoneResult(this.getMaxCodeLen());
95
96 while (!result.isComplete() && index <= value.length() - 1) {
97 switch (value.charAt(index)) {
98 case 'A':
99 case 'E':
100 case 'I':
101 case 'O':
102 case 'U':
103 case 'Y':
104 index = handleAEIOUY(result, index);
105 break;
106 case 'B':
107 result.append('P');
108 index = charAt(value, index + 1) == 'B' ? index + 2 : index + 1;
109 break;
110 case '\u00C7':
111
112 result.append('S');
113 index++;
114 break;
115 case 'C':
116 index = handleC(value, result, index);
117 break;
118 case 'D':
119 index = handleD(value, result, index);
120 break;
121 case 'F':
122 result.append('F');
123 index = charAt(value, index + 1) == 'F' ? index + 2 : index + 1;
124 break;
125 case 'G':
126 index = handleG(value, result, index, slavoGermanic);
127 break;
128 case 'H':
129 index = handleH(value, result, index);
130 break;
131 case 'J':
132 index = handleJ(value, result, index, slavoGermanic);
133 break;
134 case 'K':
135 result.append('K');
136 index = charAt(value, index + 1) == 'K' ? index + 2 : index + 1;
137 break;
138 case 'L':
139 index = handleL(value, result, index);
140 break;
141 case 'M':
142 result.append('M');
143 index = conditionM0(value, index) ? index + 2 : index + 1;
144 break;
145 case 'N':
146 result.append('N');
147 index = charAt(value, index + 1) == 'N' ? index + 2 : index + 1;
148 break;
149 case '\u00D1':
150
151 result.append('N');
152 index++;
153 break;
154 case 'P':
155 index = handleP(value, result, index);
156 break;
157 case 'Q':
158 result.append('K');
159 index = charAt(value, index + 1) == 'Q' ? index + 2 : index + 1;
160 break;
161 case 'R':
162 index = handleR(value, result, index, slavoGermanic);
163 break;
164 case 'S':
165 index = handleS(value, result, index, slavoGermanic);
166 break;
167 case 'T':
168 index = handleT(value, result, index);
169 break;
170 case 'V':
171 result.append('F');
172 index = charAt(value, index + 1) == 'V' ? index + 2 : index + 1;
173 break;
174 case 'W':
175 index = handleW(value, result, index);
176 break;
177 case 'X':
178 index = handleX(value, result, index);
179 break;
180 case 'Z':
181 index = handleZ(value, result, index, slavoGermanic);
182 break;
183 default:
184 index++;
185 break;
186 }
187 }
188
189 return alternate ? result.getAlternate() : result.getPrimary();
190 }
191
192
193
194
195
196
197
198
199
200 @Override
201 public Object encode(final Object obj) throws EncoderException {
202 if (!(obj instanceof String)) {
203 throw new EncoderException("DoubleMetaphone encode parameter is not of type String");
204 }
205 return doubleMetaphone((String) obj);
206 }
207
208
209
210
211
212
213
214 @Override
215 public String encode(final String value) {
216 return doubleMetaphone(value);
217 }
218
219
220
221
222
223
224
225
226
227
228
229 public boolean isDoubleMetaphoneEqual(final String value1, final String value2) {
230 return isDoubleMetaphoneEqual(value1, value2, false);
231 }
232
233
234
235
236
237
238
239
240
241
242
243 public boolean isDoubleMetaphoneEqual(final String value1, final String value2, final boolean alternate) {
244 return doubleMetaphone(value1, alternate).equals(doubleMetaphone(value2, alternate));
245 }
246
247
248
249
250
251 public int getMaxCodeLen() {
252 return this.maxCodeLen;
253 }
254
255
256
257
258
259 public void setMaxCodeLen(final int maxCodeLen) {
260 this.maxCodeLen = maxCodeLen;
261 }
262
263
264
265
266
267
268 private int handleAEIOUY(final DoubleMetaphoneResult result, final int index) {
269 if (index == 0) {
270 result.append('A');
271 }
272 return index + 1;
273 }
274
275
276
277
278 private int handleC(final String value, final DoubleMetaphoneResult result, int index) {
279 if (conditionC0(value, index)) {
280 result.append('K');
281 index += 2;
282 } else if (index == 0 && contains(value, index, 6, "CAESAR")) {
283 result.append('S');
284 index += 2;
285 } else if (contains(value, index, 2, "CH")) {
286 index = handleCH(value, result, index);
287 } else if (contains(value, index, 2, "CZ") &&
288 !contains(value, index - 2, 4, "WICZ")) {
289
290 result.append('S', 'X');
291 index += 2;
292 } else if (contains(value, index + 1, 3, "CIA")) {
293
294 result.append('X');
295 index += 3;
296 } else if (contains(value, index, 2, "CC") &&
297 !(index == 1 && charAt(value, 0) == 'M')) {
298
299 return handleCC(value, result, index);
300 } else if (contains(value, index, 2, "CK", "CG", "CQ")) {
301 result.append('K');
302 index += 2;
303 } else if (contains(value, index, 2, "CI", "CE", "CY")) {
304
305 if (contains(value, index, 3, "CIO", "CIE", "CIA")) {
306 result.append('S', 'X');
307 } else {
308 result.append('S');
309 }
310 index += 2;
311 } else {
312 result.append('K');
313 if (contains(value, index + 1, 2, " C", " Q", " G")) {
314
315 index += 3;
316 } else if (contains(value, index + 1, 1, "C", "K", "Q") &&
317 !contains(value, index + 1, 2, "CE", "CI")) {
318 index += 2;
319 } else {
320 index++;
321 }
322 }
323
324 return index;
325 }
326
327
328
329
330 private int handleCC(final String value, final DoubleMetaphoneResult result, int index) {
331 if (contains(value, index + 2, 1, "I", "E", "H") &&
332 !contains(value, index + 2, 2, "HU")) {
333
334 if ((index == 1 && charAt(value, index - 1) == 'A') ||
335 contains(value, index - 1, 5, "UCCEE", "UCCES")) {
336
337 result.append("KS");
338 } else {
339
340 result.append('X');
341 }
342 index += 3;
343 } else {
344 result.append('K');
345 index += 2;
346 }
347
348 return index;
349 }
350
351
352
353
354 private int handleCH(final String value, final DoubleMetaphoneResult result, final int index) {
355 if (index > 0 && contains(value, index, 4, "CHAE")) {
356 result.append('K', 'X');
357 return index + 2;
358 } else if (conditionCH0(value, index)) {
359
360 result.append('K');
361 return index + 2;
362 } else if (conditionCH1(value, index)) {
363
364 result.append('K');
365 return index + 2;
366 } else {
367 if (index > 0) {
368 if (contains(value, 0, 2, "MC")) {
369 result.append('K');
370 } else {
371 result.append('X', 'K');
372 }
373 } else {
374 result.append('X');
375 }
376 return index + 2;
377 }
378 }
379
380
381
382
383 private int handleD(final String value, final DoubleMetaphoneResult result, int index) {
384 if (contains(value, index, 2, "DG")) {
385
386 if (contains(value, index + 2, 1, "I", "E", "Y")) {
387 result.append('J');
388 index += 3;
389
390 } else {
391 result.append("TK");
392 index += 2;
393 }
394 } else if (contains(value, index, 2, "DT", "DD")) {
395 result.append('T');
396 index += 2;
397 } else {
398 result.append('T');
399 index++;
400 }
401 return index;
402 }
403
404
405
406
407 private int handleG(final String value, final DoubleMetaphoneResult result, int index,
408 final boolean slavoGermanic) {
409 if (charAt(value, index + 1) == 'H') {
410 index = handleGH(value, result, index);
411 } else if (charAt(value, index + 1) == 'N') {
412 if (index == 1 && isVowel(charAt(value, 0)) && !slavoGermanic) {
413 result.append("KN", "N");
414 } else if (!contains(value, index + 2, 2, "EY") &&
415 charAt(value, index + 1) != 'Y' && !slavoGermanic) {
416 result.append("N", "KN");
417 } else {
418 result.append("KN");
419 }
420 index = index + 2;
421 } else if (contains(value, index + 1, 2, "LI") && !slavoGermanic) {
422 result.append("KL", "L");
423 index += 2;
424 } else if (index == 0 &&
425 (charAt(value, index + 1) == 'Y' ||
426 contains(value, index + 1, 2, ES_EP_EB_EL_EY_IB_IL_IN_IE_EI_ER))) {
427
428 result.append('K', 'J');
429 index += 2;
430 } else if ((contains(value, index + 1, 2, "ER") ||
431 charAt(value, index + 1) == 'Y') &&
432 !contains(value, 0, 6, "DANGER", "RANGER", "MANGER") &&
433 !contains(value, index - 1, 1, "E", "I") &&
434 !contains(value, index - 1, 3, "RGY", "OGY")) {
435
436 result.append('K', 'J');
437 index += 2;
438 } else if (contains(value, index + 1, 1, "E", "I", "Y") ||
439 contains(value, index - 1, 4, "AGGI", "OGGI")) {
440
441 if (contains(value, 0 ,4, "VAN ", "VON ") ||
442 contains(value, 0, 3, "SCH") ||
443 contains(value, index + 1, 2, "ET")) {
444
445 result.append('K');
446 } else if (contains(value, index + 1, 3, "IER")) {
447 result.append('J');
448 } else {
449 result.append('J', 'K');
450 }
451 index += 2;
452 } else if (charAt(value, index + 1) == 'G') {
453 index += 2;
454 result.append('K');
455 } else {
456 index++;
457 result.append('K');
458 }
459 return index;
460 }
461
462
463
464
465 private int handleGH(final String value, final DoubleMetaphoneResult result, int index) {
466 if (index > 0 && !isVowel(charAt(value, index - 1))) {
467 result.append('K');
468 index += 2;
469 } else if (index == 0) {
470 if (charAt(value, index + 2) == 'I') {
471 result.append('J');
472 } else {
473 result.append('K');
474 }
475 index += 2;
476 } else if ((index > 1 && contains(value, index - 2, 1, "B", "H", "D")) ||
477 (index > 2 && contains(value, index - 3, 1, "B", "H", "D")) ||
478 (index > 3 && contains(value, index - 4, 1, "B", "H"))) {
479
480 index += 2;
481 } else {
482 if (index > 2 && charAt(value, index - 1) == 'U' &&
483 contains(value, index - 3, 1, "C", "G", "L", "R", "T")) {
484
485 result.append('F');
486 } else if (index > 0 && charAt(value, index - 1) != 'I') {
487 result.append('K');
488 }
489 index += 2;
490 }
491 return index;
492 }
493
494
495
496
497 private int handleH(final String value, final DoubleMetaphoneResult result, int index) {
498
499 if ((index == 0 || isVowel(charAt(value, index - 1))) &&
500 isVowel(charAt(value, index + 1))) {
501 result.append('H');
502 index += 2;
503
504 } else {
505 index++;
506 }
507 return index;
508 }
509
510
511
512
513 private int handleJ(final String value, final DoubleMetaphoneResult result, int index,
514 final boolean slavoGermanic) {
515 if (contains(value, index, 4, "JOSE") || contains(value, 0, 4, "SAN ")) {
516
517 if ((index == 0 && (charAt(value, index + 4) == ' ') ||
518 value.length() == 4) || contains(value, 0, 4, "SAN ")) {
519 result.append('H');
520 } else {
521 result.append('J', 'H');
522 }
523 index++;
524 } else {
525 if (index == 0 && !contains(value, index, 4, "JOSE")) {
526 result.append('J', 'A');
527 } else if (isVowel(charAt(value, index - 1)) && !slavoGermanic &&
528 (charAt(value, index + 1) == 'A' || charAt(value, index + 1) == 'O')) {
529 result.append('J', 'H');
530 } else if (index == value.length() - 1) {
531 result.append('J', ' ');
532 } else if (!contains(value, index + 1, 1, L_T_K_S_N_M_B_Z) &&
533 !contains(value, index - 1, 1, "S", "K", "L")) {
534 result.append('J');
535 }
536
537 if (charAt(value, index + 1) == 'J') {
538 index += 2;
539 } else {
540 index++;
541 }
542 }
543 return index;
544 }
545
546
547
548
549 private int handleL(final String value, final DoubleMetaphoneResult result, int index) {
550 if (charAt(value, index + 1) == 'L') {
551 if (conditionL0(value, index)) {
552 result.appendPrimary('L');
553 } else {
554 result.append('L');
555 }
556 index += 2;
557 } else {
558 index++;
559 result.append('L');
560 }
561 return index;
562 }
563
564
565
566
567 private int handleP(final String value, final DoubleMetaphoneResult result, int index) {
568 if (charAt(value, index + 1) == 'H') {
569 result.append('F');
570 index += 2;
571 } else {
572 result.append('P');
573 index = contains(value, index + 1, 1, "P", "B") ? index + 2 : index + 1;
574 }
575 return index;
576 }
577
578
579
580
581 private int handleR(final String value, final DoubleMetaphoneResult result, final int index,
582 final boolean slavoGermanic) {
583 if (index == value.length() - 1 && !slavoGermanic &&
584 contains(value, index - 2, 2, "IE") &&
585 !contains(value, index - 4, 2, "ME", "MA")) {
586 result.appendAlternate('R');
587 } else {
588 result.append('R');
589 }
590 return charAt(value, index + 1) == 'R' ? index + 2 : index + 1;
591 }
592
593
594
595
596 private int handleS(final String value, final DoubleMetaphoneResult result, int index,
597 final boolean slavoGermanic) {
598 if (contains(value, index - 1, 3, "ISL", "YSL")) {
599
600 index++;
601 } else if (index == 0 && contains(value, index, 5, "SUGAR")) {
602
603 result.append('X', 'S');
604 index++;
605 } else if (contains(value, index, 2, "SH")) {
606 if (contains(value, index + 1, 4, "HEIM", "HOEK", "HOLM", "HOLZ")) {
607
608 result.append('S');
609 } else {
610 result.append('X');
611 }
612 index += 2;
613 } else if (contains(value, index, 3, "SIO", "SIA") || contains(value, index, 4, "SIAN")) {
614
615 if (slavoGermanic) {
616 result.append('S');
617 } else {
618 result.append('S', 'X');
619 }
620 index += 3;
621 } else if ((index == 0 && contains(value, index + 1, 1, "M", "N", "L", "W")) ||
622 contains(value, index + 1, 1, "Z")) {
623
624
625
626
627 result.append('S', 'X');
628 index = contains(value, index + 1, 1, "Z") ? index + 2 : index + 1;
629 } else if (contains(value, index, 2, "SC")) {
630 index = handleSC(value, result, index);
631 } else {
632 if (index == value.length() - 1 && contains(value, index - 2, 2, "AI", "OI")) {
633
634 result.appendAlternate('S');
635 } else {
636 result.append('S');
637 }
638 index = contains(value, index + 1, 1, "S", "Z") ? index + 2 : index + 1;
639 }
640 return index;
641 }
642
643
644
645
646 private int handleSC(final String value, final DoubleMetaphoneResult result, final int index) {
647 if (charAt(value, index + 2) == 'H') {
648
649 if (contains(value, index + 3, 2, "OO", "ER", "EN", "UY", "ED", "EM")) {
650
651 if (contains(value, index + 3, 2, "ER", "EN")) {
652
653 result.append("X", "SK");
654 } else {
655 result.append("SK");
656 }
657 } else {
658 if (index == 0 && !isVowel(charAt(value, 3)) && charAt(value, 3) != 'W') {
659 result.append('X', 'S');
660 } else {
661 result.append('X');
662 }
663 }
664 } else if (contains(value, index + 2, 1, "I", "E", "Y")) {
665 result.append('S');
666 } else {
667 result.append("SK");
668 }
669 return index + 3;
670 }
671
672
673
674
675 private int handleT(final String value, final DoubleMetaphoneResult result, int index) {
676 if (contains(value, index, 4, "TION")) {
677 result.append('X');
678 index += 3;
679 } else if (contains(value, index, 3, "TIA", "TCH")) {
680 result.append('X');
681 index += 3;
682 } else if (contains(value, index, 2, "TH") || contains(value, index, 3, "TTH")) {
683 if (contains(value, index + 2, 2, "OM", "AM") ||
684
685 contains(value, 0, 4, "VAN ", "VON ") ||
686 contains(value, 0, 3, "SCH")) {
687 result.append('T');
688 } else {
689 result.append('0', 'T');
690 }
691 index += 2;
692 } else {
693 result.append('T');
694 index = contains(value, index + 1, 1, "T", "D") ? index + 2 : index + 1;
695 }
696 return index;
697 }
698
699
700
701
702 private int handleW(final String value, final DoubleMetaphoneResult result, int index) {
703 if (contains(value, index, 2, "WR")) {
704
705 result.append('R');
706 index += 2;
707 } else {
708 if (index == 0 && (isVowel(charAt(value, index + 1)) ||
709 contains(value, index, 2, "WH"))) {
710 if (isVowel(charAt(value, index + 1))) {
711
712 result.append('A', 'F');
713 } else {
714
715 result.append('A');
716 }
717 index++;
718 } else if ((index == value.length() - 1 && isVowel(charAt(value, index - 1))) ||
719 contains(value, index - 1, 5, "EWSKI", "EWSKY", "OWSKI", "OWSKY") ||
720 contains(value, 0, 3, "SCH")) {
721
722 result.appendAlternate('F');
723 index++;
724 } else if (contains(value, index, 4, "WICZ", "WITZ")) {
725
726 result.append("TS", "FX");
727 index += 4;
728 } else {
729 index++;
730 }
731 }
732 return index;
733 }
734
735
736
737
738 private int handleX(final String value, final DoubleMetaphoneResult result, int index) {
739 if (index == 0) {
740 result.append('S');
741 index++;
742 } else {
743 if (!((index == value.length() - 1) &&
744 (contains(value, index - 3, 3, "IAU", "EAU") ||
745 contains(value, index - 2, 2, "AU", "OU")))) {
746
747 result.append("KS");
748 }
749 index = contains(value, index + 1, 1, "C", "X") ? index + 2 : index + 1;
750 }
751 return index;
752 }
753
754
755
756
757 private int handleZ(final String value, final DoubleMetaphoneResult result, int index,
758 final boolean slavoGermanic) {
759 if (charAt(value, index + 1) == 'H') {
760
761 result.append('J');
762 index += 2;
763 } else {
764 if (contains(value, index + 1, 2, "ZO", "ZI", "ZA") ||
765 (slavoGermanic && (index > 0 && charAt(value, index - 1) != 'T'))) {
766 result.append("S", "TS");
767 } else {
768 result.append('S');
769 }
770 index = charAt(value, index + 1) == 'Z' ? index + 2 : index + 1;
771 }
772 return index;
773 }
774
775
776
777
778
779
780 private boolean conditionC0(final String value, final int index) {
781 if (contains(value, index, 4, "CHIA")) {
782 return true;
783 } else if (index <= 1) {
784 return false;
785 } else if (isVowel(charAt(value, index - 2))) {
786 return false;
787 } else if (!contains(value, index - 1, 3, "ACH")) {
788 return false;
789 } else {
790 final char c = charAt(value, index + 2);
791 return (c != 'I' && c != 'E') ||
792 contains(value, index - 2, 6, "BACHER", "MACHER");
793 }
794 }
795
796
797
798
799 private boolean conditionCH0(final String value, final int index) {
800 if (index != 0) {
801 return false;
802 } else if (!contains(value, index + 1, 5, "HARAC", "HARIS") &&
803 !contains(value, index + 1, 3, "HOR", "HYM", "HIA", "HEM")) {
804 return false;
805 } else if (contains(value, 0, 5, "CHORE")) {
806 return false;
807 } else {
808 return true;
809 }
810 }
811
812
813
814
815 private boolean conditionCH1(final String value, final int index) {
816 return ((contains(value, 0, 4, "VAN ", "VON ") || contains(value, 0, 3, "SCH")) ||
817 contains(value, index - 2, 6, "ORCHES", "ARCHIT", "ORCHID") ||
818 contains(value, index + 2, 1, "T", "S") ||
819 ((contains(value, index - 1, 1, "A", "O", "U", "E") || index == 0) &&
820 (contains(value, index + 2, 1, L_R_N_M_B_H_F_V_W_SPACE) || index + 1 == value.length() - 1)));
821 }
822
823
824
825
826 private boolean conditionL0(final String value, final int index) {
827 if (index == value.length() - 3 &&
828 contains(value, index - 1, 4, "ILLO", "ILLA", "ALLE")) {
829 return true;
830 } else if ((contains(value, value.length() - 2, 2, "AS", "OS") ||
831 contains(value, value.length() - 1, 1, "A", "O")) &&
832 contains(value, index - 1, 4, "ALLE")) {
833 return true;
834 } else {
835 return false;
836 }
837 }
838
839
840
841
842 private boolean conditionM0(final String value, final int index) {
843 if (charAt(value, index + 1) == 'M') {
844 return true;
845 }
846 return contains(value, index - 1, 3, "UMB") &&
847 ((index + 1) == value.length() - 1 || contains(value, index + 2, 2, "ER"));
848 }
849
850
851
852
853
854
855
856 private boolean isSlavoGermanic(final String value) {
857 return value.indexOf('W') > -1 || value.indexOf('K') > -1 ||
858 value.indexOf("CZ") > -1 || value.indexOf("WITZ") > -1;
859 }
860
861
862
863
864 private boolean isVowel(final char ch) {
865 return VOWELS.indexOf(ch) != -1;
866 }
867
868
869
870
871
872
873 private boolean isSilentStart(final String value) {
874 boolean result = false;
875 for (final String element : SILENT_START) {
876 if (value.startsWith(element)) {
877 result = true;
878 break;
879 }
880 }
881 return result;
882 }
883
884
885
886
887 private String cleanInput(String input) {
888 if (input == null) {
889 return null;
890 }
891 input = input.trim();
892 if (input.length() == 0) {
893 return null;
894 }
895 return input.toUpperCase(java.util.Locale.ENGLISH);
896 }
897
898
899
900
901
902
903 protected char charAt(final String value, final int index) {
904 if (index < 0 || index >= value.length()) {
905 return Character.MIN_VALUE;
906 }
907 return value.charAt(index);
908 }
909
910
911
912
913
914 protected static boolean contains(final String value, final int start, final int length,
915 final String... criteria) {
916 boolean result = false;
917 if (start >= 0 && start + length <= value.length()) {
918 final String target = value.substring(start, start + length);
919
920 for (final String element : criteria) {
921 if (target.equals(element)) {
922 result = true;
923 break;
924 }
925 }
926 }
927 return result;
928 }
929
930
931
932
933
934
935 public class DoubleMetaphoneResult {
936
937 private final StringBuilder primary = new StringBuilder(getMaxCodeLen());
938 private final StringBuilder alternate = new StringBuilder(getMaxCodeLen());
939 private final int maxLength;
940
941 public DoubleMetaphoneResult(final int maxLength) {
942 this.maxLength = maxLength;
943 }
944
945 public void append(final char value) {
946 appendPrimary(value);
947 appendAlternate(value);
948 }
949
950 public void append(final char primary, final char alternate) {
951 appendPrimary(primary);
952 appendAlternate(alternate);
953 }
954
955 public void appendPrimary(final char value) {
956 if (this.primary.length() < this.maxLength) {
957 this.primary.append(value);
958 }
959 }
960
961 public void appendAlternate(final char value) {
962 if (this.alternate.length() < this.maxLength) {
963 this.alternate.append(value);
964 }
965 }
966
967 public void append(final String value) {
968 appendPrimary(value);
969 appendAlternate(value);
970 }
971
972 public void append(final String primary, final String alternate) {
973 appendPrimary(primary);
974 appendAlternate(alternate);
975 }
976
977 public void appendPrimary(final String value) {
978 final int addChars = this.maxLength - this.primary.length();
979 if (value.length() <= addChars) {
980 this.primary.append(value);
981 } else {
982 this.primary.append(value.substring(0, addChars));
983 }
984 }
985
986 public void appendAlternate(final String value) {
987 final int addChars = this.maxLength - this.alternate.length();
988 if (value.length() <= addChars) {
989 this.alternate.append(value);
990 } else {
991 this.alternate.append(value.substring(0, addChars));
992 }
993 }
994
995 public String getPrimary() {
996 return this.primary.toString();
997 }
998
999 public String getAlternate() {
1000 return this.alternate.toString();
1001 }
1002
1003 public boolean isComplete() {
1004 return this.primary.length() >= this.maxLength &&
1005 this.alternate.length() >= this.maxLength;
1006 }
1007 }
1008 }