1 | // SECTION-START[License Header] |
2 | // <editor-fold defaultstate="collapsed" desc=" Generated License "> |
3 | /* |
4 | * jDTAUS ⁑ ISO-13616 |
5 | * Copyright (C) Christian Schulte, 2013-222 |
6 | * |
7 | * Permission to use, copy, modify, and/or distribute this software for any |
8 | * purpose with or without fee is hereby granted, provided that the above |
9 | * copyright notice and this permission notice appear in all copies. |
10 | * |
11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
18 | * |
19 | * $JDTAUS: IBAN.java 8832 2013-10-01 05:35:51Z schulte $ |
20 | * |
21 | */ |
22 | // </editor-fold> |
23 | // SECTION-END |
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 | // SECTION-START[Documentation] |
50 | // <editor-fold defaultstate="collapsed" desc=" Generated Documentation "> |
51 | /** |
52 | * |
53 | * International Bank Account Number. |
54 | * <p>The IBAN structure is defined in ISO 13616-1 and consists of a two-letter ISO 3166-1 country code, followed by two |
55 | * check digits and up to thirty alphanumeric characters for a BBAN (Basic Bank Account Number) which has a fixed length |
56 | * per country and, included within it, a bank identifier with a fixed position and a fixed length per country. The |
57 | * check digits are calculated based on the scheme defined in ISO/IEC 7064 (MOD97-10). The Society for Worldwide |
58 | * Interbank Financial Telecommunication SCRL, SWIFT, has been designated by the ISO Technical Management Board to act |
59 | * as the Registration Authority for ISO 13616. Nationally-agreed, ISO 13616-compliant IBAN formats are submitted to the |
60 | * registration authority exclusively by the National Standards Body or the National Central Bank of the country. For |
61 | * further information see the <a href="../../../doc-files/IBAN-Registry_Release-45_April-2013.pdf">IBAN REGISTRY</a>. |
62 | * An updated version of the document may be found at <a href="http://www.swift.com">SWIFT</a>.</p> |
63 | * |
64 | * <dl> |
65 | * <dt><b>Identifier:</b></dt><dd>jDTAUS ⁑ ISO-13616 ⁑ IBAN</dd> |
66 | * <dt><b>Name:</b></dt><dd>jDTAUS ⁑ ISO-13616 ⁑ IBAN</dd> |
67 | * <dt><b>Abstract:</b></dt><dd>No</dd> |
68 | * <dt><b>Final:</b></dt><dd>Yes</dd> |
69 | * <dt><b>Stateless:</b></dt><dd>No</dd> |
70 | * </dl> |
71 | * |
72 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
73 | * @version 1.0 |
74 | */ |
75 | // </editor-fold> |
76 | // SECTION-END |
77 | // SECTION-START[Annotations] |
78 | // <editor-fold defaultstate="collapsed" desc=" Generated Annotations "> |
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 | // </editor-fold> |
81 | // SECTION-END |
82 | public final class IBAN implements CharSequence, Comparable<IBAN>, Formattable, Serializable |
83 | { |
84 | // SECTION-START[IBAN] |
85 | |
86 | /** |
87 | * International bank account number structure. |
88 | * <p>The following character representations are used: |
89 | * <table border="0"> |
90 | * <tr><td>a</td><td>Upper case letters (alphabetic characters A-Z only)</td></tr> |
91 | * <tr><td>c</td><td>Upper and lower case alphanumeric characters (A-Z, a-z and 0-9)</td></tr> |
92 | * <tr><td>e</td><td>Blank space</td></tr> |
93 | * <tr><td>n</td><td>Digits (numeric characters 0 to 9 only)</td></tr> |
94 | * </table></p> |
95 | * <p>The following length indications are used: |
96 | * <table border="0"> |
97 | * <tr><td>nn!</td><td>fixed length</td></tr> |
98 | * <tr><td>nn</td><td>maximum length</td></tr> |
99 | * </table></p> |
100 | * |
101 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
102 | * @version $JDTAUS: IBAN.java 8832 2013-10-01 05:35:51Z schulte $ |
103 | */ |
104 | private static final class Structure |
105 | { |
106 | |
107 | /** |
108 | * Part of a structure. |
109 | * |
110 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
111 | * @version $JDTAUS: IBAN.java 8832 2013-10-01 05:35:51Z schulte $ |
112 | */ |
113 | private static final class Part |
114 | { |
115 | |
116 | /** Type of the part. */ |
117 | private final char type; |
118 | |
119 | /** The literal of the part or {@code null}. */ |
120 | private final String literal; |
121 | |
122 | /** The fixed length of the part or {@code null}. */ |
123 | private final Integer fixedLength; |
124 | |
125 | /** The maximum length of the part or {@code null}. */ |
126 | private final Integer maximumLength; |
127 | |
128 | /** |
129 | * Creates a new {@code Part} instance. |
130 | * |
131 | * @param type The type of the part. |
132 | * @param literal The literal of the part or {@code null}. |
133 | * @param fixedLength The fixed length of the part or {@code null}. |
134 | * @param maximumLength The maximum length of the part or {@code null}. |
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 | * Gets the type of the part. |
148 | * <p>The following types are used: |
149 | * <table border="0"> |
150 | * <tr><td>a</td><td>Upper case letters (alphabetic characters A-Z only)</td></tr> |
151 | * <tr><td>c</td><td>Upper and lower case alphanumeric characters (A-Z, a-z and 0-9)</td></tr> |
152 | * <tr><td>e</td><td>Blank space</td></tr> |
153 | * <tr><td>n</td><td>Digits (numeric characters 0 to 9 only)</td></tr> |
154 | * <tr><td>l</td><td>Literal</td></tr> |
155 | * </table></p> |
156 | * |
157 | * @return The type of the part. |
158 | */ |
159 | private char getType() |
160 | { |
161 | return this.type; |
162 | } |
163 | |
164 | /** |
165 | * Gets the literal of the part. |
166 | * |
167 | * @return The literal of the part or {@code null}, if the type of the part is not equal to {@code l}. |
168 | */ |
169 | private String getLiteral() |
170 | { |
171 | return this.literal; |
172 | } |
173 | |
174 | /** |
175 | * Gets the fixed length of the part. |
176 | * |
177 | * @return The fixed length of the part or {@code null}, if the part has no fixed length but a maximum |
178 | * length. |
179 | */ |
180 | private Integer getFixedLength() |
181 | { |
182 | return this.fixedLength; |
183 | } |
184 | |
185 | /** |
186 | * Gets the maximum length of the part. |
187 | * |
188 | * @return The maximum length of the part or {@code null}, if the part has no maximum length but a fixed |
189 | * length. |
190 | */ |
191 | private Integer getMaximumLength() |
192 | { |
193 | return this.maximumLength; |
194 | } |
195 | |
196 | } |
197 | |
198 | /** The parts of the structure. */ |
199 | private final List<Part> parts; |
200 | |
201 | /** |
202 | * Creates a new {@code Structure} instance. |
203 | * |
204 | * @param parts The parts of the structure. |
205 | */ |
206 | private Structure( final List<Part> parts ) |
207 | { |
208 | super(); |
209 | this.parts = parts; |
210 | } |
211 | |
212 | /** |
213 | * Gets the parts of the structure. |
214 | * |
215 | * @return The parts of the structure. |
216 | */ |
217 | private List<Part> getParts() |
218 | { |
219 | return this.parts; |
220 | } |
221 | |
222 | } |
223 | |
224 | /** |
225 | * Context of a parse operation. |
226 | * |
227 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
228 | * @version $JDTAUS: IBAN.java 8832 2013-10-01 05:35:51Z schulte $ |
229 | */ |
230 | private static final class ParseContext |
231 | { |
232 | |
233 | /** The text to parse. */ |
234 | private final String text; |
235 | |
236 | /** The {@code ParsePosition} of the context. */ |
237 | private final ParsePosition parsePosition; |
238 | |
239 | /** The format to parse. */ |
240 | private final IbanFormat format; |
241 | |
242 | /** The number of electronic format characters parsed. */ |
243 | private int length; |
244 | |
245 | /** The previous character parsed. */ |
246 | private Character previous; |
247 | |
248 | /** |
249 | * Creates a new {@code ParseContext} instance. |
250 | * |
251 | * @param text The text to parse. |
252 | * @param pos The {@code ParsePosition} backing the parse. |
253 | * @param format The format to parse. |
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 | /** Serial version UID for backwards compatibility with 2007.45.x classes. */ |
267 | private static final long serialVersionUID = -8123668345632147105L; |
268 | |
269 | /** Used to cache instances. */ |
270 | private static volatile Reference<Map<String, IBAN>> cacheReference = new SoftReference<Map<String, IBAN>>( null ); |
271 | |
272 | /** Mappings of two-letter ISO 3166-1 country codes to SEPA country flags. */ |
273 | private static final Map<String, Boolean> SEPA_COUNTRY_FLAGS = new HashMap<String, Boolean>( 128 ); |
274 | |
275 | /** Mappings of two-letter ISO 3166-1 country codes to BBAN structures. */ |
276 | private static final Map<String, Structure> BBAN_STRUCTURES = new HashMap<String, Structure>( 128 ); |
277 | |
278 | /** Mappings of two-letter ISO 3166-1 country codes to BBAN bank identifier part indices. */ |
279 | private static final Map<String, List<Number>> BBAN_BANK_ID_PARTS = new HashMap<String, List<Number>>( 128 ); |
280 | |
281 | /** Mappings of two-letter ISO 3166-1 country codes to BBAN branch identifier part indices. */ |
282 | private static final Map<String, List<Number>> BBAN_BRANCH_ID_PARTS = new HashMap<String, List<Number>>( 128 ); |
283 | |
284 | /** Mappings of two-letter ISO 3166-1 country codes to IBAN structures. */ |
285 | private static final Map<String, Structure> IBAN_STRUCTURES = new HashMap<String, Structure>( 128 ); |
286 | |
287 | /** Mappings of two-letter ISO 3166-1 country codes to IBAN bank identifier part indices. */ |
288 | private static final Map<String, List<Number>> IBAN_BANK_ID_PARTS = new HashMap<String, List<Number>>( 128 ); |
289 | |
290 | /** Mappings of two-letter ISO 3166-1 country codes to IBAN branch identifier part indices. */ |
291 | private static final Map<String, List<Number>> IBAN_BRANCH_ID_PARTS = new HashMap<String, List<Number>>( 128 ); |
292 | |
293 | /** List of supported two-letter ISO 3166-1 country codes. */ |
294 | private static final List<String> COUNTRIES = new ArrayList<String>( 128 ); |
295 | |
296 | /** {@code BigInteger} constant {@code 97}. */ |
297 | private static final BigInteger INTEGER_97 = BigInteger.valueOf( 97L ); |
298 | |
299 | /** {@code BigInteger} constant {@code 98}. */ |
300 | private static final BigInteger INTEGER_98 = BigInteger.valueOf( 98L ); |
301 | |
302 | static |
303 | { |
304 | InputStream in = null; |
305 | |
306 | try |
307 | { |
308 | final Properties properties = new Properties(); |
309 | final URL resource = IBAN.class.getResource( "IBAN.properties" ); |
310 | assert resource != null : "Expected resource 'IBAN.properties' to exist."; |
311 | |
312 | if ( resource != null ) |
313 | { |
314 | in = resource.openStream(); |
315 | properties.load( in ); |
316 | in.close(); |
317 | in = null; |
318 | |
319 | if ( properties.containsKey( "countries" ) ) |
320 | { |
321 | final String[] countries = properties.getProperty( "countries" ).split( "\\|" ); |
322 | COUNTRIES.addAll( Arrays.asList( countries ) ); |
323 | |
324 | for ( int i = 0, s0 = countries.length; i < s0; i++ ) |
325 | { |
326 | if ( countries[i].length() != 2 ) |
327 | { |
328 | throw new AssertionError( countries[i] ); |
329 | } |
330 | |
331 | final String bbanStructure = properties.getProperty( countries[i] + ".bban.structure" ); |
332 | final String bbanBankIds = properties.getProperty( countries[i] + ".bban.bankidparts" ); |
333 | final String bbanBranchIds = properties.getProperty( countries[i] + ".bban.branchidparts" ); |
334 | final String ibanStructure = properties.getProperty( countries[i] + ".iban.structure" ); |
335 | final String ibanBankIds = properties.getProperty( countries[i] + ".iban.bankidparts" ); |
336 | final String ibanBranchIds = properties.getProperty( countries[i] + ".iban.branchidparts" ); |
337 | final String sepa = properties.getProperty( countries[i] + ".sepa" ); |
338 | |
339 | SEPA_COUNTRY_FLAGS.put( countries[i], Boolean.valueOf( sepa ) ); |
340 | |
341 | if ( bbanStructure != null ) |
342 | { |
343 | BBAN_STRUCTURES.put( countries[i], parseStructure( bbanStructure ) ); |
344 | assert bbanBankIds != null : |
345 | "Expected '" + countries[i] + ".bban.bankidparts' property to exists."; |
346 | |
347 | if ( bbanBankIds != null ) |
348 | { |
349 | BBAN_BANK_ID_PARTS.put( countries[i], splitNumbers( bbanBankIds ) ); |
350 | } |
351 | |
352 | if ( bbanBranchIds != null ) |
353 | { |
354 | BBAN_BRANCH_ID_PARTS.put( countries[i], splitNumbers( bbanBranchIds ) ); |
355 | } |
356 | } |
357 | |
358 | if ( ibanStructure != null ) |
359 | { |
360 | IBAN_STRUCTURES.put( countries[i], parseStructure( ibanStructure ) ); |
361 | assert ibanBankIds != null : |
362 | "Expected '" + countries[i] + ".iban.bankidparts' property to exists."; |
363 | |
364 | if ( ibanBankIds != null ) |
365 | { |
366 | IBAN_BANK_ID_PARTS.put( countries[i], splitNumbers( ibanBankIds ) ); |
367 | } |
368 | |
369 | if ( ibanBranchIds != null ) |
370 | { |
371 | IBAN_BRANCH_ID_PARTS.put( countries[i], splitNumbers( ibanBranchIds ) ); |
372 | } |
373 | } |
374 | } |
375 | } |
376 | } |
377 | } |
378 | catch ( final ParseException e ) |
379 | { |
380 | throw new AssertionError( e ); |
381 | } |
382 | catch ( final IOException e ) |
383 | { |
384 | throw new AssertionError( e ); |
385 | } |
386 | finally |
387 | { |
388 | try |
389 | { |
390 | if ( in != null ) |
391 | { |
392 | in.close(); |
393 | } |
394 | } |
395 | catch ( final IOException e ) |
396 | { |
397 | throw new AssertionError( e ); |
398 | } |
399 | } |
400 | } |
401 | |
402 | /** Maximum number of characters of an {@code IBAN}. */ |
403 | public static final int MAX_CHARACTERS = 42; |
404 | |
405 | /** |
406 | * The two-letter ISO 3166-1 country code of the IBAN. |
407 | * @serial |
408 | */ |
409 | private String countryCode; |
410 | |
411 | /** |
412 | * Flag indicating the country of the IBAN is a member to the Single Euro Payments Area. |
413 | * @serial |
414 | */ |
415 | private boolean sepaCountry; |
416 | |
417 | /** |
418 | * The bank identifier part of the BBAN of the IBAN. |
419 | * @serial |
420 | */ |
421 | private String bankIdentifier; |
422 | |
423 | /** |
424 | * The branch identifier part of the BBAN of the IBAN. |
425 | * @serial |
426 | */ |
427 | private String branchIdentifier; |
428 | |
429 | /** |
430 | * The electronic format of the IBAN. |
431 | * @serial |
432 | */ |
433 | private String electronicFormat; |
434 | |
435 | /** |
436 | * The letter format of the IBAN. |
437 | * @serial |
438 | */ |
439 | private String letterFormat; |
440 | |
441 | /** |
442 | * The parts of the IBAN. |
443 | * @serial |
444 | */ |
445 | private Comparable[] parts; |
446 | |
447 | /** Cached string representation of the instance. */ |
448 | private transient String string; |
449 | |
450 | /** |
451 | * Creates a new {@code IBAN} instance. |
452 | * |
453 | * @param countryCode The two-letter ISO 3166-1 country code of the IBAN. |
454 | * @param sepaCountry Flag indicating the country is a member to the Single Euro Payments Area. |
455 | * @param bankIdentifier The bank identifier part of the BBAN of the IBAN. |
456 | * @param branchIdentifier The branch identifier part of the BBAN of the IBAN or {@code null}. |
457 | * @param electronicFormat The electronic format representation of the IBAN. |
458 | * @param letterFormat The letter format representation of the IBAN. |
459 | * @param parts The parts of the IBAN. |
460 | * |
461 | * @see #parse(String, ParsePosition) |
462 | */ |
463 | private IBAN( final String countryCode, final boolean sepaCountry, final String bankIdentifier, |
464 | final String branchIdentifier, final String electronicFormat, final String letterFormat, |
465 | final Comparable[] parts ) |
466 | { |
467 | super(); |
468 | this.countryCode = countryCode; |
469 | this.sepaCountry = sepaCountry; |
470 | this.bankIdentifier = bankIdentifier; |
471 | this.branchIdentifier = branchIdentifier; |
472 | this.electronicFormat = electronicFormat; |
473 | this.letterFormat = letterFormat; |
474 | this.parts = parts; |
475 | getCache().put( electronicFormat, this ); |
476 | getCache().put( letterFormat, this ); |
477 | } |
478 | |
479 | /** |
480 | * Gets an array holding two-letter ISO 3166-1 country codes of all countries that have implemented the IBAN |
481 | * standard. |
482 | * |
483 | * @return An array holding two-letter ISO 3166-1 country codes of all countries that have implemented the IBAN |
484 | * standard. |
485 | */ |
486 | public static String[] getCountryCodes() |
487 | { |
488 | return COUNTRIES.toArray( new String[ COUNTRIES.size() ] ); |
489 | } |
490 | |
491 | /** |
492 | * Gets the two-letter ISO 3166-1 country code of the IBAN. |
493 | * |
494 | * @return The two-letter ISO 3166-1 country code of the IBAN. |
495 | * |
496 | * @see #getCountryCodes() |
497 | */ |
498 | public String getCountryCode() |
499 | { |
500 | return this.countryCode; |
501 | } |
502 | |
503 | /** |
504 | * Gets a flag indicating Single Euro Payments Area membership of the country of the IBAN. |
505 | * |
506 | * @return {@code true}, if the country of the IBAN is a member of the Single Euro Payments Area; |
507 | * {@code false}, else. |
508 | */ |
509 | public boolean isSepaCountry() |
510 | { |
511 | return this.sepaCountry; |
512 | } |
513 | |
514 | /** |
515 | * Gets the bank identifier part of the BBAN of the IBAN. |
516 | * |
517 | * @return The bank identifier part of the BBAN of the IBAN. |
518 | */ |
519 | public String getBankIdentifier() |
520 | { |
521 | return this.bankIdentifier; |
522 | } |
523 | |
524 | /** |
525 | * Gets the branch identifier part of the BBAN of the IBAN. |
526 | * |
527 | * @return The branch identifier part of the BBAN of the IBAN or {@code null}. |
528 | */ |
529 | public String getBranchIdentifier() |
530 | { |
531 | return this.branchIdentifier; |
532 | } |
533 | |
534 | /** |
535 | * Parses text from a BBAN string to produce an {@code IBAN} instance. |
536 | * |
537 | * @param countryCode The two-letter ISO 3166-1 country code of the IBAN to create. |
538 | * @param bban A string to parse BBAN characters from. |
539 | * |
540 | * @return The parsed value. |
541 | * |
542 | * @throws NullPointerException if {@code countryCode} or {@code bban} is {@code null}. |
543 | * @throws IllegalArgumentException if the country denoted by {@code countryCode} has not implemented the IBAN |
544 | * standard, that is, {@code countryCode} is not contained in the array returned by method {@code getCountryCodes}. |
545 | * @throws IbanSyntaxException if the parse fails or the length of {@code bban} is invalid. |
546 | * |
547 | * @see #getCountryCodes() |
548 | */ |
549 | public static IBAN parse( final String countryCode, final String bban ) throws IbanSyntaxException |
550 | { |
551 | if ( countryCode == null ) |
552 | { |
553 | throw new NullPointerException( "countryCode" ); |
554 | } |
555 | if ( bban == null ) |
556 | { |
557 | throw new NullPointerException( "bban" ); |
558 | } |
559 | |
560 | final Structure structure = BBAN_STRUCTURES.get( countryCode ); |
561 | |
562 | if ( structure == null ) |
563 | { |
564 | throw new IllegalArgumentException( countryCode ); |
565 | } |
566 | |
567 | final List<Number> bankIdParts = BBAN_BANK_ID_PARTS.get( countryCode ); |
568 | final List<Number> branchIdParts = BBAN_BRANCH_ID_PARTS.get( countryCode ); |
569 | final boolean sepa_country = SEPA_COUNTRY_FLAGS.get( countryCode ); |
570 | |
571 | // Parse the parts. |
572 | final StringBuilder electronic_format_builder = new StringBuilder( MAX_CHARACTERS ); |
573 | final StringBuilder bank_id_builder = new StringBuilder( MAX_CHARACTERS ); |
574 | final StringBuilder branch_id_builder = new StringBuilder( MAX_CHARACTERS ); |
575 | final List<Comparable<?>> comparables = new ArrayList<Comparable<?>>( structure.getParts().size() + 2 ); |
576 | final ParseContext context = |
577 | new ParseContext( bban, new ParsePosition( 0 ), bban.length() > 4 && bban.charAt( 4 ) == ' ' |
578 | ? IbanFormat.PRINT : IbanFormat.ELECTRONIC ); |
579 | |
580 | for ( int p = 0, s0 = structure.getParts().size(); p < s0 && context.parsePosition.getErrorIndex() < 0; p++ ) |
581 | { |
582 | final Integer idKey = Integer.valueOf( p + 1 ); |
583 | final Structure.Part part = structure.getParts().get( p ); |
584 | final String chars = parsePart( context, part ); |
585 | |
586 | if ( context.parsePosition.getErrorIndex() < 0 ) |
587 | { |
588 | if ( part.getFixedLength() != null && chars.length() != part.getFixedLength().intValue() ) |
589 | { // Fixed length mismatch. |
590 | throw new IbanSyntaxException( bban, context.parsePosition.getIndex() ); |
591 | } |
592 | |
593 | electronic_format_builder.append( chars ); |
594 | |
595 | if ( bankIdParts != null && bankIdParts.contains( idKey ) ) |
596 | { |
597 | bank_id_builder.append( chars ); |
598 | } |
599 | if ( branchIdParts != null && branchIdParts.contains( idKey ) ) |
600 | { |
601 | branch_id_builder.append( chars ); |
602 | } |
603 | |
604 | switch ( part.getType() ) |
605 | { |
606 | case 'n': |
607 | comparables.add( new BigInteger( chars ) ); |
608 | break; |
609 | default: |
610 | comparables.add( chars ); |
611 | break; |
612 | } |
613 | } |
614 | else |
615 | { // Invalid part. |
616 | throw new IbanSyntaxException( bban, context.parsePosition.getErrorIndex() ); |
617 | } |
618 | } |
619 | |
620 | // Calculate checksum. |
621 | final StringBuilder integer_builder = new StringBuilder( MAX_CHARACTERS * 2 ); |
622 | appendIso7064Digits( integer_builder, electronic_format_builder.toString() ); |
623 | appendIso7064Digits( integer_builder, countryCode ); |
624 | appendIso7064Digits( integer_builder, "00" ); |
625 | |
626 | final BigInteger integer = new BigInteger( integer_builder.toString() ); |
627 | final BigInteger checksum = INTEGER_98.subtract( integer.remainder( INTEGER_97 ) ); |
628 | final String checksumPart = checksum.compareTo( BigInteger.TEN ) < 0 ? "0" + checksum : checksum.toString(); |
629 | |
630 | comparables.add( 0, checksumPart ); |
631 | comparables.add( 0, countryCode ); |
632 | |
633 | electronic_format_builder.insert( 0, checksumPart ); |
634 | electronic_format_builder.insert( 0, countryCode ); |
635 | |
636 | return new IBAN( countryCode, sepa_country, bank_id_builder.toString(), |
637 | branch_id_builder.length() > 0 ? branch_id_builder.toString() : null, |
638 | electronic_format_builder.toString(), toLetterFormat( electronic_format_builder.toString() ), |
639 | comparables.toArray( new Comparable<?>[ comparables.size() ] ) ); |
640 | |
641 | } |
642 | |
643 | /** |
644 | * Parses text from a string to produce an {@code IBAN} instance. |
645 | * <p>The method attempts to parse text starting at the index given by {@code pos}. If parsing succeeds, then the |
646 | * index of {@code pos} is updated to the index after the last character used (parsing does not necessarily use all |
647 | * characters up to the end of the string), and the parsed value is returned. The updated {@code pos} can be used to |
648 | * indicate the starting point for the next call to this method.</p> |
649 | * |
650 | * @param text A string to parse IBAN characters from. |
651 | * @param pos A {@code ParsePosition} object with index and error index information as described above. |
652 | * |
653 | * @return The parsed value or {@code null}, if the parse fails. |
654 | * |
655 | * @throws NullPointerException if {@code text} or {@code pos} is {@code null}. |
656 | * @throws IbanCheckDigitsException if check digits validation of the parsed value fails. |
657 | */ |
658 | public static IBAN parse( final String text, final ParsePosition pos ) throws IbanCheckDigitsException |
659 | { |
660 | if ( text == null ) |
661 | { |
662 | throw new NullPointerException( "text" ); |
663 | } |
664 | if ( pos == null ) |
665 | { |
666 | throw new NullPointerException( "pos" ); |
667 | } |
668 | |
669 | IBAN ret = null; |
670 | final int begin_index = pos.getIndex(); |
671 | |
672 | // Extract the country code to query properties. |
673 | if ( text.length() > begin_index + 1 ) |
674 | { |
675 | final String country_code = text.substring( begin_index, begin_index + 2 ); |
676 | |
677 | if ( !isLiteral( country_code.charAt( 0 ) ) ) |
678 | { |
679 | pos.setIndex( begin_index ); |
680 | pos.setErrorIndex( begin_index ); |
681 | } |
682 | else if ( !isLiteral( country_code.charAt( 1 ) ) ) |
683 | { |
684 | pos.setIndex( begin_index ); |
685 | pos.setErrorIndex( begin_index + 1 ); |
686 | } |
687 | else |
688 | { |
689 | final Structure structure = IBAN_STRUCTURES.get( country_code ); |
690 | |
691 | if ( structure != null ) |
692 | { // Parse the parts. |
693 | final List<Number> bankIdParts = IBAN_BANK_ID_PARTS.get( country_code ); |
694 | final List<Number> branchIdParts = IBAN_BRANCH_ID_PARTS.get( country_code ); |
695 | final boolean sepa_country = SEPA_COUNTRY_FLAGS.get( country_code ); |
696 | final StringBuilder electronic_format_builder = new StringBuilder( MAX_CHARACTERS ); |
697 | final StringBuilder bank_id_builder = new StringBuilder( MAX_CHARACTERS ); |
698 | final StringBuilder branch_id_builder = new StringBuilder( MAX_CHARACTERS ); |
699 | final List<Comparable<?>> comparables = new ArrayList<Comparable<?>>( structure.getParts().size() ); |
700 | final ParseContext context = |
701 | new ParseContext( text, pos, |
702 | text.length() > begin_index + 4 && text.charAt( begin_index + 4 ) == ' ' |
703 | ? IbanFormat.PRINT : IbanFormat.ELECTRONIC ); |
704 | |
705 | for ( int p = 0, s0 = structure.getParts().size(); |
706 | p < s0 && context.parsePosition.getErrorIndex() < 0; |
707 | p++ ) |
708 | { |
709 | final Integer idKey = Integer.valueOf( p + 1 ); |
710 | final Structure.Part part = structure.getParts().get( p ); |
711 | final String chars = parsePart( context, part ); |
712 | |
713 | if ( context.parsePosition.getErrorIndex() < 0 ) |
714 | { |
715 | if ( part.getFixedLength() != null && chars.length() != part.getFixedLength().intValue() ) |
716 | { // Fixed length mismatch. |
717 | context.parsePosition.setErrorIndex( context.parsePosition.getIndex() ); |
718 | context.parsePosition.setIndex( begin_index ); |
719 | break; |
720 | } |
721 | |
722 | electronic_format_builder.append( chars ); |
723 | |
724 | if ( bankIdParts != null && bankIdParts.contains( idKey ) ) |
725 | { |
726 | bank_id_builder.append( chars ); |
727 | } |
728 | if ( branchIdParts != null && branchIdParts.contains( idKey ) ) |
729 | { |
730 | branch_id_builder.append( chars ); |
731 | } |
732 | |
733 | switch ( part.getType() ) |
734 | { |
735 | case 'n': |
736 | comparables.add( new BigInteger( chars ) ); |
737 | break; |
738 | default: |
739 | comparables.add( chars ); |
740 | break; |
741 | } |
742 | } |
743 | } |
744 | |
745 | if ( context.parsePosition.getErrorIndex() < 0 ) |
746 | { // Validate checksum. |
747 | final StringBuilder integer_builder = new StringBuilder( MAX_CHARACTERS * 2 ); |
748 | appendIso7064Digits( integer_builder, electronic_format_builder.substring( 4 ) ); |
749 | appendIso7064Digits( integer_builder, electronic_format_builder.substring( 0, 4 ) ); |
750 | |
751 | final BigInteger integer = new BigInteger( integer_builder.toString() ); |
752 | |
753 | if ( integer.remainder( INTEGER_97 ).equals( BigInteger.ONE ) ) |
754 | { |
755 | ret = new IBAN( country_code, sepa_country, bank_id_builder.toString(), |
756 | branch_id_builder.length() > 0 ? branch_id_builder.toString() : null, |
757 | electronic_format_builder.toString(), |
758 | toLetterFormat( electronic_format_builder.toString() ), |
759 | comparables.toArray( new Comparable<?>[ comparables.size() ] ) ); |
760 | |
761 | } |
762 | else |
763 | { // Invalid checksum. |
764 | context.parsePosition.setIndex( begin_index ); |
765 | context.parsePosition.setErrorIndex( begin_index + 2 ); |
766 | |
767 | throw new IbanCheckDigitsException( electronic_format_builder.toString(), |
768 | (Number) comparables.get( 1 ) ); |
769 | |
770 | } |
771 | } |
772 | } |
773 | else |
774 | { // Unsupported country code. |
775 | pos.setIndex( begin_index ); |
776 | pos.setErrorIndex( begin_index + 1 ); |
777 | } |
778 | } |
779 | } |
780 | else |
781 | { // No country code provided. |
782 | pos.setIndex( begin_index ); |
783 | pos.setErrorIndex( begin_index ); |
784 | |
785 | if ( begin_index < text.length() && isLiteral( text.charAt( begin_index ) ) ) |
786 | { |
787 | pos.setErrorIndex( begin_index + 1 ); |
788 | } |
789 | } |
790 | |
791 | return ret; |
792 | } |
793 | |
794 | /** |
795 | * Parses text from the beginning of the given string to produce an {@code IBAN} instance. |
796 | * <p>Unlike the {@link #parse(String, ParsePosition)} method this method throws an {@code IbanSyntaxException} if |
797 | * {@code text} cannot be parsed or is of invalid length.</p> |
798 | * |
799 | * @param text A string to parse IBAN characters from. |
800 | * |
801 | * @return The parsed value. |
802 | * |
803 | * @throws NullPointerException if {@code text} is {@code null}. |
804 | * @throws IbanSyntaxException if the parse fails or the length of {@code text} is invalid. |
805 | * @throws IbanCheckDigitsException if check digits validation of the parsed value fails. |
806 | * |
807 | * @see #valueOf(java.lang.String) |
808 | */ |
809 | public static IBAN parse( final String text ) throws IbanSyntaxException, IbanCheckDigitsException |
810 | { |
811 | if ( text == null ) |
812 | { |
813 | throw new NullPointerException( "text" ); |
814 | } |
815 | |
816 | IBAN iban = getCache().get( text ); |
817 | |
818 | if ( iban == null ) |
819 | { |
820 | final ParsePosition pos = new ParsePosition( 0 ); |
821 | iban = IBAN.parse( text, pos ); |
822 | |
823 | if ( iban == null || pos.getErrorIndex() != -1 || pos.getIndex() < text.length() ) |
824 | { |
825 | throw new IbanSyntaxException( text, pos.getErrorIndex() != -1 |
826 | ? pos.getErrorIndex() |
827 | : pos.getIndex() ); |
828 | |
829 | } |
830 | } |
831 | |
832 | return iban; |
833 | } |
834 | |
835 | /** |
836 | * Parses text from the beginning of the given string to produce an {@code IBAN} instance. |
837 | * <p>Unlike the {@link #parse(String)} method this method throws an {@code IllegalArgumentException} if |
838 | * {@code text} cannot be parsed, is of invalid length or check digits validation fails.</p> |
839 | * |
840 | * @param text A string to parse IBAN characters from. |
841 | * |
842 | * @return The parsed value. |
843 | * |
844 | * @throws NullPointerException if {@code text} is {@code null}. |
845 | * @throws IllegalArgumentException if the parse fails, the length of {@code text} is invalid or if check digits |
846 | * validation of the parsed value fails. |
847 | * |
848 | * @see #parse(java.lang.String) |
849 | */ |
850 | public static IBAN valueOf( final String text ) |
851 | { |
852 | if ( text == null ) |
853 | { |
854 | throw new NullPointerException( "text" ); |
855 | } |
856 | |
857 | try |
858 | { |
859 | return IBAN.parse( text ); |
860 | } |
861 | catch ( final IbanSyntaxException e ) |
862 | { |
863 | throw new IllegalArgumentException( text, e ); |
864 | } |
865 | catch ( final IbanCheckDigitsException e ) |
866 | { |
867 | throw new IllegalArgumentException( text, e ); |
868 | } |
869 | } |
870 | |
871 | /** |
872 | * Checks a given character to belong to the IBAN alphabet. |
873 | * |
874 | * @param c The character to check. |
875 | * |
876 | * @return {@code true} if {@code c} is a character of the IBAN alphabet; {@code false}, else. |
877 | */ |
878 | public static boolean isIbanAlphabet( final char c ) |
879 | { |
880 | return ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) || ( c >= '0' && c <= '9' ); |
881 | } |
882 | |
883 | /** |
884 | * Parses text from a string to peek at a national {@code IBAN} format. |
885 | * <p>This method peeks at the given string to test whether it denotes a possibly valid national IBAN format.</p> |
886 | * |
887 | * @param text A string to peek at. |
888 | * |
889 | * @return {@code true}, if {@code text} denotes a (possibly partially) valid national IBAN format; {@code false}, |
890 | * if {@code text} does not denote a valid national IBAN format. |
891 | * |
892 | * @throws NullPointerException if {@code text} is {@code null}. |
893 | * @throws IbanCheckDigitsException if check digits validation fails. |
894 | */ |
895 | public static boolean peekIban( final String text ) throws IbanCheckDigitsException |
896 | { |
897 | if ( text == null ) |
898 | { |
899 | throw new NullPointerException( "text" ); |
900 | } |
901 | |
902 | boolean valid = true; |
903 | boolean complete = true; |
904 | |
905 | // Extract the country code to query properties. |
906 | if ( text.length() > 1 ) |
907 | { |
908 | final String country_code = text.substring( 0, 2 ); |
909 | final Structure structure = IBAN_STRUCTURES.get( country_code ); |
910 | |
911 | if ( structure != null ) |
912 | { // Parse the parts. |
913 | final StringBuilder electronic_format_builder = new StringBuilder( MAX_CHARACTERS ); |
914 | final ParseContext context = |
915 | new ParseContext( text, new ParsePosition( 0 ), text.length() > 4 && text.charAt( 4 ) == ' ' |
916 | ? IbanFormat.PRINT : IbanFormat.ELECTRONIC ); |
917 | |
918 | for ( int p = 0, s0 = structure.getParts().size(); |
919 | p < s0 && context.parsePosition.getErrorIndex() < 0 && complete; |
920 | p++ ) |
921 | { |
922 | final Structure.Part part = structure.getParts().get( p ); |
923 | final String chars = parsePart( context, part ); |
924 | |
925 | if ( context.parsePosition.getErrorIndex() < 0 ) |
926 | { |
927 | if ( part.getFixedLength() != null && chars.length() != part.getFixedLength().intValue() ) |
928 | { // Fixed length mismatch. |
929 | complete = false; |
930 | break; |
931 | } |
932 | |
933 | electronic_format_builder.append( chars ); |
934 | } |
935 | } |
936 | |
937 | if ( context.parsePosition.getErrorIndex() < 0 ) |
938 | { |
939 | if ( complete ) |
940 | { // Validate checksum. |
941 | final StringBuilder integer_builder = new StringBuilder( MAX_CHARACTERS * 2 ); |
942 | appendIso7064Digits( integer_builder, electronic_format_builder.substring( 4 ) ); |
943 | appendIso7064Digits( integer_builder, electronic_format_builder.substring( 0, 4 ) ); |
944 | |
945 | final BigInteger integer = new BigInteger( integer_builder.toString() ); |
946 | |
947 | valid = integer.remainder( INTEGER_97 ).equals( BigInteger.ONE ); |
948 | |
949 | if ( !valid ) |
950 | { |
951 | throw new IbanCheckDigitsException( |
952 | electronic_format_builder.toString(), |
953 | new BigInteger( electronic_format_builder.substring( 2, 4 ) ) ); |
954 | |
955 | } |
956 | |
957 | if ( context.parsePosition.getIndex() != text.length() ) |
958 | { // Unexpected length. |
959 | valid = false; |
960 | } |
961 | } |
962 | } |
963 | else |
964 | { // Invalid part. |
965 | valid = false; |
966 | } |
967 | } |
968 | else |
969 | { // Unsupported country code. |
970 | valid = false; |
971 | } |
972 | } |
973 | else if ( text.length() > 0 && !( text.charAt( 0 ) >= 'A' && text.charAt( 0 ) <= 'Z' ) ) |
974 | { // Illegal first character. |
975 | valid = false; |
976 | } |
977 | |
978 | return valid; |
979 | } |
980 | |
981 | /** |
982 | * Formats an IBAN and appends the resulting text to a given {@code Appendable}. |
983 | * |
984 | * @param format The format to use. |
985 | * @param appendable The {@code Appendable} to append the formatted IBAN to. |
986 | * |
987 | * @return The value passed in as {@code appendable}. |
988 | * |
989 | * @throws NullPointerException if {@code format} or {@code appendable} is {@code null}. |
990 | * @throws IOException if appending fails. |
991 | */ |
992 | public Appendable append( final IbanFormat format, final Appendable appendable ) throws IOException |
993 | { |
994 | if ( format == null ) |
995 | { |
996 | throw new NullPointerException( "format" ); |
997 | } |
998 | if ( appendable == null ) |
999 | { |
1000 | throw new NullPointerException( "appendable" ); |
1001 | } |
1002 | |
1003 | switch ( format ) |
1004 | { |
1005 | case ELECTRONIC: |
1006 | return appendable.append( this.electronicFormat ); |
1007 | case PRINT: |
1008 | return appendable.append( this.letterFormat ); |
1009 | default: |
1010 | throw new AssertionError( format ); |
1011 | } |
1012 | } |
1013 | |
1014 | /** |
1015 | * Formats an IBAN to produce a string. |
1016 | * |
1017 | * @param format The format to use. |
1018 | * |
1019 | * @return The formatted string. |
1020 | * |
1021 | * @throws NullPointerException if {@code format} is {@code null}. |
1022 | */ |
1023 | public String toString( final IbanFormat format ) |
1024 | { |
1025 | if ( format == null ) |
1026 | { |
1027 | throw new NullPointerException( "format" ); |
1028 | } |
1029 | |
1030 | switch ( format ) |
1031 | { |
1032 | case ELECTRONIC: |
1033 | return this.electronicFormat; |
1034 | case PRINT: |
1035 | return this.letterFormat; |
1036 | default: |
1037 | throw new AssertionError( format ); |
1038 | } |
1039 | } |
1040 | |
1041 | /** |
1042 | * Formats the object using the provided {@code Formatter}. |
1043 | * <p>This method uses the {@code ELECTRONIC} format by default. The {@code PRINT} format can be used by specifying |
1044 | * the alternate form flag ({@code #}) in a format specifier.</p> |
1045 | * |
1046 | * @param formatter The {@code Formatter}. |
1047 | * @param flags The flags to modify the output format. |
1048 | * @param width The minimum number of characters to be written to the output. |
1049 | * @param precision The maximum number of characters to be written to the output. |
1050 | * |
1051 | * @throws IllegalFormatFlagsException if the {@code UPPERCASE} flag is set. |
1052 | */ |
1053 | public void formatTo( final Formatter formatter, final int flags, final int width, final int precision ) |
1054 | { |
1055 | if ( formatter == null ) |
1056 | { |
1057 | throw new NullPointerException( "formatter" ); |
1058 | } |
1059 | if ( ( flags & UPPERCASE ) == UPPERCASE ) |
1060 | { |
1061 | final StringBuilder flagsBuilder = new StringBuilder( 3 ); |
1062 | |
1063 | if ( ( flags & ALTERNATE ) == ALTERNATE ) |
1064 | { |
1065 | flagsBuilder.append( "#" ); |
1066 | } |
1067 | if ( ( flags & LEFT_JUSTIFY ) == LEFT_JUSTIFY ) |
1068 | { |
1069 | flagsBuilder.append( "-" ); |
1070 | } |
1071 | |
1072 | flagsBuilder.append( "^" ); |
1073 | |
1074 | throw new IllegalFormatFlagsException( flagsBuilder.toString() ); |
1075 | } |
1076 | |
1077 | final IbanFormat format = ( flags & ALTERNATE ) == ALTERNATE ? IbanFormat.PRINT : IbanFormat.ELECTRONIC; |
1078 | |
1079 | String str = this.toString( format ); |
1080 | if ( precision != -1 && precision < str.length() ) |
1081 | { |
1082 | str = str.substring( 0, precision ); |
1083 | } |
1084 | |
1085 | final StringBuilder stringBuilder = new StringBuilder( str ); |
1086 | |
1087 | if ( width != -1 ) |
1088 | { |
1089 | final int len = width - stringBuilder.length(); |
1090 | |
1091 | if ( len > 0 ) |
1092 | { |
1093 | final char[] pad = new char[ len ]; |
1094 | Arrays.fill( pad, ' ' ); |
1095 | |
1096 | if ( ( flags & LEFT_JUSTIFY ) == LEFT_JUSTIFY ) |
1097 | { |
1098 | stringBuilder.append( pad ); |
1099 | } |
1100 | else |
1101 | { |
1102 | stringBuilder.insert( 0, pad ); |
1103 | } |
1104 | } |
1105 | } |
1106 | |
1107 | formatter.format( stringBuilder.toString() ); |
1108 | } |
1109 | |
1110 | /** |
1111 | * Returns the length of this character sequence. |
1112 | * <p>The length is the number of 16-bit {@code char}s in the sequence.</p> |
1113 | * |
1114 | * @return The number of {@code char}s in this sequence. |
1115 | */ |
1116 | public int length() |
1117 | { |
1118 | return this.electronicFormat.length(); |
1119 | } |
1120 | |
1121 | /** |
1122 | * Returns the {@code char} value at the specified index. |
1123 | * <p>An index ranges from zero to {@code length() - 1}. The first {@code char} value of the sequence is at index |
1124 | * zero, the next at index one, and so on, as for array indexing.</p> |
1125 | * <p>If the {@code char} value specified by the index is a surrogate, the surrogate value is returned.</p> |
1126 | * |
1127 | * @param index The index of the {@code char} value to return. |
1128 | * |
1129 | * @return The {@code char} value at {@code index}. |
1130 | * |
1131 | * @throws IndexOutOfBoundsException if {@code index} is negative or not less than the length of the character |
1132 | * sequence. |
1133 | * |
1134 | * @see #length() |
1135 | */ |
1136 | public char charAt( final int index ) |
1137 | { |
1138 | return this.electronicFormat.charAt( index ); |
1139 | } |
1140 | |
1141 | /** |
1142 | * Returns a new {@code CharSequence} that is a subsequence of this sequence. |
1143 | * <p>The subsequence starts with the {@code char} value at the specified index and ends with the {@code char} value |
1144 | * at index {@code end - 1}. The length (in {@code char}s) of the returned sequence is {@code end - start}, so if |
1145 | * {@code start == end} then an empty sequence is returned.</p> |
1146 | * |
1147 | * @param start The start index, inclusive. |
1148 | * @param end The end index, exclusive. |
1149 | * |
1150 | * @return The subsequence starting at {@code start} up to {@code end -1}. |
1151 | * |
1152 | * @throws IndexOutOfBoundsException if {@code start} or {@code end} are negative, if {@code end} is greater than |
1153 | * the length of the character sequence, or if {@code start} is greater than {@code end}. |
1154 | */ |
1155 | public CharSequence subSequence( final int start, final int end ) |
1156 | { |
1157 | return this.electronicFormat.subSequence( start, end ); |
1158 | } |
1159 | |
1160 | /** |
1161 | * Gets the hash code value of the object. |
1162 | * |
1163 | * @return The hash code value of the object. |
1164 | */ |
1165 | @Override |
1166 | public int hashCode() |
1167 | { |
1168 | return this.electronicFormat.hashCode(); |
1169 | } |
1170 | |
1171 | /** |
1172 | * Indicates whether some other object is equal to this one. |
1173 | * |
1174 | * @param o The reference object with which to compare. |
1175 | * |
1176 | * @return {@code true} if this object is the same as {@code o}; {@code false} otherwise. |
1177 | */ |
1178 | @Override |
1179 | public boolean equals( final Object o ) |
1180 | { |
1181 | boolean equal = this == o; |
1182 | |
1183 | if ( !equal && ( o instanceof IBAN ) ) |
1184 | { |
1185 | equal = this.electronicFormat.equals( ( (IBAN) o ).electronicFormat ); |
1186 | } |
1187 | |
1188 | return equal; |
1189 | } |
1190 | |
1191 | /** |
1192 | * Gets a string representation of the object. |
1193 | * |
1194 | * @return A string representation of the object. |
1195 | */ |
1196 | @Override |
1197 | public String toString() |
1198 | { |
1199 | return super.toString() + this.internalString(); |
1200 | } |
1201 | |
1202 | /** |
1203 | * Compares this object with the specified object for order. |
1204 | * <p>IBAN comparison respects the national IBAN formats when comparing IBANs of the same country. Any country codes |
1205 | * and check digits are ignored.</p> |
1206 | * |
1207 | * @param o The Object to be compared. |
1208 | * |
1209 | * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or greater than |
1210 | * the specified object. |
1211 | * |
1212 | * @throws NullPointerException if {@code o} is {@code null}. |
1213 | */ |
1214 | public int compareTo( final IBAN o ) |
1215 | { |
1216 | if ( o == null ) |
1217 | { |
1218 | throw new NullPointerException( "o" ); |
1219 | } |
1220 | |
1221 | int r = this.getCountryCode().compareTo( o.getCountryCode() ); |
1222 | for ( int i = 2, s0 = this.parts.length; r == 0 && i < s0; r = this.parts[i].compareTo( o.parts[i] ), i++ ); |
1223 | return r; |
1224 | } |
1225 | |
1226 | /** |
1227 | * Gets the current cache instance. |
1228 | * |
1229 | * @return Current cache instance. |
1230 | */ |
1231 | private static Map<String, IBAN> getCache() |
1232 | { |
1233 | Map<String, IBAN> cache = cacheReference.get(); |
1234 | |
1235 | if ( cache == null ) |
1236 | { |
1237 | cache = new ConcurrentHashMap<String, IBAN>( 1024 ); |
1238 | cacheReference = new SoftReference<Map<String, IBAN>>( cache ); |
1239 | } |
1240 | |
1241 | return cache; |
1242 | } |
1243 | |
1244 | /** |
1245 | * Creates a string representing the properties of the instance. |
1246 | * |
1247 | * @return A string representing the properties of the instance. |
1248 | */ |
1249 | private String internalString() |
1250 | { |
1251 | if ( this.string == null ) |
1252 | { |
1253 | final StringBuilder b = new StringBuilder( 500 ).append( "{" ); |
1254 | b.append( "countryCode=" ).append( this.getCountryCode() ). |
1255 | append( ", sepaCountry=" ).append( this.sepaCountry ). |
1256 | append( ", bankIdentifier=" ).append( this.bankIdentifier ). |
1257 | append( ", branchIdentifier=" ).append( this.branchIdentifier ). |
1258 | append( ", electronicFormat=" ).append( this.electronicFormat ). |
1259 | append( ", letterFormat=" ).append( this.letterFormat ). |
1260 | append( ", parts={" ); |
1261 | |
1262 | final StringBuilder partBuilder = new StringBuilder(); |
1263 | for ( int i = 0, s0 = this.parts.length; i < s0; i++ ) |
1264 | { |
1265 | partBuilder.append( ",[" ).append( i ).append( "]=" ).append( this.parts[i] ); |
1266 | } |
1267 | |
1268 | this.string = b.append( partBuilder.substring( 1 ) ).append( "}}" ).toString(); |
1269 | } |
1270 | |
1271 | return this.string; |
1272 | } |
1273 | |
1274 | /** |
1275 | * Parses a given structure string to produce a list of {@code Part}s. |
1276 | * |
1277 | * @param structure The structure string to parse. |
1278 | * |
1279 | * @return The parsed {@code Structure}. |
1280 | * |
1281 | * @throws ParseException if parsing {@code structure} fails. |
1282 | */ |
1283 | private static Structure parseStructure( final String structure ) throws ParseException |
1284 | { |
1285 | boolean in_literal = false; |
1286 | boolean in_part = false; |
1287 | boolean fixed_length_part = false; |
1288 | final List<Structure.Part> parts = new ArrayList<Structure.Part>( IBAN.MAX_CHARACTERS ); |
1289 | final StringBuilder literalBuilder = new StringBuilder( IBAN.MAX_CHARACTERS ); |
1290 | final StringBuilder numberBuilder = new StringBuilder( IBAN.MAX_CHARACTERS ); |
1291 | |
1292 | for ( int i = 0, s0 = structure.length(); i < s0; i++ ) |
1293 | { |
1294 | final char c = structure.charAt( i ); |
1295 | |
1296 | if ( in_part ) |
1297 | { |
1298 | // Expect number or "(n|a|c|e)|!(n|a|c|e)" |
1299 | if ( isDigit( c ) ) |
1300 | { |
1301 | if ( fixed_length_part ) |
1302 | { // Expect type after length indicator. |
1303 | throw new ParseException( structure, i ); |
1304 | } |
1305 | numberBuilder.append( c ); |
1306 | } |
1307 | else if ( isTypeIdentifier( c ) ) |
1308 | { |
1309 | if ( fixed_length_part ) |
1310 | { |
1311 | parts.add( new Structure.Part( c, null, Integer.parseInt( numberBuilder.toString() ), null ) ); |
1312 | } |
1313 | else |
1314 | { |
1315 | parts.add( new Structure.Part( c, null, null, Integer.parseInt( numberBuilder.toString() ) ) ); |
1316 | } |
1317 | numberBuilder.setLength( 0 ); |
1318 | in_part = false; |
1319 | fixed_length_part = false; |
1320 | } |
1321 | else if ( isLengthIndicator( c ) ) |
1322 | { |
1323 | if ( fixed_length_part ) |
1324 | { // Expect type after length indicator. |
1325 | throw new ParseException( structure, i ); |
1326 | } |
1327 | fixed_length_part = true; |
1328 | } |
1329 | else |
1330 | { |
1331 | throw new ParseException( structure, i ); |
1332 | } |
1333 | } |
1334 | else if ( in_literal ) |
1335 | { |
1336 | // Expect literal or number starting a part. |
1337 | if ( isLiteral( c ) ) |
1338 | { |
1339 | literalBuilder.append( c ); |
1340 | } |
1341 | else if ( isDigit( c ) ) |
1342 | { |
1343 | parts.add( new Structure.Part( 'l', literalBuilder.toString(), literalBuilder.length(), null ) ); |
1344 | numberBuilder.append( c ); |
1345 | literalBuilder.setLength( 0 ); |
1346 | in_part = true; |
1347 | in_literal = false; |
1348 | } |
1349 | else |
1350 | { |
1351 | throw new ParseException( structure, i ); |
1352 | } |
1353 | } |
1354 | else |
1355 | { |
1356 | if ( fixed_length_part ) |
1357 | { // Expect type after length indicator. |
1358 | throw new ParseException( structure, i ); |
1359 | } |
1360 | |
1361 | // Expect number starting a part or upper-case letter starting a literal. |
1362 | if ( isDigit( c ) ) |
1363 | { |
1364 | numberBuilder.append( c ); |
1365 | in_part = true; |
1366 | } |
1367 | else if ( isLiteral( c ) ) |
1368 | { |
1369 | literalBuilder.append( c ); |
1370 | in_literal = true; |
1371 | } |
1372 | else |
1373 | { |
1374 | throw new ParseException( structure, i ); |
1375 | } |
1376 | } |
1377 | } |
1378 | |
1379 | if ( fixed_length_part ) |
1380 | { // Expect type after length indicator. |
1381 | throw new ParseException( structure, structure.length() ); |
1382 | } |
1383 | |
1384 | if ( in_part ) |
1385 | { // Unclosed part. |
1386 | throw new ParseException( structure, structure.length() ); |
1387 | } |
1388 | |
1389 | if ( in_literal ) |
1390 | { // Literal at end of structure. |
1391 | parts.add( new Structure.Part( 'l', literalBuilder.toString(), literalBuilder.length(), null ) ); |
1392 | } |
1393 | |
1394 | return new Structure( parts ); |
1395 | } |
1396 | |
1397 | /** |
1398 | * Parses text from a string according to a given {@code Part} to produce a {@code String} instance. |
1399 | * |
1400 | * @param context The context of the parse. |
1401 | * @param part The {@code Part} to parse. |
1402 | * |
1403 | * @return The parsed value, or {@code null} if the parse fails. |
1404 | */ |
1405 | private static String parsePart( final ParseContext context, final Structure.Part part ) |
1406 | { |
1407 | final StringBuilder partBuilder = new StringBuilder( MAX_CHARACTERS ); |
1408 | final int start_index = context.parsePosition.getIndex(); |
1409 | |
1410 | next: |
1411 | for ( int index = context.parsePosition.getIndex(), text_len = context.text.length(), literal_index = 0, |
1412 | part_len = part.getFixedLength() != null |
1413 | ? part.getFixedLength().intValue() : part.getMaximumLength().intValue(); |
1414 | index < text_len && index - start_index < part_len; index++, context.parsePosition.setIndex( index ) ) |
1415 | { |
1416 | final char current = context.text.charAt( index ); |
1417 | |
1418 | if ( current == ' ' && part.getType() != 'e' ) |
1419 | { |
1420 | if ( context.format == IbanFormat.PRINT && context.length % 4 == 0 |
1421 | && ( context.previous == null || context.previous.charValue() != ' ' ) ) |
1422 | { // Skip letter format separator. |
1423 | part_len++; |
1424 | context.previous = Character.valueOf( current ); |
1425 | continue next; |
1426 | } |
1427 | else |
1428 | { // Unexpected letter format separator. |
1429 | context.parsePosition.setIndex( start_index ); |
1430 | context.parsePosition.setErrorIndex( index ); |
1431 | break next; |
1432 | } |
1433 | } |
1434 | |
1435 | switch ( part.getType() ) |
1436 | { |
1437 | case 'a': |
1438 | // Upper case letters (alphabetic characters A-Z only) |
1439 | if ( current >= 'A' && current <= 'Z' ) |
1440 | { |
1441 | partBuilder.append( current ); |
1442 | context.length++; |
1443 | } |
1444 | else |
1445 | { |
1446 | context.parsePosition.setIndex( start_index ); |
1447 | context.parsePosition.setErrorIndex( index ); |
1448 | } |
1449 | break; |
1450 | case 'c': |
1451 | // Upper and lower case alphanumeric characters (A-Z, a-z and 0-9) |
1452 | if ( ( current >= 'A' && current <= 'Z' ) |
1453 | || ( current >= 'a' && current <= 'z' ) |
1454 | || ( current >= '0' && current <= '9' ) ) |
1455 | { |
1456 | partBuilder.append( current ); |
1457 | context.length++; |
1458 | } |
1459 | else |
1460 | { |
1461 | context.parsePosition.setIndex( start_index ); |
1462 | context.parsePosition.setErrorIndex( index ); |
1463 | } |
1464 | break; |
1465 | case 'e': |
1466 | // blank space |
1467 | if ( current == ' ' ) |
1468 | { |
1469 | partBuilder.append( current ); |
1470 | context.length++; |
1471 | } |
1472 | else |
1473 | { |
1474 | context.parsePosition.setIndex( start_index ); |
1475 | context.parsePosition.setErrorIndex( index ); |
1476 | } |
1477 | break; |
1478 | case 'n': |
1479 | // Digits (numeric characters 0 to 9 only) |
1480 | if ( current >= '0' && current <= '9' ) |
1481 | { |
1482 | partBuilder.append( current ); |
1483 | context.length++; |
1484 | } |
1485 | else |
1486 | { |
1487 | context.parsePosition.setIndex( start_index ); |
1488 | context.parsePosition.setErrorIndex( index ); |
1489 | } |
1490 | break; |
1491 | case 'l': |
1492 | // Literal |
1493 | if ( current == part.getLiteral().charAt( literal_index++ ) ) |
1494 | { |
1495 | context.length++; |
1496 | partBuilder.append( current ); |
1497 | } |
1498 | else |
1499 | { |
1500 | context.parsePosition.setIndex( start_index ); |
1501 | context.parsePosition.setErrorIndex( index ); |
1502 | } |
1503 | break; |
1504 | default: |
1505 | context.parsePosition.setIndex( start_index ); |
1506 | context.parsePosition.setErrorIndex( index ); |
1507 | break next; |
1508 | } |
1509 | |
1510 | context.previous = Character.valueOf( current ); |
1511 | } |
1512 | |
1513 | return context.parsePosition.getErrorIndex() < 0 ? partBuilder.toString() : null; |
1514 | } |
1515 | |
1516 | /** |
1517 | * Tests a given character to conform to a literal. |
1518 | * |
1519 | * @param c The character to test. |
1520 | * |
1521 | * @return {@code true}, if {@code c} conforms to a literal; {@code false}, else. |
1522 | */ |
1523 | private static boolean isLiteral( final char c ) |
1524 | { |
1525 | return ( c >= 'A' && c <= 'Z' ); |
1526 | } |
1527 | |
1528 | /** |
1529 | * Tests a given character to conform to a digit. |
1530 | * |
1531 | * @param c The character to test. |
1532 | * |
1533 | * @return {@code true}, if {@code c} conforms to a digit; {@code false}, else. |
1534 | */ |
1535 | private static boolean isDigit( final char c ) |
1536 | { |
1537 | return ( c >= '0' && c <= '9' ); |
1538 | } |
1539 | |
1540 | /** |
1541 | * Tests a given character to conform to a type identifier. |
1542 | * |
1543 | * @param c The character to test. |
1544 | * |
1545 | * @return {@code true}, if {@code c} conforms to a type identifier; {@code false}, else. |
1546 | */ |
1547 | private static boolean isTypeIdentifier( final char c ) |
1548 | { |
1549 | return c == 'a' || c == 'c' || c == 'e' || c == 'n'; |
1550 | } |
1551 | |
1552 | /** |
1553 | * Tests a given character to conform to a length indicator. |
1554 | * |
1555 | * @param c The character to test. |
1556 | * |
1557 | * @return {@code true}, if {@code c} conforms to a length indicator; {@code false}, else. |
1558 | */ |
1559 | private static boolean isLengthIndicator( final char c ) |
1560 | { |
1561 | return c == '!'; |
1562 | } |
1563 | |
1564 | /** |
1565 | * Splits a given string containing numbers separated by {@code |} characters to a list of numbers. |
1566 | * |
1567 | * @param str The string to split. |
1568 | * |
1569 | * @return The numbers of {@code str}. |
1570 | */ |
1571 | private static List<Number> splitNumbers( final String str ) |
1572 | { |
1573 | final String[] parts = str.split( "\\|" ); |
1574 | final List<Number> numbers = new ArrayList<Number>( parts.length ); |
1575 | for ( int i = 0, l0 = parts.length; i < l0; numbers.add( Integer.valueOf( parts[i] ) ), i++ ); |
1576 | return numbers; |
1577 | } |
1578 | |
1579 | /** |
1580 | * Translates characters to digits according to the MOD 97-10 (ISO 7064) algorithm. |
1581 | * |
1582 | * @param buffer The buffer to append digits to. |
1583 | * @param chars The characters to translate. |
1584 | */ |
1585 | private static void appendIso7064Digits( final StringBuilder buffer, final String chars ) |
1586 | { |
1587 | for ( int i = 0, l0 = chars.length(); i < l0; i++ ) |
1588 | { |
1589 | final char c = chars.charAt( i ); |
1590 | |
1591 | if ( c >= 'A' && c <= 'Z' ) |
1592 | { |
1593 | buffer.append( Integer.toString( ( c - 'A' ) + 10 ) ); |
1594 | } |
1595 | else if ( c >= 'a' && c <= 'z' ) |
1596 | { |
1597 | buffer.append( Integer.toString( ( c - 'a' ) + 10 ) ); |
1598 | } |
1599 | else if ( c >= '0' && c <= '9' ) |
1600 | { |
1601 | buffer.append( c ); |
1602 | } |
1603 | else |
1604 | { |
1605 | throw new AssertionError( c ); |
1606 | } |
1607 | } |
1608 | } |
1609 | |
1610 | /** |
1611 | * Formats an electronic format IBAN to a letter format IBAN. |
1612 | * |
1613 | * @param electronicFormat An electronic format IBAN. |
1614 | * |
1615 | * @return The given IBAN formatted to the letter format representation. |
1616 | */ |
1617 | private static String toLetterFormat( final String electronicFormat ) |
1618 | { |
1619 | final StringBuilder letter_format_builder = new StringBuilder( electronicFormat.length() ); |
1620 | |
1621 | for ( int i = 0, l0 = electronicFormat.length(); i < l0; i++ ) |
1622 | { |
1623 | if ( i > 0 && i % 4 == 0 ) |
1624 | { |
1625 | letter_format_builder.append( ' ' ); |
1626 | } |
1627 | |
1628 | letter_format_builder.append( electronicFormat.charAt( i ) ); |
1629 | } |
1630 | |
1631 | return letter_format_builder.toString(); |
1632 | } |
1633 | |
1634 | // SECTION-END |
1635 | // SECTION-START[Dependencies] |
1636 | // SECTION-END |
1637 | // SECTION-START[Properties] |
1638 | // SECTION-END |
1639 | // SECTION-START[Messages] |
1640 | // SECTION-END |
1641 | } |