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.util.HashMap; 029import java.util.Iterator; 030import java.util.Locale; 031import java.util.Map; 032import org.jdtaus.banking.BankleitzahlInfo; 033import org.jdtaus.banking.messages.UpdatesBankleitzahlenDateiMessage; 034import org.jdtaus.core.container.ContainerFactory; 035import org.jdtaus.core.container.PropertyException; 036import org.jdtaus.core.logging.spi.Logger; 037import org.jdtaus.core.monitor.spi.Task; 038import org.jdtaus.core.monitor.spi.TaskMonitor; 039 040/** 041 * German Bankleitzahlendatei for the format as of 2006-06-01. 042 * <p>For further information see the 043 * <a href="../../../../doc-files/merkblatt_bankleitzahlendatei.pdf">Merkblatt Bankleitzahlendatei</a>. 044 * An updated version of the document may be found at 045 * <a href="http://www.bundesbank.de">Deutsche Bundesbank</a>. 046 * </p> 047 * 048 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 049 * @version $JDTAUS: BankleitzahlenDatei.java 8811 2012-12-04 00:48:00Z schulte $ 050 */ 051public final class BankleitzahlenDatei 052{ 053 //--Dependencies------------------------------------------------------------ 054 055// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies 056 // This section is managed by jdtaus-container-mojo. 057 058 /** 059 * Gets the configured <code>Logger</code> implementation. 060 * 061 * @return The configured <code>Logger</code> implementation. 062 */ 063 private Logger getLogger() 064 { 065 return (Logger) ContainerFactory.getContainer(). 066 getDependency( this, "Logger" ); 067 068 } 069 070 /** 071 * Gets the configured <code>TaskMonitor</code> implementation. 072 * 073 * @return The configured <code>TaskMonitor</code> implementation. 074 */ 075 private TaskMonitor getTaskMonitor() 076 { 077 return (TaskMonitor) ContainerFactory.getContainer(). 078 getDependency( this, "TaskMonitor" ); 079 080 } 081 082 /** 083 * Gets the configured <code>Locale</code> implementation. 084 * 085 * @return The configured <code>Locale</code> implementation. 086 */ 087 private Locale getLocale() 088 { 089 return (Locale) ContainerFactory.getContainer(). 090 getDependency( this, "Locale" ); 091 092 } 093 094// </editor-fold>//GEN-END:jdtausDependencies 095 096 //------------------------------------------------------------Dependencies-- 097 //--Properties-------------------------------------------------------------- 098 099// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties 100 // This section is managed by jdtaus-container-mojo. 101 102 /** 103 * Gets the value of property <code>defaultEncoding</code>. 104 * 105 * @return Default encoding to use when reading bankfile resources. 106 */ 107 private java.lang.String getDefaultEncoding() 108 { 109 return (java.lang.String) ContainerFactory.getContainer(). 110 getProperty( this, "defaultEncoding" ); 111 112 } 113 114// </editor-fold>//GEN-END:jdtausProperties 115 116 //--------------------------------------------------------------Properties-- 117 //--BankleitzahlenDatei----------------------------------------------------- 118 119 /** Records held by the instance. */ 120 private Map records = new HashMap( 20000 ); 121 private BankleitzahlInfo[] cachedRecords; 122 123 /** Encoding to use when reading bankfile resources. */ 124 private String encoding; 125 126 /** 127 * Reads a Bankleitzahlendatei form an URL initializing the instance to 128 * hold its data. 129 * 130 * @param resource an URL to a Bankleitzahlendatei. 131 * 132 * @throws NullPointerException if {@code resource} is {@code null}. 133 * @throws PropertyException for invalid property values. 134 * @throws IllegalArgumentException if {@code resource} does not provide 135 * a valid Bankleitzahlendatei. 136 * @throws IOException if reading fails. 137 */ 138 public BankleitzahlenDatei( final URL resource ) throws IOException 139 { 140 super(); 141 this.assertValidProperties(); 142 this.readBankfile( resource ); 143 } 144 145 /** 146 * Reads a Bankleitzahlendatei form an URL initializing the instance to 147 * hold its data taking the encoding to use when reading the file. 148 * 149 * @param resource an URL to a Bankleitzahlendatei. 150 * @param encoding the encoding to use when reading {@code resource}. 151 * 152 * @throws NullPointerException if either {@code resource} or 153 * {@code encoding} is {@code null}. 154 * @throws PropertyException for invalid property values. 155 * @throws IllegalArgumentException if {@code resource} does not provide 156 * a valid Bankleitzahlendatei. 157 * @throws IOException if reading fails. 158 */ 159 public BankleitzahlenDatei( final URL resource, final String encoding ) 160 throws IOException 161 { 162 super(); 163 this.encoding = encoding; 164 this.assertValidProperties(); 165 this.readBankfile( resource ); 166 } 167 168 /** 169 * Gets the encoding used for reading bankfile resources. 170 * 171 * @return the encoding used for reading bankfile resources. 172 */ 173 public String getEncoding() 174 { 175 if ( this.encoding == null ) 176 { 177 this.encoding = this.getDefaultEncoding(); 178 } 179 180 return this.encoding; 181 } 182 183 /** 184 * Gets all records held by the instance. 185 * 186 * @return all records held by the instance. 187 */ 188 public BankleitzahlInfo[] getRecords() 189 { 190 if ( this.cachedRecords == null ) 191 { 192 this.cachedRecords = (BankleitzahlInfo[]) this.records.values(). 193 toArray( new BankleitzahlInfo[ this.records.size() ] ); 194 195 } 196 197 return this.cachedRecords; 198 } 199 200 /** 201 * Gets a record identified by a serial number. 202 * 203 * @param serialNumber the serial number of the record to return. 204 * 205 * @return the record with serial number {@code serialNumber} or 206 * {@code null} if no record matching {@code serialNumber} exists in the 207 * file. 208 * 209 * @throws NullPointerException if {@code serialNumber} is {@code null}. 210 */ 211 public BankleitzahlInfo getRecord( final Integer serialNumber ) 212 { 213 if ( serialNumber == null ) 214 { 215 throw new NullPointerException( "serialNumber" ); 216 } 217 218 return (BankleitzahlInfo) this.records.get( serialNumber ); 219 } 220 221 /** 222 * Given a newer version of the Bankleitzahlendatei updates the records of 223 * the instance to reflect the changes. 224 * 225 * @param file a newer version of the Bankleitzahlendatei to use for 226 * updating the records of this instance. 227 * 228 * @throws NullPointerException if {@code file} is {@code null}. 229 * @throws IllegalArgumentException if {@code file} cannot be used for 230 * updating this instance. 231 */ 232 public void update( final BankleitzahlenDatei file ) 233 { 234 if ( file == null ) 235 { 236 throw new NullPointerException( "file" ); 237 } 238 239 int i; 240 final boolean log = this.getLogger().isDebugEnabled(); 241 BankleitzahlInfo oldVersion; 242 BankleitzahlInfo newVersion; 243 244 int progress = 0; 245 Task task = new Task(); 246 task.setIndeterminate( false ); 247 task.setCancelable( false ); 248 task.setDescription( new UpdatesBankleitzahlenDateiMessage() ); 249 task.setMinimum( 0 ); 250 task.setMaximum( file.getRecords().length ); 251 task.setProgress( progress ); 252 253 try 254 { 255 this.getTaskMonitor().monitor( task ); 256 257 for ( i = file.getRecords().length - 1; i >= 0; i-- ) 258 { 259 task.setProgress( progress++ ); 260 newVersion = file.getRecords()[i]; 261 if ( 'A' == newVersion.getChangeLabel() ) 262 { 263 oldVersion = (BankleitzahlInfo) this.records.get( 264 newVersion.getSerialNumber() ); 265 266 if ( oldVersion != null && 267 oldVersion.getChangeLabel() != 'D' ) 268 { 269 throw new IllegalArgumentException( 270 this.getCannotAddDuplicateRecordMessage( 271 this.getLocale(), 272 newVersion.getSerialNumber() ) ); 273 274 } 275 276 this.records.put( newVersion.getSerialNumber(), newVersion ); 277 278 if ( log ) 279 { 280 this.getLogger().debug( 281 this.getAddRecordInfoMessage( 282 this.getLocale(), 283 String.valueOf( newVersion.getChangeLabel() ), 284 newVersion.getSerialNumber() ) ); 285 286 } 287 } 288 else if ( 'M' == newVersion.getChangeLabel() || 289 'D' == newVersion.getChangeLabel() ) 290 { 291 if ( this.records.put( newVersion.getSerialNumber(), 292 newVersion ) == null ) 293 { 294 throw new IllegalArgumentException( 295 this.getCannotModifyNonexistentRecordMessage( 296 this.getLocale(), newVersion.getSerialNumber() ) ); 297 298 } 299 300 if ( log ) 301 { 302 this.getLogger().debug( 303 this.getModifyRecordInfoMessage( 304 this.getLocale(), 305 String.valueOf( newVersion.getChangeLabel() ), 306 newVersion.getSerialNumber() ) ); 307 308 } 309 310 } 311 else if ( 'U' == newVersion.getChangeLabel() && 312 !this.records.containsKey( newVersion.getSerialNumber() ) ) 313 { 314 throw new IllegalArgumentException( 315 this.getCannotModifyNonexistentRecordMessage( 316 this.getLocale(), newVersion.getSerialNumber() ) ); 317 318 } 319 } 320 } 321 finally 322 { 323 this.getTaskMonitor().finish( task ); 324 } 325 326 progress = 0; 327 task = new Task(); 328 task.setIndeterminate( false ); 329 task.setCancelable( false ); 330 task.setDescription( new UpdatesBankleitzahlenDateiMessage() ); 331 task.setMinimum( 0 ); 332 task.setMaximum( this.records.size() ); 333 task.setProgress( progress ); 334 335 try 336 { 337 this.getTaskMonitor().monitor( task ); 338 for ( Iterator it = this.records.values().iterator(); it.hasNext();) 339 { 340 task.setProgress( progress++ ); 341 oldVersion = (BankleitzahlInfo) it.next(); 342 343 if ( 'D' == oldVersion.getChangeLabel() ) 344 { 345 newVersion = file.getRecord( oldVersion.getSerialNumber() ); 346 if ( newVersion == null ) 347 { 348 it.remove(); 349 350 if ( log ) 351 { 352 this.getLogger().debug( 353 this.getRemoveRecordInfoMessage( 354 this.getLocale(), 355 String.valueOf( oldVersion.getChangeLabel() ), 356 oldVersion.getSerialNumber() ) ); 357 358 } 359 } 360 } 361 } 362 } 363 finally 364 { 365 this.getTaskMonitor().finish( task ); 366 } 367 368 this.cachedRecords = null; 369 } 370 371 /** 372 * Checks configured properties. 373 * 374 * @throws PropertyException for invalid property values. 375 */ 376 private void assertValidProperties() 377 { 378 if ( this.getEncoding() == null || this.getEncoding().length() == 0 ) 379 { 380 throw new PropertyException( "encoding", this.getEncoding() ); 381 } 382 383 try 384 { 385 "".getBytes( this.getEncoding() ); 386 } 387 catch ( UnsupportedEncodingException e ) 388 { 389 throw new PropertyException( "encoding", this.getEncoding(), e ); 390 } 391 } 392 393 /** 394 * Reads a Bankleitzahlendatei from an URL initializing the instance to 395 * hold its data. 396 * 397 * @param resource An URL to a Bankleitzahlendatei. 398 * 399 * @throws NullPointerException if {@code resource} is {@code null}. 400 * @throws IllegalArgumentException if {@code resource} does not provide 401 * a valid Bankleitzahlendatei. 402 * @throws IOException if reading fails. 403 */ 404 private void readBankfile( final URL resource ) throws IOException 405 { 406 if ( resource == null ) 407 { 408 throw new NullPointerException( "resource" ); 409 } 410 411 this.records.clear(); 412 413 if ( this.getLogger().isDebugEnabled() ) 414 { 415 this.getLogger().debug( this.getFileNameInfoMessage( 416 this.getLocale(), resource.toExternalForm() ) ); 417 418 } 419 420 LineNumberReader reader = null; 421 422 try 423 { 424 reader = new LineNumberReader( new InputStreamReader( 425 resource.openStream(), this.getEncoding() ) ); 426 427 String line; 428 boolean emptyLine = false; 429 while ( ( line = reader.readLine() ) != null ) 430 { 431 if ( line.trim().length() == 0 ) 432 { 433 emptyLine = true; 434 continue; 435 } 436 437 if ( emptyLine ) 438 { 439 throw new IllegalArgumentException( 440 this.getUnexpectedDataMessage( 441 this.getLocale(), new Integer( reader.getLineNumber() ), 442 resource.toExternalForm() ) ); 443 444 } 445 446 447 final BankleitzahlInfo rec = new BankleitzahlInfo(); 448 rec.parse( line ); 449 450 if ( this.records.put( rec.getSerialNumber(), rec ) != null ) 451 { 452 throw new IllegalArgumentException( 453 this.getCannotAddDuplicateRecordMessage( 454 this.getLocale(), rec.getSerialNumber() ) ); 455 456 } 457 } 458 } 459 finally 460 { 461 this.cachedRecords = null; 462 463 if ( reader != null ) 464 { 465 reader.close(); 466 } 467 } 468 } 469 470 //-----------------------------------------------------BankleitzahlenDatei-- 471 //--Messages---------------------------------------------------------------- 472 473// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages 474 // This section is managed by jdtaus-container-mojo. 475 476 /** 477 * Gets the text of message <code>fileNameInfo</code>. 478 * <blockquote><pre>Lädt Bankleitzahlendatei "{0}".</pre></blockquote> 479 * <blockquote><pre>Loading Bankleitzahlendatei "{0}".</pre></blockquote> 480 * 481 * @param locale The locale of the message instance to return. 482 * @param fileName format parameter. 483 * 484 * @return the text of message <code>fileNameInfo</code>. 485 */ 486 private String getFileNameInfoMessage( final Locale locale, 487 final java.lang.String fileName ) 488 { 489 return ContainerFactory.getContainer(). 490 getMessage( this, "fileNameInfo", locale, 491 new Object[] 492 { 493 fileName 494 }); 495 496 } 497 498 /** 499 * Gets the text of message <code>addRecordInfo</code>. 500 * <blockquote><pre>{0}: Datensatz {1, number} hinzugefügt.</pre></blockquote> 501 * <blockquote><pre>{0}: Added record {1, number}.</pre></blockquote> 502 * 503 * @param locale The locale of the message instance to return. 504 * @param label format parameter. 505 * @param serialNumber format parameter. 506 * 507 * @return the text of message <code>addRecordInfo</code>. 508 */ 509 private String getAddRecordInfoMessage( final Locale locale, 510 final java.lang.String label, 511 final java.lang.Number serialNumber ) 512 { 513 return ContainerFactory.getContainer(). 514 getMessage( this, "addRecordInfo", locale, 515 new Object[] 516 { 517 label, 518 serialNumber 519 }); 520 521 } 522 523 /** 524 * Gets the text of message <code>modifyRecordInfo</code>. 525 * <blockquote><pre>{0}: Datensatz {1, number} aktualisiert.</pre></blockquote> 526 * <blockquote><pre>{0}: Updated record {1, number}.</pre></blockquote> 527 * 528 * @param locale The locale of the message instance to return. 529 * @param label format parameter. 530 * @param serialNumber format parameter. 531 * 532 * @return the text of message <code>modifyRecordInfo</code>. 533 */ 534 private String getModifyRecordInfoMessage( final Locale locale, 535 final java.lang.String label, 536 final java.lang.Number serialNumber ) 537 { 538 return ContainerFactory.getContainer(). 539 getMessage( this, "modifyRecordInfo", locale, 540 new Object[] 541 { 542 label, 543 serialNumber 544 }); 545 546 } 547 548 /** 549 * Gets the text of message <code>removeRecordInfo</code>. 550 * <blockquote><pre>{0}: Datensatz {1, number} entfernt.</pre></blockquote> 551 * <blockquote><pre>{0}: Removed record {1, number}.</pre></blockquote> 552 * 553 * @param locale The locale of the message instance to return. 554 * @param label format parameter. 555 * @param serialNumber format parameter. 556 * 557 * @return the text of message <code>removeRecordInfo</code>. 558 */ 559 private String getRemoveRecordInfoMessage( final Locale locale, 560 final java.lang.String label, 561 final java.lang.Number serialNumber ) 562 { 563 return ContainerFactory.getContainer(). 564 getMessage( this, "removeRecordInfo", locale, 565 new Object[] 566 { 567 label, 568 serialNumber 569 }); 570 571 } 572 573 /** 574 * Gets the text of message <code>cannotAddDuplicateRecord</code>. 575 * <blockquote><pre>Datensatz mit Seriennummer {0,number} existiert bereits und kann nicht hinzugefügt werden.</pre></blockquote> 576 * <blockquote><pre>Record with serial number {0,number} already exists and cannot be added.</pre></blockquote> 577 * 578 * @param locale The locale of the message instance to return. 579 * @param serialNumber format parameter. 580 * 581 * @return the text of message <code>cannotAddDuplicateRecord</code>. 582 */ 583 private String getCannotAddDuplicateRecordMessage( final Locale locale, 584 final java.lang.Number serialNumber ) 585 { 586 return ContainerFactory.getContainer(). 587 getMessage( this, "cannotAddDuplicateRecord", locale, 588 new Object[] 589 { 590 serialNumber 591 }); 592 593 } 594 595 /** 596 * Gets the text of message <code>cannotModifyNonexistentRecord</code>. 597 * <blockquote><pre>Ein Datensatz mit Seriennummer {0,number} existiert nicht und kann nicht aktualisiert werden.</pre></blockquote> 598 * <blockquote><pre>Record with serial number {0,number} does not exist and cannot be updated.</pre></blockquote> 599 * 600 * @param locale The locale of the message instance to return. 601 * @param serialNumber format parameter. 602 * 603 * @return the text of message <code>cannotModifyNonexistentRecord</code>. 604 */ 605 private String getCannotModifyNonexistentRecordMessage( final Locale locale, 606 final java.lang.Number serialNumber ) 607 { 608 return ContainerFactory.getContainer(). 609 getMessage( this, "cannotModifyNonexistentRecord", locale, 610 new Object[] 611 { 612 serialNumber 613 }); 614 615 } 616 617 /** 618 * Gets the text of message <code>unexpectedData</code>. 619 * <blockquote><pre>Unerwartete Daten in Zeile {0,number} bei der Verarbeitung von {1}.</pre></blockquote> 620 * <blockquote><pre>Unexpected data at line {0,number} processing {1}.</pre></blockquote> 621 * 622 * @param locale The locale of the message instance to return. 623 * @param lineNumber format parameter. 624 * @param resourceName format parameter. 625 * 626 * @return the text of message <code>unexpectedData</code>. 627 */ 628 private String getUnexpectedDataMessage( final Locale locale, 629 final java.lang.Number lineNumber, 630 final java.lang.String resourceName ) 631 { 632 return ContainerFactory.getContainer(). 633 getMessage( this, "unexpectedData", locale, 634 new Object[] 635 { 636 lineNumber, 637 resourceName 638 }); 639 640 } 641 642// </editor-fold>//GEN-END:jdtausMessages 643 644 //----------------------------------------------------------------Messages-- 645}