001/*
002 *  jDTAUS Banking RI DTAUS
003 *  Copyright (C) 2005 Christian Schulte
004 *  <cs@schulte.it>
005 *
006 *  This library is free software; you can redistribute it and/or
007 *  modify it under the terms of the GNU Lesser General Public
008 *  License as published by the Free Software Foundation; either
009 *  version 2.1 of the License, or any later version.
010 *
011 *  This library is distributed in the hope that it will be useful,
012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 *  Lesser General Public License for more details.
015 *
016 *  You should have received a copy of the GNU Lesser General Public
017 *  License along with this library; if not, write to the Free Software
018 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
019 *
020 */
021package org.jdtaus.banking.dtaus.ri.zka;
022
023import java.io.IOException;
024import java.text.ParseException;
025import java.util.Arrays;
026import java.util.Calendar;
027import java.util.Date;
028import java.util.EventListener;
029import java.util.Locale;
030import javax.swing.event.EventListenerList;
031import org.jdtaus.banking.AlphaNumericText27;
032import org.jdtaus.banking.TextschluesselVerzeichnis;
033import org.jdtaus.banking.dtaus.Checksum;
034import org.jdtaus.banking.dtaus.CorruptedException;
035import org.jdtaus.banking.dtaus.Header;
036import org.jdtaus.banking.dtaus.LogicalFile;
037import org.jdtaus.banking.dtaus.Transaction;
038import org.jdtaus.banking.dtaus.spi.CurrencyCounter;
039import org.jdtaus.banking.dtaus.spi.Fields;
040import org.jdtaus.banking.dtaus.spi.HeaderValidator;
041import org.jdtaus.banking.dtaus.spi.IllegalHeaderException;
042import org.jdtaus.banking.dtaus.spi.IllegalTransactionException;
043import org.jdtaus.banking.dtaus.spi.TransactionValidator;
044import org.jdtaus.banking.messages.ChecksumErrorMessage;
045import org.jdtaus.banking.messages.ChecksumsFileMessage;
046import org.jdtaus.banking.messages.IllegalDataMessage;
047import org.jdtaus.banking.spi.CurrencyMapper;
048import org.jdtaus.core.container.ContainerFactory;
049import org.jdtaus.core.container.Implementation;
050import org.jdtaus.core.io.FileOperations;
051import org.jdtaus.core.io.util.FlushableFileOperations;
052import org.jdtaus.core.lang.spi.MemoryManager;
053import org.jdtaus.core.logging.spi.Logger;
054import org.jdtaus.core.messages.DeletesBlocksMessage;
055import org.jdtaus.core.messages.InsertsBlocksMessage;
056import org.jdtaus.core.monitor.spi.Task;
057import org.jdtaus.core.monitor.spi.TaskMonitor;
058import org.jdtaus.core.nio.util.Charsets;
059import org.jdtaus.core.text.Message;
060import org.jdtaus.core.text.spi.ApplicationLogger;
061
062/**
063 * Abstrakte Klasse für {@code LogicalFile}-Implementierungen.
064 * <p>Stellt diverse Hilfs-Methoden sowie die Überprüfung von Vor- und Nachbedingungen zur Verfügung.</p>
065 * <p><b>Hinweis:</b><br/>
066 * Implementierung ist nicht vor gleichzeitigen Zugriffen unterschiedlicher Threads geschützt.</p>
067 *
068 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
069 * @version $JDTAUS: AbstractLogicalFile.java 8810 2012-12-04 00:45:37Z schulte $
070 */
071public abstract class AbstractLogicalFile implements LogicalFile
072{
073
074    public interface Listener extends EventListener
075    {
076
077        /**
078         * Gets called whenever bytes were inserted into an instance the listener is registered with. The byte
079         * previously at {@code position} will have moved to {@code position + insertedBytes}.
080         *
081         * @param position The position of the first inserted byte.
082         * @param bytes The number of bytes which were inserted at {@code position}.
083         *
084         * @throws IOException if reading or writing fails.
085         */
086        void bytesInserted( long position, long bytes ) throws IOException;
087
088        /**
089         * Gets called whenever bytes were deleted an instance the listener is registered with. The byte previously at
090         * {@code position + bytes} will have moved to {@code position}.
091         *
092         * @param position The position of the first deleted byte.
093         * @param bytes The number of bytes which were deleted starting at {@code position} inclusive.
094         *
095         * @throws IOException if reading or writing fails.
096         */
097        void bytesDeleted( long position, long bytes ) throws IOException;
098
099    }
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}