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