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