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