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