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