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