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