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.5", comments = "See http://www.jomc.org/jomc/1.5/jomc-tools-1.5" )
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 public static boolean isSepaCountry( final String countryCode )
517 {
518 if ( countryCode == null )
519 {
520 throw new NullPointerException( "countryCode" );
521 }
522
523 final Boolean sepaCountry = SEPA_COUNTRY_FLAGS.get( countryCode );
524 return sepaCountry != null ? sepaCountry : false;
525 }
526
527
528
529
530
531
532
533
534 public String getCountryCode()
535 {
536 return this.countryCode;
537 }
538
539
540
541
542
543
544
545
546
547 public boolean isSepaCountry()
548 {
549 return this.sepaCountry;
550 }
551
552
553
554
555
556
557 public String getBankIdentifier()
558 {
559 return this.bankIdentifier;
560 }
561
562
563
564
565
566
567 public String getBranchIdentifier()
568 {
569 return this.branchIdentifier;
570 }
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588 public static IBAN parse( final String countryCode, final String bban ) throws IbanSyntaxException
589 {
590 if ( countryCode == null )
591 {
592 throw new NullPointerException( "countryCode" );
593 }
594 if ( bban == null )
595 {
596 throw new NullPointerException( "bban" );
597 }
598
599 final String ibanCountryCode = toIbanCountryCode( countryCode );
600 final Structure structure = BBAN_STRUCTURES.get( ibanCountryCode );
601
602 if ( structure == null )
603 {
604 throw new IllegalArgumentException( countryCode );
605 }
606
607 final List<Number> bankIdParts = BBAN_BANK_ID_PARTS.get( ibanCountryCode );
608 final List<Number> branchIdParts = BBAN_BRANCH_ID_PARTS.get( ibanCountryCode );
609 final boolean sepa_country = SEPA_COUNTRY_FLAGS.get( countryCode );
610
611
612 final StringBuilder electronic_format_builder = new StringBuilder( MAX_CHARACTERS );
613 final StringBuilder bank_id_builder = new StringBuilder( MAX_CHARACTERS );
614 final StringBuilder branch_id_builder = new StringBuilder( MAX_CHARACTERS );
615 final List<Comparable<?>> comparables = new ArrayList<Comparable<?>>( structure.getParts().size() + 2 );
616 final ParseContext context =
617 new ParseContext( bban, new ParsePosition( 0 ), bban.length() > 4 && bban.charAt( 4 ) == ' '
618 ? IbanFormat.PRINT : IbanFormat.ELECTRONIC );
619
620 for ( int p = 0, s0 = structure.getParts().size(); p < s0 && context.parsePosition.getErrorIndex() < 0; p++ )
621 {
622 final Integer idKey = Integer.valueOf( p + 1 );
623 final Structure.Part part = structure.getParts().get( p );
624 final String chars = parsePart( context, part );
625
626 if ( context.parsePosition.getErrorIndex() < 0 )
627 {
628 if ( part.getFixedLength() != null && chars.length() != part.getFixedLength().intValue() )
629 {
630 throw new IbanSyntaxException( bban, context.parsePosition.getIndex() );
631 }
632
633 electronic_format_builder.append( chars );
634
635 if ( bankIdParts != null && bankIdParts.contains( idKey ) )
636 {
637 bank_id_builder.append( chars );
638 }
639 if ( branchIdParts != null && branchIdParts.contains( idKey ) )
640 {
641 branch_id_builder.append( chars );
642 }
643
644 switch ( part.getType() )
645 {
646 case 'n':
647 comparables.add( new BigInteger( chars ) );
648 break;
649 default:
650 comparables.add( chars );
651 break;
652 }
653 }
654 else
655 {
656 throw new IbanSyntaxException( bban, context.parsePosition.getErrorIndex() );
657 }
658 }
659
660
661 final StringBuilder integer_builder = new StringBuilder( MAX_CHARACTERS * 2 );
662 appendIso7064Digits( integer_builder, electronic_format_builder.toString() );
663 appendIso7064Digits( integer_builder, ibanCountryCode );
664 appendIso7064Digits( integer_builder, "00" );
665
666 final BigInteger integer = new BigInteger( integer_builder.toString() );
667 final BigInteger checksum = INTEGER_98.subtract( integer.remainder( INTEGER_97 ) );
668 final String checksumPart = checksum.compareTo( BigInteger.TEN ) < 0 ? "0" + checksum : checksum.toString();
669
670 comparables.add( 0, checksumPart );
671 comparables.add( 0, ibanCountryCode );
672
673 electronic_format_builder.insert( 0, checksumPart );
674 electronic_format_builder.insert( 0, ibanCountryCode );
675
676 return new IBAN( countryCode, sepa_country, bank_id_builder.toString(),
677 branch_id_builder.length() > 0 ? branch_id_builder.toString() : null,
678 electronic_format_builder.toString(), toLetterFormat( electronic_format_builder.toString() ),
679 comparables.toArray( new Comparable<?>[ comparables.size() ] ) );
680
681 }
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698 public static IBAN parse( final String text, final ParsePosition pos ) throws IbanCheckDigitsException
699 {
700 if ( text == null )
701 {
702 throw new NullPointerException( "text" );
703 }
704 if ( pos == null )
705 {
706 throw new NullPointerException( "pos" );
707 }
708
709 IBAN ret = null;
710 final int begin_index = pos.getIndex();
711
712
713 if ( text.length() > begin_index + 1 )
714 {
715 final String country_code = text.substring( begin_index, begin_index + 2 );
716
717 if ( !isLiteral( country_code.charAt( 0 ) ) )
718 {
719 pos.setIndex( begin_index );
720 pos.setErrorIndex( begin_index );
721 }
722 else if ( !isLiteral( country_code.charAt( 1 ) ) )
723 {
724 pos.setIndex( begin_index );
725 pos.setErrorIndex( begin_index + 1 );
726 }
727 else
728 {
729 final Structure structure = IBAN_STRUCTURES.get( country_code );
730
731 if ( structure != null )
732 {
733 final List<Number> bankIdParts = IBAN_BANK_ID_PARTS.get( country_code );
734 final List<Number> branchIdParts = IBAN_BRANCH_ID_PARTS.get( country_code );
735 final boolean sepa_country = SEPA_COUNTRY_FLAGS.get( country_code );
736 final StringBuilder electronic_format_builder = new StringBuilder( MAX_CHARACTERS );
737 final StringBuilder bank_id_builder = new StringBuilder( MAX_CHARACTERS );
738 final StringBuilder branch_id_builder = new StringBuilder( MAX_CHARACTERS );
739 final List<Comparable<?>> comparables = new ArrayList<Comparable<?>>( structure.getParts().size() );
740 final ParseContext context =
741 new ParseContext( text, pos,
742 text.length() > begin_index + 4 && text.charAt( begin_index + 4 ) == ' '
743 ? IbanFormat.PRINT : IbanFormat.ELECTRONIC );
744
745 for ( int p = 0, s0 = structure.getParts().size();
746 p < s0 && context.parsePosition.getErrorIndex() < 0;
747 p++ )
748 {
749 final Integer idKey = Integer.valueOf( p + 1 );
750 final Structure.Part part = structure.getParts().get( p );
751 final String chars = parsePart( context, part );
752
753 if ( context.parsePosition.getErrorIndex() < 0 )
754 {
755 if ( part.getFixedLength() != null && chars.length() != part.getFixedLength().intValue() )
756 {
757 context.parsePosition.setErrorIndex( context.parsePosition.getIndex() );
758 context.parsePosition.setIndex( begin_index );
759 break;
760 }
761
762 electronic_format_builder.append( chars );
763
764 if ( bankIdParts != null && bankIdParts.contains( idKey ) )
765 {
766 bank_id_builder.append( chars );
767 }
768 if ( branchIdParts != null && branchIdParts.contains( idKey ) )
769 {
770 branch_id_builder.append( chars );
771 }
772
773 switch ( part.getType() )
774 {
775 case 'n':
776 comparables.add( new BigInteger( chars ) );
777 break;
778 default:
779 comparables.add( chars );
780 break;
781 }
782 }
783 }
784
785 if ( context.parsePosition.getErrorIndex() < 0 )
786 {
787 final StringBuilder integer_builder = new StringBuilder( MAX_CHARACTERS * 2 );
788 appendIso7064Digits( integer_builder, electronic_format_builder.substring( 4 ) );
789 appendIso7064Digits( integer_builder, electronic_format_builder.substring( 0, 4 ) );
790
791 final BigInteger integer = new BigInteger( integer_builder.toString() );
792
793 if ( integer.remainder( INTEGER_97 ).equals( BigInteger.ONE ) )
794 {
795 ret = new IBAN( country_code, sepa_country, bank_id_builder.toString(),
796 branch_id_builder.length() > 0 ? branch_id_builder.toString() : null,
797 electronic_format_builder.toString(),
798 toLetterFormat( electronic_format_builder.toString() ),
799 comparables.toArray( new Comparable<?>[ comparables.size() ] ) );
800
801 }
802 else
803 {
804 context.parsePosition.setIndex( begin_index );
805 context.parsePosition.setErrorIndex( begin_index + 2 );
806
807 throw new IbanCheckDigitsException( electronic_format_builder.toString(),
808 (Number) comparables.get( 1 ) );
809
810 }
811 }
812 }
813 else
814 {
815 pos.setIndex( begin_index );
816 pos.setErrorIndex( begin_index + 1 );
817 }
818 }
819 }
820 else
821 {
822 pos.setIndex( begin_index );
823 pos.setErrorIndex( begin_index );
824
825 if ( begin_index < text.length() && isLiteral( text.charAt( begin_index ) ) )
826 {
827 pos.setErrorIndex( begin_index + 1 );
828 }
829 }
830
831 return ret;
832 }
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849 public static IBAN parse( final String text ) throws IbanSyntaxException, IbanCheckDigitsException
850 {
851 if ( text == null )
852 {
853 throw new NullPointerException( "text" );
854 }
855
856 IBAN iban = getCache().get( text );
857
858 if ( iban == null )
859 {
860 final ParsePosition pos = new ParsePosition( 0 );
861 iban = IBAN.parse( text, pos );
862
863 if ( iban == null || pos.getErrorIndex() != -1 || pos.getIndex() < text.length() )
864 {
865 throw new IbanSyntaxException( text, pos.getErrorIndex() != -1
866 ? pos.getErrorIndex()
867 : pos.getIndex() );
868
869 }
870 }
871
872 return iban;
873 }
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890 public static IBAN valueOf( final String text )
891 {
892 if ( text == null )
893 {
894 throw new NullPointerException( "text" );
895 }
896
897 try
898 {
899 return IBAN.parse( text );
900 }
901 catch ( final IbanSyntaxException e )
902 {
903 throw new IllegalArgumentException( text, e );
904 }
905 catch ( final IbanCheckDigitsException e )
906 {
907 throw new IllegalArgumentException( text, e );
908 }
909 }
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929 public static IBAN valueOf( final String countryCode, final String bban )
930 {
931 if ( countryCode == null )
932 {
933 throw new NullPointerException( "countryCode" );
934 }
935 if ( bban == null )
936 {
937 throw new NullPointerException( "bban" );
938 }
939
940 try
941 {
942 return IBAN.parse( countryCode, bban );
943 }
944 catch ( final IbanSyntaxException e )
945 {
946 throw new IllegalArgumentException( bban, e );
947 }
948 }
949
950
951
952
953
954
955
956
957 public static boolean isIbanAlphabet( final char c )
958 {
959 return ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) || ( c >= '0' && c <= '9' );
960 }
961
962
963
964
965
966
967
968
969
970
971
972
973
974 public static boolean peekIban( final String text ) throws IbanCheckDigitsException
975 {
976 if ( text == null )
977 {
978 throw new NullPointerException( "text" );
979 }
980
981 boolean valid = true;
982 boolean complete = true;
983
984
985 if ( text.length() > 1 )
986 {
987 final String country_code = text.substring( 0, 2 );
988 final Structure structure = IBAN_STRUCTURES.get( country_code );
989
990 if ( structure != null )
991 {
992 final StringBuilder electronic_format_builder = new StringBuilder( MAX_CHARACTERS );
993 final ParseContext context =
994 new ParseContext( text, new ParsePosition( 0 ), text.length() > 4 && text.charAt( 4 ) == ' '
995 ? IbanFormat.PRINT : IbanFormat.ELECTRONIC );
996
997 for ( int p = 0, s0 = structure.getParts().size();
998 p < s0 && context.parsePosition.getErrorIndex() < 0 && complete;
999 p++ )
1000 {
1001 final Structure.Part part = structure.getParts().get( p );
1002 final String chars = parsePart( context, part );
1003
1004 if ( context.parsePosition.getErrorIndex() < 0 )
1005 {
1006 if ( part.getFixedLength() != null && chars.length() != part.getFixedLength().intValue() )
1007 {
1008 complete = false;
1009 break;
1010 }
1011
1012 electronic_format_builder.append( chars );
1013 }
1014 }
1015
1016 if ( context.parsePosition.getErrorIndex() < 0 )
1017 {
1018 if ( complete )
1019 {
1020 final StringBuilder integer_builder = new StringBuilder( MAX_CHARACTERS * 2 );
1021 appendIso7064Digits( integer_builder, electronic_format_builder.substring( 4 ) );
1022 appendIso7064Digits( integer_builder, electronic_format_builder.substring( 0, 4 ) );
1023
1024 final BigInteger integer = new BigInteger( integer_builder.toString() );
1025
1026 valid = integer.remainder( INTEGER_97 ).equals( BigInteger.ONE );
1027
1028 if ( !valid )
1029 {
1030 throw new IbanCheckDigitsException(
1031 electronic_format_builder.toString(),
1032 new BigInteger( electronic_format_builder.substring( 2, 4 ) ) );
1033
1034 }
1035
1036 if ( context.parsePosition.getIndex() != text.length() )
1037 {
1038 valid = false;
1039 }
1040 }
1041 }
1042 else
1043 {
1044 valid = false;
1045 }
1046 }
1047 else
1048 {
1049 valid = false;
1050 }
1051 }
1052 else if ( text.length() > 0 && !( text.charAt( 0 ) >= 'A' && text.charAt( 0 ) <= 'Z' ) )
1053 {
1054 valid = false;
1055 }
1056
1057 return valid;
1058 }
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071 public Appendable append( final IbanFormat format, final Appendable appendable ) throws IOException
1072 {
1073 if ( format == null )
1074 {
1075 throw new NullPointerException( "format" );
1076 }
1077 if ( appendable == null )
1078 {
1079 throw new NullPointerException( "appendable" );
1080 }
1081
1082 switch ( format )
1083 {
1084 case ELECTRONIC:
1085 return appendable.append( this.electronicFormat );
1086 case PRINT:
1087 return appendable.append( this.letterFormat );
1088 default:
1089 throw new AssertionError( format );
1090 }
1091 }
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102 public String toString( final IbanFormat format )
1103 {
1104 if ( format == null )
1105 {
1106 throw new NullPointerException( "format" );
1107 }
1108
1109 switch ( format )
1110 {
1111 case ELECTRONIC:
1112 return this.electronicFormat;
1113 case PRINT:
1114 return this.letterFormat;
1115 default:
1116 throw new AssertionError( format );
1117 }
1118 }
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132 public void formatTo( final Formatter formatter, final int flags, final int width, final int precision )
1133 {
1134 if ( formatter == null )
1135 {
1136 throw new NullPointerException( "formatter" );
1137 }
1138 if ( ( flags & UPPERCASE ) == UPPERCASE )
1139 {
1140 final StringBuilder flagsBuilder = new StringBuilder( 3 );
1141
1142 if ( ( flags & ALTERNATE ) == ALTERNATE )
1143 {
1144 flagsBuilder.append( "#" );
1145 }
1146 if ( ( flags & LEFT_JUSTIFY ) == LEFT_JUSTIFY )
1147 {
1148 flagsBuilder.append( "-" );
1149 }
1150
1151 flagsBuilder.append( "^" );
1152
1153 throw new IllegalFormatFlagsException( flagsBuilder.toString() );
1154 }
1155
1156 final IbanFormat format = ( flags & ALTERNATE ) == ALTERNATE ? IbanFormat.PRINT : IbanFormat.ELECTRONIC;
1157
1158 String str = this.toString( format );
1159 if ( precision != -1 && precision < str.length() )
1160 {
1161 str = str.substring( 0, precision );
1162 }
1163
1164 final StringBuilder stringBuilder = new StringBuilder( str );
1165
1166 if ( width != -1 )
1167 {
1168 final int len = width - stringBuilder.length();
1169
1170 if ( len > 0 )
1171 {
1172 final char[] pad = new char[ len ];
1173 Arrays.fill( pad, ' ' );
1174
1175 if ( ( flags & LEFT_JUSTIFY ) == LEFT_JUSTIFY )
1176 {
1177 stringBuilder.append( pad );
1178 }
1179 else
1180 {
1181 stringBuilder.insert( 0, pad );
1182 }
1183 }
1184 }
1185
1186 formatter.format( stringBuilder.toString() );
1187 }
1188
1189
1190
1191
1192
1193
1194
1195 public int length()
1196 {
1197 return this.electronicFormat.length();
1198 }
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215 public char charAt( final int index )
1216 {
1217 return this.electronicFormat.charAt( index );
1218 }
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234 public CharSequence subSequence( final int start, final int end )
1235 {
1236 return this.electronicFormat.subSequence( start, end );
1237 }
1238
1239
1240
1241
1242
1243
1244 @Override
1245 public int hashCode()
1246 {
1247 return this.electronicFormat.hashCode();
1248 }
1249
1250
1251
1252
1253
1254
1255
1256
1257 @Override
1258 public boolean equals( final Object o )
1259 {
1260 boolean equal = this == o;
1261
1262 if ( !equal && ( o instanceof IBAN ) )
1263 {
1264 equal = this.electronicFormat.equals( ( (IBAN) o ).electronicFormat );
1265 }
1266
1267 return equal;
1268 }
1269
1270
1271
1272
1273
1274
1275 @Override
1276 public String toString()
1277 {
1278 return super.toString() + this.internalString();
1279 }
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293 public int compareTo( final IBAN o )
1294 {
1295 if ( o == null )
1296 {
1297 throw new NullPointerException( "o" );
1298 }
1299
1300 int r = this.getCountryCode().compareTo( o.getCountryCode() );
1301 for ( int i = 2, s0 = this.parts.length; r == 0 && i < s0; r = this.parts[i].compareTo( o.parts[i] ), i++ );
1302 return r;
1303 }
1304
1305
1306
1307
1308
1309
1310 private static Map<String, IBAN> getCache()
1311 {
1312 Map<String, IBAN> cache = cacheReference.get();
1313
1314 if ( cache == null )
1315 {
1316 cache = new ConcurrentHashMap<String, IBAN>( 1024 );
1317 cacheReference = new SoftReference<Map<String, IBAN>>( cache );
1318 }
1319
1320 return cache;
1321 }
1322
1323
1324
1325
1326
1327
1328 private String internalString()
1329 {
1330 if ( this.string == null )
1331 {
1332 final StringBuilder b = new StringBuilder( 500 ).append( "{" );
1333 b.append( "countryCode=" ).append( this.countryCode ).
1334 append( ", sepaCountry=" ).append( this.sepaCountry ).
1335 append( ", bankIdentifier=" ).append( this.bankIdentifier ).
1336 append( ", branchIdentifier=" ).append( this.branchIdentifier ).
1337 append( ", electronicFormat=" ).append( this.electronicFormat ).
1338 append( ", letterFormat=" ).append( this.letterFormat ).
1339 append( ", parts={" );
1340
1341 final StringBuilder partBuilder = new StringBuilder();
1342 for ( int i = 0, s0 = this.parts.length; i < s0; i++ )
1343 {
1344 partBuilder.append( ",[" ).append( i ).append( "]=" ).append( this.parts[i] );
1345 }
1346
1347 this.string = b.append( partBuilder.substring( 1 ) ).append( "}}" ).toString();
1348 }
1349
1350 return this.string;
1351 }
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362 private static Structure parseStructure( final String structure ) throws ParseException
1363 {
1364 boolean in_literal = false;
1365 boolean in_part = false;
1366 boolean fixed_length_part = false;
1367 final List<Structure.Part> parts = new ArrayList<Structure.Part>( IBAN.MAX_CHARACTERS );
1368 final StringBuilder literalBuilder = new StringBuilder( IBAN.MAX_CHARACTERS );
1369 final StringBuilder numberBuilder = new StringBuilder( IBAN.MAX_CHARACTERS );
1370
1371 for ( int i = 0, s0 = structure.length(); i < s0; i++ )
1372 {
1373 final char c = structure.charAt( i );
1374
1375 if ( in_part )
1376 {
1377
1378 if ( isDigit( c ) )
1379 {
1380 if ( fixed_length_part )
1381 {
1382 throw new ParseException( structure, i );
1383 }
1384 numberBuilder.append( c );
1385 }
1386 else if ( isTypeIdentifier( c ) )
1387 {
1388 if ( fixed_length_part )
1389 {
1390 parts.add( new Structure.Part( c, null, Integer.parseInt( numberBuilder.toString() ), null ) );
1391 }
1392 else
1393 {
1394 parts.add( new Structure.Part( c, null, null, Integer.parseInt( numberBuilder.toString() ) ) );
1395 }
1396 numberBuilder.setLength( 0 );
1397 in_part = false;
1398 fixed_length_part = false;
1399 }
1400 else if ( isLengthIndicator( c ) )
1401 {
1402 if ( fixed_length_part )
1403 {
1404 throw new ParseException( structure, i );
1405 }
1406 fixed_length_part = true;
1407 }
1408 else
1409 {
1410 throw new ParseException( structure, i );
1411 }
1412 }
1413 else if ( in_literal )
1414 {
1415
1416 if ( isLiteral( c ) )
1417 {
1418 literalBuilder.append( c );
1419 }
1420 else if ( isDigit( c ) )
1421 {
1422 parts.add( new Structure.Part( 'l', literalBuilder.toString(), literalBuilder.length(), null ) );
1423 numberBuilder.append( c );
1424 literalBuilder.setLength( 0 );
1425 in_part = true;
1426 in_literal = false;
1427 }
1428 else
1429 {
1430 throw new ParseException( structure, i );
1431 }
1432 }
1433 else
1434 {
1435 if ( fixed_length_part )
1436 {
1437 throw new ParseException( structure, i );
1438 }
1439
1440
1441 if ( isDigit( c ) )
1442 {
1443 numberBuilder.append( c );
1444 in_part = true;
1445 }
1446 else if ( isLiteral( c ) )
1447 {
1448 literalBuilder.append( c );
1449 in_literal = true;
1450 }
1451 else
1452 {
1453 throw new ParseException( structure, i );
1454 }
1455 }
1456 }
1457
1458 if ( fixed_length_part )
1459 {
1460 throw new ParseException( structure, structure.length() );
1461 }
1462
1463 if ( in_part )
1464 {
1465 throw new ParseException( structure, structure.length() );
1466 }
1467
1468 if ( in_literal )
1469 {
1470 parts.add( new Structure.Part( 'l', literalBuilder.toString(), literalBuilder.length(), null ) );
1471 }
1472
1473 return new Structure( parts );
1474 }
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484 private static String parsePart( final ParseContext context, final Structure.Part part )
1485 {
1486 final StringBuilder partBuilder = new StringBuilder( MAX_CHARACTERS );
1487 final int start_index = context.parsePosition.getIndex();
1488
1489 next:
1490 for ( int index = context.parsePosition.getIndex(), text_len = context.text.length(), literal_index = 0,
1491 part_len = part.getFixedLength() != null
1492 ? part.getFixedLength().intValue() : part.getMaximumLength().intValue();
1493 index < text_len && index - start_index < part_len; index++, context.parsePosition.setIndex( index ) )
1494 {
1495 final char current = context.text.charAt( index );
1496
1497 if ( current == ' ' && part.getType() != 'e' )
1498 {
1499 if ( context.format == IbanFormat.PRINT && context.length % 4 == 0
1500 && ( context.previous == null || context.previous.charValue() != ' ' ) )
1501 {
1502 part_len++;
1503 context.previous = Character.valueOf( current );
1504 continue next;
1505 }
1506 else
1507 {
1508 context.parsePosition.setIndex( start_index );
1509 context.parsePosition.setErrorIndex( index );
1510 break next;
1511 }
1512 }
1513
1514 switch ( part.getType() )
1515 {
1516 case 'a':
1517
1518 if ( current >= 'A' && current <= 'Z' )
1519 {
1520 partBuilder.append( current );
1521 context.length++;
1522 }
1523 else
1524 {
1525 context.parsePosition.setIndex( start_index );
1526 context.parsePosition.setErrorIndex( index );
1527 }
1528 break;
1529 case 'c':
1530
1531 if ( ( current >= 'A' && current <= 'Z' )
1532 || ( current >= 'a' && current <= 'z' )
1533 || ( current >= '0' && current <= '9' ) )
1534 {
1535 partBuilder.append( current );
1536 context.length++;
1537 }
1538 else
1539 {
1540 context.parsePosition.setIndex( start_index );
1541 context.parsePosition.setErrorIndex( index );
1542 }
1543 break;
1544 case 'e':
1545
1546 if ( current == ' ' )
1547 {
1548 partBuilder.append( current );
1549 context.length++;
1550 }
1551 else
1552 {
1553 context.parsePosition.setIndex( start_index );
1554 context.parsePosition.setErrorIndex( index );
1555 }
1556 break;
1557 case 'n':
1558
1559 if ( current >= '0' && current <= '9' )
1560 {
1561 partBuilder.append( current );
1562 context.length++;
1563 }
1564 else
1565 {
1566 context.parsePosition.setIndex( start_index );
1567 context.parsePosition.setErrorIndex( index );
1568 }
1569 break;
1570 case 'l':
1571
1572 if ( current == part.getLiteral().charAt( literal_index++ ) )
1573 {
1574 context.length++;
1575 partBuilder.append( current );
1576 }
1577 else
1578 {
1579 context.parsePosition.setIndex( start_index );
1580 context.parsePosition.setErrorIndex( index );
1581 }
1582 break;
1583 default:
1584 context.parsePosition.setIndex( start_index );
1585 context.parsePosition.setErrorIndex( index );
1586 break next;
1587 }
1588
1589 context.previous = Character.valueOf( current );
1590 }
1591
1592 return context.parsePosition.getErrorIndex() < 0 ? partBuilder.toString() : null;
1593 }
1594
1595
1596
1597
1598
1599
1600
1601
1602 private static boolean isLiteral( final char c )
1603 {
1604 return ( c >= 'A' && c <= 'Z' );
1605 }
1606
1607
1608
1609
1610
1611
1612
1613
1614 private static boolean isDigit( final char c )
1615 {
1616 return ( c >= '0' && c <= '9' );
1617 }
1618
1619
1620
1621
1622
1623
1624
1625
1626 private static boolean isTypeIdentifier( final char c )
1627 {
1628 return c == 'a' || c == 'c' || c == 'e' || c == 'n';
1629 }
1630
1631
1632
1633
1634
1635
1636
1637
1638 private static boolean isLengthIndicator( final char c )
1639 {
1640 return c == '!';
1641 }
1642
1643
1644
1645
1646
1647
1648
1649
1650 private static List<Number> splitNumbers( final String str )
1651 {
1652 final String[] parts = str.split( "\\|" );
1653 final List<Number> numbers = new ArrayList<Number>( parts.length );
1654 for ( int i = 0, l0 = parts.length; i < l0; numbers.add( Integer.valueOf( parts[i] ) ), i++ );
1655 return numbers;
1656 }
1657
1658
1659
1660
1661
1662
1663
1664 private static void appendIso7064Digits( final StringBuilder buffer, final String chars )
1665 {
1666 for ( int i = 0, l0 = chars.length(); i < l0; i++ )
1667 {
1668 final char c = chars.charAt( i );
1669
1670 if ( c >= 'A' && c <= 'Z' )
1671 {
1672 buffer.append( Integer.toString( ( c - 'A' ) + 10 ) );
1673 }
1674 else if ( c >= 'a' && c <= 'z' )
1675 {
1676 buffer.append( Integer.toString( ( c - 'a' ) + 10 ) );
1677 }
1678 else if ( c >= '0' && c <= '9' )
1679 {
1680 buffer.append( c );
1681 }
1682 else
1683 {
1684 throw new AssertionError( c );
1685 }
1686 }
1687 }
1688
1689
1690
1691
1692
1693
1694
1695
1696 private static String toLetterFormat( final String electronicFormat )
1697 {
1698 final StringBuilder letter_format_builder = new StringBuilder( electronicFormat.length() );
1699
1700 for ( int i = 0, l0 = electronicFormat.length(); i < l0; i++ )
1701 {
1702 if ( i > 0 && i % 4 == 0 )
1703 {
1704 letter_format_builder.append( ' ' );
1705 }
1706
1707 letter_format_builder.append( electronicFormat.charAt( i ) );
1708 }
1709
1710 return letter_format_builder.toString();
1711 }
1712
1713 private static String toIbanCountryCode( final String countryCode )
1714 {
1715 final String ibanCountryCode = IBAN_COUNTRY_CODES.get( countryCode );
1716 return ibanCountryCode != null ? ibanCountryCode : countryCode;
1717 }
1718
1719
1720
1721
1722
1723
1724
1725
1726 }