1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.jdtaus.iso13616;
25
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.Serializable;
29 import java.lang.ref.Reference;
30 import java.lang.ref.SoftReference;
31 import java.math.BigInteger;
32 import java.net.URL;
33 import java.text.ParseException;
34 import java.text.ParsePosition;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Collections;
38 import java.util.Formattable;
39 import java.util.Formatter;
40 import java.util.HashMap;
41 import java.util.IllegalFormatFlagsException;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Properties;
45 import java.util.concurrent.ConcurrentHashMap;
46
47 import static java.util.FormattableFlags.ALTERNATE;
48 import static java.util.FormattableFlags.LEFT_JUSTIFY;
49 import static java.util.FormattableFlags.UPPERCASE;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 @javax.annotation.Generated( value = "org.jomc.tools.SourceFileProcessor 1.9", comments = "See http://www.jomc.org/jomc/1.9/jomc-tools-1.9" )
82
83
84 public final class IBAN implements CharSequence, Comparable<IBAN>, Formattable, Serializable
85 {
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 private static final class Structure
107 {
108
109
110
111
112
113
114
115 private static final class Part
116 {
117
118
119 private final char type;
120
121
122 private final String literal;
123
124
125 private final Integer fixedLength;
126
127
128 private final Integer maximumLength;
129
130
131
132
133
134
135
136
137
138 private Part( final char type, final String literal, final Integer fixedLength,
139 final Integer maximumLength )
140 {
141 super();
142 this.type = type;
143 this.literal = literal;
144 this.fixedLength = fixedLength;
145 this.maximumLength = maximumLength;
146 }
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 private char getType()
162 {
163 return this.type;
164 }
165
166
167
168
169
170
171 private String getLiteral()
172 {
173 return this.literal;
174 }
175
176
177
178
179
180
181
182 private Integer getFixedLength()
183 {
184 return this.fixedLength;
185 }
186
187
188
189
190
191
192
193 private Integer getMaximumLength()
194 {
195 return this.maximumLength;
196 }
197
198 }
199
200
201 private final List<Part> parts;
202
203
204
205
206
207
208 private Structure( final List<Part> parts )
209 {
210 super();
211 this.parts = parts;
212 }
213
214
215
216
217
218
219 private List<Part> getParts()
220 {
221 return this.parts;
222 }
223
224 }
225
226
227
228
229
230
231
232 private static final class ParseContext
233 {
234
235
236 private final String text;
237
238
239 private final ParsePosition parsePosition;
240
241
242 private final IbanFormat format;
243
244
245 private int length;
246
247
248 private Character previous;
249
250
251
252
253
254
255
256
257 private ParseContext( final String text, final ParsePosition pos, final IbanFormat format )
258 {
259 super();
260 this.text = text;
261 this.parsePosition = pos;
262 this.format = format;
263 this.length = 0;
264 }
265
266 }
267
268
269 private static final long serialVersionUID = -8123668345632147105L;
270
271
272 private static volatile Reference<Map<String, IBAN>> cacheReference = new SoftReference<Map<String, IBAN>>( null );
273
274
275 private static final Map<String, Boolean> SEPA_COUNTRY_FLAGS = new HashMap<String, Boolean>( 128 );
276
277
278 private static final Map<String, Structure> BBAN_STRUCTURES = new HashMap<String, Structure>( 128 );
279
280
281 private static final Map<String, List<Integer>> BBAN_BANK_ID_INDICES = new HashMap<String, List<Integer>>( 128 );
282
283
284 private static final Map<String, List<Integer>> BBAN_BRANCH_ID_INDICES = new HashMap<String, List<Integer>>( 128 );
285
286
287 private static final Map<String, Structure> IBAN_STRUCTURES = new HashMap<String, Structure>( 128 );
288
289
290 private static final Map<String, String> IBAN_COUNTRY_CODES = new HashMap<String, String>( 128 );
291
292
293 private static final List<String> COUNTRIES = new ArrayList<String>( 128 );
294
295
296 private static final BigInteger INTEGER_97 = BigInteger.valueOf( 97L );
297
298
299 private static final BigInteger INTEGER_98 = BigInteger.valueOf( 98L );
300
301 static
302 {
303 InputStream in = null;
304
305 try
306 {
307 final Properties properties = new Properties();
308 final URL resource = IBAN.class.getResource( "IBAN.properties" );
309 assert resource != null : "Expected resource 'IBAN.properties' to exist.";
310
311 if ( resource != null )
312 {
313 in = resource.openStream();
314 properties.load( in );
315 in.close();
316 in = null;
317
318 if ( properties.containsKey( "countries" ) )
319 {
320 final String[] countries = properties.getProperty( "countries" ).split( "\\|" );
321 COUNTRIES.addAll( Arrays.asList( countries ) );
322
323 for ( int i = 0, s0 = countries.length; i < s0; i++ )
324 {
325 if ( countries[i].length() != 2 )
326 {
327 throw new AssertionError( countries[i] );
328 }
329
330 final String bbanStructure = properties.getProperty( countries[i] + ".bban.structure" );
331 final String bbanBankIds = properties.getProperty( countries[i] + ".bban.bankid.indices" );
332 final String bbanBranchIds = properties.getProperty( countries[i] + ".bban.branchid.indices" );
333 final String ibanCountryCode = properties.getProperty( countries[i] + ".iban.countrycode" );
334 final String ibanStructure = properties.getProperty( countries[i] + ".iban.structure" );
335 final String sepa = properties.getProperty( countries[i] + ".sepa" );
336
337 SEPA_COUNTRY_FLAGS.put( countries[i], Boolean.valueOf( sepa ) );
338
339 if ( ibanCountryCode != null )
340 {
341 IBAN_COUNTRY_CODES.put( countries[i], ibanCountryCode );
342 }
343
344 if ( bbanStructure != null )
345 {
346 BBAN_STRUCTURES.put( countries[i], parseStructure( bbanStructure ) );
347
348 if ( bbanBankIds != null )
349 {
350 BBAN_BANK_ID_INDICES.put( countries[i], parseIndices( bbanBankIds ) );
351 }
352
353 if ( bbanBranchIds != null )
354 {
355 BBAN_BRANCH_ID_INDICES.put( countries[i], parseIndices( bbanBranchIds ) );
356 }
357 }
358
359 if ( ibanStructure != null )
360 {
361 IBAN_STRUCTURES.put( countries[i], parseStructure( ibanStructure ) );
362 }
363 }
364 }
365 }
366 }
367 catch ( final ParseException e )
368 {
369 throw new AssertionError( e );
370 }
371 catch ( final IOException e )
372 {
373 throw new AssertionError( e );
374 }
375 finally
376 {
377 try
378 {
379 if ( in != null )
380 {
381 in.close();
382 }
383 }
384 catch ( final IOException e )
385 {
386 throw new AssertionError( e );
387 }
388 }
389 }
390
391
392 public static final int MAX_CHARACTERS = 42;
393
394
395
396
397
398 private String countryCode;
399
400
401
402
403
404 private boolean sepaCountry;
405
406
407
408
409
410 private String bankIdentifier;
411
412
413
414
415
416 private String branchIdentifier;
417
418
419
420
421
422 private String electronicFormat;
423
424
425
426
427
428 private String letterFormat;
429
430
431
432
433
434 private Comparable[] parts;
435
436
437 private transient String string;
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452 private IBAN( final String countryCode, final boolean sepaCountry, final String bankIdentifier,
453 final String branchIdentifier, final String electronicFormat, final String letterFormat,
454 final Comparable[] parts )
455 {
456 super();
457 this.countryCode = countryCode;
458 this.sepaCountry = sepaCountry;
459 this.bankIdentifier = bankIdentifier;
460 this.branchIdentifier = branchIdentifier;
461 this.electronicFormat = electronicFormat;
462 this.letterFormat = letterFormat;
463 this.parts = parts;
464 getCache().put( electronicFormat, this );
465 getCache().put( letterFormat, this );
466 }
467
468
469
470
471
472
473
474
475 public static String[] getCountryCodes()
476 {
477 return COUNTRIES.toArray( new String[ COUNTRIES.size() ] );
478 }
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498 public static boolean isSepaCountry( final String countryCode )
499 {
500 if ( countryCode == null )
501 {
502 throw new NullPointerException( "countryCode" );
503 }
504
505 final Boolean sepaCountry = SEPA_COUNTRY_FLAGS.get( countryCode );
506 return sepaCountry != null ? sepaCountry : false;
507 }
508
509
510
511
512
513
514
515
516 public String getCountryCode()
517 {
518 return this.countryCode;
519 }
520
521
522
523
524
525
526
527
528
529
530
531 public boolean isSepaCountry()
532 {
533 return this.sepaCountry;
534 }
535
536
537
538
539
540
541 public String getBankIdentifier()
542 {
543 return this.bankIdentifier;
544 }
545
546
547
548
549
550
551 public String getBranchIdentifier()
552 {
553 return this.branchIdentifier;
554 }
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572 public static IBAN parse( final String countryCode, final String bban ) throws IbanSyntaxException
573 {
574 if ( countryCode == null )
575 {
576 throw new NullPointerException( "countryCode" );
577 }
578 if ( bban == null )
579 {
580 throw new NullPointerException( "bban" );
581 }
582
583 final String ibanCountryCode = toIbanCountryCode( countryCode );
584 final Structure structure = BBAN_STRUCTURES.get( ibanCountryCode );
585
586 if ( structure == null )
587 {
588 throw new IllegalArgumentException( countryCode );
589 }
590
591 final List<Integer> bankIdIndices = BBAN_BANK_ID_INDICES.get( ibanCountryCode );
592 final List<Integer> branchIdIndices = BBAN_BRANCH_ID_INDICES.get( ibanCountryCode );
593 final boolean sepa_country = SEPA_COUNTRY_FLAGS.get( countryCode );
594
595
596 final StringBuilder electronic_format_builder = new StringBuilder( MAX_CHARACTERS );
597 final StringBuilder bank_id_builder = new StringBuilder( MAX_CHARACTERS );
598 final StringBuilder branch_id_builder = new StringBuilder( MAX_CHARACTERS );
599 final List<Comparable<?>> comparables = new ArrayList<Comparable<?>>( structure.getParts().size() + 2 );
600 final ParseContext context =
601 new ParseContext( bban, new ParsePosition( 0 ), bban.length() > 4 && bban.charAt( 4 ) == ' '
602 ? IbanFormat.PRINT : IbanFormat.ELECTRONIC );
603
604 for ( int p = 0, s0 = structure.getParts().size(); p < s0 && context.parsePosition.getErrorIndex() < 0; p++ )
605 {
606 final Structure.Part part = structure.getParts().get( p );
607 final String chars = parsePart( context, part );
608
609 if ( context.parsePosition.getErrorIndex() < 0 )
610 {
611 if ( part.getFixedLength() != null && chars.length() != part.getFixedLength().intValue() )
612 {
613 throw new IbanSyntaxException( bban, context.parsePosition.getIndex() );
614 }
615
616 electronic_format_builder.append( chars );
617
618 switch ( part.getType() )
619 {
620 case 'n':
621 comparables.add( new BigInteger( chars ) );
622 break;
623 default:
624 comparables.add( chars );
625 break;
626 }
627 }
628 else
629 {
630 throw new IbanSyntaxException( bban, context.parsePosition.getErrorIndex() );
631 }
632 }
633
634
635 if ( bankIdIndices != null )
636 {
637 bank_id_builder.append( electronic_format_builder.subSequence( bankIdIndices.get( 0 ),
638 bankIdIndices.get( 1 ) ) );
639
640 }
641
642 if ( branchIdIndices != null )
643 {
644 branch_id_builder.append( electronic_format_builder.subSequence( branchIdIndices.get( 0 ),
645 branchIdIndices.get( 1 ) ) );
646
647 }
648
649
650 final StringBuilder integer_builder = new StringBuilder( MAX_CHARACTERS * 2 );
651 appendIso7064Digits( integer_builder, electronic_format_builder.toString() );
652 appendIso7064Digits( integer_builder, ibanCountryCode );
653 appendIso7064Digits( integer_builder, "00" );
654
655 final BigInteger integer = new BigInteger( integer_builder.toString() );
656 final BigInteger checksum = INTEGER_98.subtract( integer.remainder( INTEGER_97 ) );
657 final String checksumPart = checksum.compareTo( BigInteger.TEN ) < 0 ? "0" + checksum : checksum.toString();
658
659 comparables.add( 0, checksumPart );
660 comparables.add( 0, ibanCountryCode );
661
662 electronic_format_builder.insert( 0, checksumPart );
663 electronic_format_builder.insert( 0, ibanCountryCode );
664
665 return new IBAN( countryCode, sepa_country, bank_id_builder.length() > 0 ? bank_id_builder.toString() : null,
666 branch_id_builder.length() > 0 ? branch_id_builder.toString() : null,
667 electronic_format_builder.toString(), toLetterFormat( electronic_format_builder.toString() ),
668 comparables.toArray( new Comparable<?>[ comparables.size() ] ) );
669
670 }
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687 public static IBAN parse( final String text, final ParsePosition pos ) throws IbanCheckDigitsException
688 {
689 if ( text == null )
690 {
691 throw new NullPointerException( "text" );
692 }
693 if ( pos == null )
694 {
695 throw new NullPointerException( "pos" );
696 }
697
698 IBAN ret = null;
699 final int begin_index = pos.getIndex();
700
701
702 if ( text.length() > begin_index + 1 )
703 {
704 final String country_code = text.substring( begin_index, begin_index + 2 );
705
706 if ( !isLiteral( country_code.charAt( 0 ) ) )
707 {
708 pos.setIndex( begin_index );
709 pos.setErrorIndex( begin_index );
710 }
711 else if ( !isLiteral( country_code.charAt( 1 ) ) )
712 {
713 pos.setIndex( begin_index );
714 pos.setErrorIndex( begin_index + 1 );
715 }
716 else
717 {
718 final Structure structure = IBAN_STRUCTURES.get( country_code );
719
720 if ( structure != null )
721 {
722 final boolean sepa_country = SEPA_COUNTRY_FLAGS.get( country_code );
723 final StringBuilder electronic_format_builder = new StringBuilder( MAX_CHARACTERS );
724 final StringBuilder bank_id_builder = new StringBuilder( MAX_CHARACTERS );
725 final StringBuilder branch_id_builder = new StringBuilder( MAX_CHARACTERS );
726 final List<Comparable<?>> comparables = new ArrayList<Comparable<?>>( structure.getParts().size() );
727 final ParseContext context =
728 new ParseContext( text, pos,
729 text.length() > begin_index + 4 && text.charAt( begin_index + 4 ) == ' '
730 ? IbanFormat.PRINT : IbanFormat.ELECTRONIC );
731
732 for ( int p = 0, s0 = structure.getParts().size();
733 p < s0 && context.parsePosition.getErrorIndex() < 0;
734 p++ )
735 {
736 final Structure.Part part = structure.getParts().get( p );
737 final String chars = parsePart( context, part );
738
739 if ( context.parsePosition.getErrorIndex() < 0 )
740 {
741 if ( part.getFixedLength() != null && chars.length() != part.getFixedLength().intValue() )
742 {
743 context.parsePosition.setErrorIndex( context.parsePosition.getIndex() );
744 context.parsePosition.setIndex( begin_index );
745 break;
746 }
747
748 electronic_format_builder.append( chars );
749
750 switch ( part.getType() )
751 {
752 case 'n':
753 comparables.add( new BigInteger( chars ) );
754 break;
755 default:
756 comparables.add( chars );
757 break;
758 }
759 }
760 }
761
762 if ( context.parsePosition.getErrorIndex() < 0 )
763 {
764 final StringBuilder integer_builder = new StringBuilder( MAX_CHARACTERS * 2 );
765 appendIso7064Digits( integer_builder, electronic_format_builder.substring( 4 ) );
766 appendIso7064Digits( integer_builder, electronic_format_builder.substring( 0, 4 ) );
767
768 final BigInteger integer = new BigInteger( integer_builder.toString() );
769
770 if ( integer.remainder( INTEGER_97 ).equals( BigInteger.ONE ) )
771 {
772 final List<Integer> bankIdIndices = BBAN_BANK_ID_INDICES.get( country_code );
773 final List<Integer> branchIdIndices = BBAN_BRANCH_ID_INDICES.get( country_code );
774
775 if ( bankIdIndices != null )
776 {
777 bank_id_builder.append( electronic_format_builder.subSequence(
778 bankIdIndices.get( 0 ) + 4, bankIdIndices.get( 1 ) + 4 ) );
779
780 }
781
782 if ( branchIdIndices != null )
783 {
784 branch_id_builder.append( electronic_format_builder.subSequence(
785 branchIdIndices.get( 0 ) + 4, branchIdIndices.get( 1 ) + 4 ) );
786
787 }
788
789 ret = new IBAN( country_code, sepa_country,
790 bank_id_builder.length() > 0 ? bank_id_builder.toString() : null,
791 branch_id_builder.length() > 0 ? branch_id_builder.toString() : null,
792 electronic_format_builder.toString(),
793 toLetterFormat( electronic_format_builder.toString() ),
794 comparables.toArray( new Comparable<?>[ comparables.size() ] ) );
795
796 }
797 else
798 {
799 context.parsePosition.setIndex( begin_index );
800 context.parsePosition.setErrorIndex( begin_index + 2 );
801
802 throw new IbanCheckDigitsException( electronic_format_builder.toString(),
803 (Number) comparables.get( 1 ) );
804
805 }
806 }
807 }
808 else
809 {
810 pos.setIndex( begin_index );
811 pos.setErrorIndex( begin_index + 1 );
812 }
813 }
814 }
815 else
816 {
817 pos.setIndex( begin_index );
818 pos.setErrorIndex( begin_index );
819
820 if ( begin_index < text.length() && isLiteral( text.charAt( begin_index ) ) )
821 {
822 pos.setErrorIndex( begin_index + 1 );
823 }
824 }
825
826 return ret;
827 }
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844 public static IBAN parse( final String text ) throws IbanSyntaxException, IbanCheckDigitsException
845 {
846 if ( text == null )
847 {
848 throw new NullPointerException( "text" );
849 }
850
851 IBAN iban = getCache().get( text );
852
853 if ( iban == null )
854 {
855 final ParsePosition pos = new ParsePosition( 0 );
856 iban = IBAN.parse( text, pos );
857
858 if ( iban == null || pos.getErrorIndex() != -1 || pos.getIndex() < text.length() )
859 {
860 throw new IbanSyntaxException( text, pos.getErrorIndex() != -1
861 ? pos.getErrorIndex()
862 : pos.getIndex() );
863
864 }
865 }
866
867 return iban;
868 }
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885 public static IBAN valueOf( final String text )
886 {
887 if ( text == null )
888 {
889 throw new NullPointerException( "text" );
890 }
891
892 try
893 {
894 return IBAN.parse( text );
895 }
896 catch ( final IbanSyntaxException e )
897 {
898 throw new IllegalArgumentException( text, e );
899 }
900 catch ( final IbanCheckDigitsException e )
901 {
902 throw new IllegalArgumentException( text, e );
903 }
904 }
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924 public static IBAN valueOf( final String countryCode, final String bban )
925 {
926 if ( countryCode == null )
927 {
928 throw new NullPointerException( "countryCode" );
929 }
930 if ( bban == null )
931 {
932 throw new NullPointerException( "bban" );
933 }
934
935 try
936 {
937 return IBAN.parse( countryCode, bban );
938 }
939 catch ( final IbanSyntaxException e )
940 {
941 throw new IllegalArgumentException( bban, e );
942 }
943 }
944
945
946
947
948
949
950
951
952 public static boolean isIbanAlphabet( final char c )
953 {
954 return ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) || ( c >= '0' && c <= '9' );
955 }
956
957
958
959
960
961
962
963
964
965
966
967
968
969 public static boolean peekIban( final String text ) throws IbanCheckDigitsException
970 {
971 if ( text == null )
972 {
973 throw new NullPointerException( "text" );
974 }
975
976 boolean valid = true;
977 boolean complete = true;
978
979
980 if ( text.length() > 1 )
981 {
982 final String country_code = text.substring( 0, 2 );
983 final Structure structure = IBAN_STRUCTURES.get( country_code );
984
985 if ( structure != null )
986 {
987 final StringBuilder electronic_format_builder = new StringBuilder( MAX_CHARACTERS );
988 final ParseContext context =
989 new ParseContext( text, new ParsePosition( 0 ), text.length() > 4 && text.charAt( 4 ) == ' '
990 ? IbanFormat.PRINT : IbanFormat.ELECTRONIC );
991
992 for ( int p = 0, s0 = structure.getParts().size();
993 p < s0 && context.parsePosition.getErrorIndex() < 0 && complete;
994 p++ )
995 {
996 final Structure.Part part = structure.getParts().get( p );
997 final String chars = parsePart( context, part );
998
999 if ( context.parsePosition.getErrorIndex() < 0 )
1000 {
1001 if ( part.getFixedLength() != null && chars.length() != part.getFixedLength().intValue() )
1002 {
1003 complete = false;
1004 break;
1005 }
1006
1007 electronic_format_builder.append( chars );
1008 }
1009 }
1010
1011 if ( context.parsePosition.getErrorIndex() < 0 )
1012 {
1013 if ( complete )
1014 {
1015 final StringBuilder integer_builder = new StringBuilder( MAX_CHARACTERS * 2 );
1016 appendIso7064Digits( integer_builder, electronic_format_builder.substring( 4 ) );
1017 appendIso7064Digits( integer_builder, electronic_format_builder.substring( 0, 4 ) );
1018
1019 final BigInteger integer = new BigInteger( integer_builder.toString() );
1020
1021 valid = integer.remainder( INTEGER_97 ).equals( BigInteger.ONE );
1022
1023 if ( !valid )
1024 {
1025 throw new IbanCheckDigitsException(
1026 electronic_format_builder.toString(),
1027 new BigInteger( electronic_format_builder.substring( 2, 4 ) ) );
1028
1029 }
1030
1031 if ( context.parsePosition.getIndex() != text.length() )
1032 {
1033 valid = false;
1034 }
1035 }
1036 }
1037 else
1038 {
1039 valid = false;
1040 }
1041 }
1042 else
1043 {
1044 valid = false;
1045 }
1046 }
1047 else if ( text.length() > 0 && !( text.charAt( 0 ) >= 'A' && text.charAt( 0 ) <= 'Z' ) )
1048 {
1049 valid = false;
1050 }
1051
1052 return valid;
1053 }
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066 public Appendable append( final IbanFormat format, final Appendable appendable ) throws IOException
1067 {
1068 if ( format == null )
1069 {
1070 throw new NullPointerException( "format" );
1071 }
1072 if ( appendable == null )
1073 {
1074 throw new NullPointerException( "appendable" );
1075 }
1076
1077 switch ( format )
1078 {
1079 case ELECTRONIC:
1080 return appendable.append( this.electronicFormat );
1081 case PRINT:
1082 return appendable.append( this.letterFormat );
1083 default:
1084 throw new AssertionError( format );
1085 }
1086 }
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097 public String toString( final IbanFormat format )
1098 {
1099 if ( format == null )
1100 {
1101 throw new NullPointerException( "format" );
1102 }
1103
1104 switch ( format )
1105 {
1106 case ELECTRONIC:
1107 return this.electronicFormat;
1108 case PRINT:
1109 return this.letterFormat;
1110 default:
1111 throw new AssertionError( format );
1112 }
1113 }
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127 public void formatTo( final Formatter formatter, final int flags, final int width, final int precision )
1128 {
1129 if ( formatter == null )
1130 {
1131 throw new NullPointerException( "formatter" );
1132 }
1133 if ( ( flags & UPPERCASE ) == UPPERCASE )
1134 {
1135 final StringBuilder flagsBuilder = new StringBuilder( 3 );
1136
1137 if ( ( flags & ALTERNATE ) == ALTERNATE )
1138 {
1139 flagsBuilder.append( "#" );
1140 }
1141 if ( ( flags & LEFT_JUSTIFY ) == LEFT_JUSTIFY )
1142 {
1143 flagsBuilder.append( "-" );
1144 }
1145
1146 flagsBuilder.append( "^" );
1147
1148 throw new IllegalFormatFlagsException( flagsBuilder.toString() );
1149 }
1150
1151 final IbanFormat format = ( flags & ALTERNATE ) == ALTERNATE ? IbanFormat.PRINT : IbanFormat.ELECTRONIC;
1152
1153 String str = this.toString( format );
1154 if ( precision != -1 && precision < str.length() )
1155 {
1156 str = str.substring( 0, precision );
1157 }
1158
1159 final StringBuilder stringBuilder = new StringBuilder( str );
1160
1161 if ( width != -1 )
1162 {
1163 final int len = width - stringBuilder.length();
1164
1165 if ( len > 0 )
1166 {
1167 final char[] pad = new char[ len ];
1168 Arrays.fill( pad, ' ' );
1169
1170 if ( ( flags & LEFT_JUSTIFY ) == LEFT_JUSTIFY )
1171 {
1172 stringBuilder.append( pad );
1173 }
1174 else
1175 {
1176 stringBuilder.insert( 0, pad );
1177 }
1178 }
1179 }
1180
1181 formatter.format( stringBuilder.toString() );
1182 }
1183
1184
1185
1186
1187
1188
1189
1190 public int length()
1191 {
1192 return this.electronicFormat.length();
1193 }
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210 public char charAt( final int index )
1211 {
1212 return this.electronicFormat.charAt( index );
1213 }
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229 public CharSequence subSequence( final int start, final int end )
1230 {
1231 return this.electronicFormat.subSequence( start, end );
1232 }
1233
1234
1235
1236
1237
1238
1239 @Override
1240 public int hashCode()
1241 {
1242 return this.electronicFormat.hashCode();
1243 }
1244
1245
1246
1247
1248
1249
1250
1251
1252 @Override
1253 public boolean equals( final Object o )
1254 {
1255 boolean equal = this == o;
1256
1257 if ( !equal && ( o instanceof IBAN ) )
1258 {
1259 equal = this.electronicFormat.equals( ( (IBAN) o ).electronicFormat );
1260 }
1261
1262 return equal;
1263 }
1264
1265
1266
1267
1268
1269
1270 @Override
1271 public String toString()
1272 {
1273 return super.toString() + this.internalString();
1274 }
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288 public int compareTo( final IBAN o )
1289 {
1290 if ( o == null )
1291 {
1292 throw new NullPointerException( "o" );
1293 }
1294
1295 int r = this.getCountryCode().compareTo( o.getCountryCode() );
1296 for ( int i = 2, s0 = this.parts.length; r == 0 && i < s0; r = this.parts[i].compareTo( o.parts[i] ), i++ );
1297 return r;
1298 }
1299
1300
1301
1302
1303
1304
1305 private static Map<String, IBAN> getCache()
1306 {
1307 Map<String, IBAN> cache = cacheReference.get();
1308
1309 if ( cache == null )
1310 {
1311 cache = new ConcurrentHashMap<String, IBAN>( 1024 );
1312 cacheReference = new SoftReference<Map<String, IBAN>>( cache );
1313 }
1314
1315 return cache;
1316 }
1317
1318
1319
1320
1321
1322
1323 private String internalString()
1324 {
1325 if ( this.string == null )
1326 {
1327 final StringBuilder b = new StringBuilder( 500 ).append( "{" );
1328 b.append( "countryCode=" ).append( this.countryCode ).
1329 append( ", sepaCountry=" ).append( this.sepaCountry ).
1330 append( ", bankIdentifier=" ).append( this.bankIdentifier ).
1331 append( ", branchIdentifier=" ).append( this.branchIdentifier ).
1332 append( ", electronicFormat=" ).append( this.electronicFormat ).
1333 append( ", letterFormat=" ).append( this.letterFormat ).
1334 append( ", parts={" );
1335
1336 final StringBuilder partBuilder = new StringBuilder();
1337 for ( int i = 0, s0 = this.parts.length; i < s0; i++ )
1338 {
1339 partBuilder.append( ",[" ).append( i ).append( "]=" ).append( this.parts[i] );
1340 }
1341
1342 this.string = b.append( partBuilder.substring( 1 ) ).append( "}}" ).toString();
1343 }
1344
1345 return this.string;
1346 }
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357 private static Structure parseStructure( final String structure ) throws ParseException
1358 {
1359 boolean in_literal = false;
1360 boolean in_part = false;
1361 boolean fixed_length_part = false;
1362 final List<Structure.Part> parts = new ArrayList<Structure.Part>( IBAN.MAX_CHARACTERS );
1363 final StringBuilder literalBuilder = new StringBuilder( IBAN.MAX_CHARACTERS );
1364 final StringBuilder numberBuilder = new StringBuilder( IBAN.MAX_CHARACTERS );
1365
1366 for ( int i = 0, s0 = structure.length(); i < s0; i++ )
1367 {
1368 final char c = structure.charAt( i );
1369
1370 if ( in_part )
1371 {
1372
1373 if ( isDigit( c ) )
1374 {
1375 if ( fixed_length_part )
1376 {
1377 throw new ParseException( structure, i );
1378 }
1379 numberBuilder.append( c );
1380 }
1381 else if ( isTypeIdentifier( c ) )
1382 {
1383 if ( fixed_length_part )
1384 {
1385 parts.add( new Structure.Part( c, null, Integer.parseInt( numberBuilder.toString() ), null ) );
1386 }
1387 else
1388 {
1389 parts.add( new Structure.Part( c, null, null, Integer.parseInt( numberBuilder.toString() ) ) );
1390 }
1391 numberBuilder.setLength( 0 );
1392 in_part = false;
1393 fixed_length_part = false;
1394 }
1395 else if ( isLengthIndicator( c ) )
1396 {
1397 if ( fixed_length_part )
1398 {
1399 throw new ParseException( structure, i );
1400 }
1401 fixed_length_part = true;
1402 }
1403 else
1404 {
1405 throw new ParseException( structure, i );
1406 }
1407 }
1408 else if ( in_literal )
1409 {
1410
1411 if ( isLiteral( c ) )
1412 {
1413 literalBuilder.append( c );
1414 }
1415 else if ( isDigit( c ) )
1416 {
1417 parts.add( new Structure.Part( 'l', literalBuilder.toString(), literalBuilder.length(), null ) );
1418 numberBuilder.append( c );
1419 literalBuilder.setLength( 0 );
1420 in_part = true;
1421 in_literal = false;
1422 }
1423 else
1424 {
1425 throw new ParseException( structure, i );
1426 }
1427 }
1428 else
1429 {
1430 if ( fixed_length_part )
1431 {
1432 throw new ParseException( structure, i );
1433 }
1434
1435
1436 if ( isDigit( c ) )
1437 {
1438 numberBuilder.append( c );
1439 in_part = true;
1440 }
1441 else if ( isLiteral( c ) )
1442 {
1443 literalBuilder.append( c );
1444 in_literal = true;
1445 }
1446 else
1447 {
1448 throw new ParseException( structure, i );
1449 }
1450 }
1451 }
1452
1453 if ( fixed_length_part )
1454 {
1455 throw new ParseException( structure, structure.length() );
1456 }
1457
1458 if ( in_part )
1459 {
1460 throw new ParseException( structure, structure.length() );
1461 }
1462
1463 if ( in_literal )
1464 {
1465 parts.add( new Structure.Part( 'l', literalBuilder.toString(), literalBuilder.length(), null ) );
1466 }
1467
1468 return new Structure( parts );
1469 }
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479 private static String parsePart( final ParseContext context, final Structure.Part part )
1480 {
1481 final StringBuilder partBuilder = new StringBuilder( MAX_CHARACTERS );
1482 final int start_index = context.parsePosition.getIndex();
1483
1484 next:
1485 for ( int index = context.parsePosition.getIndex(), text_len = context.text.length(), literal_index = 0,
1486 part_len = part.getFixedLength() != null
1487 ? part.getFixedLength().intValue() : part.getMaximumLength().intValue();
1488 index < text_len && index - start_index < part_len; index++, context.parsePosition.setIndex( index ) )
1489 {
1490 final char current = context.text.charAt( index );
1491
1492 if ( current == ' ' && part.getType() != 'e' )
1493 {
1494 if ( context.format == IbanFormat.PRINT && context.length % 4 == 0
1495 && ( context.previous == null || context.previous.charValue() != ' ' ) )
1496 {
1497 part_len++;
1498 context.previous = Character.valueOf( current );
1499 continue next;
1500 }
1501 else
1502 {
1503 context.parsePosition.setIndex( start_index );
1504 context.parsePosition.setErrorIndex( index );
1505 break next;
1506 }
1507 }
1508
1509 switch ( part.getType() )
1510 {
1511 case 'a':
1512
1513 if ( current >= 'A' && current <= 'Z' )
1514 {
1515 partBuilder.append( current );
1516 context.length++;
1517 }
1518 else
1519 {
1520 context.parsePosition.setIndex( start_index );
1521 context.parsePosition.setErrorIndex( index );
1522 }
1523 break;
1524 case 'c':
1525
1526 if ( ( current >= 'A' && current <= 'Z' )
1527 || ( current >= 'a' && current <= 'z' )
1528 || ( current >= '0' && current <= '9' ) )
1529 {
1530 partBuilder.append( current );
1531 context.length++;
1532 }
1533 else
1534 {
1535 context.parsePosition.setIndex( start_index );
1536 context.parsePosition.setErrorIndex( index );
1537 }
1538 break;
1539 case 'e':
1540
1541 if ( current == ' ' )
1542 {
1543 partBuilder.append( current );
1544 context.length++;
1545 }
1546 else
1547 {
1548 context.parsePosition.setIndex( start_index );
1549 context.parsePosition.setErrorIndex( index );
1550 }
1551 break;
1552 case 'n':
1553
1554 if ( current >= '0' && current <= '9' )
1555 {
1556 partBuilder.append( current );
1557 context.length++;
1558 }
1559 else
1560 {
1561 context.parsePosition.setIndex( start_index );
1562 context.parsePosition.setErrorIndex( index );
1563 }
1564 break;
1565 case 'l':
1566
1567 if ( current == part.getLiteral().charAt( literal_index++ ) )
1568 {
1569 context.length++;
1570 partBuilder.append( current );
1571 }
1572 else
1573 {
1574 context.parsePosition.setIndex( start_index );
1575 context.parsePosition.setErrorIndex( index );
1576 }
1577 break;
1578 default:
1579 context.parsePosition.setIndex( start_index );
1580 context.parsePosition.setErrorIndex( index );
1581 break next;
1582 }
1583
1584 context.previous = Character.valueOf( current );
1585 }
1586
1587 return context.parsePosition.getErrorIndex() < 0 ? partBuilder.toString() : null;
1588 }
1589
1590
1591
1592
1593
1594
1595
1596
1597 private static boolean isLiteral( final char c )
1598 {
1599 return ( c >= 'A' && c <= 'Z' );
1600 }
1601
1602
1603
1604
1605
1606
1607
1608
1609 private static boolean isDigit( final char c )
1610 {
1611 return ( c >= '0' && c <= '9' );
1612 }
1613
1614
1615
1616
1617
1618
1619
1620
1621 private static boolean isTypeIdentifier( final char c )
1622 {
1623 return c == 'a' || c == 'c' || c == 'e' || c == 'n';
1624 }
1625
1626
1627
1628
1629
1630
1631
1632
1633 private static boolean isLengthIndicator( final char c )
1634 {
1635 return c == '!';
1636 }
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646 private static List<Integer> parseIndices( final String str )
1647 {
1648 final String[] parts = str.split( "-" );
1649 assert parts.length == 2 : "Unexpected bank/branch id indices '" + str + "'.";
1650 final List<Integer> indices = new ArrayList<Integer>( parts.length );
1651 indices.add( 0, Integer.valueOf( parts[0] ) - 1 );
1652 indices.add( 1, Integer.valueOf( parts[1] ) );
1653 return Collections.unmodifiableList( indices );
1654 }
1655
1656
1657
1658
1659
1660
1661
1662 private static void appendIso7064Digits( final StringBuilder buffer, final String chars )
1663 {
1664 for ( int i = 0, l0 = chars.length(); i < l0; i++ )
1665 {
1666 final char c = chars.charAt( i );
1667
1668 if ( c >= 'A' && c <= 'Z' )
1669 {
1670 buffer.append( Integer.toString( ( c - 'A' ) + 10 ) );
1671 }
1672 else if ( c >= 'a' && c <= 'z' )
1673 {
1674 buffer.append( Integer.toString( ( c - 'a' ) + 10 ) );
1675 }
1676 else if ( c >= '0' && c <= '9' )
1677 {
1678 buffer.append( c );
1679 }
1680 else
1681 {
1682 throw new AssertionError( c );
1683 }
1684 }
1685 }
1686
1687
1688
1689
1690
1691
1692
1693
1694 private static String toLetterFormat( final String electronicFormat )
1695 {
1696 final StringBuilder letter_format_builder = new StringBuilder( electronicFormat.length() );
1697
1698 for ( int i = 0, l0 = electronicFormat.length(); i < l0; i++ )
1699 {
1700 if ( i > 0 && i % 4 == 0 )
1701 {
1702 letter_format_builder.append( ' ' );
1703 }
1704
1705 letter_format_builder.append( electronicFormat.charAt( i ) );
1706 }
1707
1708 return letter_format_builder.toString();
1709 }
1710
1711 private static String toIbanCountryCode( final String countryCode )
1712 {
1713 final String ibanCountryCode = IBAN_COUNTRY_CODES.get( countryCode );
1714 return ibanCountryCode != null ? ibanCountryCode : countryCode;
1715 }
1716
1717
1718
1719
1720
1721
1722
1723
1724 }