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