001/*
002 *  jDTAUS Banking RI DTAUS
003 *  Copyright (C) 2005 Christian Schulte
004 *  <cs@schulte.it>
005 *
006 *  This library is free software; you can redistribute it and/or
007 *  modify it under the terms of the GNU Lesser General Public
008 *  License as published by the Free Software Foundation; either
009 *  version 2.1 of the License, or any later version.
010 *
011 *  This library is distributed in the hope that it will be useful,
012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 *  Lesser General Public License for more details.
015 *
016 *  You should have received a copy of the GNU Lesser General Public
017 *  License along with this library; if not, write to the Free Software
018 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
019 *
020 */
021package org.jdtaus.banking.dtaus.ri.zka;
022
023import java.io.EOFException;
024import java.io.File;
025import java.io.IOException;
026import java.io.RandomAccessFile;
027import java.util.Iterator;
028import java.util.Locale;
029import java.util.Map;
030import org.jdtaus.banking.dtaus.CorruptedException;
031import org.jdtaus.banking.dtaus.PhysicalFile;
032import org.jdtaus.banking.dtaus.PhysicalFileException;
033import org.jdtaus.banking.dtaus.PhysicalFileFactory;
034import org.jdtaus.banking.dtaus.spi.Fields;
035import org.jdtaus.banking.messages.IllegalDataMessage;
036import org.jdtaus.banking.messages.IllegalFileLengthMessage;
037import org.jdtaus.core.container.ContainerFactory;
038import org.jdtaus.core.container.Implementation;
039import org.jdtaus.core.container.ModelFactory;
040import org.jdtaus.core.container.PropertyException;
041import org.jdtaus.core.io.FileOperations;
042import org.jdtaus.core.io.util.CoalescingFileOperations;
043import org.jdtaus.core.io.util.RandomAccessFileOperations;
044import org.jdtaus.core.io.util.ReadAheadFileOperations;
045import org.jdtaus.core.nio.util.Charsets;
046import org.jdtaus.core.text.Message;
047
048/**
049 * Default {@code PhysicalFileFactory} implementation.
050 *
051 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
052 * @version $JDTAUS: DefaultPhysicalFileFactory.java 8865 2014-01-10 17:13:42Z schulte $
053 */
054public final class DefaultPhysicalFileFactory implements PhysicalFileFactory
055{
056
057    /**
058     * Constant for the name of attribute {@code readAheadCaching}.
059     * <p>
060     * The {@code readAheadCaching} attribute is used to enabled or disable the
061     * use of a read-ahead caching algorithm. Its expected value is of type
062     * {@code Boolean}.
063     * </p>
064     */
065    public static final String ATTRIBUTE_READAHEAD_CACHING =
066        DefaultPhysicalFileFactory.class.getName() + ".readAheadCaching";
067
068    /**
069     * Constant for the name of attribute {@code readAheadCacheSize}.
070     * <p>
071     * The {@code readAheadCacheSize} attribute is used to specify the
072     * size of the read-ahead cache. Its expected value is of type
073     * {@code Integer}.
074     * </p>
075     */
076    public static final String ATTRIBUTE_READAHEAD_CACHESIZE =
077        DefaultPhysicalFileFactory.class.getName() + ".readAheadCacheSize";
078
079    /**
080     * Constant for the name of attribute {@code coalescingCaching}.
081     * <p>
082     * The {@code coalescingCaching} attribute is used to enabled or disable the
083     * use of a coalescing caching algorithm. Its expected value is of type
084     * {@code Boolean}.
085     * </p>
086     */
087    public static final String ATTRIBUTE_COALESCING_CACHING =
088        DefaultPhysicalFileFactory.class.getName() + ".coalescingCaching";
089
090    /**
091     * Constant for the name of attribute {@code coalescingBlockSize}.
092     * <p>
093     * The {@code coalescingBlockSize} attribute is used to specify the
094     * value of property {@code blockSize} to use when constructing the
095     * coalescing cache implementation. Its expected value is of type
096     * {@code Integer}.
097     * </p>
098     */
099    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}