View Javadoc
1   /*
2    *  jDTAUS Banking RI DTAUS
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.dtaus.ri.zka;
22  
23  import java.io.IOException;
24  import java.text.ParseException;
25  import java.util.Arrays;
26  import java.util.Calendar;
27  import java.util.Date;
28  import java.util.EventListener;
29  import java.util.Locale;
30  import javax.swing.event.EventListenerList;
31  import org.jdtaus.banking.AlphaNumericText27;
32  import org.jdtaus.banking.TextschluesselVerzeichnis;
33  import org.jdtaus.banking.dtaus.Checksum;
34  import org.jdtaus.banking.dtaus.CorruptedException;
35  import org.jdtaus.banking.dtaus.Header;
36  import org.jdtaus.banking.dtaus.LogicalFile;
37  import org.jdtaus.banking.dtaus.Transaction;
38  import org.jdtaus.banking.dtaus.spi.CurrencyCounter;
39  import org.jdtaus.banking.dtaus.spi.Fields;
40  import org.jdtaus.banking.dtaus.spi.HeaderValidator;
41  import org.jdtaus.banking.dtaus.spi.IllegalHeaderException;
42  import org.jdtaus.banking.dtaus.spi.IllegalTransactionException;
43  import org.jdtaus.banking.dtaus.spi.TransactionValidator;
44  import org.jdtaus.banking.messages.ChecksumErrorMessage;
45  import org.jdtaus.banking.messages.ChecksumsFileMessage;
46  import org.jdtaus.banking.messages.IllegalDataMessage;
47  import org.jdtaus.banking.spi.CurrencyMapper;
48  import org.jdtaus.core.container.ContainerFactory;
49  import org.jdtaus.core.container.Implementation;
50  import org.jdtaus.core.io.FileOperations;
51  import org.jdtaus.core.io.util.FlushableFileOperations;
52  import org.jdtaus.core.lang.spi.MemoryManager;
53  import org.jdtaus.core.logging.spi.Logger;
54  import org.jdtaus.core.messages.DeletesBlocksMessage;
55  import org.jdtaus.core.messages.InsertsBlocksMessage;
56  import org.jdtaus.core.monitor.spi.Task;
57  import org.jdtaus.core.monitor.spi.TaskMonitor;
58  import org.jdtaus.core.nio.util.Charsets;
59  import org.jdtaus.core.text.Message;
60  import org.jdtaus.core.text.spi.ApplicationLogger;
61  
62  /**
63   * Abstrakte Klasse für {@code LogicalFile}-Implementierungen.
64   * <p>Stellt diverse Hilfs-Methoden sowie die Überprüfung von Vor- und Nachbedingungen zur Verfügung.</p>
65   * <p><b>Hinweis:</b><br/>
66   * Implementierung ist nicht vor gleichzeitigen Zugriffen unterschiedlicher Threads geschützt.</p>
67   *
68   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
69   * @version $JDTAUS: AbstractLogicalFile.java 8810 2012-12-04 00:45:37Z schulte $
70   */
71  public abstract class AbstractLogicalFile implements LogicalFile
72  {
73  
74      public interface Listener extends EventListener
75      {
76  
77          /**
78           * Gets called whenever bytes were inserted into an instance the listener is registered with. The byte
79           * previously at {@code position} will have moved to {@code position + insertedBytes}.
80           *
81           * @param position The position of the first inserted byte.
82           * @param bytes The number of bytes which were inserted at {@code position}.
83           *
84           * @throws IOException if reading or writing fails.
85           */
86          void bytesInserted( long position, long bytes ) throws IOException;
87  
88          /**
89           * Gets called whenever bytes were deleted an instance the listener is registered with. The byte previously at
90           * {@code position + bytes} will have moved to {@code position}.
91           *
92           * @param position The position of the first deleted byte.
93           * @param bytes The number of bytes which were deleted starting at {@code position} inclusive.
94           *
95           * @throws IOException if reading or writing fails.
96           */
97          void bytesDeleted( long position, long bytes ) throws IOException;
98  
99      }
100 
101     /** Konstante für ASCII-Zeichensatz. */
102     protected static final int ENCODING_ASCII = 1;
103 
104     /** Konstante für EBCDI-Zeichensatz. */
105     protected static final int ENCODING_EBCDI = 2;
106 
107     /** Return-Code. */
108     protected static final long NO_NUMBER = Long.MIN_VALUE;
109 
110     /** Maximum allowed days between create and execution date. */
111     protected static final int MAX_SCHEDULEDAYS = 15;
112 
113     /** Maximale Anzahl unterstützter Transaktionen pro logischer Datei. */
114     private static final int MAX_TRANSACTIONS = 9999999;
115 
116     /** 01/01/1980 00:00:00 CET. */
117     private static final long VALID_DATES_START_MILLIS = 315529200000L;
118 
119     /** 12/31/2079 23:59:59 CET. */
120     private static final long VALID_DATES_END_MILLIS = 3471289199999L;
121 
122     /** Anzahl Ziffern der größten, abbildbaren Zahl des Formats. */
123     private static final int FORMAT_MAX_DIGITS = 17;
124 
125     /** Anzahl Zeichen der größten, abbildbaren Zeichenkette des Formats. */
126     private static final int FORMAT_MAX_CHARS = 105;
127 
128     /**
129      * Index = Exponent,
130      * Wert = 10er Potenz.
131      */
132     protected static final long[] EXP10 = new long[ FORMAT_MAX_DIGITS + 1 ];
133 
134     /**
135      * Index = Ziffer,
136      * Wert = ASCII-Zeichen.
137      */
138     private static final byte[] DIGITS_TO_ASCII =
139     {
140         48, 49, 50, 51, 52, 53, 54, 55, 56, 57
141     };
142 
143     /**
144      * Index = ASCII-Code einer Ziffer,
145      * Wert = Ziffer.
146      */
147     private static final byte[] ASCII_TO_DIGITS = new byte[ 60 ];
148 
149     /**
150      * Index = Ziffer,
151      * Wert = EBCDI-Zeichen.
152      */
153     private static final byte[] DIGITS_TO_EBCDI =
154     {
155         (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,
156         (byte) 0xF8, (byte) 0xF9
157     };
158 
159     /**
160      * Index = EBCDI-Code einer Ziffer,
161      * Wert = Ziffer.
162      */
163     private static final byte[] EBCDI_TO_DIGITS = new byte[ 0xFA ];
164 
165     /** Charset name for the disk format. */
166     private static final String DIN66003 = "ISO646-DE";
167 
168     /** Charset name for the tape format. */
169     private static final String IBM273 = "IBM273";
170 
171     /** ASCII space character. */
172     private static final byte ASCII_SPACE = (byte) 32;
173 
174     /** EBCDI space character. */
175     private static final byte EBCDI_SPACE = (byte) 0x40;
176 
177     /** Verwendete {@code FileOperations} Implementierung. */
178     private FileOperations fileOperations;
179 
180     /** Position des A-Datensatzes. */
181     private long headerPosition;
182 
183     /** Position des E-Datensatzes. */
184     private long checksumPosition;
185 
186     /**
187      * Index = laufende Transaktionsnummer,
188      * Wert = Position an der die Transaktion beginnt relativ zur Position des A-Datensatzes.
189      */
190     private long[] index;
191 
192     /** Zwischengespeicherter A Datensatz. */
193     private Header cachedHeader = null;
194 
195     /** Zwischengespeicherter E Datensatz. */
196     private Checksum cachedChecksum = null;
197 
198     /** Calendar der Instanz. */
199     private final Calendar calendar = Calendar.getInstance( Locale.GERMANY );
200 
201     /** Puffer zum Lesen und Schreiben von Daten. */
202     private final byte[] buffer = new byte[ FORMAT_MAX_CHARS + 1 ];
203 
204     /** Hilfs-Puffer. */
205     private final StringBuffer shortDateBuffer = new StringBuffer( 6 );
206 
207     /** Hilfs-Puffer. */
208     private final StringBuffer longDateBuffer = new StringBuffer( 8 );
209 
210     /** Abbildung von ISO Währungs-Codes zur Anzahl der vorhandenen Zahlungen mit der entsprechenden Währung. */
211     private CurrencyCounter counter;
212 
213     /** Implementation configuration. */
214     private Configuration configuration;
215 
216     /** Mininum number of bytes to copy to start any task monitoring. */
217     private Integer monitoringThreshold;
218 
219     /** {@code Listener}s of the instance. */
220     private final EventListenerList listeners = new EventListenerList();
221 
222     /** Pre-allocated temporary buffer. */
223     private byte[] defaultBuffer;
224 
225     /**
226      * Maximal erlaubte Anzahl Erweiterungsteile in einem C-Datensatz.
227      * @since 1.12
228      */
229     private Long maximumExtensionCount;
230 
231     /** Statische Initialisierung der konstanten Felder. */
232     static
233     {
234         for ( int i = 0; i <= FORMAT_MAX_DIGITS; i++ )
235         {
236             EXP10[i] = (long) Math.floor( Math.pow( 10.00D, i ) );
237         }
238 
239         Arrays.fill( ASCII_TO_DIGITS, (byte) -1 );
240         Arrays.fill( EBCDI_TO_DIGITS, (byte) -1 );
241         ASCII_TO_DIGITS[48] = 0;
242         ASCII_TO_DIGITS[49] = 1;
243         ASCII_TO_DIGITS[50] = 2;
244         ASCII_TO_DIGITS[51] = 3;
245         ASCII_TO_DIGITS[52] = 4;
246         ASCII_TO_DIGITS[53] = 5;
247         ASCII_TO_DIGITS[54] = 6;
248         ASCII_TO_DIGITS[55] = 7;
249         ASCII_TO_DIGITS[56] = 8;
250         ASCII_TO_DIGITS[57] = 9;
251         EBCDI_TO_DIGITS[0xF0] = 0;
252         EBCDI_TO_DIGITS[0xF1] = 1;
253         EBCDI_TO_DIGITS[0xF2] = 2;
254         EBCDI_TO_DIGITS[0xF3] = 3;
255         EBCDI_TO_DIGITS[0xF4] = 4;
256         EBCDI_TO_DIGITS[0xF5] = 5;
257         EBCDI_TO_DIGITS[0xF6] = 6;
258         EBCDI_TO_DIGITS[0xF7] = 7;
259         EBCDI_TO_DIGITS[0xF8] = 8;
260         EBCDI_TO_DIGITS[0xF9] = 9;
261     }
262 
263     /**
264      * Erzeugt eine neue {@code AbstractLogicalFile} Instanz.
265      *
266      * @see #setHeaderPosition(long)
267      * @see #setChecksumPosition(long)
268      * @see #setFileOperations(org.jdtaus.core.io.FileOperations)
269      * @see #checksum()
270      */
271     protected AbstractLogicalFile()
272     {
273         this.calendar.setLenient( false );
274         Arrays.fill( this.buffer, (byte) -1 );
275     }
276 
277     /**
278      * Gets the value of property {@code configuration}.
279      *
280      * @return Implementation configuration.
281      */
282     protected Configuration getConfiguration()
283     {
284         if ( this.configuration == null )
285         {
286             this.configuration = new Configuration();
287         }
288 
289         return this.configuration;
290     }
291 
292     /**
293      * Sets the value of property {@code configuration}.
294      *
295      * @param configuration Implementation configuration.
296      */
297     protected void setConfiguration( final Configuration configuration )
298     {
299         this.configuration = configuration;
300     }
301 
302     /**
303      * Liest den Wert der Property {@code headerPosition}.
304      *
305      * @return Position des A Datensatzes.
306      */
307     protected long getHeaderPosition()
308     {
309         return this.headerPosition;
310     }
311 
312     /**
313      * Schreibt den Wert der Property {@code headerPosition}.
314      *
315      * @param headerPosition Position des A Datensatzes.
316      *
317      * @throws IllegalArgumentException wenn {@code headerPosition} negativ ist.
318      * @throws IOException wenn die aktuelle Anzahl Bytes nicht ermittelt werden kann.
319      */
320     protected void setHeaderPosition( final long headerPosition ) throws IOException
321     {
322         if ( headerPosition < 0L )
323         {
324             throw new IllegalArgumentException( Long.toString( headerPosition ) );
325         }
326 
327         this.headerPosition = headerPosition;
328     }
329 
330     /**
331      * Liest den Wert der Property {@code checksumPosition}.
332      *
333      * @return Position des E-Datensatzes.
334      */
335     protected long getChecksumPosition()
336     {
337         return this.checksumPosition;
338     }
339 
340     /**
341      * Schreibt den Wert der Property {@code checksumPosition}.
342      *
343      * @param checksumPosition Position des E-Datensatzes.
344      *
345      * @throws IllegalArgumentException wenn {@code checksumPosition} negativ ist.
346      * @throws IOException wenn die aktuelle Anzahl Byte nicht ermittelt werden kann.
347      */
348     protected void setChecksumPosition( final long checksumPosition ) throws IOException
349     {
350         if ( checksumPosition <= this.getHeaderPosition() )
351         {
352             throw new IllegalArgumentException( Long.toString( checksumPosition ) );
353         }
354 
355         this.checksumPosition = checksumPosition;
356     }
357 
358     /**
359      * Ermittelt die zugrunde liegende {@code FileOperations} Implementierung.
360      *
361      * @return zugrunde liegende {@code FileOperations} Implementierung.
362      */
363     protected FileOperations getFileOperations()
364     {
365         return this.fileOperations;
366     }
367 
368     /**
369      * Ändert die zu Grunde liegende {@code FileOperations} Implementierung.
370      *
371      * @param fileOperations neue {@code FileOperations} Implementierung.
372      *
373      * @throws NullPointerException wenn {@code fileOperations} {@code null} ist.
374      * @throws IOException wenn zwischengespeicherte Änderungen der vorherigen Instanz nicht geschrieben werden können.
375      */
376     protected void setFileOperations( final FileOperations fileOperations ) throws IOException
377     {
378         if ( fileOperations == null )
379         {
380             throw new NullPointerException( "fileOperations" );
381         }
382 
383         if ( this.fileOperations != null && this.fileOperations instanceof FlushableFileOperations )
384         {
385             ( (FlushableFileOperations) this.fileOperations ).flush();
386         }
387 
388         this.fileOperations = fileOperations;
389         this.cachedHeader = null;
390         this.cachedChecksum = null;
391         this.index = null;
392         Arrays.fill( this.buffer, (byte) -1 );
393     }
394 
395     /**
396      * Gets the value of property {@code monitoringThreshold}.
397      *
398      * @return The mininum number of bytes to copy to start any task monitoring.
399      */
400     public int getMonitoringThreshold()
401     {
402         if ( this.monitoringThreshold == null )
403         {
404             this.monitoringThreshold = this.getDefaultMonitoringThreshold();
405         }
406 
407         return this.monitoringThreshold.intValue();
408     }
409 
410     /**
411      * Sets the value of property {@code monitoringThreshold}.
412      *
413      * @param value The mininum number of bytes to copy to start any task monitoring.
414      */
415     public void setMonitoringThreshold( final int value )
416     {
417         this.monitoringThreshold = new Integer( value );
418     }
419 
420     /**
421      * Gets the maximum allowed number of extensions.
422      *
423      * @return The maximum allowed number of extensions.
424      *
425      * @since 1.12
426      */
427     public long getMaximumExtensionCount()
428     {
429         if ( this.maximumExtensionCount == null )
430         {
431             this.maximumExtensionCount = this.getDefaultMaximumExtensionCount();
432         }
433 
434         return this.maximumExtensionCount.longValue();
435     }
436 
437     /**
438      * Sets the maximum allowed number of extensions.
439      *
440      * @param value The new maximum allowed number of extensions or {@code null}.
441      *
442      * @since 1.12
443      */
444     public void setMaximumExtensionCount( final Long value )
445     {
446         this.maximumExtensionCount = value;
447     }
448 
449     /**
450      * Adds a {@code Listener} to the listener list.
451      *
452      * @param listener The listener to be added to the listener list.
453      *
454      * @throws NullPointerException if {@code listener} is {@code null}.
455      */
456     public void addListener( final Listener listener )
457     {
458         this.listeners.add( Listener.class, listener );
459     }
460 
461     /**
462      * Removes a {@code Listener} from the listener list.
463      *
464      * @param listener The listener to be removed from the listener list.
465      *
466      * @throws NullPointerException if {@code listener} is {@code null}.
467      */
468     public void removeFileOperationsListener( final Listener listener )
469     {
470         this.listeners.remove( Listener.class, listener );
471     }
472 
473     /**
474      * Gets all currently registered {@code Listener}s.
475      *
476      * @return all currently registered {@code Listener}s.
477      */
478     public Listener[] getListeners()
479     {
480         return (Listener[]) this.listeners.getListeners( Listener.class );
481     }
482 
483     /**
484      * Notifies all registered listeners about inserted bytes.
485      *
486      * @param position The position of the first inserted byte.
487      * @param bytes The number of bytes which were inserted at {@code position}.
488      *
489      * @throws IOException if reading or writing fails.
490      */
491     protected void fireBytesInserted( final long position, final long bytes ) throws IOException
492     {
493         final Object[] list = this.listeners.getListenerList();
494         for ( int i = list.length - 2; i >= 0; i -= 2 )
495         {
496             if ( list[i] == Listener.class )
497             {
498                 ( (Listener) list[i + 1] ).bytesInserted( position, bytes );
499             }
500         }
501     }
502 
503     /**
504      * Notifies all registered listeners about deleted bytes.
505      *
506      * @param position The position of the first deleted byte.
507      * @param bytes The number of bytes which were deleted starting at {@code position} inclusive.
508      *
509      * @throws IOException if reading or writing fails.
510      */
511     protected void fireBytesDeleted( final long position, final long bytes ) throws IOException
512     {
513         final Object[] list = this.listeners.getListenerList();
514         for ( int i = list.length - 2; i >= 0; i -= 2 )
515         {
516             if ( list[i] == Listener.class )
517             {
518                 ( (Listener) list[i + 1] ).bytesDeleted( position, bytes );
519             }
520         }
521     }
522 
523     /**
524      * Hilfs-Methode zum Lesen von Zahlen.
525      * <p>Sollten ungültige Daten gelesen werden, so wird {@code NO_NUMBER} zurückgeliefert und eine entsprechende
526      * {@code IllegalDataMessage} erzeugt.</p>
527      *
528      * @param field Feld-Konstante des zu lesenden Feldes.
529      * @param position Position ab der Ziffern gelesen werden sollen.
530      * @param len Anzahl von Ziffern, die gelesen werden sollen.
531      * @param encoding zu verwendende Kodierung.
532      *
533      * @return gelesene Zahl oder {@code NO_NUMBER} wenn gelesene Daten nicht als Zahl interpretiert werden konnten.
534      *
535      * @throws CorruptedException wenn die Datei Fehler enthält und {@link ThreadLocalMessages#isErrorsEnabled()} gleich
536      * {@code true} ist.
537      * @throws IOException wenn nicht gelesen werden kann.
538      *
539      * @see #ENCODING_ASCII
540      * @see #ENCODING_EBCDI
541      * @see #NO_NUMBER
542      * @see org.jdtaus.banking.dtaus.spi.Fields
543      * @see ThreadLocalMessages#isErrorsEnabled()
544      */
545     protected Long readNumber( final int field, final long position, final int len, final int encoding )
546         throws IOException
547     {
548         return this.readNumber(
549             field, position, len, encoding, this.getConfiguration().isSpaceCharacterAllowed( field ) );
550 
551     }
552 
553     /**
554      * Hilfs-Methode zum Lesen von Zahlen mit gegebenenfalls Konvertierung von Leerzeichen zu Nullen.
555      * <p>Die Verwendung dieser Methode mit {@code allowSpaces == true} entspricht einem Verstoß gegen die
556      * Spezifikation. Diese Methode existiert ausschließlich um ungültige Dateien lesen zu können und sollte nur in
557      * diesen Fällen verwendet werden.</p>
558      * <p>Sollten ungültige Daten gelesen werden, so wird {@code NO_NUMBER} zurückgeliefert und eine entsprechende
559      * {@code IllegalDataMessage} erzeugt.</p>
560      *
561      * @param field Feld-Konstante des zu lesenden Feldes.
562      * @param position Position aber der die Ziffern gelesen werden sollen.
563      * @param len Anzahl von Ziffern, die gelesen werden sollen.
564      * @param encoding Zu verwendende Kodierung.
565      * @param allowSpaces {@code true} wenn vorhandene Leerzeichen durch Nullen ersetzt werden sollen; {@code false}
566      * für eine strikte Einhaltung der Spezifikation.
567      *
568      * @return gelesene Zahl oder {@code NO_NUMBER} wenn gelesene Daten nicht als Zahl interpretiert werden konnten.
569      *
570      * @throws CorruptedException wenn die Datei Fehler enthält und {@link ThreadLocalMessages#isErrorsEnabled()} gleich
571      * {@code true} ist.
572      * @throws IOException wenn nicht gelesen werden kann.
573      *
574      * @see #ENCODING_ASCII
575      * @see #ENCODING_EBCDI
576      * @see #NO_NUMBER
577      * @see org.jdtaus.banking.dtaus.spi.Fields
578      * @see org.jdtaus.banking.dtaus.ri.zka.ThreadLocalMessages
579      */
580     protected Long readNumber( final int field, final long position, final int len, final int encoding,
581                                final boolean allowSpaces ) throws IOException
582     {
583         long ret = 0L;
584         final byte space;
585         final byte[] table;
586         final byte[] revTable;
587         final String cset;
588         String logViolation = null; // Wenn != null wird der Verstoß geloggt.
589 
590         if ( encoding == ENCODING_ASCII )
591         {
592             table = DIGITS_TO_ASCII;
593             revTable = ASCII_TO_DIGITS;
594             space = ASCII_SPACE;
595             cset = DIN66003;
596         }
597         else if ( encoding == ENCODING_EBCDI )
598         {
599             table = DIGITS_TO_EBCDI;
600             revTable = EBCDI_TO_DIGITS;
601             space = EBCDI_SPACE;
602             cset = IBM273;
603         }
604         else
605         {
606             throw new IllegalArgumentException( Integer.toString( encoding ) );
607         }
608 
609         this.fileOperations.setFilePointer( position );
610         this.fileOperations.read( this.buffer, 0, len );
611 
612         for ( int read = 0; read < len; read++ )
613         {
614             if ( allowSpaces && this.buffer[read] == space )
615             {
616                 if ( logViolation == null )
617                 {
618                     logViolation = Charsets.decode( this.buffer, 0, len, cset );
619                 }
620 
621                 this.buffer[read] = table[0];
622             }
623 
624             if ( !( this.buffer[read] >= table[0] && this.buffer[read] <= table[9] ) )
625             {
626                 if ( ThreadLocalMessages.isErrorsEnabled() )
627                 {
628                     throw new CorruptedException( this.getImplementation(), position );
629                 }
630                 else
631                 {
632                     final Message msg = new IllegalDataMessage(
633                         field, IllegalDataMessage.TYPE_NUMERIC, position,
634                         Charsets.decode( this.buffer, 0, len, cset ) );
635 
636                     ThreadLocalMessages.getMessages().addMessage( msg );
637                 }
638 
639                 ret = NO_NUMBER;
640                 logViolation = null;
641                 break;
642             }
643             else
644             {
645                 ret += revTable[this.buffer[read] & 0xFF] * EXP10[len - read - 1];
646             }
647         }
648 
649         if ( logViolation != null )
650         {
651             if ( this.getLogger().isInfoEnabled() )
652             {
653                 this.getLogger().info( this.getReadNumberIllegalFileInfoMessage(
654                     this.getLocale(), logViolation, new Long( ret ) ) );
655 
656             }
657         }
658 
659         return new Long( ret );
660     }
661 
662     /**
663      * Hilfs-Methode zum Schreiben von Zahlen.
664      *
665      * @param field Feld-Konstante des zu beschreibenden Feldes.
666      * @param position Position ab der die Daten geschrieben werden sollen.
667      * @param len Anzahl an Ziffern die geschrieben werden sollen. Hierbei wird linksseitig mit Nullen aufgefüllt, so
668      * dass exakt {@code len} Ziffern geschrieben werden.
669      * @param number Die zu schreibende Zahl.
670      * @param encoding Zu verwendende Kodierung.
671      *
672      * @throws IllegalArgumentException wenn {@code number} nicht mit {@code len} Ziffern darstellbar ist.
673      * @throws IOException wenn nicht geschrieben werden kann.
674      *
675      * @see #ENCODING_ASCII
676      * @see #ENCODING_EBCDI
677      * @see org.jdtaus.banking.dtaus.spi.Fields
678      */
679     protected void writeNumber( final int field, final long position, final int len, long number, final int encoding )
680         throws IOException
681     {
682         int i;
683         int pos;
684         final long maxValue = EXP10[len] - 1L;
685         int digit;
686         final byte[] table;
687 
688         if ( number < 0L || number > maxValue )
689         {
690             throw new IllegalArgumentException( Long.toString( number ) );
691         }
692 
693         if ( encoding == ENCODING_ASCII )
694         {
695             table = DIGITS_TO_ASCII;
696         }
697         else if ( encoding == ENCODING_EBCDI )
698         {
699             table = DIGITS_TO_EBCDI;
700         }
701         else
702         {
703             throw new IllegalArgumentException( Integer.toString( encoding ) );
704         }
705 
706         for ( i = len - 1, pos = 0; i >= 0; i--, pos++ )
707         {
708             digit = (int) Math.floor( number / EXP10[i] );
709             number -= ( digit * EXP10[i] );
710             this.buffer[pos] = table[digit];
711         }
712 
713         this.fileOperations.setFilePointer( position );
714         this.fileOperations.write( this.buffer, 0, len );
715     }
716 
717     /**
718      * Hilds-Methode zum Lesen einer alpha-numerischen Zeichenkette.
719      * <p>Sollten ungültige Daten gelesen werden, so wird {@code null} zurückgeliefert und eine entsprechende
720      * {@code IllegalDataMessage} erzeugt.</p>
721      *
722      * @param field Feld-Konstante des zu lesenden Feldes.
723      * @param position Position ab der die Zeichen gelesen werden sollen.
724      * @param len Anzahl von Zeichen, die gelesen werden sollen.
725      * @param encoding Zu verwendende Kodierung.
726      *
727      * @return gelesene Zeichenkette oder {@code null} wenn ungültige Zeichen gelesen werden.
728      *
729      * @throws CorruptedException wenn die Datei Fehler enthält und {@link ThreadLocalMessages#isErrorsEnabled()} gleich
730      * {@code true} ist.
731      * @throws IOException wenn nicht gelesen werden kann.
732      *
733      * @see #ENCODING_ASCII
734      * @see #ENCODING_EBCDI
735      * @see org.jdtaus.banking.dtaus.spi.Fields
736      * @see org.jdtaus.banking.dtaus.ri.zka.ThreadLocalMessages
737      */
738     protected AlphaNumericText27 readAlphaNumeric( final int field, final long position, final int len,
739                                                    final int encoding ) throws IOException
740     {
741         final String cset;
742         final String str;
743         AlphaNumericText27 txt = null;
744 
745         if ( encoding == ENCODING_ASCII )
746         {
747             cset = DIN66003;
748         }
749         else if ( encoding == ENCODING_EBCDI )
750         {
751             cset = IBM273;
752         }
753         else
754         {
755             throw new IllegalArgumentException( Integer.toString( encoding ) );
756         }
757 
758         this.fileOperations.setFilePointer( position );
759         this.fileOperations.read( this.buffer, 0, len );
760         str = Charsets.decode( this.buffer, 0, len, cset );
761 
762         try
763         {
764             txt = AlphaNumericText27.parse( str );
765         }
766         catch ( ParseException e )
767         {
768             if ( this.getLogger().isDebugEnabled() )
769             {
770                 this.getLogger().debug( e.toString() );
771             }
772 
773             txt = null;
774             if ( ThreadLocalMessages.isErrorsEnabled() )
775             {
776                 throw new CorruptedException( this.getImplementation(), position );
777             }
778             else
779             {
780                 final Message msg =
781                     new IllegalDataMessage( field, IllegalDataMessage.TYPE_ALPHA_NUMERIC, position, str );
782 
783                 ThreadLocalMessages.getMessages().addMessage( msg );
784             }
785         }
786 
787         return txt;
788     }
789 
790     /**
791      * Hilfs-Methode zum Schreiben einer Zeichenkette.
792      *
793      * @param field Feld-Konstante des zu beschreibenden Feldes.
794      * @param position Position ab der die Zeichen geschrieben werden sollen.
795      * @param len Anzahl von Zeichen die maximal geschrieben werden sollen. Sollte {@code str} kürzer als {@code len}
796      * sein, wird linksseitig mit Leerzeichen aufgefüllt.
797      * @param str Die zu schreibende Zeichenkette.
798      * @param encoding Zu verwendende Kodierung.
799      *
800      * @throws NullPointerException wenn {@code str null} ist.
801      * @throws IllegalArgumentException wenn {@code str} länger als {@code len} Zeichen lang ist oder ungültige Zeichen
802      * enthält.
803      * @throws IOException wenn nicht geschrieben werden kann.
804      *
805      * @see #ENCODING_ASCII
806      * @see #ENCODING_EBCDI
807      * @see org.jdtaus.banking.dtaus.spi.Fields
808      */
809     protected void writeAlphaNumeric( final int field, final long position, final int len, final String str,
810                                       final int encoding ) throws IOException
811     {
812         final int length;
813         final int delta;
814         final char[] c;
815         final byte[] buf;
816         final byte space;
817         final String cset;
818 
819         if ( str == null )
820         {
821             throw new NullPointerException( "str" );
822         }
823         if ( ( length = str.length() ) > len )
824         {
825             throw new IllegalArgumentException( str );
826         }
827 
828         if ( encoding == ENCODING_ASCII )
829         {
830             space = ASCII_SPACE;
831             cset = DIN66003;
832         }
833         else if ( encoding == ENCODING_EBCDI )
834         {
835             space = EBCDI_SPACE;
836             cset = IBM273;
837         }
838         else
839         {
840             throw new IllegalArgumentException( Integer.toString( encoding ) );
841         }
842 
843         c = str.toCharArray();
844         for ( int i = c.length - 1; i >= 0; i-- )
845         {
846             if ( !AlphaNumericText27.checkAlphaNumeric( c[i] ) )
847             {
848                 throw new IllegalArgumentException( Character.toString( c[i] ) );
849             }
850         }
851 
852         buf = Charsets.encode( str, cset );
853         if ( length < len )
854         {
855             delta = len - length;
856             System.arraycopy( buf, 0, this.buffer, 0, buf.length );
857             Arrays.fill( this.buffer, buf.length, buf.length + delta, space );
858         }
859         else
860         {
861             System.arraycopy( buf, 0, this.buffer, 0, buf.length );
862         }
863 
864         this.fileOperations.setFilePointer( position );
865         this.fileOperations.write( this.buffer, 0, len );
866     }
867 
868     /**
869      * Hilfs-Methode zum Lesen einer Datums-Angabe mit zweistelliger Jahres-Zahl.
870      * <p>Zweistellige Jahres-Angaben kleiner oder gleich 79 werden als {@code 2000 + zweistelliges Jahr} interpretiert.
871      * Zweistellige Jahres-Angaben größer oder gleich 80 werden als {@code 1900 + zweistelliges Jahr} interpretiert.</p>
872      * <p>Sollten ungültige Daten gelesen werden, so wird {@code null} zurückgeliefert und eine entsprechende
873      * {@code IllegalDataMessage} erzeugt.</p>
874      *
875      * @param field Feld-Konstante des zu lesenden Feldes.
876      * @param position Position ab der die Zeichen gelesen werden sollen.
877      * @param encoding Zu verwendende Kodierung.
878      *
879      * @return das gelesene Datum oder {@code null} wenn kein Datum gelesen werden kann.
880      *
881      * @throws CorruptedException wenn die Datei Fehler enthält und {@link ThreadLocalMessages#isErrorsEnabled()} gleich
882      * {@code true} ist.
883      * @throws IOException wenn nicht gelesen werden kann.
884      *
885      * @see #ENCODING_ASCII
886      * @see #ENCODING_EBCDI
887      * @see org.jdtaus.banking.dtaus.spi.Fields
888      * @see org.jdtaus.banking.dtaus.ri.zka.ThreadLocalMessages
889      */
890     protected Date readShortDate( final int field, final long position, final int encoding ) throws IOException
891     {
892         final int len;
893         final String cset;
894 
895         Date ret = null;
896         String str = null;
897         boolean legal = false;
898         Message msg;
899 
900         if ( encoding == ENCODING_ASCII )
901         {
902             cset = DIN66003;
903         }
904         else if ( encoding == ENCODING_EBCDI )
905         {
906             cset = IBM273;
907         }
908         else
909         {
910             throw new IllegalArgumentException( Integer.toString( encoding ) );
911         }
912 
913         try
914         {
915             this.fileOperations.setFilePointer( position );
916             this.fileOperations.read( this.buffer, 0, 6 );
917             str = Charsets.decode( this.buffer, 0, 6, cset );
918             len = str.trim().length();
919 
920             if ( len == 6 )
921             {
922                 this.calendar.clear();
923                 // Tag
924                 this.calendar.set( Calendar.DAY_OF_MONTH, Integer.valueOf( str.substring( 0, 2 ) ).intValue() );
925 
926                 // Monat
927                 this.calendar.set( Calendar.MONTH, Integer.valueOf( str.substring( 2, 4 ) ).intValue() - 1 );
928 
929                 // Jahr
930                 int year = Integer.valueOf( str.substring( 4, 6 ) ).intValue();
931                 year = year <= 79 ? 2000 + year : 1900 + year;
932 
933                 this.calendar.set( Calendar.YEAR, year );
934                 ret = this.calendar.getTime();
935 
936                 if ( !this.checkDate( ret ) )
937                 {
938                     if ( ThreadLocalMessages.isErrorsEnabled() )
939                     {
940                         throw new CorruptedException( this.getImplementation(), position );
941                     }
942                     else
943                     {
944                         msg = new IllegalDataMessage( field, IllegalDataMessage.TYPE_SHORTDATE, position, str );
945                         ThreadLocalMessages.getMessages().addMessage( msg );
946                     }
947 
948                     ret = null;
949                 }
950             }
951 
952             if ( len == 0 || len == 6 )
953             {
954                 legal = true;
955             }
956 
957         }
958         catch ( NumberFormatException e )
959         {
960             if ( this.getLogger().isDebugEnabled() )
961             {
962                 this.getLogger().debug( e.toString() );
963             }
964 
965             ret = null;
966             legal = false;
967         }
968 
969         if ( !legal )
970         {
971             if ( ThreadLocalMessages.isErrorsEnabled() )
972             {
973                 throw new CorruptedException( this.getImplementation(), position );
974             }
975             else
976             {
977                 msg = new IllegalDataMessage( field, IllegalDataMessage.TYPE_SHORTDATE, position, str );
978                 ThreadLocalMessages.getMessages().addMessage( msg );
979             }
980         }
981 
982         return ret;
983     }
984 
985     /**
986      * Hilfs-Methode zum Schreiben einer Datums-Angabe mit zweistelliger Jahres-Zahl.
987      * <p>Es werden nur Daten mit Jahren größer oder gleich 1980 und kleiner oder gleich 2079 akzeptiert.</p>
988      *
989      * @param field Feld-Konstante des zu beschreibenden Feldes.
990      * @param position Position ab der die Zeichen geschrieben werden sollen.
991      * @param date Die zu schreibende Datums-Angabe oder {@code null} um eine optionale Datums-Angabe zu entfernen.
992      * @param encoding Zu verwendende Kodierung.
993      *
994      * @throws IllegalArgumentException wenn das Jahr von {@code date} nicht größer oder gleich 1980 und kleiner oder
995      * gleich 2079 ist.
996      * @throws IOException wenn nicht geschrieben werden kann.
997      *
998      * @see #ENCODING_ASCII
999      * @see #ENCODING_EBCDI
1000      * @see org.jdtaus.banking.dtaus.spi.Fields
1001      */
1002     protected void writeShortDate( final int field, final long position, final Date date, final int encoding )
1003         throws IOException
1004     {
1005         int i;
1006         final byte[] buf;
1007         final String cset;
1008 
1009         if ( encoding == ENCODING_ASCII )
1010         {
1011             cset = DIN66003;
1012         }
1013         else if ( encoding == ENCODING_EBCDI )
1014         {
1015             cset = IBM273;
1016         }
1017         else
1018         {
1019             throw new IllegalArgumentException( Integer.toString( encoding ) );
1020         }
1021 
1022         if ( date != null )
1023         {
1024             if ( !this.checkDate( date ) )
1025             {
1026                 throw new IllegalArgumentException( date.toString() );
1027             }
1028 
1029             this.shortDateBuffer.setLength( 0 );
1030             this.calendar.clear();
1031             this.calendar.setTime( date );
1032             // Tag
1033             i = this.calendar.get( Calendar.DAY_OF_MONTH );
1034             if ( i < 10 )
1035             {
1036                 this.shortDateBuffer.append( '0' );
1037             }
1038             this.shortDateBuffer.append( i );
1039             // Monat
1040             i = this.calendar.get( Calendar.MONTH ) + 1;
1041             if ( i < 10 )
1042             {
1043                 this.shortDateBuffer.append( '0' );
1044             }
1045             this.shortDateBuffer.append( i );
1046             // Jahr
1047             i = this.calendar.get( Calendar.YEAR );
1048             this.shortDateBuffer.append( i >= 2000 && i <= 2009 ? "0" : "" );
1049             this.shortDateBuffer.append( i >= 1980 && i < 2000 ? i - 1900 : i - 2000 );
1050 
1051             buf = Charsets.encode( this.shortDateBuffer.toString(), cset );
1052         }
1053         else
1054         {
1055             buf = Charsets.encode( "      ", cset );
1056         }
1057 
1058         this.fileOperations.setFilePointer( position );
1059         this.fileOperations.write( buf, 0, 6 );
1060     }
1061 
1062     /**
1063      * Hilfs-Methode zum Lesen einer Datums-Angabe mit vierstelliger Jahres-Zahl.
1064      * <p>Sollten ungültige Daten gelesen werden, so wird {@code null} zurückgeliefert und eine entsprechende
1065      * {@code IllegalDataMessage} erzeugt.</p>
1066      *
1067      * @param field Feld-Konstante des zu lesenden Feldes.
1068      * @param position Position ab der die Zeichen gelesen werden sollen.
1069      * @param encoding Zu verwendende Kodierung.
1070      *
1071      * @return gelesenes Datum oder {@code null} wenn nicht gelesen werden kann.
1072      *
1073      * @throws IOException wenn nicht gelesen werden kann.
1074      *
1075      * @see #ENCODING_ASCII
1076      * @see #ENCODING_EBCDI
1077      * @see org.jdtaus.banking.dtaus.spi.Fields
1078      * @see org.jdtaus.banking.dtaus.ri.zka.ThreadLocalMessages
1079      */
1080     protected Date readLongDate( final int field, final long position, final int encoding ) throws IOException
1081     {
1082         final int len;
1083         final String cset;
1084 
1085         boolean legal = false;
1086         Date ret = null;
1087         String str = null;
1088         Message msg;
1089 
1090         if ( encoding == ENCODING_ASCII )
1091         {
1092             cset = DIN66003;
1093         }
1094         else if ( encoding == ENCODING_EBCDI )
1095         {
1096             cset = IBM273;
1097         }
1098         else
1099         {
1100             throw new IllegalArgumentException( Integer.toString( encoding ) );
1101         }
1102 
1103         try
1104         {
1105             this.fileOperations.setFilePointer( position );
1106             this.fileOperations.read( this.buffer, 0, 8 );
1107             str = Charsets.decode( this.buffer, 0, 8, cset );
1108             len = str.trim().length();
1109             if ( len == 8 )
1110             {
1111                 this.calendar.clear();
1112                 // Tag
1113                 this.calendar.set( Calendar.DAY_OF_MONTH, Integer.valueOf( str.substring( 0, 2 ) ).intValue() );
1114 
1115                 // Monat
1116                 this.calendar.set( Calendar.MONTH, Integer.valueOf( str.substring( 2, 4 ) ).intValue() - 1 );
1117 
1118                 // Jahr
1119                 this.calendar.set( Calendar.YEAR, Integer.valueOf( str.substring( 4, 8 ) ).intValue() );
1120 
1121                 ret = this.calendar.getTime();
1122                 if ( !this.checkDate( ret ) )
1123                 {
1124                     if ( ThreadLocalMessages.isErrorsEnabled() )
1125                     {
1126                         throw new CorruptedException( this.getImplementation(), position );
1127                     }
1128                     else
1129                     {
1130                         msg = new IllegalDataMessage( field, IllegalDataMessage.TYPE_LONGDATE, position, str );
1131                         ThreadLocalMessages.getMessages().addMessage( msg );
1132                     }
1133 
1134                     ret = null;
1135                 }
1136 
1137             }
1138 
1139             if ( len == 0 || len == 8 )
1140             {
1141                 legal = true;
1142             }
1143 
1144         }
1145         catch ( NumberFormatException e )
1146         {
1147             if ( this.getLogger().isDebugEnabled() )
1148             {
1149                 this.getLogger().debug( e.toString() );
1150             }
1151 
1152             legal = false;
1153             ret = null;
1154         }
1155 
1156         if ( !legal )
1157         {
1158             if ( ThreadLocalMessages.isErrorsEnabled() )
1159             {
1160                 throw new CorruptedException( this.getImplementation(), position );
1161             }
1162             else
1163             {
1164                 msg = new IllegalDataMessage( field, IllegalDataMessage.TYPE_LONGDATE, position, str );
1165                 ThreadLocalMessages.getMessages().addMessage( msg );
1166             }
1167         }
1168 
1169         return ret;
1170     }
1171 
1172     /**
1173      * Hilfs-Methode zum Schreiben einer Datums-Angabe mit vierstelliger Jahres-Zahl.
1174      *
1175      * @param field Feld-Konstante des zu beschreibenden Feldes.
1176      * @param position Position ab der die Zeichen geschrieben werden sollen.
1177      * @param date Die zu schreibende Datums-Angabe oder {@code null} um eine optionale Datums-Angabe zu entfernen.
1178      * @param encoding Zu verwendende Kodierung.
1179      *
1180      * @throws IllegalArgumentException wenn das Jahr von {@code date} nicht größer oder gleich 1980 und kleiner oder
1181      * gleich 2079 ist.
1182      * @throws IOException wenn nicht geschrieben werden kann.
1183      *
1184      * @see #ENCODING_ASCII
1185      * @see #ENCODING_EBCDI
1186      * @see org.jdtaus.banking.dtaus.spi.Fields
1187      * @see org.jdtaus.banking.dtaus.ri.zka.ThreadLocalMessages
1188      */
1189     protected void writeLongDate( final int field, final long position, final Date date, final int encoding )
1190         throws IOException
1191     {
1192         int i;
1193         final byte[] buf;
1194         final String cset;
1195 
1196         if ( encoding == ENCODING_ASCII )
1197         {
1198             cset = DIN66003;
1199         }
1200         else if ( encoding == ENCODING_EBCDI )
1201         {
1202             cset = IBM273;
1203         }
1204         else
1205         {
1206             throw new IllegalArgumentException( Integer.toString( encoding ) );
1207         }
1208 
1209         if ( date != null )
1210         {
1211             if ( !this.checkDate( date ) )
1212             {
1213                 throw new IllegalArgumentException( date.toString() );
1214             }
1215 
1216             this.longDateBuffer.setLength( 0 );
1217             this.calendar.clear();
1218             this.calendar.setTime( date );
1219             // Tag
1220             i = this.calendar.get( Calendar.DAY_OF_MONTH );
1221             if ( i < 10 )
1222             {
1223                 this.longDateBuffer.append( '0' );
1224             }
1225             this.longDateBuffer.append( i );
1226             // Monat
1227             i = this.calendar.get( Calendar.MONTH ) + 1;
1228             if ( i < 10 )
1229             {
1230                 this.longDateBuffer.append( '0' );
1231             }
1232             this.longDateBuffer.append( i );
1233             // Jahr
1234             i = this.calendar.get( Calendar.YEAR );
1235             this.longDateBuffer.append( i );
1236             buf = Charsets.encode( this.longDateBuffer.toString(), cset );
1237         }
1238         else
1239         {
1240             buf = Charsets.encode( "        ", cset );
1241         }
1242 
1243         this.fileOperations.setFilePointer( position );
1244         this.fileOperations.write( buf, 0, 8 );
1245     }
1246 
1247     /**
1248      * Hilfs-Methode zum Lesen von gepackten EBCDI Zahlen.
1249      * <p>Sollten ungültige Daten gelesen werden, so wird {@code NO_NUMBER} zurückgeliefert und eine entsprechende
1250      * {@code IllegalDataMessage} erzeugt.</p>
1251      *
1252      * @param field Feld-Konstante des zu lesenden Feldes.
1253      * @param position Position ab der die Daten gelesen werden sollen.
1254      * @param len Anzahl von Byte, die gelesen werden sollen.
1255      * @param sign {@code true} wenn ein Vorzeichen erwartet wird; {@code false} wenn kein Vorzeichen erwartet wird.
1256      *
1257      * @return gelesene Zahl oder {@code NO_NUMBER} wenn gelesene Daten nicht als Zahl interpretiert werden konnten.
1258      *
1259      * @throws CorruptedException wenn die Datei Fehler enthält und {@link ThreadLocalMessages#isErrorsEnabled()} gleich
1260      * {@code true} ist.
1261      * @throws IOException wenn nicht gelesen werden kann.
1262      *
1263      * @see org.jdtaus.banking.dtaus.spi.Fields
1264      * @see ThreadLocalMessages#isErrorsEnabled()
1265      * @see #NO_NUMBER
1266      */
1267     protected long readNumberPackedPositive( final int field, final long position, final int len, final boolean sign )
1268         throws IOException
1269     {
1270         long ret = 0L;
1271         final int nibbles = 2 * len;
1272         int exp = nibbles - ( sign ? 2 : 1 );
1273         boolean highNibble = true;
1274         int read = 0;
1275         Message msg;
1276 
1277         this.fileOperations.setFilePointer( position );
1278         this.fileOperations.read( this.buffer, 0, len );
1279 
1280         for ( int nibble = 0; nibble < nibbles; nibble++, exp-- )
1281         {
1282             final int digit = highNibble ? ( ( this.buffer[read] & 0xF0 ) >> 4 ) : ( this.buffer[read++] & 0xF );
1283 
1284             highNibble = !highNibble;
1285 
1286             // Vorzeichen des letzten Nibbles.
1287             if ( sign && exp < 0 )
1288             {
1289                 if ( digit != 0xC )
1290                 {
1291                     if ( ThreadLocalMessages.isErrorsEnabled() )
1292                     {
1293                         throw new CorruptedException( this.getImplementation(), position );
1294                     }
1295                     else
1296                     {
1297                         msg = new IllegalDataMessage(
1298                             field, IllegalDataMessage.TYPE_PACKET_POSITIVE, position, Integer.toString( digit ) );
1299 
1300                         ThreadLocalMessages.getMessages().addMessage( msg );
1301                     }
1302 
1303                     ret = NO_NUMBER;
1304                     break;
1305                 }
1306             }
1307             else
1308             {
1309                 if ( digit < 0 || digit > 9 )
1310                 {
1311                     if ( !ThreadLocalMessages.isErrorsEnabled() )
1312                     {
1313                         throw new CorruptedException( this.getImplementation(), position );
1314                     }
1315                     else
1316                     {
1317                         msg = new IllegalDataMessage(
1318                             field, IllegalDataMessage.TYPE_PACKET_POSITIVE, position, Integer.toString( digit ) );
1319 
1320                         ThreadLocalMessages.getMessages().addMessage( msg );
1321                     }
1322 
1323                     ret = NO_NUMBER;
1324                     break;
1325                 }
1326 
1327                 ret += ( digit * EXP10[exp] );
1328             }
1329         }
1330 
1331         return ret;
1332     }
1333 
1334     /**
1335      * Hilfs-Methode zum Schreiben von gepackten EBCDI-Zahlen.
1336      *
1337      * @param field Feld-Konstante des zu beschreibenden Feldes.
1338      * @param position Position ab der die Daten geschrieben werden sollen.
1339      * @param len Anzahl an Byte die geschrieben werden sollen. Hierbei wird linksseitig mit Nullen aufgefüllt, so dass
1340      * exakt {@code len} Ziffern geschrieben werden.
1341      * @param number Die zu schreibende Zahl.
1342      * @param sign {@code true} wenn ein Vorzeichen geschrieben werden soll; {@code false} wenn kein Vorzeichen
1343      * geschrieben werden soll.
1344      *
1345      * @throws IllegalArgumentException wenn {@code number} nicht mit {@code len} Byte darstellbar ist.
1346      * @throws IOException wenn nicht geschrieben werden kann.
1347      *
1348      * @see org.jdtaus.banking.dtaus.spi.Fields
1349      */
1350     protected void writeNumberPackedPositive( final int field, final long position, final int len, long number,
1351                                               final boolean sign ) throws IOException
1352     {
1353         int i;
1354         int pos = 0;
1355         final int nibbles = len * 2;
1356         final int digits = nibbles - ( sign ? 1 : 0 );
1357         int exp = digits - 1;
1358         final long maxValue = EXP10[digits] - 1L;
1359         byte b = 0;
1360         boolean highNibble = true;
1361 
1362         if ( number < 0L || number > maxValue )
1363         {
1364             throw new IllegalArgumentException( Long.toString( number ) );
1365         }
1366 
1367         for ( i = 0; i < nibbles; i++, exp-- )
1368         {
1369             final int digit;
1370 
1371             if ( sign && exp < 0 )
1372             {
1373                 digit = 0xC;
1374             }
1375             else
1376             {
1377                 digit = (int) Math.floor( number / EXP10[exp] );
1378                 number -= ( digit * EXP10[exp] );
1379             }
1380             if ( highNibble )
1381             {
1382                 b = (byte) ( ( digit << 4 ) & 0xF0 );
1383             }
1384             else
1385             {
1386                 this.buffer[pos++] = (byte) ( b | digit );
1387             }
1388 
1389             highNibble = !highNibble;
1390         }
1391 
1392         this.fileOperations.setFilePointer( position );
1393         this.fileOperations.write( this.buffer, 0, len );
1394     }
1395 
1396     /**
1397      * Hilfs-Methode zum Lesen von binär gespeicherten Zahlen.
1398      *
1399      * @param field Feld-Konstante des zu lesenden Feldes.
1400      * @param position Position ab der die Daten gelesen werden sollen.
1401      * @param len Anzahl von Byte, die gelesen werden sollen.
1402      *
1403      * @return gelesene Zahl.
1404      *
1405      * @throws IllegalArgumentException wenn {@code len} negativ, {@code 0} oder größer als {@code 8} ist.
1406      * @throws IOException wenn nicht gelesen werden kann.
1407      *
1408      * @see org.jdtaus.banking.dtaus.spi.Fields
1409      */
1410     protected long readNumberBinary( final int field, final long position, final int len ) throws IOException
1411     {
1412         if ( len <= 0 || len > 8 )
1413         {
1414             throw new IllegalArgumentException( Integer.toString( len ) );
1415         }
1416 
1417         long ret = 0L;
1418         int shift = ( len - 1 ) * 8;
1419 
1420         this.fileOperations.setFilePointer( position );
1421         this.fileOperations.read( this.buffer, 0, len );
1422 
1423         for ( int i = 0; i < len; i++, shift -= 8 )
1424         {
1425             ret |= ( ( this.buffer[i] & 0xFF ) << shift );
1426         }
1427 
1428         return ret;
1429     }
1430 
1431     /**
1432      * Hilfs-Methode zum Schreiben von binär gespeicherten Zahlen.
1433      *
1434      * @param field Feld-Konstante des zu beschreibenden Feldes.
1435      * @param position Position ab der die Daten geschrieben werden sollen.
1436      * @param len Anzahl an Byte die geschrieben werden sollen.
1437      * @param number Die zu schreibende Zahl.
1438      *
1439      * @throws IllegalArgumentException wenn {@code len} negativ, {@code 0} oder größer als {@code 8} ist.
1440      * @throws IOException wenn nicht geschrieben werden kann.
1441      *
1442      * @see org.jdtaus.banking.dtaus.spi.Fields
1443      */
1444     protected void writeNumberBinary( final int field, final long position, final int len, final long number )
1445         throws IOException
1446     {
1447         if ( len <= 0 || len > 8 )
1448         {
1449             throw new IllegalArgumentException( Integer.toString( len ) );
1450         }
1451 
1452         int shift = ( len - 1 ) * 8;
1453         int i;
1454 
1455         for ( i = 0; i < len; i++, shift -= 8 )
1456         {
1457             this.buffer[i] = (byte) ( ( number >> shift ) & 0xFFL );
1458         }
1459 
1460         this.fileOperations.setFilePointer( position );
1461         this.fileOperations.write( this.buffer, 0, len );
1462     }
1463 
1464     /**
1465      * Prüfung einer laufenden Transaktionsnummer.
1466      *
1467      * @param id zu prüfende Transaktionsnummer.
1468      * @param checksum aktuelle Prüfsumme.
1469      *
1470      * @throws NullPointerException {@code if(checksum == null)}
1471      */
1472     protected boolean checkTransactionId( final int id, final Checksum checksum )
1473     {
1474         if ( checksum == null )
1475         {
1476             throw new NullPointerException( "checksum" );
1477         }
1478 
1479         final int count = checksum.getTransactionCount();
1480         return count > 0 && id >= 0 && id < count;
1481     }
1482 
1483     /**
1484      * Prüfung einer Menge von Transaktionen.
1485      *
1486      * @param transactionCount zu prüfende Menge Transaktionen.
1487      */
1488     protected boolean checkTransactionCount( final int transactionCount )
1489     {
1490         return transactionCount >= 0 && transactionCount <= MAX_TRANSACTIONS;
1491     }
1492 
1493     /**
1494      * Prüfung eines Datums.
1495      *
1496      * @param date zu prüfendes Datum.
1497      *
1498      * @return {@code true} wenn {@code date} im gültigen Bereich liegt; {@code false} wenn nicht,
1499      */
1500     protected boolean checkDate( final Date date )
1501     {
1502         boolean valid = false;
1503 
1504         if ( date != null )
1505         {
1506             final long millis = date.getTime();
1507             valid = millis >= VALID_DATES_START_MILLIS && millis <= VALID_DATES_END_MILLIS;
1508         }
1509 
1510         return valid;
1511     }
1512 
1513     /**
1514      * Hilfsmethode zum dynamischen Vergrössern des Index.
1515      *
1516      * @param index laufende Transaktionsnummer, für die der Index angepasst werden soll.
1517      * @param checksum aktuelle Prüfsumme zur Initialisierung des Index.
1518      */
1519     protected void resizeIndex( final int index, final Checksum checksum )
1520     {
1521         if ( this.index == null )
1522         {
1523             this.index = this.getMemoryManager().allocateLongs( checksum.getTransactionCount() + 1 );
1524             Arrays.fill( this.index, -1L );
1525         }
1526 
1527         while ( this.index.length < index + 1 )
1528         {
1529             int newLength = this.index.length * 2;
1530             if ( newLength <= index )
1531             {
1532                 newLength = index + 1;
1533             }
1534             else if ( newLength > MAX_TRANSACTIONS )
1535             {
1536                 newLength = MAX_TRANSACTIONS;
1537             }
1538 
1539             final long[] newIndex = this.getMemoryManager().allocateLongs( newLength );
1540             System.arraycopy( this.index, 0, newIndex, 0, this.index.length );
1541             Arrays.fill( newIndex, this.index.length, newIndex.length, -1L );
1542             this.index = newIndex;
1543         }
1544     }
1545 
1546     /**
1547      * Inserts a given number of bytes at a given position.
1548      *
1549      * @param position The position to insert bytes at.
1550      * @param bytes The number of bytes to insert.
1551      *
1552      * @throws IOException if inserting bytes fails.
1553      */
1554     protected void insertBytes( final long position, final long bytes ) throws IOException
1555     {
1556         final Task task = new Task();
1557         long toMoveByte = this.getFileOperations().getLength() - position;
1558         long progress = 0L;
1559         long progressDivisor = 1L;
1560         long maxProgress = toMoveByte;
1561 
1562         if ( toMoveByte <= 0L )
1563         {
1564             this.getFileOperations().setLength( this.getFileOperations().getLength() + bytes );
1565             this.fireBytesInserted( position, bytes );
1566             return;
1567         }
1568 
1569         final byte[] buf = this.getBuffer( toMoveByte > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) toMoveByte );
1570         while ( maxProgress > Integer.MAX_VALUE )
1571         {
1572             maxProgress /= 2L;
1573             progressDivisor *= 2L;
1574         }
1575 
1576         task.setIndeterminate( false );
1577         task.setCancelable( false );
1578         task.setMinimum( 0 );
1579         task.setMaximum( (int) maxProgress );
1580         task.setProgress( (int) progress );
1581         task.setDescription( new InsertsBlocksMessage() );
1582 
1583         final boolean monitoring = toMoveByte > this.getMonitoringThreshold();
1584         if ( monitoring )
1585         {
1586             this.getTaskMonitor().monitor( task );
1587         }
1588 
1589         try
1590         {
1591             long readPos = this.getFileOperations().getLength();
1592             while ( toMoveByte > 0L )
1593             {
1594                 final int moveLen = buf.length >= toMoveByte ? (int) toMoveByte : buf.length;
1595                 readPos -= moveLen;
1596                 final long writePos = readPos + bytes;
1597 
1598                 this.getFileOperations().setFilePointer( readPos );
1599                 int read = 0;
1600                 int total = 0;
1601 
1602                 do
1603                 {
1604                     read = this.getFileOperations().read( buf, total, moveLen - total );
1605                     assert read != FileOperations.EOF : "Unexpected end of file.";
1606                     total += read;
1607                 }
1608                 while ( total < moveLen );
1609 
1610                 this.getFileOperations().setFilePointer( writePos );
1611                 this.getFileOperations().write( buf, 0, moveLen );
1612 
1613                 toMoveByte -= moveLen;
1614                 progress += moveLen;
1615                 task.setProgress( (int) ( progress / progressDivisor ) );
1616             }
1617         }
1618         finally
1619         {
1620             if ( monitoring )
1621             {
1622                 this.getTaskMonitor().finish( task );
1623             }
1624         }
1625 
1626         this.fireBytesInserted( position, bytes );
1627     }
1628 
1629     /**
1630      * Removes a given number of bytes at a given position.
1631      *
1632      * @param position The position to remove bytes at.
1633      * @param bytes The number of bytes to remove.
1634      *
1635      * @throws IOException if removing bytes fails.
1636      */
1637     protected void removeBytes( final long position, final long bytes ) throws IOException
1638     {
1639         final Task task = new Task();
1640         long toMoveByte = this.getFileOperations().getLength() - position - bytes;
1641         long progress = 0L;
1642         long progressDivisor = 1L;
1643         long maxProgress = toMoveByte;
1644 
1645         // No blocks are following the ones to remove.
1646         if ( toMoveByte == 0L )
1647         {
1648             this.getFileOperations().setLength( this.getFileOperations().getLength() - bytes );
1649             this.fireBytesDeleted( position, bytes );
1650             return;
1651         }
1652 
1653         final byte[] buf = this.getBuffer( toMoveByte > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) toMoveByte );
1654         while ( maxProgress > Integer.MAX_VALUE )
1655         {
1656             maxProgress /= 2L;
1657             progressDivisor *= 2L;
1658         }
1659 
1660         task.setIndeterminate( false );
1661         task.setCancelable( false );
1662         task.setMinimum( 0 );
1663         task.setMaximum( (int) maxProgress );
1664         task.setProgress( (int) progress );
1665         task.setDescription( new DeletesBlocksMessage() );
1666 
1667         final boolean monitoring = toMoveByte > this.getMonitoringThreshold();
1668         if ( monitoring )
1669         {
1670             this.getTaskMonitor().monitor( task );
1671         }
1672 
1673         try
1674         {
1675             long readPos = position + bytes;
1676             while ( toMoveByte > 0L )
1677             {
1678                 final int len = toMoveByte <= buf.length ? (int) toMoveByte : buf.length;
1679                 final long writePos = readPos - bytes;
1680 
1681                 this.getFileOperations().setFilePointer( readPos );
1682 
1683                 int read = 0;
1684                 int total = 0;
1685                 do
1686                 {
1687                     read = this.getFileOperations().read( buf, total, len - total );
1688                     assert read != FileOperations.EOF : "Unexpected end of file.";
1689                     total += read;
1690 
1691                 }
1692                 while ( total < len );
1693 
1694                 // Move the block count blocks to the beginning.
1695                 this.getFileOperations().setFilePointer( writePos );
1696                 this.getFileOperations().write( buf, 0, len );
1697 
1698                 toMoveByte -= len;
1699                 readPos += len;
1700                 progress += len;
1701                 task.setProgress( (int) ( progress / progressDivisor ) );
1702             }
1703 
1704             this.getFileOperations().setLength( this.getFileOperations().getLength() - bytes );
1705         }
1706         finally
1707         {
1708             if ( monitoring )
1709             {
1710                 this.getTaskMonitor().finish( task );
1711             }
1712         }
1713 
1714         this.fireBytesDeleted( position, bytes );
1715     }
1716 
1717     private byte[] getBuffer( final int requested ) throws IOException
1718     {
1719         final long length = this.getFileOperations().getLength();
1720 
1721         if ( requested <= 0 || requested > length )
1722         {
1723             throw new IllegalArgumentException( Integer.toString( requested ) );
1724         }
1725 
1726         if ( this.defaultBuffer == null )
1727         {
1728             this.defaultBuffer = this.getMemoryManager().allocateBytes( this.getDefaultBufferSize() );
1729         }
1730 
1731         return requested <= this.defaultBuffer.length || this.getMemoryManager().getAvailableBytes() < requested
1732                ? this.defaultBuffer : this.getMemoryManager().allocateBytes( requested );
1733 
1734     }
1735 
1736     /**
1737      * Ermittelt die Größe eines Satzabschnitts.
1738      *
1739      * @return Größe eines Satzabschnitts in Byte.
1740      */
1741     protected abstract int getBlockSize();
1742 
1743     /**
1744      * Ermittelt den Typ eines Satzabschnitts.
1745      *
1746      * @param position Position des zu lesenden Satzabschnitts.
1747      *
1748      * @return Datensatztyp des an {@code position} beginnenden Satzabschnitts {@code position}.
1749      *
1750      * @throws IOException wenn nicht gelesen werden kann.
1751      */
1752     protected abstract char getBlockType( long position ) throws IOException;
1753 
1754     /**
1755      * Ermittlung der Bytes einer Transaktion.
1756      *
1757      * @param transaction Transaktion, für die die Anzahl benötigter Bytes ermittelt werden soll.
1758      *s
1759      * @return Anzahl der von {@code transaction} belegten Bytes.
1760      */
1761     protected abstract int byteCount( Transaction transaction );
1762 
1763     /**
1764      * Gets implementation meta-data.
1765      *
1766      * @return implementation meta-data.
1767      */
1768     protected abstract Implementation getImplementation();
1769 
1770     /**
1771      * Liest den A Datensatz. Die entsprechenden Vor- und Nachbedingungen werden in
1772      * {@link AbstractLogicalFile#getHeader()} geprüft.
1773      *
1774      * @return A Datensatz.
1775      *
1776      * @throws IOException wenn nicht gelesen werden kann.
1777      *
1778      * @see #getHeaderPosition()
1779      */
1780     protected abstract Header readHeader() throws IOException;
1781 
1782     /**
1783      * Schreibt den A Datensatz. Die entsprechenden Vor- und Nachbedingungen werden in
1784      * {@link AbstractLogicalFile#setHeader(Header)} geprüft.
1785      *
1786      * @param header A Datensatz.
1787      *
1788      * @throws IOException wenn nicht geschrieben werden kann.
1789      *
1790      * @see #getHeaderPosition()
1791      */
1792     protected abstract void writeHeader( Header header ) throws IOException;
1793 
1794     /**
1795      * Liest den E Datensatz. Die entsprechenden Vor- und Nachbedingungen werden in
1796      * {@link AbstractLogicalFile#getChecksum()} geprüft.
1797      *
1798      * @return E Datensatz.
1799      *
1800      * @throws IOException wenn nicht gelesen werden kann.
1801      *
1802      * @see #getChecksumPosition()
1803      */
1804     protected abstract Checksum readChecksum() throws IOException;
1805 
1806     /**
1807      * Schreibt den E Datensatz. Die entsprechenden Vor- und Nachbedingungen werden in
1808      * {@link AbstractLogicalFile#setChecksum(Checksum)} geprüft.
1809      *
1810      * @param checksum E Datensatz.
1811      *
1812      * @throws IOException wenn nicht geschrieben werden kann.
1813      *
1814      * @see #getChecksumPosition()
1815      */
1816     protected abstract void writeChecksum( Checksum checksum ) throws IOException;
1817 
1818     /**
1819      * Liest einen C Datensatz. Die entsprechenden Vor- und Nachbedingungen werden in
1820      * {@link AbstractLogicalFile#getTransaction(int)} geprüft.
1821      *
1822      * @param position Position des C Datensatzes.
1823      * @param transaction Instanz, die die gelesenen Daten aufnehmen soll.
1824      *
1825      * @return an {@code position} beginnender C Datensatz.
1826      *
1827      * @throws IOException wenn nicht gelesen werden kann.
1828      */
1829     protected abstract Transaction readTransaction( long position, Transaction transaction ) throws IOException;
1830 
1831     /**
1832      * Schreibt einen C Datensatz. Die entsprechenden Vor- und Nachbedingungen werden in
1833      * {@link AbstractLogicalFile#setTransaction(int, Transaction)} geprüft.
1834      *
1835      * @param position Position des C Datensatzes.
1836      * @param transaction Daten des C Datensatzes.
1837      *
1838      * @throws IOException wenn nicht geschrieben werden kann.
1839      */
1840     protected abstract void writeTransaction( long position, Transaction transaction ) throws IOException;
1841 
1842     public Header getHeader() throws IOException
1843     {
1844         if ( this.cachedHeader == null )
1845         {
1846             this.cachedHeader = this.readHeader();
1847         }
1848 
1849         return (Header) this.cachedHeader.clone();
1850     }
1851 
1852     public Header setHeader( final Header header ) throws IOException
1853     {
1854         IllegalHeaderException result = null;
1855         final Header old = this.getHeader();
1856         final HeaderValidator[] validators = this.getHeaderValidator();
1857 
1858         for ( int i = validators.length - 1; i >= 0; i-- )
1859         {
1860             result = validators[i].assertValidHeader( this, header, this.counter, result );
1861         }
1862 
1863         if ( result != null && result.getMessages().length > 0 )
1864         {
1865             throw result;
1866         }
1867 
1868         this.writeHeader( header );
1869         this.cachedHeader = (Header) header.clone();
1870         return old;
1871     }
1872 
1873     public Checksum getChecksum() throws IOException
1874     {
1875         if ( this.cachedChecksum == null )
1876         {
1877             this.cachedChecksum = this.readChecksum();
1878         }
1879 
1880         return (Checksum) this.cachedChecksum.clone();
1881     }
1882 
1883     protected void setChecksum( final Checksum checksum ) throws IOException
1884     {
1885         this.writeChecksum( checksum );
1886         this.cachedChecksum = (Checksum) checksum.clone();
1887     }
1888 
1889     protected void checksum() throws IOException
1890     {
1891         final Checksum c = new Checksum();
1892         Transaction t = new Transaction();
1893         final Task task = new Task();
1894         task.setIndeterminate( true );
1895         task.setCancelable( false );
1896         task.setDescription( new ChecksumsFileMessage() );
1897 
1898         try
1899         {
1900             this.getTaskMonitor().monitor( task );
1901 
1902             final long fileLength = this.fileOperations.getLength();
1903             long position = this.getHeaderPosition();
1904             char type = this.getBlockType( position );
1905             this.setChecksumPosition( position + this.getBlockSize() );
1906             this.counter = new CurrencyCounter();
1907 
1908             if ( type == 'A' )
1909             {
1910                 this.getHeader(); // A-Datensatz prüfen.
1911 
1912                 position += this.getBlockSize();
1913                 int transactionIndex = 0;
1914 
1915                 while ( position < fileLength && ( type = this.getBlockType( position ) ) == 'C' )
1916                 {
1917                     this.resizeIndex( transactionIndex, c );
1918                     this.index[transactionIndex] = position - this.getHeaderPosition();
1919                     t = this.readTransaction( this.getHeaderPosition() + this.index[transactionIndex++], t );
1920                     final int len = this.byteCount( t );
1921 
1922                     if ( t.getCurrency() != null )
1923                     {
1924                         this.counter.add( t.getCurrency() );
1925                     }
1926                     if ( t.getAmount() != null && t.getTargetAccount() != null && t.getTargetBank() != null )
1927                     {
1928                         c.add( t );
1929                     }
1930 
1931                     position += len;
1932                     this.setChecksumPosition( position );
1933                     c.setTransactionCount( transactionIndex );
1934                 }
1935 
1936                 this.setChecksumPosition( position );
1937                 if ( type == 'E' )
1938                 {
1939                     final Checksum stored = this.getChecksum();
1940                     if ( !stored.equals( c ) )
1941                     {
1942                         if ( ThreadLocalMessages.isErrorsEnabled() )
1943                         {
1944                             throw new CorruptedException( this.getImplementation(), position );
1945                         }
1946                         else
1947                         {
1948                             final Message msg = new ChecksumErrorMessage( stored, c, this.getHeaderPosition() );
1949                             ThreadLocalMessages.getMessages().addMessage( msg );
1950                         }
1951                     }
1952                 }
1953                 else
1954                 {
1955                     if ( ThreadLocalMessages.isErrorsEnabled() )
1956                     {
1957                         throw new CorruptedException(
1958                             this.getImplementation(), position + DTAUSDisk.ERECORD_OFFSETS[1] );
1959 
1960                     }
1961                     else
1962                     {
1963                         final Message msg = new IllegalDataMessage(
1964                             Fields.FIELD_E2, IllegalDataMessage.TYPE_CONSTANT, position + DTAUSDisk.ERECORD_OFFSETS[1],
1965                             Character.toString( type ) );
1966 
1967                         ThreadLocalMessages.getMessages().addMessage( msg );
1968                     }
1969                 }
1970             }
1971             else
1972             {
1973                 if ( ThreadLocalMessages.isErrorsEnabled() )
1974                 {
1975                     throw new CorruptedException( this.getImplementation(), position + DTAUSDisk.ARECORD_OFFSETS[1] );
1976                 }
1977                 else
1978                 {
1979                     final Message msg = new IllegalDataMessage(
1980                         Fields.FIELD_A2, IllegalDataMessage.TYPE_CONSTANT, position + DTAUSDisk.ARECORD_OFFSETS[1],
1981                         Character.toString( type ) );
1982 
1983                     ThreadLocalMessages.getMessages().addMessage( msg );
1984                 }
1985             }
1986         }
1987         finally
1988         {
1989             this.getTaskMonitor().finish( task );
1990         }
1991     }
1992 
1993     public final void createTransaction( final Transaction transaction ) throws IOException
1994     {
1995         this.addTransaction( transaction );
1996     }
1997 
1998     public int addTransaction( final Transaction transaction ) throws IOException
1999     {
2000         final Checksum checksum = this.getChecksum();
2001         final int newCount = checksum.getTransactionCount() + 1;
2002 
2003         if ( !this.checkTransactionCount( newCount ) )
2004         {
2005             throw new ArrayIndexOutOfBoundsException( newCount );
2006         }
2007 
2008         IllegalTransactionException result = null;
2009         final TransactionValidator[] validators = this.getTransactionValidator();
2010 
2011         for ( int i = validators.length - 1; i >= 0; i-- )
2012         {
2013             result = validators[i].assertValidTransaction( this, transaction, result );
2014         }
2015 
2016         if ( result != null && result.getMessages().length > 0 )
2017         {
2018             throw result;
2019         }
2020 
2021         this.counter.add( transaction.getCurrency() );
2022         checksum.setTransactionCount( newCount );
2023         checksum.add( transaction );
2024 
2025         final int transactionIndex = checksum.getTransactionCount() - 1;
2026         final int len = this.byteCount( transaction );
2027         this.insertBytes( this.getChecksumPosition(), len );
2028         this.setChecksumPosition( this.getChecksumPosition() + len );
2029         this.resizeIndex( transactionIndex, checksum );
2030         this.index[transactionIndex] = this.getChecksumPosition() - len - this.getHeaderPosition();
2031         this.writeTransaction( this.getHeaderPosition() + this.index[transactionIndex], transaction );
2032         this.writeChecksum( checksum );
2033         this.cachedChecksum = checksum;
2034         return transactionIndex;
2035     }
2036 
2037     public Transaction getTransaction( final int index ) throws IOException
2038     {
2039         final Checksum checksum = this.getChecksum();
2040         if ( !this.checkTransactionId( index, checksum ) )
2041         {
2042             throw new ArrayIndexOutOfBoundsException( index );
2043         }
2044 
2045         return this.readTransaction( this.index[index] + this.getHeaderPosition(), new Transaction() );
2046     }
2047 
2048     public Transaction setTransaction( final int index, final Transaction transaction ) throws IOException
2049     {
2050         final Checksum checksum = this.getChecksum();
2051         if ( !this.checkTransactionId( index, checksum ) )
2052         {
2053             throw new ArrayIndexOutOfBoundsException( index );
2054         }
2055 
2056         IllegalTransactionException result = null;
2057         final TransactionValidator[] validators = this.getTransactionValidator();
2058 
2059         for ( int i = validators.length - 1; i >= 0; i-- )
2060         {
2061             result = validators[i].assertValidTransaction( this, transaction, result );
2062         }
2063 
2064         if ( result != null && result.getMessages().length > 0 )
2065         {
2066             throw result;
2067         }
2068 
2069         final Transaction old = this.getTransaction( index );
2070 
2071         if ( !old.getCurrency().getCurrencyCode().equals( transaction.getCurrency().getCurrencyCode() ) )
2072         {
2073             this.counter.substract( old.getCurrency() );
2074             this.counter.add( transaction.getCurrency() );
2075         }
2076 
2077         int i;
2078         checksum.subtract( old );
2079         checksum.add( transaction );
2080         final int oldLen = this.byteCount( old );
2081         final int newLen = this.byteCount( transaction );
2082         if ( oldLen < newLen )
2083         {
2084             final int delta = newLen - oldLen;
2085             this.insertBytes( this.getHeaderPosition() + this.index[index], delta );
2086 
2087             for ( i = index + 1; i < this.index.length; i++ )
2088             {
2089                 if ( this.index[i] != -1L )
2090                 {
2091                     this.index[i] += delta;
2092                 }
2093             }
2094 
2095             this.setChecksumPosition( this.getChecksumPosition() + delta );
2096         }
2097         else if ( oldLen > newLen )
2098         {
2099             final int delta = oldLen - newLen;
2100             this.removeBytes( this.getHeaderPosition() + this.index[index], delta );
2101 
2102             for ( i = index + 1; i < this.index.length; i++ )
2103             {
2104                 if ( this.index[i] != -1L )
2105                 {
2106                     this.index[i] -= delta;
2107                 }
2108             }
2109 
2110             this.setChecksumPosition( this.getChecksumPosition() - delta );
2111         }
2112 
2113         this.writeTransaction( this.getHeaderPosition() + this.index[index], transaction );
2114         this.writeChecksum( checksum );
2115         this.cachedChecksum = checksum;
2116         return old;
2117     }
2118 
2119     public Transaction removeTransaction( final int index ) throws IOException
2120     {
2121         final Checksum checksum = this.getChecksum();
2122         if ( !this.checkTransactionId( index, checksum ) )
2123         {
2124             throw new ArrayIndexOutOfBoundsException( index );
2125         }
2126 
2127         final Transaction removed = this.getTransaction( index );
2128         checksum.setTransactionCount( checksum.getTransactionCount() - 1 );
2129         checksum.subtract( removed );
2130         this.counter.substract( removed.getCurrency() );
2131 
2132         final int len = this.byteCount( removed );
2133         this.removeBytes( this.getHeaderPosition() + this.index[index], len );
2134         this.setChecksumPosition( this.getChecksumPosition() - len );
2135         for ( int i = index + 1; i < this.index.length; i++ )
2136         {
2137             if ( this.index[i] != -1L )
2138             {
2139                 this.index[i] -= len;
2140             }
2141 
2142             this.index[i - 1] = this.index[i];
2143         }
2144 
2145         this.writeChecksum( checksum );
2146         this.cachedChecksum = checksum;
2147         return removed;
2148     }
2149 
2150     //--Dependencies------------------------------------------------------------
2151 
2152 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
2153     // This section is managed by jdtaus-container-mojo.
2154 
2155     /**
2156      * Gets the configured <code>Logger</code> implementation.
2157      *
2158      * @return The configured <code>Logger</code> implementation.
2159      */
2160     protected Logger getLogger()
2161     {
2162         return (Logger) ContainerFactory.getContainer().
2163             getDependency( this, "Logger" );
2164 
2165     }
2166 
2167     /**
2168      * Gets the configured <code>MemoryManager</code> implementation.
2169      *
2170      * @return The configured <code>MemoryManager</code> implementation.
2171      */
2172     protected MemoryManager getMemoryManager()
2173     {
2174         return (MemoryManager) ContainerFactory.getContainer().
2175             getDependency( this, "MemoryManager" );
2176 
2177     }
2178 
2179     /**
2180      * Gets the configured <code>ApplicationLogger</code> implementation.
2181      *
2182      * @return The configured <code>ApplicationLogger</code> implementation.
2183      */
2184     protected ApplicationLogger getApplicationLogger()
2185     {
2186         return (ApplicationLogger) ContainerFactory.getContainer().
2187             getDependency( this, "ApplicationLogger" );
2188 
2189     }
2190 
2191     /**
2192      * Gets the configured <code>TaskMonitor</code> implementation.
2193      *
2194      * @return The configured <code>TaskMonitor</code> implementation.
2195      */
2196     protected TaskMonitor getTaskMonitor()
2197     {
2198         return (TaskMonitor) ContainerFactory.getContainer().
2199             getDependency( this, "TaskMonitor" );
2200 
2201     }
2202 
2203     /**
2204      * Gets the configured <code>TextschluesselVerzeichnis</code> implementation.
2205      *
2206      * @return The configured <code>TextschluesselVerzeichnis</code> implementation.
2207      */
2208     protected TextschluesselVerzeichnis getTextschluesselVerzeichnis()
2209     {
2210         return (TextschluesselVerzeichnis) ContainerFactory.getContainer().
2211             getDependency( this, "TextschluesselVerzeichnis" );
2212 
2213     }
2214 
2215     /**
2216      * Gets the configured <code>CurrencyMapper</code> implementation.
2217      *
2218      * @return The configured <code>CurrencyMapper</code> implementation.
2219      */
2220     protected CurrencyMapper getCurrencyMapper()
2221     {
2222         return (CurrencyMapper) ContainerFactory.getContainer().
2223             getDependency( this, "CurrencyMapper" );
2224 
2225     }
2226 
2227     /**
2228      * Gets the configured <code>HeaderValidator</code> implementation.
2229      *
2230      * @return The configured <code>HeaderValidator</code> implementation.
2231      */
2232     protected HeaderValidator[] getHeaderValidator()
2233     {
2234         return (HeaderValidator[]) ContainerFactory.getContainer().
2235             getDependency( this, "HeaderValidator" );
2236 
2237     }
2238 
2239     /**
2240      * Gets the configured <code>TransactionValidator</code> implementation.
2241      *
2242      * @return The configured <code>TransactionValidator</code> implementation.
2243      */
2244     protected TransactionValidator[] getTransactionValidator()
2245     {
2246         return (TransactionValidator[]) ContainerFactory.getContainer().
2247             getDependency( this, "TransactionValidator" );
2248 
2249     }
2250 
2251     /**
2252      * Gets the configured <code>Locale</code> implementation.
2253      *
2254      * @return The configured <code>Locale</code> implementation.
2255      */
2256     protected Locale getLocale()
2257     {
2258         return (Locale) ContainerFactory.getContainer().
2259             getDependency( this, "Locale" );
2260 
2261     }
2262 
2263 // </editor-fold>//GEN-END:jdtausDependencies
2264 
2265     //------------------------------------------------------------Dependencies--
2266     //--Properties--------------------------------------------------------------
2267 
2268 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties
2269     // This section is managed by jdtaus-container-mojo.
2270 
2271     /**
2272      * Gets the value of property <code>defaultMonitoringThreshold</code>.
2273      *
2274      * @return Number of bytes which need to minimally be copied to enable any task monitoring during copy operations.
2275      */
2276     protected java.lang.Integer getDefaultMonitoringThreshold()
2277     {
2278         return (java.lang.Integer) ContainerFactory.getContainer().
2279             getProperty( this, "defaultMonitoringThreshold" );
2280 
2281     }
2282 
2283     /**
2284      * Gets the value of property <code>defaultMaximumExtensionCount</code>.
2285      *
2286      * @return Default maximum number of extensions allowed in a C record (field C18).
2287      */
2288     protected java.lang.Long getDefaultMaximumExtensionCount()
2289     {
2290         return (java.lang.Long) ContainerFactory.getContainer().
2291             getProperty( this, "defaultMaximumExtensionCount" );
2292 
2293     }
2294 
2295     /**
2296      * Gets the value of property <code>defaultBufferSize</code>.
2297      *
2298      * @return Size of the pre-alocated default buffer in byte.
2299      */
2300     protected int getDefaultBufferSize()
2301     {
2302         return ( (java.lang.Integer) ContainerFactory.getContainer().
2303             getProperty( this, "defaultBufferSize" ) ).intValue();
2304 
2305     }
2306 
2307 // </editor-fold>//GEN-END:jdtausProperties
2308 
2309     //--------------------------------------------------------------Properties--
2310     //--Messages----------------------------------------------------------------
2311 
2312 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
2313     // This section is managed by jdtaus-container-mojo.
2314 
2315     /**
2316      * Gets the text of message <code>readNumberIllegalFileInfo</code>.
2317      * <blockquote><pre>Ein ungültiges Leerzeichen in einem numerischen Feld wurde zu einer Null konvertiert. Gelesene Zeichenkette "{0}" wurde zur Zahl "{1,number}" konvertiert.</pre></blockquote>
2318      * <blockquote><pre>An illegal space character in a numeric field has been converted to zero. Converted string "{0}" to number "{1,number}".</pre></blockquote>
2319      *
2320      * @param locale The locale of the message instance to return.
2321      * @param readString format parameter.
2322      * @param convertedNumber format parameter.
2323      *
2324      * @return the text of message <code>readNumberIllegalFileInfo</code>.
2325      */
2326     protected String getReadNumberIllegalFileInfoMessage( final Locale locale,
2327             final java.lang.String readString,
2328             final java.lang.Number convertedNumber )
2329     {
2330         return ContainerFactory.getContainer().
2331             getMessage( this, "readNumberIllegalFileInfo", locale,
2332                 new Object[]
2333                 {
2334                     readString,
2335                     convertedNumber
2336                 });
2337 
2338     }
2339 
2340 // </editor-fold>//GEN-END:jdtausMessages
2341 
2342     //----------------------------------------------------------------Messages--
2343 }