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