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