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}