View Javadoc

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.util.HashMap;
29  import java.util.Iterator;
30  import java.util.Locale;
31  import java.util.Map;
32  import org.jdtaus.banking.BankleitzahlInfo;
33  import org.jdtaus.banking.messages.UpdatesBankleitzahlenDateiMessage;
34  import org.jdtaus.core.container.ContainerFactory;
35  import org.jdtaus.core.container.PropertyException;
36  import org.jdtaus.core.logging.spi.Logger;
37  import org.jdtaus.core.monitor.spi.Task;
38  import org.jdtaus.core.monitor.spi.TaskMonitor;
39  
40  /**
41   * German Bankleitzahlendatei for the format as of 2006-06-01.
42   * <p>For further information see the
43   * <a href="../../../../doc-files/merkblatt_bankleitzahlendatei.pdf">Merkblatt Bankleitzahlendatei</a>.
44   * An updated version of the document may be found at
45   * <a href="http://www.bundesbank.de">Deutsche Bundesbank</a>.
46   * </p>
47   *
48   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
49   * @version $JDTAUS: BankleitzahlenDatei.java 8811 2012-12-04 00:48:00Z schulte $
50   */
51  public final class BankleitzahlenDatei
52  {
53      //--Dependencies------------------------------------------------------------
54  
55  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
56      // This section is managed by jdtaus-container-mojo.
57  
58      /**
59       * Gets the configured <code>Logger</code> implementation.
60       *
61       * @return The configured <code>Logger</code> implementation.
62       */
63      private Logger getLogger()
64      {
65          return (Logger) ContainerFactory.getContainer().
66              getDependency( this, "Logger" );
67  
68      }
69  
70      /**
71       * Gets the configured <code>TaskMonitor</code> implementation.
72       *
73       * @return The configured <code>TaskMonitor</code> implementation.
74       */
75      private TaskMonitor getTaskMonitor()
76      {
77          return (TaskMonitor) ContainerFactory.getContainer().
78              getDependency( this, "TaskMonitor" );
79  
80      }
81  
82      /**
83       * Gets the configured <code>Locale</code> implementation.
84       *
85       * @return The configured <code>Locale</code> implementation.
86       */
87      private Locale getLocale()
88      {
89          return (Locale) ContainerFactory.getContainer().
90              getDependency( this, "Locale" );
91  
92      }
93  
94  // </editor-fold>//GEN-END:jdtausDependencies
95  
96      //------------------------------------------------------------Dependencies--
97      //--Properties--------------------------------------------------------------
98  
99  // <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 }