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