EMMA Coverage Report (generated Tue Oct 01 08:08:07 CEST 2013)
[all classes][org.jdtaus.iso13616]

COVERAGE SUMMARY FOR SOURCE FILE [IBAN.java]

nameclass, %method, %block, %line, %
IBAN.java100% (5/5)95%  (55/58)89%  (2157/2436)90%  (407.5/454)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class IBAN$Structure$Part100% (1/1)80%  (8/10)87%  (40/46)89%  (9.8/11)
access$800 (IBAN$Structure$Part): Integer 0%   (0/1)0%   (0/3)0%   (0/1)
getMaximumLength (): Integer 0%   (0/1)0%   (0/3)0%   (0/1)
IBAN$Structure$Part (char, String, Integer, Integer): void 100% (1/1)100% (15/15)100% (6/6)
IBAN$Structure$Part (char, String, Integer, Integer, IBAN$1): void 100% (1/1)100% (7/7)100% (1/1)
access$1200 (IBAN$Structure$Part): String 100% (1/1)100% (3/3)100% (1/1)
access$300 (IBAN$Structure$Part): Integer 100% (1/1)100% (3/3)100% (1/1)
access$400 (IBAN$Structure$Part): char 100% (1/1)100% (3/3)100% (1/1)
getFixedLength (): Integer 100% (1/1)100% (3/3)100% (1/1)
getLiteral (): String 100% (1/1)100% (3/3)100% (1/1)
getType (): char 100% (1/1)100% (3/3)100% (1/1)
     
class IBAN100% (1/1)97%  (33/34)88%  (2035/2306)89%  (385.7/431)
isIbanAlphabet (char): boolean 0%   (0/1)0%   (0/22)0%   (0/1)
parseStructure (String): IBAN$Structure 100% (1/1)67%  (156/234)80%  (41/51)
appendIso7064Digits (StringBuilder, String): void 100% (1/1)71%  (45/63)75%  (7.5/10)
parsePart (IBAN$ParseContext, IBAN$Structure$Part): String 100% (1/1)74%  (178/240)63%  (31.7/50)
<static initializer> 100% (1/1)78%  (284/364)81%  (48.6/60)
toString (IbanFormat): String 100% (1/1)78%  (18/23)83%  (5/6)
append (IbanFormat, Appendable): Appendable 100% (1/1)85%  (29/34)88%  (7/8)
isLengthIndicator (char): boolean 100% (1/1)86%  (6/7)85%  (0.8/1)
IBAN (String, boolean, String, String, String, String, Comparable []): void 100% (1/1)100% (34/34)100% (11/11)
charAt (int): char 100% (1/1)100% (5/5)100% (1/1)
compareTo (IBAN): int 100% (1/1)100% (38/38)100% (5/5)
equals (Object): boolean 100% (1/1)100% (21/21)100% (4/4)
formatTo (Formatter, int, int, int): void 100% (1/1)100% (114/114)100% (25/25)
getBankIdentifier (): String 100% (1/1)100% (3/3)100% (1/1)
getBranchIdentifier (): String 100% (1/1)100% (3/3)100% (1/1)
getCache (): Map 100% (1/1)100% (18/18)100% (5/5)
getCountryCode (): String 100% (1/1)100% (3/3)100% (1/1)
getCountryCodes (): String [] 100% (1/1)100% (7/7)100% (1/1)
hashCode (): int 100% (1/1)100% (4/4)100% (1/1)
internalString (): String 100% (1/1)100% (85/85)100% (8/8)
isDigit (char): boolean 100% (1/1)100% (10/10)100% (1/1)
isLiteral (char): boolean 100% (1/1)100% (10/10)100% (1/1)
isSepaCountry (): boolean 100% (1/1)100% (3/3)100% (1/1)
isTypeIdentifier (char): boolean 100% (1/1)100% (16/16)100% (1/1)
length (): int 100% (1/1)100% (4/4)100% (1/1)
parse (String): IBAN 100% (1/1)100% (50/50)100% (9/9)
parse (String, ParsePosition): IBAN 100% (1/1)100% (330/330)100% (63/63)
parse (String, String): IBAN 100% (1/1)100% (274/274)100% (45/45)
peekIban (String): boolean 100% (1/1)100% (182/182)100% (39/39)
splitNumbers (String): List 100% (1/1)100% (29/29)100% (4/4)
subSequence (int, int): CharSequence 100% (1/1)100% (6/6)100% (1/1)
toLetterFormat (String): String 100% (1/1)100% (35/35)100% (6/6)
toString (): String 100% (1/1)100% (11/11)100% (1/1)
valueOf (String): IBAN 100% (1/1)100% (24/24)100% (7/7)
     
class IBAN$1100% (1/1)100% (1/1)89%  (17/19)89%  (0.9/1)
<static initializer> 100% (1/1)89%  (17/19)89%  (0.9/1)
     
class IBAN$ParseContext100% (1/1)100% (9/9)100% (49/49)100% (7/7)
IBAN$ParseContext (String, ParsePosition, IbanFormat): void 100% (1/1)100% (15/15)100% (6/6)
IBAN$ParseContext (String, ParsePosition, IbanFormat, IBAN$1): void 100% (1/1)100% (6/6)100% (1/1)
access$1000 (IBAN$ParseContext): int 100% (1/1)100% (3/3)100% (1/1)
access$1008 (IBAN$ParseContext): int 100% (1/1)100% (8/8)100% (1/1)
access$1100 (IBAN$ParseContext): Character 100% (1/1)100% (3/3)100% (1/1)
access$1102 (IBAN$ParseContext, Character): Character 100% (1/1)100% (5/5)100% (1/1)
access$200 (IBAN$ParseContext): ParsePosition 100% (1/1)100% (3/3)100% (1/1)
access$700 (IBAN$ParseContext): String 100% (1/1)100% (3/3)100% (1/1)
access$900 (IBAN$ParseContext): IbanFormat 100% (1/1)100% (3/3)100% (1/1)
     
class IBAN$Structure100% (1/1)100% (4/4)100% (16/16)100% (5/5)
IBAN$Structure (List): void 100% (1/1)100% (6/6)100% (3/3)
IBAN$Structure (List, IBAN$1): void 100% (1/1)100% (4/4)100% (1/1)
access$000 (IBAN$Structure): List 100% (1/1)100% (3/3)100% (1/1)
getParts (): List 100% (1/1)100% (3/3)100% (1/1)

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

[all classes][org.jdtaus.iso13616]
EMMA 2.1.5320 (stable) (C) Vladimir Roubtsov