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.EOFException; |
24 | import java.io.File; |
25 | import java.io.IOException; |
26 | import java.io.RandomAccessFile; |
27 | import java.util.Iterator; |
28 | import java.util.Locale; |
29 | import java.util.Map; |
30 | import org.jdtaus.banking.dtaus.CorruptedException; |
31 | import org.jdtaus.banking.dtaus.PhysicalFile; |
32 | import org.jdtaus.banking.dtaus.PhysicalFileException; |
33 | import org.jdtaus.banking.dtaus.PhysicalFileFactory; |
34 | import org.jdtaus.banking.dtaus.spi.Fields; |
35 | import org.jdtaus.banking.messages.IllegalDataMessage; |
36 | import org.jdtaus.banking.messages.IllegalFileLengthMessage; |
37 | import org.jdtaus.core.container.ContainerFactory; |
38 | import org.jdtaus.core.container.Implementation; |
39 | import org.jdtaus.core.container.ModelFactory; |
40 | import org.jdtaus.core.container.PropertyException; |
41 | import org.jdtaus.core.io.FileOperations; |
42 | import org.jdtaus.core.io.util.CoalescingFileOperations; |
43 | import org.jdtaus.core.io.util.RandomAccessFileOperations; |
44 | import org.jdtaus.core.io.util.ReadAheadFileOperations; |
45 | import org.jdtaus.core.nio.util.Charsets; |
46 | import org.jdtaus.core.text.Message; |
47 | |
48 | /** |
49 | * Default {@code PhysicalFileFactory} implementation. |
50 | * |
51 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
52 | * @version $JDTAUS: DefaultPhysicalFileFactory.java 8865 2014-01-10 17:13:42Z schulte $ |
53 | */ |
54 | public final class DefaultPhysicalFileFactory implements PhysicalFileFactory |
55 | { |
56 | |
57 | /** |
58 | * Constant for the name of attribute {@code readAheadCaching}. |
59 | * <p> |
60 | * The {@code readAheadCaching} attribute is used to enabled or disable the |
61 | * use of a read-ahead caching algorithm. Its expected value is of type |
62 | * {@code Boolean}. |
63 | * </p> |
64 | */ |
65 | public static final String ATTRIBUTE_READAHEAD_CACHING = |
66 | DefaultPhysicalFileFactory.class.getName() + ".readAheadCaching"; |
67 | |
68 | /** |
69 | * Constant for the name of attribute {@code readAheadCacheSize}. |
70 | * <p> |
71 | * The {@code readAheadCacheSize} attribute is used to specify the |
72 | * size of the read-ahead cache. Its expected value is of type |
73 | * {@code Integer}. |
74 | * </p> |
75 | */ |
76 | public static final String ATTRIBUTE_READAHEAD_CACHESIZE = |
77 | DefaultPhysicalFileFactory.class.getName() + ".readAheadCacheSize"; |
78 | |
79 | /** |
80 | * Constant for the name of attribute {@code coalescingCaching}. |
81 | * <p> |
82 | * The {@code coalescingCaching} attribute is used to enabled or disable the |
83 | * use of a coalescing caching algorithm. Its expected value is of type |
84 | * {@code Boolean}. |
85 | * </p> |
86 | */ |
87 | public static final String ATTRIBUTE_COALESCING_CACHING = |
88 | DefaultPhysicalFileFactory.class.getName() + ".coalescingCaching"; |
89 | |
90 | /** |
91 | * Constant for the name of attribute {@code coalescingBlockSize}. |
92 | * <p> |
93 | * The {@code coalescingBlockSize} attribute is used to specify the |
94 | * value of property {@code blockSize} to use when constructing the |
95 | * coalescing cache implementation. Its expected value is of type |
96 | * {@code Integer}. |
97 | * </p> |
98 | */ |
99 | public static final String ATTRIBUTE_COALESCING_BLOCKSIZE = |
100 | DefaultPhysicalFileFactory.class.getName() + ".coalescingBlockSize"; |
101 | |
102 | /** |
103 | * Constant for the name of attribute {@code spaceCharactersAllowed}. |
104 | * <p> |
105 | * The {@code spaceCharactersAllowed} attribute is used to specify numeric |
106 | * fields for which space characters are to be allowed. It is used as |
107 | * a prefix with the hexadecimal field constant appended. Its expected value |
108 | * is of type {@code Boolean}. |
109 | * </p> |
110 | */ |
111 | public static final String ATTRIBUTE_SPACE_CHARACTERS_ALLOWED = |
112 | DefaultPhysicalFileFactory.class.getName() + ".spaceCharactersAllowed."; |
113 | |
114 | /** Implementation meta-data. */ |
115 | private Implementation implementation; |
116 | |
117 | public int analyse( final File file ) throws PhysicalFileException, IOException |
118 | { |
119 | if ( file == null ) |
120 | { |
121 | throw new NullPointerException( "file" ); |
122 | } |
123 | |
124 | this.assertValidProperties(); |
125 | final FileOperations ops = new RandomAccessFileOperations( new RandomAccessFile( file, "r" ) ); |
126 | final int format = this.analyse( ops ); |
127 | ops.close(); |
128 | return format; |
129 | } |
130 | |
131 | public int analyse( final FileOperations fileOperations ) throws PhysicalFileException, IOException |
132 | { |
133 | int blockSize = 128; |
134 | long remainder = 0; |
135 | int read = 0; |
136 | int ret = FORMAT_DISK; |
137 | int total = 0; |
138 | |
139 | final Message[] messages; |
140 | final byte[] buf = new byte[ 4 ]; |
141 | final String str; |
142 | final long length; |
143 | |
144 | if ( fileOperations == null ) |
145 | { |
146 | throw new NullPointerException( "fileOperations" ); |
147 | } |
148 | |
149 | this.assertValidProperties(); |
150 | length = fileOperations.getLength(); |
151 | try |
152 | { |
153 | ThreadLocalMessages.getMessages().clear(); |
154 | ThreadLocalMessages.setErrorsEnabled( false ); |
155 | |
156 | if ( length >= 128 ) |
157 | { // mindestens ein Disketten-Satzabschnitt. |
158 | // die ersten 4 Byte lesen. |
159 | fileOperations.setFilePointer( 0L ); |
160 | do |
161 | { |
162 | read = fileOperations.read( buf, total, buf.length - total ); |
163 | if ( read == FileOperations.EOF ) |
164 | { |
165 | throw new EOFException(); |
166 | } |
167 | else |
168 | { |
169 | total += read; |
170 | } |
171 | } |
172 | while ( total < buf.length ); |
173 | |
174 | // Diskettenformat prüfen "0128". |
175 | str = Charsets.decode( buf, "ISO646-DE" ); |
176 | if ( "0128".equals( str ) ) |
177 | { |
178 | remainder = length % blockSize; |
179 | } |
180 | else |
181 | { |
182 | final int size = ( ( buf[0] & 0xFF ) << 8 ) | ( buf[1] & 0xFF ); |
183 | if ( size == 150 ) |
184 | { |
185 | ret = FORMAT_TAPE; |
186 | blockSize = 150; |
187 | remainder = 0; // Variable blocksize. |
188 | } |
189 | else |
190 | { |
191 | if ( ThreadLocalMessages.isErrorsEnabled() ) |
192 | { |
193 | throw new CorruptedException( this.getImplementation(), 0L ); |
194 | } |
195 | else |
196 | { |
197 | final Message msg = new IllegalDataMessage( |
198 | Fields.FIELD_A1, IllegalDataMessage.TYPE_CONSTANT, 0L, str ); |
199 | |
200 | ThreadLocalMessages.getMessages().addMessage( msg ); |
201 | } |
202 | } |
203 | } |
204 | } |
205 | else |
206 | { |
207 | if ( ThreadLocalMessages.isErrorsEnabled() ) |
208 | { |
209 | throw new CorruptedException( this.getImplementation(), length ); |
210 | } |
211 | else |
212 | { |
213 | final Message msg = new IllegalFileLengthMessage( length, blockSize ); |
214 | ThreadLocalMessages.getMessages().addMessage( msg ); |
215 | } |
216 | } |
217 | |
218 | if ( remainder > 0 ) |
219 | { |
220 | if ( ThreadLocalMessages.isErrorsEnabled() ) |
221 | { |
222 | throw new CorruptedException( this.getImplementation(), length ); |
223 | } |
224 | else |
225 | { |
226 | final Message msg = new IllegalFileLengthMessage( length, blockSize ); |
227 | ThreadLocalMessages.getMessages().addMessage( msg ); |
228 | } |
229 | } |
230 | |
231 | messages = ThreadLocalMessages.getMessages().getMessages(); |
232 | if ( messages.length > 0 ) |
233 | { |
234 | throw new PhysicalFileException( messages ); |
235 | } |
236 | |
237 | return ret; |
238 | } |
239 | finally |
240 | { |
241 | ThreadLocalMessages.setErrorsEnabled( true ); |
242 | } |
243 | } |
244 | |
245 | public PhysicalFile createPhysicalFile( final File file, final int format ) |
246 | throws IOException |
247 | { |
248 | return this.createPhysicalFile( file, format, this.getDefaultProperties() ); |
249 | } |
250 | |
251 | public PhysicalFile createPhysicalFile( final File file, final int format, final java.util.Properties properties ) |
252 | throws IOException |
253 | { |
254 | if ( file == null ) |
255 | { |
256 | throw new NullPointerException( "file" ); |
257 | } |
258 | |
259 | this.assertValidProperties(); |
260 | this.assertValidProperties( properties ); |
261 | |
262 | FileOperations ops = new RandomAccessFileOperations( new RandomAccessFile( file, "rw" ) ); |
263 | ops = this.configureCoalescingCaching( ops, properties ); |
264 | return this.createPhysicalFile( ops, format, properties ); |
265 | } |
266 | |
267 | public PhysicalFile createPhysicalFile( |
268 | final FileOperations ops, final int format ) throws IOException |
269 | { |
270 | return this.createPhysicalFile( ops, format, |
271 | this.getDefaultProperties() ); |
272 | |
273 | } |
274 | |
275 | public PhysicalFile createPhysicalFile( FileOperations ops, final int format, final java.util.Properties properties ) |
276 | throws IOException |
277 | { |
278 | if ( ops == null ) |
279 | { |
280 | throw new NullPointerException( "ops" ); |
281 | } |
282 | if ( format != FORMAT_DISK && format != FORMAT_TAPE ) |
283 | { |
284 | throw new IllegalArgumentException( Integer.toString( format ) ); |
285 | } |
286 | |
287 | this.assertValidProperties(); |
288 | this.assertValidProperties( properties ); |
289 | |
290 | try |
291 | { |
292 | ops.setLength( 0L ); |
293 | return this.getPhysicalFile( ops, format, properties ); |
294 | } |
295 | catch ( PhysicalFileException e ) |
296 | { |
297 | throw new AssertionError( e ); |
298 | } |
299 | } |
300 | |
301 | public PhysicalFile getPhysicalFile( final File file ) throws PhysicalFileException, IOException |
302 | { |
303 | return this.getPhysicalFile( file, this.getDefaultProperties() ); |
304 | } |
305 | |
306 | public PhysicalFile getPhysicalFile( final FileOperations ops ) throws PhysicalFileException, IOException |
307 | { |
308 | return this.getPhysicalFile( ops, this.getDefaultProperties() ); |
309 | } |
310 | |
311 | public PhysicalFile getPhysicalFile( final FileOperations ops, final java.util.Properties properties ) |
312 | throws PhysicalFileException, IOException |
313 | { |
314 | if ( ops == null ) |
315 | { |
316 | throw new NullPointerException( "ops" ); |
317 | } |
318 | |
319 | this.assertValidProperties(); |
320 | this.assertValidProperties( properties ); |
321 | return this.getPhysicalFile( ops, this.getDefaultFormat(), properties ); |
322 | } |
323 | |
324 | public PhysicalFile getPhysicalFile( final File file, final java.util.Properties properties ) |
325 | throws PhysicalFileException, IOException |
326 | { |
327 | if ( file == null ) |
328 | { |
329 | throw new NullPointerException( "file" ); |
330 | } |
331 | |
332 | this.assertValidProperties(); |
333 | this.assertValidProperties( properties ); |
334 | |
335 | FileOperations ops = new RandomAccessFileOperations( new RandomAccessFile( file, "rw" ) ); |
336 | ops = this.configureReadAheadCaching( ops, properties ); |
337 | return this.getPhysicalFile( ops, properties ); |
338 | } |
339 | |
340 | /** |
341 | * Checks configured properties. |
342 | * |
343 | * @throws PropertyException for illegal property values. |
344 | */ |
345 | private void assertValidProperties() |
346 | { |
347 | final int defaultFormat = this.getDefaultFormat(); |
348 | if ( defaultFormat != FORMAT_DISK && defaultFormat != FORMAT_TAPE ) |
349 | { |
350 | throw new PropertyException( "defaultFormat", new Integer( defaultFormat ) ); |
351 | } |
352 | } |
353 | |
354 | /** |
355 | * Checks that given properties are valid. |
356 | * |
357 | * @param properties the properties to check. |
358 | * |
359 | * @throws NullPointerException if {@code properties} is {@code null}. |
360 | * @throws IllegalArgumentException if {@code properties} holds invalid values. |
361 | */ |
362 | private void assertValidProperties( final java.util.Properties properties ) |
363 | { |
364 | if ( properties == null ) |
365 | { |
366 | throw new NullPointerException( "properties" ); |
367 | } |
368 | |
369 | for ( Iterator it = properties.entrySet().iterator(); it.hasNext(); ) |
370 | { |
371 | final Map.Entry entry = (Map.Entry) it.next(); |
372 | final String name = (String) entry.getKey(); |
373 | final String value = (String) entry.getValue(); |
374 | |
375 | if ( name.startsWith( ATTRIBUTE_SPACE_CHARACTERS_ALLOWED ) ) |
376 | { |
377 | try |
378 | { |
379 | Integer.parseInt( name.substring( name.lastIndexOf( '.' ) + 1 ), 16 ); |
380 | } |
381 | catch ( NumberFormatException e ) |
382 | { |
383 | throw (IllegalArgumentException) new IllegalArgumentException( |
384 | name + ": " + e.getMessage() ).initCause( e ); |
385 | |
386 | } |
387 | } |
388 | |
389 | if ( value != null && ( ATTRIBUTE_READAHEAD_CACHESIZE.equals( name ) || |
390 | ATTRIBUTE_COALESCING_BLOCKSIZE.equals( name ) ) ) |
391 | { |
392 | try |
393 | { |
394 | Integer.parseInt( value ); |
395 | } |
396 | catch ( NumberFormatException e ) |
397 | { |
398 | throw (IllegalArgumentException) new IllegalArgumentException( this.getIllegalAttributeTypeMessage( |
399 | this.getLocale(), name, ( value != null ? value.getClass().getName() : null ), |
400 | Integer.class.getName() ) ).initCause( e ); |
401 | |
402 | } |
403 | } |
404 | } |
405 | } |
406 | |
407 | private java.util.Properties getDefaultProperties() |
408 | { |
409 | final java.util.Properties properties = new java.util.Properties(); |
410 | properties.setProperty( ATTRIBUTE_READAHEAD_CACHING, Boolean.toString( true ) ); |
411 | properties.setProperty( ATTRIBUTE_COALESCING_CACHING, Boolean.toString( true ) ); |
412 | return properties; |
413 | } |
414 | |
415 | private FileOperations configureReadAheadCaching( FileOperations ops, final java.util.Properties properties ) |
416 | throws IOException |
417 | { |
418 | final String readAheadCaching = properties.getProperty( ATTRIBUTE_READAHEAD_CACHING ); |
419 | final String readAheadCacheSize = properties.getProperty( ATTRIBUTE_READAHEAD_CACHESIZE ); |
420 | final boolean isReadAheadCaching = |
421 | readAheadCaching != null && Boolean.valueOf( readAheadCaching ).booleanValue(); |
422 | |
423 | if ( isReadAheadCaching ) |
424 | { |
425 | if ( readAheadCacheSize != null ) |
426 | { |
427 | ops = new ReadAheadFileOperations( ops, Integer.parseInt( readAheadCacheSize ) ); |
428 | } |
429 | else |
430 | { |
431 | ops = new ReadAheadFileOperations( ops ); |
432 | } |
433 | } |
434 | |
435 | return ops; |
436 | } |
437 | |
438 | private FileOperations configureCoalescingCaching( FileOperations ops, final java.util.Properties properties ) |
439 | throws IOException |
440 | { |
441 | final String coalescingCaching = properties.getProperty( ATTRIBUTE_COALESCING_CACHING ); |
442 | final String coalescingBlockSize = properties.getProperty( ATTRIBUTE_COALESCING_BLOCKSIZE ); |
443 | final boolean isCoalescingCaching = |
444 | coalescingCaching != null && Boolean.valueOf( coalescingCaching ).booleanValue(); |
445 | |
446 | if ( isCoalescingCaching ) |
447 | { |
448 | if ( coalescingBlockSize != null ) |
449 | { |
450 | ops = new CoalescingFileOperations( ops, Integer.parseInt( coalescingBlockSize ) ); |
451 | } |
452 | else |
453 | { |
454 | ops = new CoalescingFileOperations( ops ); |
455 | } |
456 | } |
457 | |
458 | return ops; |
459 | } |
460 | |
461 | private PhysicalFile getPhysicalFile( final FileOperations ops, int format, final java.util.Properties properties ) |
462 | throws PhysicalFileException, IOException |
463 | { |
464 | if ( ops == null ) |
465 | { |
466 | throw new NullPointerException( "ops" ); |
467 | } |
468 | if ( format != FORMAT_DISK && format != FORMAT_TAPE ) |
469 | { |
470 | throw new IllegalArgumentException( Integer.toString( format ) ); |
471 | } |
472 | |
473 | this.assertValidProperties( properties ); |
474 | |
475 | final DefaultPhysicalFile ret; |
476 | final Message[] messages; |
477 | format = ops.getLength() > 0 ? this.analyse( ops ) : format; |
478 | |
479 | try |
480 | { |
481 | ThreadLocalMessages.getMessages().clear(); |
482 | ThreadLocalMessages.setErrorsEnabled( false ); |
483 | ret = new DefaultPhysicalFile( format, ops, properties ); |
484 | messages = ThreadLocalMessages.getMessages().getMessages(); |
485 | if ( messages.length > 0 ) |
486 | { |
487 | throw new PhysicalFileException( messages ); |
488 | } |
489 | |
490 | return ret; |
491 | } |
492 | finally |
493 | { |
494 | ThreadLocalMessages.setErrorsEnabled( true ); |
495 | } |
496 | } |
497 | |
498 | protected Implementation getImplementation() |
499 | { |
500 | if ( this.implementation == null ) |
501 | { |
502 | this.implementation = ModelFactory.getModel().getModules(). |
503 | getImplementation( DefaultPhysicalFileFactory.class.getName() ); |
504 | |
505 | } |
506 | |
507 | return this.implementation; |
508 | } |
509 | |
510 | //--Constructors------------------------------------------------------------ |
511 | |
512 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausConstructors |
513 | // This section is managed by jdtaus-container-mojo. |
514 | |
515 | /** Standard implementation constructor <code>org.jdtaus.banking.dtaus.ri.zka.DefaultPhysicalFileFactory</code>. */ |
516 | public DefaultPhysicalFileFactory() |
517 | { |
518 | super(); |
519 | } |
520 | |
521 | // </editor-fold>//GEN-END:jdtausConstructors |
522 | |
523 | //------------------------------------------------------------Constructors-- |
524 | //--Properties-------------------------------------------------------------- |
525 | |
526 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties |
527 | // This section is managed by jdtaus-container-mojo. |
528 | |
529 | /** |
530 | * Gets the value of property <code>defaultFormat</code>. |
531 | * |
532 | * @return The format to use for empty files. |
533 | */ |
534 | private int getDefaultFormat() |
535 | { |
536 | return ( (java.lang.Integer) ContainerFactory.getContainer(). |
537 | getProperty( this, "defaultFormat" ) ).intValue(); |
538 | |
539 | } |
540 | |
541 | // </editor-fold>//GEN-END:jdtausProperties |
542 | |
543 | //--------------------------------------------------------------Properties-- |
544 | //--Dependencies------------------------------------------------------------ |
545 | |
546 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies |
547 | // This section is managed by jdtaus-container-mojo. |
548 | |
549 | /** |
550 | * Gets the configured <code>Locale</code> implementation. |
551 | * |
552 | * @return The configured <code>Locale</code> implementation. |
553 | */ |
554 | private Locale getLocale() |
555 | { |
556 | return (Locale) ContainerFactory.getContainer(). |
557 | getDependency( this, "Locale" ); |
558 | |
559 | } |
560 | |
561 | // </editor-fold>//GEN-END:jdtausDependencies |
562 | |
563 | //------------------------------------------------------------Dependencies-- |
564 | //--Messages---------------------------------------------------------------- |
565 | |
566 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages |
567 | // This section is managed by jdtaus-container-mojo. |
568 | |
569 | /** |
570 | * Gets the text of message <code>illegalAttributeType</code>. |
571 | * <blockquote><pre>Ungültiger Attribut-Typ {1} für Attribut {0}. Erwartet Typ {2}.</pre></blockquote> |
572 | * <blockquote><pre>The type {1} for attribute {0} is invalid. Expected {2}.</pre></blockquote> |
573 | * |
574 | * @param locale The locale of the message instance to return. |
575 | * @param attributeName format parameter. |
576 | * @param typeName format parameter. |
577 | * @param expectedTypeName format parameter. |
578 | * |
579 | * @return the text of message <code>illegalAttributeType</code>. |
580 | */ |
581 | private String getIllegalAttributeTypeMessage( final Locale locale, |
582 | final java.lang.String attributeName, |
583 | final java.lang.String typeName, |
584 | final java.lang.String expectedTypeName ) |
585 | { |
586 | return ContainerFactory.getContainer(). |
587 | getMessage( this, "illegalAttributeType", locale, |
588 | new Object[] |
589 | { |
590 | attributeName, |
591 | typeName, |
592 | expectedTypeName |
593 | }); |
594 | |
595 | } |
596 | |
597 | // </editor-fold>//GEN-END:jdtausMessages |
598 | |
599 | //----------------------------------------------------------------Messages-- |
600 | } |