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