1 /*
2 * jDTAUS Banking Utilities
3 * Copyright (C) 2005 Christian Schulte
4 * <cs@schulte.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 *
20 */
21 package org.jdtaus.banking.util;
22
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.io.LineNumberReader;
26 import java.io.UnsupportedEncodingException;
27 import java.net.URL;
28 import java.text.DecimalFormat;
29 import java.text.NumberFormat;
30 import java.text.ParseException;
31 import java.util.ArrayList;
32 import java.util.Date;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import org.jdtaus.banking.Bankleitzahl;
39 import org.jdtaus.banking.BankleitzahlInfo;
40 import org.jdtaus.banking.messages.UpdatesBankleitzahlenDateiMessage;
41 import org.jdtaus.core.container.ContainerFactory;
42 import org.jdtaus.core.container.PropertyException;
43 import org.jdtaus.core.logging.spi.Logger;
44 import org.jdtaus.core.monitor.spi.Task;
45 import org.jdtaus.core.monitor.spi.TaskMonitor;
46
47 /**
48 * German Bankleitzahlendatei for the format as of 2006-06-01.
49 * <p>For further information see the
50 * <a href="../../../../doc-files/merkblatt_bankleitzahlendatei.pdf">Merkblatt Bankleitzahlendatei</a>.
51 * An updated version of the document may be found at
52 * <a href="http://www.bundesbank.de">Deutsche Bundesbank</a>.
53 * </p>
54 *
55 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
56 * @version $JDTAUS: BankleitzahlenDatei.java 8861 2014-01-10 17:09:50Z schulte $
57 */
58 public final class BankleitzahlenDatei
59 {
60 //--Dependencies------------------------------------------------------------
61
62 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
63 // This section is managed by jdtaus-container-mojo.
64
65 /**
66 * Gets the configured <code>Logger</code> implementation.
67 *
68 * @return The configured <code>Logger</code> implementation.
69 */
70 private Logger getLogger()
71 {
72 return (Logger) ContainerFactory.getContainer().
73 getDependency( this, "Logger" );
74
75 }
76
77 /**
78 * Gets the configured <code>TaskMonitor</code> implementation.
79 *
80 * @return The configured <code>TaskMonitor</code> implementation.
81 */
82 private TaskMonitor getTaskMonitor()
83 {
84 return (TaskMonitor) ContainerFactory.getContainer().
85 getDependency( this, "TaskMonitor" );
86
87 }
88
89 /**
90 * Gets the configured <code>Locale</code> implementation.
91 *
92 * @return The configured <code>Locale</code> implementation.
93 */
94 private Locale getLocale()
95 {
96 return (Locale) ContainerFactory.getContainer().
97 getDependency( this, "Locale" );
98
99 }
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 }