001/* 002 * jDTAUS Banking Utilities 003 * Copyright (C) 2005 Christian Schulte 004 * <cs@schulte.it> 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License, or any later version. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 019 * 020 */ 021package org.jdtaus.banking.util; 022 023import java.io.IOException; 024import java.io.InputStreamReader; 025import java.io.LineNumberReader; 026import java.io.UnsupportedEncodingException; 027import java.net.URL; 028import java.text.DecimalFormat; 029import java.text.NumberFormat; 030import java.text.ParseException; 031import java.util.ArrayList; 032import java.util.Date; 033import java.util.HashMap; 034import java.util.Iterator; 035import java.util.List; 036import java.util.Locale; 037import java.util.Map; 038import org.jdtaus.banking.Bankleitzahl; 039import org.jdtaus.banking.BankleitzahlInfo; 040import org.jdtaus.banking.messages.UpdatesBankleitzahlenDateiMessage; 041import org.jdtaus.core.container.ContainerFactory; 042import org.jdtaus.core.container.PropertyException; 043import org.jdtaus.core.logging.spi.Logger; 044import org.jdtaus.core.monitor.spi.Task; 045import org.jdtaus.core.monitor.spi.TaskMonitor; 046 047/** 048 * German Bankleitzahlendatei for the format as of 2006-06-01. 049 * <p>For further information see the 050 * <a href="../../../../doc-files/merkblatt_bankleitzahlendatei.pdf">Merkblatt Bankleitzahlendatei</a>. 051 * An updated version of the document may be found at 052 * <a href="http://www.bundesbank.de">Deutsche Bundesbank</a>. 053 * </p> 054 * 055 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 056 * @version $JDTAUS: BankleitzahlenDatei.java 8861 2014-01-10 17:09:50Z schulte $ 057 */ 058public final class BankleitzahlenDatei 059{ 060 //--Dependencies------------------------------------------------------------ 061 062// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies 063 // This section is managed by jdtaus-container-mojo. 064 065 /** 066 * Gets the configured <code>Logger</code> implementation. 067 * 068 * @return The configured <code>Logger</code> implementation. 069 */ 070 private Logger getLogger() 071 { 072 return (Logger) ContainerFactory.getContainer(). 073 getDependency( this, "Logger" ); 074 075 } 076 077 /** 078 * Gets the configured <code>TaskMonitor</code> implementation. 079 * 080 * @return The configured <code>TaskMonitor</code> implementation. 081 */ 082 private TaskMonitor getTaskMonitor() 083 { 084 return (TaskMonitor) ContainerFactory.getContainer(). 085 getDependency( this, "TaskMonitor" ); 086 087 } 088 089 /** 090 * Gets the configured <code>Locale</code> implementation. 091 * 092 * @return The configured <code>Locale</code> implementation. 093 */ 094 private Locale getLocale() 095 { 096 return (Locale) ContainerFactory.getContainer(). 097 getDependency( this, "Locale" ); 098 099 } 100 101// </editor-fold>//GEN-END:jdtausDependencies 102 103 //------------------------------------------------------------Dependencies-- 104 //--Properties-------------------------------------------------------------- 105 106// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties 107 // This section is managed by jdtaus-container-mojo. 108 109 /** 110 * Gets the value of property <code>defaultEncoding</code>. 111 * 112 * @return Default encoding to use when reading bankfile resources. 113 */ 114 private java.lang.String getDefaultEncoding() 115 { 116 return (java.lang.String) ContainerFactory.getContainer(). 117 getProperty( this, "defaultEncoding" ); 118 119 } 120 121// </editor-fold>//GEN-END:jdtausProperties 122 123 //--------------------------------------------------------------Properties-- 124 //--BankleitzahlenDatei----------------------------------------------------- 125 126 /** 127 * Empty {@code BankleitzahlInfo} array. 128 * @since 1.15 129 */ 130 private static final BankleitzahlInfo[] NO_RECORDS = 131 { 132 }; 133 134 /** 135 * Constant for the format as of june 2006. 136 * @since 1.15 137 */ 138 public static final int JUNE_2006_FORMAT = 20060601; 139 140 /** 141 * Constant for the format as of june 2013. 142 * @since 1.15 143 */ 144 public static final int JUNE_2013_FORMAT = 20130601; 145 146 /** 147 * index = index of the field in the record line; value = offset the field's value starts in the record line. 148 */ 149 private static final int[] FIELD_TO_OFFSET = 150 { 151 0, 8, 9, 67, 72, 107, 134, 139, 150, 152, 158, 159, 160, 168, 172 152 }; 153 154 /** 155 * index = index of the field in the record line; value = length of the field's value in the record line. 156 */ 157 private static final int[] FIELD_TO_LENGTH = 158 { 159 8, 1, 58, 5, 35, 27, 5, 11, 2, 6, 1, 1, 8, 4, 2 160 }; 161 162 /** 163 * index = index of the field in the record line; value = end offset in the record line exclusive. 164 */ 165 private static final int[] FIELD_TO_ENDOFFSET = 166 { 167 FIELD_TO_OFFSET[0] + FIELD_TO_LENGTH[0], 168 FIELD_TO_OFFSET[1] + FIELD_TO_LENGTH[1], 169 FIELD_TO_OFFSET[2] + FIELD_TO_LENGTH[2], 170 FIELD_TO_OFFSET[3] + FIELD_TO_LENGTH[3], 171 FIELD_TO_OFFSET[4] + FIELD_TO_LENGTH[4], 172 FIELD_TO_OFFSET[5] + FIELD_TO_LENGTH[5], 173 FIELD_TO_OFFSET[6] + FIELD_TO_LENGTH[6], 174 FIELD_TO_OFFSET[7] + FIELD_TO_LENGTH[7], 175 FIELD_TO_OFFSET[8] + FIELD_TO_LENGTH[8], 176 FIELD_TO_OFFSET[9] + FIELD_TO_LENGTH[9], 177 FIELD_TO_OFFSET[10] + FIELD_TO_LENGTH[10], 178 FIELD_TO_OFFSET[11] + FIELD_TO_LENGTH[11], 179 FIELD_TO_OFFSET[12] + FIELD_TO_LENGTH[12], 180 FIELD_TO_OFFSET[13] + FIELD_TO_LENGTH[13], 181 FIELD_TO_OFFSET[14] + FIELD_TO_LENGTH[14] 182 }; 183 184 /** Records held by the instance. */ 185 private Map records = new HashMap( 5000 ); 186 private Map deletedRecords = new HashMap( 5000 ); 187 private Map headOffices = new HashMap( 5000 ); 188 private Map branchOffices = new HashMap( 5000 ); 189 private Map deletedHeadOffices = new HashMap( 5000 ); 190 private Map deletedBranchOffices = new HashMap( 5000 ); 191 private BankleitzahlInfo[] cachedRecords; 192 private BankleitzahlInfo[] cachedDeletedRecords; 193 194 /** Encoding to use when reading bankfile resources. */ 195 private String encoding; 196 197 /** 198 * Format of the file backing the instance. 199 * @since 1.15 200 */ 201 private int format; 202 203 /** 204 * The date of validity of the file. 205 * @since 1.15 206 */ 207 private Date dateOfValidity; 208 209 /** 210 * The date of expiration of the file. 211 * @since 1.15 212 */ 213 private Date dateOfExpiration; 214 215 /** 216 * Reads a Bankleitzahlendatei form an URL initializing the instance to hold its data. 217 * <p>Calling this constructor is the same as calling<blockquote><pre> 218 * new BankleitzahlenDatei( url, JUNE_2006_FORMAT ); 219 * </pre></blockquote></p> 220 * 221 * @param resource An URL to a Bankleitzahlendatei. 222 * 223 * @throws NullPointerException if {@code resource} is {@code null}. 224 * @throws PropertyException for invalid property values. 225 * @throws IllegalArgumentException if {@code resource} does not provide a valid Bankleitzahlendatei. 226 * @throws IOException if reading fails. 227 * 228 * @deprecated As of 1.15, replaced by constructor 229 * {@link BankleitzahlenDatei#BankleitzahlenDatei(java.net.URL, int, java.util.Date, java.util.Date)}. 230 */ 231 public BankleitzahlenDatei( final URL resource ) throws IOException 232 { 233 super(); 234 235 if ( resource == null ) 236 { 237 throw new NullPointerException( "resource" ); 238 } 239 240 this.assertValidProperties(); 241 this.format = JUNE_2006_FORMAT; 242 this.dateOfValidity = null; 243 this.dateOfExpiration = null; 244 this.readBankfile( resource ); 245 } 246 247 /** 248 * Reads a Bankleitzahlendatei form an URL initializing the instance to hold its data taking a format constant. 249 * 250 * @param resource An URL to a Bankleitzahlendatei. 251 * @param format The format of the file to parse. 252 * @param dateOfValidity The date of validity of the file. 253 * @param dateOfExpiration The date of expiration of the file. 254 * 255 * @throws NullPointerException if {@code resource}, {@code dateOfValidity} or {@code dateOfExpiration} is 256 * {@code null}. 257 * @throws PropertyException for invalid property values. 258 * @throws IllegalArgumentException if {@code resource} does not provide a valid Bankleitzahlendatei or if 259 * {@code format} does not equal one of the format constants defined in this class. 260 * @throws IOException if reading fails. 261 * 262 * @see #JUNE_2006_FORMAT 263 * @see #JUNE_2013_FORMAT 264 */ 265 public BankleitzahlenDatei( final URL resource, final int format, final Date dateOfValidity, 266 final Date dateOfExpiration ) throws IOException 267 { 268 super(); 269 270 if ( resource == null ) 271 { 272 throw new NullPointerException( "resource" ); 273 } 274 if ( dateOfValidity == null ) 275 { 276 throw new NullPointerException( "dateOfValidity" ); 277 } 278 if ( dateOfExpiration == null ) 279 { 280 throw new NullPointerException( "dateOfExpiration" ); 281 } 282 283 assertValidFormat( format ); 284 this.assertValidProperties(); 285 this.format = format; 286 this.dateOfValidity = (Date) dateOfValidity.clone(); 287 this.dateOfExpiration = (Date) dateOfExpiration.clone(); 288 this.readBankfile( resource ); 289 } 290 291 /** 292 * Reads a Bankleitzahlendatei form an URL initializing the instance to hold its data taking the encoding to use 293 * when reading the file. 294 * <p>Calling this constructor is the same as calling<blockquote><pre> 295 * new BankleitzahlenDatei( url, encoding, JUNE_2006_FORMAT ); 296 * </pre></blockquote></p> 297 * 298 * @param resource An URL to a Bankleitzahlendatei. 299 * @param encoding The encoding to use when reading {@code resource}. 300 * 301 * @throws NullPointerException if either {@code resource} or {@code encoding} is {@code null}. 302 * @throws PropertyException for invalid property values. 303 * @throws IllegalArgumentException if {@code resource} does not provide a valid Bankleitzahlendatei. 304 * @throws IOException if reading fails. 305 * 306 * @deprecated As of 1.15, replaced by constructor 307 * {@link BankleitzahlenDatei#BankleitzahlenDatei(java.net.URL, java.lang.String, int, java.util.Date, java.util.Date)}. 308 */ 309 public BankleitzahlenDatei( final URL resource, final String encoding ) throws IOException 310 { 311 super(); 312 313 if ( resource == null ) 314 { 315 throw new NullPointerException( "resource" ); 316 } 317 if ( encoding == null ) 318 { 319 throw new NullPointerException( "encoding" ); 320 } 321 322 this.assertValidProperties(); 323 this.format = JUNE_2006_FORMAT; 324 this.encoding = encoding; 325 this.dateOfValidity = null; 326 this.dateOfExpiration = null; 327 this.readBankfile( resource ); 328 } 329 330 /** 331 * Reads a Bankleitzahlendatei form an URL initializing the instance to hold its data taking the encoding of the 332 * file and a format constant. 333 * 334 * @param resource An URL to a Bankleitzahlendatei. 335 * @param encoding The encoding to use when reading {@code resource}. 336 * @param format The format of the file to parse. 337 * @param dateOfValidity The date of validity of the file. 338 * @param dateOfExpiration The date of expiration of the file. 339 * 340 * @throws NullPointerException if {@code resource}, {@code encoding}, {@code dateOfValidity} or 341 * {@code dateOfExpiration} is {@code null}. 342 * @throws PropertyException for invalid property values. 343 * @throws IllegalArgumentException if {@code resource} does not provide a valid Bankleitzahlendatei or if 344 * {@code format} does not equal one of the format constants defined in this class. 345 * @throws IOException if reading fails. 346 * 347 * @see #JUNE_2006_FORMAT 348 * @see #JUNE_2013_FORMAT 349 */ 350 public BankleitzahlenDatei( final URL resource, final String encoding, final int format, 351 final Date dateOfValidity, final Date dateOfExpiration ) throws IOException 352 { 353 super(); 354 355 if ( resource == null ) 356 { 357 throw new NullPointerException( "resource" ); 358 } 359 if ( encoding == null ) 360 { 361 throw new NullPointerException( "encoding" ); 362 } 363 if ( dateOfValidity == null ) 364 { 365 throw new NullPointerException( "dateOfValidity" ); 366 } 367 if ( dateOfExpiration == null ) 368 { 369 throw new NullPointerException( "dateOfExpiration" ); 370 } 371 372 assertValidFormat( format ); 373 this.assertValidProperties(); 374 this.encoding = encoding; 375 this.format = format; 376 this.dateOfValidity = (Date) dateOfValidity.clone(); 377 this.dateOfExpiration = (Date) dateOfExpiration.clone(); 378 this.readBankfile( resource ); 379 } 380 381 /** 382 * Gets the encoding used for reading bankfile resources. 383 * 384 * @return The encoding used for reading bankfile resources. 385 */ 386 public String getEncoding() 387 { 388 if ( this.encoding == null ) 389 { 390 this.encoding = this.getDefaultEncoding(); 391 } 392 393 return this.encoding; 394 } 395 396 /** 397 * Gets the format of the bankfile backing the instance. 398 * 399 * @return The format of the bankfile backing the instance. 400 * 401 * @since 1.15 402 */ 403 public int getFormat() 404 { 405 return this.format; 406 } 407 408 /** 409 * Gets the date of validity of the file. 410 * 411 * @return The date of validity of the file or {@code null}, if the instance got created by using one of the 412 * deprecated constructors. 413 * 414 * @since 1.15 415 */ 416 public Date getDateOfValidity() 417 { 418 return (Date) ( this.dateOfValidity != null ? this.dateOfValidity.clone() : null ); 419 } 420 421 /** 422 * Gets the date of expiration of the file. 423 * 424 * @return The date of expiration of the file or {@code null}, if the instance got created by using one of the 425 * deprecated constructors. 426 * 427 * @since 1.15 428 */ 429 public Date getDateOfExpiration() 430 { 431 return (Date) ( this.dateOfExpiration != null ? this.dateOfExpiration.clone() : null ); 432 } 433 434 /** 435 * Gets all records held by the instance. 436 * 437 * @return All records held by the instance. 438 */ 439 public BankleitzahlInfo[] getRecords() 440 { 441 if ( this.cachedRecords == null ) 442 { 443 this.cachedRecords = (BankleitzahlInfo[]) this.records.values(). 444 toArray( new BankleitzahlInfo[ this.records.size() ] ); 445 446 } 447 448 return this.cachedRecords; 449 } 450 451 /** 452 * Gets all records deleted during updating. 453 * 454 * @return All records deleted during updating. 455 * 456 * @see #update(org.jdtaus.banking.util.BankleitzahlenDatei) 457 * 458 * @see #update(org.jdtaus.banking.util.BankleitzahlenDatei) 459 * @since 1.15 460 */ 461 public BankleitzahlInfo[] getDeletedRecords() 462 { 463 if ( this.cachedDeletedRecords == null ) 464 { 465 this.cachedDeletedRecords = (BankleitzahlInfo[]) this.deletedRecords.values(). 466 toArray( new BankleitzahlInfo[ this.deletedRecords.size() ] ); 467 468 } 469 470 return this.cachedDeletedRecords; 471 } 472 473 /** 474 * Gets a record identified by a serial number. 475 * 476 * @param serialNumber The serial number of the record to return. 477 * 478 * @return The record with serial number {@code serialNumber} or {@code null}, if no record matching 479 * {@code serialNumber} exists in the file. 480 * 481 * @throws NullPointerException if {@code serialNumber} is {@code null}. 482 */ 483 public BankleitzahlInfo getRecord( final Integer serialNumber ) 484 { 485 if ( serialNumber == null ) 486 { 487 throw new NullPointerException( "serialNumber" ); 488 } 489 490 return (BankleitzahlInfo) this.records.get( serialNumber ); 491 } 492 493 /** 494 * Gets a deleted record identified by a serial number. 495 * 496 * @param serialNumber The serial number of the deleted record to return. 497 * 498 * @return The deleted record with serial number {@code serialNumber} or {@code null}, if no such record is found. 499 * 500 * @throws NullPointerException if {@code serialNumber} is {@code null}. 501 * 502 * @see #getDeletedRecords() 503 * @see #update(org.jdtaus.banking.util.BankleitzahlenDatei) 504 * @since 1.15 505 */ 506 public BankleitzahlInfo getDeletedRecord( final Integer serialNumber ) 507 { 508 if ( serialNumber == null ) 509 { 510 throw new NullPointerException( "serialNumber" ); 511 } 512 513 return (BankleitzahlInfo) this.deletedRecords.get( serialNumber ); 514 } 515 516 /** 517 * Gets a head office record for a given bank code. 518 * 519 * @param bankCode The bank code of the head office record to return. 520 * 521 * @return The head office record of the bank identified by {@code bankCode} or {@code null}, if no such record is 522 * found. 523 * 524 * @throws NullPointerException if {@code bankCode} is {@code null}. 525 * 526 * @see #getRecords() 527 * @see BankleitzahlInfo#isHeadOffice() 528 * @since 1.15 529 */ 530 public BankleitzahlInfo getHeadOfficeRecord( final Bankleitzahl bankCode ) 531 { 532 if ( bankCode == null ) 533 { 534 throw new NullPointerException( "bankCode" ); 535 } 536 537 return (BankleitzahlInfo) this.headOffices.get( bankCode ); 538 } 539 540 /** 541 * Gets a deleted head office record for a given bank code. 542 * 543 * @param bankCode The bank code of the deleted head office record to return. 544 * 545 * @return The deleted head office record of the bank identified by {@code bankCode} or {@code null}, if no such 546 * record is found. 547 * 548 * @throws NullPointerException if {@code bankCode} is {@code null}. 549 * 550 * @see #getDeletedRecords() 551 * @see BankleitzahlInfo#isHeadOffice() 552 * @see #update(org.jdtaus.banking.util.BankleitzahlenDatei) 553 * @since 1.15 554 */ 555 public BankleitzahlInfo getDeletedHeadOfficeRecord( final Bankleitzahl bankCode ) 556 { 557 if ( bankCode == null ) 558 { 559 throw new NullPointerException( "bankCode" ); 560 } 561 562 return (BankleitzahlInfo) this.deletedHeadOffices.get( bankCode ); 563 } 564 565 /** 566 * Gets branch office records for a given bank code. 567 * 568 * @param bankCode The bank code of the branch office records to return. 569 * 570 * @return The branch office records of the bank identified by {@code bankCode}. 571 * 572 * @throws NullPointerException if {@code bankCode} is {@code null}. 573 * 574 * @see #getRecords() 575 * @see BankleitzahlInfo#isHeadOffice() 576 * @since 1.15 577 */ 578 public BankleitzahlInfo[] getBranchOfficeRecords( final Bankleitzahl bankCode ) 579 { 580 if ( bankCode == null ) 581 { 582 throw new NullPointerException( "bankCode" ); 583 } 584 585 final List records = (List) this.branchOffices.get( bankCode ); 586 return records != null 587 ? (BankleitzahlInfo[]) records.toArray( new BankleitzahlInfo[ records.size() ] ) 588 : NO_RECORDS; 589 590 } 591 592 /** 593 * Gets deleted branch office records for a given bank code. 594 * 595 * @param bankCode The bank code of the deleted branch office records to return. 596 * 597 * @return The deleted branch office records of the bank identified by {@code bankCode}. 598 * 599 * @throws NullPointerException if {@code bankCode} is {@code null}. 600 * 601 * @see #getDeletedRecords() 602 * @see BankleitzahlInfo#isHeadOffice() 603 * @since 1.15 604 */ 605 public BankleitzahlInfo[] getDeletedBranchOfficeRecords( final Bankleitzahl bankCode ) 606 { 607 if ( bankCode == null ) 608 { 609 throw new NullPointerException( "bankCode" ); 610 } 611 612 final List records = (List) this.deletedBranchOffices.get( bankCode ); 613 return records != null 614 ? (BankleitzahlInfo[]) records.toArray( new BankleitzahlInfo[ records.size() ] ) 615 : NO_RECORDS; 616 617 } 618 619 /** 620 * Given a newer version of the Bankleitzahlendatei updates the records of the instance to reflect the changes. 621 * 622 * @param file A newer version of the Bankleitzahlendatei to use for updating the records of this instance. 623 * 624 * @throws NullPointerException if {@code file} is {@code null}. 625 * @throws IllegalArgumentException if {@code file} cannot be used for updating this instance. 626 */ 627 public void update( final BankleitzahlenDatei file ) 628 { 629 if ( file == null ) 630 { 631 throw new NullPointerException( "file" ); 632 } 633 if ( file.getFormat() < this.getFormat() ) 634 { 635 throw new IllegalArgumentException( this.getCannotUpdateIncomptibleFileMessage( 636 this.getLocale(), toFormatName( this.getFormat() ), toFormatName( file.getFormat() ) ) ); 637 638 } 639 640 final boolean log = this.getLogger().isDebugEnabled(); 641 final boolean upgrade = this.getFormat() < file.getFormat(); 642 643 int progress = 0; 644 Task task = new Task(); 645 task.setIndeterminate( false ); 646 task.setCancelable( false ); 647 task.setDescription( new UpdatesBankleitzahlenDateiMessage() ); 648 task.setMinimum( 0 ); 649 task.setMaximum( file.getRecords().length ); 650 task.setProgress( progress ); 651 652 try 653 { 654 this.getTaskMonitor().monitor( task ); 655 656 for ( int i = file.getRecords().length - 1; i >= 0; i-- ) 657 { 658 task.setProgress( progress++ ); 659 final BankleitzahlInfo newVersion = file.getRecords()[i]; 660 661 if ( 'A' == newVersion.getChangeLabel() ) 662 { 663 final BankleitzahlInfo oldVersion = 664 (BankleitzahlInfo) this.records.get( newVersion.getSerialNumber() ); 665 666 if ( oldVersion != null && oldVersion.getChangeLabel() != 'D' ) 667 { 668 this.resetRecords(); 669 throw new IllegalArgumentException( this.getCannotAddDuplicateRecordMessage( 670 this.getLocale(), newVersion.getSerialNumber() ) ); 671 672 } 673 674 this.records.put( newVersion.getSerialNumber(), newVersion ); 675 676 if ( log ) 677 { 678 this.getLogger().debug( this.getAddRecordInfoMessage( 679 this.getLocale(), String.valueOf( newVersion.getChangeLabel() ), 680 newVersion.getSerialNumber() ) ); 681 682 } 683 } 684 else if ( 'M' == newVersion.getChangeLabel() || 'D' == newVersion.getChangeLabel() ) 685 { 686 if ( this.records.put( newVersion.getSerialNumber(), newVersion ) == null ) 687 { 688 this.resetRecords(); 689 throw new IllegalArgumentException( this.getCannotModifyNonexistentRecordMessage( 690 this.getLocale(), newVersion.getSerialNumber() ) ); 691 692 } 693 694 if ( log ) 695 { 696 this.getLogger().debug( this.getModifyRecordInfoMessage( 697 this.getLocale(), String.valueOf( newVersion.getChangeLabel() ), 698 newVersion.getSerialNumber() ) ); 699 700 } 701 } 702 else if ( 'U' == newVersion.getChangeLabel() ) 703 { 704 if ( ( upgrade && this.records.put( newVersion.getSerialNumber(), newVersion ) == null ) 705 || !this.records.containsKey( newVersion.getSerialNumber() ) ) 706 { 707 this.resetRecords(); 708 throw new IllegalArgumentException( this.getCannotModifyNonexistentRecordMessage( 709 this.getLocale(), newVersion.getSerialNumber() ) ); 710 711 } 712 } 713 } 714 715 if ( upgrade ) 716 { 717 if ( this.getLogger().isInfoEnabled() ) 718 { 719 this.getLogger().info( this.getBankcodeFileUpgradeInfoMessage( 720 this.getLocale(), toFormatName( this.format ), toFormatName( file.getFormat() ) ) ); 721 722 } 723 724 this.format = file.getFormat(); 725 } 726 727 this.dateOfValidity = file.getDateOfValidity(); 728 this.dateOfExpiration = file.getDateOfExpiration(); 729 } 730 finally 731 { 732 this.getTaskMonitor().finish( task ); 733 } 734 735 progress = 0; 736 task = new Task(); 737 task.setIndeterminate( false ); 738 task.setCancelable( false ); 739 task.setDescription( new UpdatesBankleitzahlenDateiMessage() ); 740 task.setMinimum( 0 ); 741 task.setMaximum( this.records.size() ); 742 task.setProgress( progress ); 743 744 try 745 { 746 this.getTaskMonitor().monitor( task ); 747 748 for ( final Iterator it = this.records.values().iterator(); it.hasNext(); ) 749 { 750 task.setProgress( progress++ ); 751 final BankleitzahlInfo oldVersion = (BankleitzahlInfo) it.next(); 752 753 if ( 'D' == oldVersion.getChangeLabel() ) 754 { 755 final BankleitzahlInfo newVersion = file.getRecord( oldVersion.getSerialNumber() ); 756 757 if ( newVersion == null ) 758 { 759 if ( this.deletedRecords.put( oldVersion.getSerialNumber(), oldVersion ) != null ) 760 { 761 this.resetRecords(); 762 throw new IllegalStateException( this.getCannotRemoveDuplicateRecordMessage( 763 this.getLocale(), oldVersion.getSerialNumber() ) ); 764 765 } 766 767 it.remove(); 768 769 if ( log ) 770 { 771 this.getLogger().debug( this.getRemoveRecordInfoMessage( 772 this.getLocale(), String.valueOf( oldVersion.getChangeLabel() ), 773 oldVersion.getSerialNumber() ) ); 774 775 } 776 } 777 } 778 } 779 } 780 finally 781 { 782 this.getTaskMonitor().finish( task ); 783 } 784 785 this.updateRecords(); 786 } 787 788 /** 789 * Checks configured properties. 790 * 791 * @throws PropertyException for invalid property values. 792 */ 793 private void assertValidProperties() 794 { 795 if ( this.getEncoding() == null || this.getEncoding().length() == 0 ) 796 { 797 throw new PropertyException( "encoding", this.getEncoding() ); 798 } 799 800 try 801 { 802 "".getBytes( this.getEncoding() ); 803 } 804 catch ( final UnsupportedEncodingException e ) 805 { 806 throw new PropertyException( "encoding", this.getEncoding(), e ); 807 } 808 } 809 810 /** 811 * Checks a given integer to equal one of the format constants defined in this class. 812 * 813 * @param value The value to check. 814 * 815 * @throws IllegalArgumentException if {@code value} does not equal one of the format constants defined in this 816 * class. 817 */ 818 private static void assertValidFormat( final int value ) 819 { 820 if ( value != JUNE_2006_FORMAT && value != JUNE_2013_FORMAT ) 821 { 822 throw new IllegalArgumentException( Integer.toString( value ) ); 823 } 824 } 825 826 /** 827 * Reads a Bankleitzahlendatei from an URL initializing the instance to hold its data. 828 * 829 * @param resource An URL to a Bankleitzahlendatei. 830 * 831 * @throws NullPointerException if {@code resource} is {@code null}. 832 * @throws IllegalArgumentException if {@code resource} does not provide a valid Bankleitzahlendatei. 833 * @throws IOException if reading fails. 834 */ 835 private void readBankfile( final URL resource ) throws IOException 836 { 837 if ( resource == null ) 838 { 839 throw new NullPointerException( "resource" ); 840 } 841 842 this.records.clear(); 843 844 if ( this.getLogger().isDebugEnabled() ) 845 { 846 this.getLogger().debug( this.getFileNameInfoMessage( this.getLocale(), resource.toExternalForm() ) ); 847 } 848 849 LineNumberReader reader = null; 850 final NumberFormat plzFmt = new DecimalFormat( "00000" ); 851 final NumberFormat serFmt = new DecimalFormat( "000000" ); 852 final NumberFormat blzFmt = new DecimalFormat( "00000000" ); 853 854 try 855 { 856 reader = new LineNumberReader( new InputStreamReader( resource.openStream(), this.getEncoding() ) ); 857 boolean emptyLine = false; 858 859 for ( String line = reader.readLine(); line != null; line = reader.readLine() ) 860 { 861 if ( line.trim().length() == 0 ) 862 { 863 emptyLine = true; 864 continue; 865 } 866 867 if ( emptyLine ) 868 { 869 throw new IllegalArgumentException( this.getUnexpectedDataMessage( 870 this.getLocale(), new Integer( reader.getLineNumber() ), resource.toExternalForm() ) ); 871 872 } 873 874 final BankleitzahlInfo r = new BankleitzahlInfo(); 875 876 // Field 1 877 r.setBankCode( Bankleitzahl.parse( field( line, FIELD_TO_OFFSET[0], FIELD_TO_ENDOFFSET[0] ) ) ); 878 // Field 2 879 r.setHeadOffice( "1".equals( field( line, FIELD_TO_OFFSET[1], FIELD_TO_ENDOFFSET[1] ) ) ); 880 // Field 3 881 r.setName( field( line, FIELD_TO_OFFSET[2], FIELD_TO_ENDOFFSET[2] ) ); 882 // Field 4 883 r.setPostalCode( plzFmt.parse( field( line, FIELD_TO_OFFSET[3], FIELD_TO_ENDOFFSET[3] ) ).intValue() ); 884 // Field 5 885 r.setCity( field( line, FIELD_TO_OFFSET[4], FIELD_TO_ENDOFFSET[4] ) ); 886 // Field 6 887 r.setDescription( field( line, FIELD_TO_OFFSET[5], FIELD_TO_ENDOFFSET[5] ) ); 888 // Field 7 889 String field = field( line, FIELD_TO_OFFSET[6], FIELD_TO_ENDOFFSET[6] ); 890 r.setPanInstituteNumber( field.length() > 0 ? plzFmt.parse( field ).intValue() : 0 ); 891 // Field 8 892 r.setBic( field( line, FIELD_TO_OFFSET[7], FIELD_TO_ENDOFFSET[7] ) ); 893 // Field 9 894 r.setValidationLabel( field( line, FIELD_TO_OFFSET[8], FIELD_TO_ENDOFFSET[8] ) ); 895 // Field 10 896 field = field( line, FIELD_TO_OFFSET[9], FIELD_TO_ENDOFFSET[9] ); 897 r.setSerialNumber( new Integer( serFmt.parse( field ).intValue() ) ); 898 // Field 11 899 r.setChangeLabel( field( line, FIELD_TO_OFFSET[10], FIELD_TO_ENDOFFSET[10] ).toCharArray()[0] ); 900 // Field 12 901 r.setMarkedForDeletion( "1".equals( field( line, FIELD_TO_OFFSET[11], FIELD_TO_ENDOFFSET[11] ) ) ); 902 // Field 13 903 Number blz = blzFmt.parse( field( line, FIELD_TO_OFFSET[12], FIELD_TO_ENDOFFSET[12] ) ); 904 if ( blz.intValue() != 0 ) 905 { 906 r.setReplacingBankCode( Bankleitzahl.valueOf( blz ) ); 907 } 908 else 909 { 910 r.setReplacingBankCode( null ); 911 } 912 913 if ( this.getFormat() >= JUNE_2013_FORMAT ) 914 { 915 // Field 14 916 r.setIbanRuleLabel( Integer.valueOf( field( line, FIELD_TO_OFFSET[13], 917 FIELD_TO_ENDOFFSET[13] ) ) ); 918 919 r.setIbanRuleVersion( Integer.valueOf( field( line, FIELD_TO_OFFSET[14], 920 FIELD_TO_ENDOFFSET[14] ) ) ); 921 922 } 923 924 switch ( r.getChangeLabel() ) 925 { 926 case 'A': 927 r.setCreationDate( this.getDateOfValidity() ); 928 break; 929 case 'M': 930 r.setModificationDate( this.getDateOfValidity() ); 931 break; 932 case 'D': 933 r.setDeletionDate( this.getDateOfExpiration() ); 934 break; 935 case 'U': 936 // ignored 937 break; 938 default: 939 throw new AssertionError( r.getChangeLabel() ); 940 } 941 942 if ( this.records.put( r.getSerialNumber(), r ) != null ) 943 { 944 this.resetRecords(); 945 throw new IllegalArgumentException( this.getCannotAddDuplicateRecordMessage( 946 this.getLocale(), r.getSerialNumber() ) ); 947 948 } 949 } 950 } 951 catch ( final ParseException e ) 952 { 953 this.resetRecords(); 954 throw (IllegalArgumentException) new IllegalArgumentException( resource.toExternalForm() ).initCause( e ); 955 } 956 catch ( final IndexOutOfBoundsException e ) 957 { 958 this.resetRecords(); 959 throw (IllegalArgumentException) new IllegalArgumentException( resource.toExternalForm() ).initCause( e ); 960 } 961 catch ( final IOException e ) 962 { 963 this.resetRecords(); 964 throw e; 965 } 966 finally 967 { 968 this.cachedRecords = null; 969 this.cachedDeletedRecords = null; 970 971 if ( reader != null ) 972 { 973 reader.close(); 974 } 975 } 976 } 977 978 private void resetRecords() 979 { 980 this.records.clear(); 981 this.deletedRecords.clear(); 982 this.updateRecords(); 983 } 984 985 private void updateRecords() 986 { 987 this.headOffices.clear(); 988 this.deletedHeadOffices.clear(); 989 this.branchOffices.clear(); 990 this.deletedBranchOffices.clear(); 991 this.cachedRecords = null; 992 this.cachedDeletedRecords = null; 993 994 for ( int i = 0, l0 = this.getRecords().length; i < l0; i++ ) 995 { 996 final BankleitzahlInfo record = this.getRecords()[i]; 997 998 if ( record.isHeadOffice() ) 999 { 1000 if ( this.headOffices.put( record.getBankCode(), record ) != null ) 1001 { 1002 this.resetRecords(); 1003 throw new IllegalStateException( this.getCannotAddDuplicateHeadOfficeRecordMessage( 1004 this.getLocale(), record.getBankCode() ) ); 1005 1006 } 1007 } 1008 else 1009 { 1010 List list = (List) this.branchOffices.get( record.getBankCode() ); 1011 1012 if ( list == null ) 1013 { 1014 list = new ArrayList(); 1015 this.branchOffices.put( record.getBankCode(), list ); 1016 } 1017 1018 list.add( record ); 1019 } 1020 } 1021 1022 for ( int i = 0, l0 = this.getDeletedRecords().length; i < l0; i++ ) 1023 { 1024 final BankleitzahlInfo record = this.getDeletedRecords()[i]; 1025 1026 if ( record.isHeadOffice() ) 1027 { 1028 if ( this.deletedHeadOffices.put( record.getBankCode(), record ) != null ) 1029 { 1030 this.resetRecords(); 1031 throw new IllegalStateException( this.getCannotAddDuplicateHeadOfficeRecordMessage( 1032 this.getLocale(), record.getBankCode() ) ); 1033 1034 } 1035 } 1036 else 1037 { 1038 List list = (List) this.deletedBranchOffices.get( record.getBankCode() ); 1039 1040 if ( list == null ) 1041 { 1042 list = new ArrayList(); 1043 this.deletedBranchOffices.put( record.getBankCode(), list ); 1044 } 1045 1046 list.add( record ); 1047 } 1048 } 1049 } 1050 1051 private static String field( final String line, final int startOffset, final int endOffset ) 1052 { 1053 return line.substring( startOffset, endOffset ).trim(); 1054 } 1055 1056 private static String toFormatName( final long format ) 1057 { 1058 String name = ""; 1059 1060 if ( format == JUNE_2006_FORMAT ) 1061 { 1062 name = "JUNE2006"; 1063 } 1064 else if ( format == JUNE_2013_FORMAT ) 1065 { 1066 name = "JUNE2013"; 1067 } 1068 1069 return name; 1070 } 1071 1072 //-----------------------------------------------------BankleitzahlenDatei-- 1073 //--Messages---------------------------------------------------------------- 1074 1075// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages 1076 // This section is managed by jdtaus-container-mojo. 1077 1078 /** 1079 * Gets the text of message <code>fileNameInfo</code>. 1080 * <blockquote><pre>Lädt Bankleitzahlendatei "{0}".</pre></blockquote> 1081 * <blockquote><pre>Loading Bankleitzahlendatei "{0}".</pre></blockquote> 1082 * 1083 * @param locale The locale of the message instance to return. 1084 * @param fileName format parameter. 1085 * 1086 * @return the text of message <code>fileNameInfo</code>. 1087 */ 1088 private String getFileNameInfoMessage( final Locale locale, 1089 final java.lang.String fileName ) 1090 { 1091 return ContainerFactory.getContainer(). 1092 getMessage( this, "fileNameInfo", locale, 1093 new Object[] 1094 { 1095 fileName 1096 }); 1097 1098 } 1099 1100 /** 1101 * Gets the text of message <code>addRecordInfo</code>. 1102 * <blockquote><pre>{0}: Datensatz {1, number} hinzugefügt.</pre></blockquote> 1103 * <blockquote><pre>{0}: Added record {1, number}.</pre></blockquote> 1104 * 1105 * @param locale The locale of the message instance to return. 1106 * @param label format parameter. 1107 * @param serialNumber format parameter. 1108 * 1109 * @return the text of message <code>addRecordInfo</code>. 1110 */ 1111 private String getAddRecordInfoMessage( final Locale locale, 1112 final java.lang.String label, 1113 final java.lang.Number serialNumber ) 1114 { 1115 return ContainerFactory.getContainer(). 1116 getMessage( this, "addRecordInfo", locale, 1117 new Object[] 1118 { 1119 label, 1120 serialNumber 1121 }); 1122 1123 } 1124 1125 /** 1126 * Gets the text of message <code>modifyRecordInfo</code>. 1127 * <blockquote><pre>{0}: Datensatz {1, number} aktualisiert.</pre></blockquote> 1128 * <blockquote><pre>{0}: Updated record {1, number}.</pre></blockquote> 1129 * 1130 * @param locale The locale of the message instance to return. 1131 * @param label format parameter. 1132 * @param serialNumber format parameter. 1133 * 1134 * @return the text of message <code>modifyRecordInfo</code>. 1135 */ 1136 private String getModifyRecordInfoMessage( final Locale locale, 1137 final java.lang.String label, 1138 final java.lang.Number serialNumber ) 1139 { 1140 return ContainerFactory.getContainer(). 1141 getMessage( this, "modifyRecordInfo", locale, 1142 new Object[] 1143 { 1144 label, 1145 serialNumber 1146 }); 1147 1148 } 1149 1150 /** 1151 * Gets the text of message <code>removeRecordInfo</code>. 1152 * <blockquote><pre>{0}: Datensatz {1, number} entfernt.</pre></blockquote> 1153 * <blockquote><pre>{0}: Removed record {1, number}.</pre></blockquote> 1154 * 1155 * @param locale The locale of the message instance to return. 1156 * @param label format parameter. 1157 * @param serialNumber format parameter. 1158 * 1159 * @return the text of message <code>removeRecordInfo</code>. 1160 */ 1161 private String getRemoveRecordInfoMessage( final Locale locale, 1162 final java.lang.String label, 1163 final java.lang.Number serialNumber ) 1164 { 1165 return ContainerFactory.getContainer(). 1166 getMessage( this, "removeRecordInfo", locale, 1167 new Object[] 1168 { 1169 label, 1170 serialNumber 1171 }); 1172 1173 } 1174 1175 /** 1176 * Gets the text of message <code>cannotAddDuplicateRecord</code>. 1177 * <blockquote><pre>Datensatz mit Seriennummer {0,number} existiert bereits und kann nicht hinzugefügt werden.</pre></blockquote> 1178 * <blockquote><pre>Record with serial number {0,number} already exists and cannot be added.</pre></blockquote> 1179 * 1180 * @param locale The locale of the message instance to return. 1181 * @param serialNumber format parameter. 1182 * 1183 * @return the text of message <code>cannotAddDuplicateRecord</code>. 1184 */ 1185 private String getCannotAddDuplicateRecordMessage( final Locale locale, 1186 final java.lang.Number serialNumber ) 1187 { 1188 return ContainerFactory.getContainer(). 1189 getMessage( this, "cannotAddDuplicateRecord", locale, 1190 new Object[] 1191 { 1192 serialNumber 1193 }); 1194 1195 } 1196 1197 /** 1198 * Gets the text of message <code>cannotAddDuplicateHeadOfficeRecord</code>. 1199 * <blockquote><pre>Datensatz der Hauptstelle {0,number} existiert bereits und kann nicht hinzugefügt werden.</pre></blockquote> 1200 * <blockquote><pre>Head office record of bank code {0,number} already exists and cannot be added.</pre></blockquote> 1201 * 1202 * @param locale The locale of the message instance to return. 1203 * @param bankCode format parameter. 1204 * 1205 * @return the text of message <code>cannotAddDuplicateHeadOfficeRecord</code>. 1206 */ 1207 private String getCannotAddDuplicateHeadOfficeRecordMessage( final Locale locale, 1208 final java.lang.Number bankCode ) 1209 { 1210 return ContainerFactory.getContainer(). 1211 getMessage( this, "cannotAddDuplicateHeadOfficeRecord", locale, 1212 new Object[] 1213 { 1214 bankCode 1215 }); 1216 1217 } 1218 1219 /** 1220 * Gets the text of message <code>cannotModifyNonexistentRecord</code>. 1221 * <blockquote><pre>Ein Datensatz mit Seriennummer {0,number} existiert nicht und kann nicht aktualisiert werden.</pre></blockquote> 1222 * <blockquote><pre>Record with serial number {0,number} does not exist and cannot be updated.</pre></blockquote> 1223 * 1224 * @param locale The locale of the message instance to return. 1225 * @param serialNumber format parameter. 1226 * 1227 * @return the text of message <code>cannotModifyNonexistentRecord</code>. 1228 */ 1229 private String getCannotModifyNonexistentRecordMessage( final Locale locale, 1230 final java.lang.Number serialNumber ) 1231 { 1232 return ContainerFactory.getContainer(). 1233 getMessage( this, "cannotModifyNonexistentRecord", locale, 1234 new Object[] 1235 { 1236 serialNumber 1237 }); 1238 1239 } 1240 1241 /** 1242 * Gets the text of message <code>cannotUpdateIncomptibleFile</code>. 1243 * <blockquote><pre>''{0}'' Bankleitzahlendatei kann nicht mit ''{1}'' Bankleitzahlendatei aktualisiert werden.</pre></blockquote> 1244 * <blockquote><pre>''{0}'' bank code file cannot be updated with a ''{1}'' bank code file.</pre></blockquote> 1245 * 1246 * @param locale The locale of the message instance to return. 1247 * @param targetBankCodeFileFormat format parameter. 1248 * @param sourceBankCodeFileFormat format parameter. 1249 * 1250 * @return the text of message <code>cannotUpdateIncomptibleFile</code>. 1251 */ 1252 private String getCannotUpdateIncomptibleFileMessage( final Locale locale, 1253 final java.lang.String targetBankCodeFileFormat, 1254 final java.lang.String sourceBankCodeFileFormat ) 1255 { 1256 return ContainerFactory.getContainer(). 1257 getMessage( this, "cannotUpdateIncomptibleFile", locale, 1258 new Object[] 1259 { 1260 targetBankCodeFileFormat, 1261 sourceBankCodeFileFormat 1262 }); 1263 1264 } 1265 1266 /** 1267 * Gets the text of message <code>unexpectedData</code>. 1268 * <blockquote><pre>Unerwartete Daten in Zeile {0,number} bei der Verarbeitung von {1}.</pre></blockquote> 1269 * <blockquote><pre>Unexpected data at line {0,number} processing {1}.</pre></blockquote> 1270 * 1271 * @param locale The locale of the message instance to return. 1272 * @param lineNumber format parameter. 1273 * @param resourceName format parameter. 1274 * 1275 * @return the text of message <code>unexpectedData</code>. 1276 */ 1277 private String getUnexpectedDataMessage( final Locale locale, 1278 final java.lang.Number lineNumber, 1279 final java.lang.String resourceName ) 1280 { 1281 return ContainerFactory.getContainer(). 1282 getMessage( this, "unexpectedData", locale, 1283 new Object[] 1284 { 1285 lineNumber, 1286 resourceName 1287 }); 1288 1289 } 1290 1291 /** 1292 * Gets the text of message <code>bankcodeFileUpgradeInfo</code>. 1293 * <blockquote><pre>''{0}'' Bankleitzahlendatei zu ''{1}'' Bankleitzahlendatei aktualisiert.</pre></blockquote> 1294 * <blockquote><pre>''{0}'' bank code file upgraded to ''{1}'' bank code file.</pre></blockquote> 1295 * 1296 * @param locale The locale of the message instance to return. 1297 * @param targetBankCodeFileFormat format parameter. 1298 * @param sourceBankCodeFileFormat format parameter. 1299 * 1300 * @return the text of message <code>bankcodeFileUpgradeInfo</code>. 1301 */ 1302 private String getBankcodeFileUpgradeInfoMessage( final Locale locale, 1303 final java.lang.String targetBankCodeFileFormat, 1304 final java.lang.String sourceBankCodeFileFormat ) 1305 { 1306 return ContainerFactory.getContainer(). 1307 getMessage( this, "bankcodeFileUpgradeInfo", locale, 1308 new Object[] 1309 { 1310 targetBankCodeFileFormat, 1311 sourceBankCodeFileFormat 1312 }); 1313 1314 } 1315 1316 /** 1317 * Gets the text of message <code>cannotRemoveDuplicateRecord</code>. 1318 * <blockquote><pre>Datensatz mit Seriennummer {0,number} bereits gelöscht.</pre></blockquote> 1319 * <blockquote><pre>Record with serial number {0,number} already deleted.</pre></blockquote> 1320 * 1321 * @param locale The locale of the message instance to return. 1322 * @param serialNumber format parameter. 1323 * 1324 * @return the text of message <code>cannotRemoveDuplicateRecord</code>. 1325 */ 1326 private String getCannotRemoveDuplicateRecordMessage( final Locale locale, 1327 final java.lang.Number serialNumber ) 1328 { 1329 return ContainerFactory.getContainer(). 1330 getMessage( this, "cannotRemoveDuplicateRecord", locale, 1331 new Object[] 1332 { 1333 serialNumber 1334 }); 1335 1336 } 1337 1338// </editor-fold>//GEN-END:jdtausMessages 1339 1340 //----------------------------------------------------------------Messages-- 1341}