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