001/*
002 *  jDTAUS Banking RI DTAUS
003 *  Copyright (C) 2005 Christian Schulte
004 *  <cs@schulte.it>
005 *
006 *  This library is free software; you can redistribute it and/or
007 *  modify it under the terms of the GNU Lesser General Public
008 *  License as published by the Free Software Foundation; either
009 *  version 2.1 of the License, or any later version.
010 *
011 *  This library is distributed in the hope that it will be useful,
012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 *  Lesser General Public License for more details.
015 *
016 *  You should have received a copy of the GNU Lesser General Public
017 *  License along with this library; if not, write to the Free Software
018 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
019 *
020 */
021package org.jdtaus.banking.dtaus.ri.zka;
022
023import java.io.IOException;
024import java.util.Iterator;
025import java.util.Map;
026import org.jdtaus.banking.dtaus.Checksum;
027import org.jdtaus.banking.dtaus.Header;
028import org.jdtaus.banking.dtaus.LogicalFile;
029import org.jdtaus.banking.dtaus.PhysicalFile;
030import org.jdtaus.banking.dtaus.PhysicalFileFactory;
031import org.jdtaus.banking.dtaus.spi.HeaderValidator;
032import org.jdtaus.banking.dtaus.spi.IllegalHeaderException;
033import org.jdtaus.banking.messages.AnalysesFileMessage;
034import org.jdtaus.core.container.ContainerFactory;
035import org.jdtaus.core.io.FileOperations;
036import org.jdtaus.core.monitor.spi.Task;
037import org.jdtaus.core.monitor.spi.TaskMonitor;
038
039/**
040 * Default {@code PhysicalFile} implementation.
041 * <p><b>Note:</b><br/>
042 * This implementation is not thread-safe.</p>
043 *
044 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
045 * @version $JDTAUS: DefaultPhysicalFile.java 8661 2012-09-27 11:29:58Z schulte $
046 */
047public final class DefaultPhysicalFile implements PhysicalFile
048{
049
050    /** Index der logischen Dateien. */
051    private AbstractLogicalFile[] index;
052
053    /** Anzahl vorhandener logischer Dateien. */
054    private int dtausCount = 0;
055
056    /** Mapping of attribute names to theire values. */
057    private final java.util.Properties properties;
058
059    /** <code>FileOperations</code> requirement. **/
060    private FileOperations fileOperations;
061
062    /** Format of this instance. */
063    private final int format;
064
065    /**
066     * Creates a new {@code DefaultPhysicalFile} instance.
067     *
068     * @param format The format of the new instance.
069     * @param fileOperations The {@code FileOperations} implementation to operate on.
070     * @param properties Configuration properties.
071     *
072     * @throws NullPointerException if either {@code fileOperations} or {@code properties} is {@code null}.
073     * @throws IllegalArgumentException if {@code format} is not equal to {@code FORMAT_DISK} and {@code FORMAT_TAPE}.
074     * @throws IOException wenn nicht gelesen werden kann.
075     *
076     * @see PhysicalFileFactory#FORMAT_DISK
077     * @see PhysicalFileFactory#FORMAT_TAPE
078     */
079    public DefaultPhysicalFile(
080        final int format, final FileOperations fileOperations, final java.util.Properties properties )
081        throws IOException
082    {
083        super();
084
085        if ( fileOperations == null )
086        {
087            throw new NullPointerException( "fileOperations" );
088        }
089        if ( properties == null )
090        {
091            throw new NullPointerException( "properties" );
092        }
093        if ( format != PhysicalFileFactory.FORMAT_DISK && format != PhysicalFileFactory.FORMAT_TAPE )
094        {
095            throw new IllegalArgumentException( Integer.toString( format ) );
096        }
097
098        this.properties = properties;
099        this.fileOperations = fileOperations;
100        this.format = format;
101        this.checksum();
102    }
103
104    public int count()
105    {
106        return this.dtausCount;
107    }
108
109    public LogicalFile add( final Header header ) throws IOException
110    {
111        if ( header == null )
112        {
113            throw new NullPointerException( "header" );
114        }
115
116        IllegalHeaderException result = null;
117        final HeaderValidator[] validators = this.getHeaderValidator();
118
119        for ( int i = validators.length - 1; i >= 0; i-- )
120        {
121            result = validators[i].assertValidHeader( header, result );
122        }
123
124        if ( result != null && result.getMessages().length > 0 )
125        {
126            throw result;
127        }
128
129        this.resizeIndex( this.dtausCount );
130
131        final AbstractLogicalFile lFile = this.newLogicalFile(
132            ( this.dtausCount == 0 ? 0L : this.index[this.dtausCount - 1].getChecksumPosition() +
133                                          this.index[this.dtausCount - 1].getBlockSize() ) );
134
135        lFile.insertBytes( lFile.getHeaderPosition(), this.format * 2 );
136        lFile.writeHeader( header );
137        lFile.writeChecksum( new Checksum() );
138        lFile.checksum();
139        this.index[this.dtausCount] = lFile;
140        return this.index[this.dtausCount++];
141    }
142
143    public LogicalFile get( int dtausId )
144    {
145        if ( !this.checkLogicalFileExists( dtausId ) )
146        {
147            throw new IllegalArgumentException( "dtausId" );
148        }
149        return this.index[dtausId];
150    }
151
152    public void remove( int dtausId ) throws IOException
153    {
154        if ( !this.checkLogicalFileExists( dtausId ) )
155        {
156            throw new IllegalArgumentException( "dtausId" );
157        }
158
159        this.index[dtausId].removeBytes(
160            this.index[dtausId].getHeaderPosition(), this.index[dtausId].getChecksumPosition() -
161                                                     this.index[dtausId].getHeaderPosition() + this.format );
162
163        System.arraycopy( this.index, dtausId + 1, this.index, dtausId, --this.dtausCount - dtausId );
164    }
165
166    public void commit() throws IOException
167    {
168        this.getFileOperations().close();
169    }
170
171    public int getLogicalFileCount() throws IOException
172    {
173        return this.count();
174    }
175
176    public LogicalFile addLogicalFile( final Header header ) throws IOException
177    {
178        return this.add( header );
179    }
180
181    public LogicalFile getLogicalFile( final int index ) throws IOException
182    {
183        return this.get( index );
184    }
185
186    public void removeLogicalFile( final int index ) throws IOException
187    {
188        this.remove( index );
189    }
190
191    /** FileOperations requirement getter method. */
192    private FileOperations getFileOperations()
193    {
194        return this.fileOperations;
195    }
196
197    private boolean checkLogicalFileExists( int dtausId )
198    {
199        return dtausId < this.dtausCount && dtausId >= 0;
200    }
201
202    private void checksum() throws IOException
203    {
204        this.dtausCount = 0;
205        int dtausIndex = 0;
206        final long length = this.getFileOperations().getLength();
207        long maximumProgress = length;
208        long progressDivisor = 1L;
209
210        while ( maximumProgress > Integer.MAX_VALUE )
211        {
212            maximumProgress /= 2L;
213            progressDivisor *= 2L;
214        }
215
216        final Task task = new Task();
217        task.setIndeterminate( false );
218        task.setCancelable( false );
219        task.setDescription( new AnalysesFileMessage() );
220        task.setMinimum( 0 );
221        task.setProgress( 0 );
222        task.setMaximum( (int) maximumProgress );
223
224        try
225        {
226            this.getTaskMonitor().monitor( task );
227
228            for ( long position = 0L; position < length;
229                  position = this.index[dtausIndex].getChecksumPosition() + this.index[dtausIndex++].getBlockSize() )
230            {
231                task.setProgress( (int) ( position / progressDivisor ) );
232                this.resizeIndex( dtausIndex );
233                this.index[dtausIndex] = this.newLogicalFile( position );
234                this.index[dtausIndex].checksum();
235                this.dtausCount++;
236            }
237        }
238        finally
239        {
240            this.getTaskMonitor().finish( task );
241        }
242    }
243
244    private AbstractLogicalFile newLogicalFile( final long headerPosition ) throws IOException
245    {
246        final AbstractLogicalFile ret;
247
248        switch ( this.format )
249        {
250            case PhysicalFileFactory.FORMAT_DISK:
251                ret = new DTAUSDisk();
252                break;
253            case PhysicalFileFactory.FORMAT_TAPE:
254                ret = new DTAUSTape();
255                break;
256            default:
257                throw new IllegalStateException();
258
259        }
260
261        ret.setFileOperations( this.getFileOperations() );
262        ret.setHeaderPosition( headerPosition );
263        ret.setChecksumPosition( headerPosition + this.format );
264
265        for ( Iterator it = this.properties.entrySet().iterator(); it.hasNext(); )
266        {
267            final Map.Entry e = (Map.Entry) it.next();
268            final String key = (String) e.getKey();
269
270            if ( key.startsWith( DefaultPhysicalFileFactory.ATTRIBUTE_SPACE_CHARACTERS_ALLOWED ) )
271            {
272                int field = Integer.parseInt( key.substring( key.lastIndexOf( '.' ) + 1 ), 16 );
273                final boolean allowed =
274                    e.getValue() != null && Boolean.valueOf( e.getValue().toString() ).booleanValue();
275
276                ret.getConfiguration().setSpaceCharacterAllowed( field, allowed );
277            }
278        }
279
280        ret.addListener( new AbstractLogicalFile.Listener()
281        {
282
283            public void bytesInserted( final long position, final long bytes ) throws IOException
284            {
285                final int fileIndex = this.getFileIndex( position );
286                if ( fileIndex >= 0 )
287                {
288                    // Increment properties headerPosition and checksumPosition for all remaining files.
289                    for ( int i = fileIndex + 1; i < dtausCount; i++ )
290                    {
291                        index[i].setHeaderPosition( index[i].getHeaderPosition() + bytes );
292                        index[i].setChecksumPosition( index[i].getChecksumPosition() + bytes );
293                    }
294                }
295            }
296
297            public void bytesDeleted( final long position, final long bytes ) throws IOException
298            {
299                final int fileIndex = this.getFileIndex( position );
300                if ( fileIndex >= 0 )
301                {
302                    // Decrement properties headerPosition and checksumPosition for all remaining files.
303                    for ( int i = fileIndex + 1; i < dtausCount; i++ )
304                    {
305                        index[i].setHeaderPosition( index[i].getHeaderPosition() - bytes );
306                        index[i].setChecksumPosition( index[i].getChecksumPosition() - bytes );
307                    }
308                }
309            }
310
311            private int getFileIndex( final long position )
312            {
313                for ( int i = dtausCount - 1; i >= 0; i-- )
314                {
315                    if ( position >= index[i].getHeaderPosition() && position <= index[i].getChecksumPosition() )
316                    {
317                        return i;
318                    }
319                }
320
321                return -1;
322            }
323
324        } );
325
326        return ret;
327    }
328
329    private void resizeIndex( int index )
330    {
331        if ( this.index == null )
332        {
333            this.index = new AbstractLogicalFile[ index + 1 ];
334        }
335        else if ( this.index.length < index + 1 )
336        {
337            while ( this.index.length < index + 1 )
338            {
339                final int newLength = this.index.length * 2;
340                final AbstractLogicalFile[] newIndex = new AbstractLogicalFile[ newLength ];
341                System.arraycopy( this.index, 0, newIndex, 0, this.index.length );
342                this.index = newIndex;
343            }
344        }
345    }
346
347    //--Dependencies------------------------------------------------------------
348
349// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
350    // This section is managed by jdtaus-container-mojo.
351
352    /**
353     * Gets the configured <code>TaskMonitor</code> implementation.
354     *
355     * @return The configured <code>TaskMonitor</code> implementation.
356     */
357    private TaskMonitor getTaskMonitor()
358    {
359        return (TaskMonitor) ContainerFactory.getContainer().
360            getDependency( this, "TaskMonitor" );
361
362    }
363
364    /**
365     * Gets the configured <code>HeaderValidator</code> implementation.
366     *
367     * @return The configured <code>HeaderValidator</code> implementation.
368     */
369    private HeaderValidator[] getHeaderValidator()
370    {
371        return (HeaderValidator[]) ContainerFactory.getContainer().
372            getDependency( this, "HeaderValidator" );
373
374    }
375
376// </editor-fold>//GEN-END:jdtausDependencies
377
378    //------------------------------------------------------------Dependencies--
379}